This is cnvwrap.m in view mode; [Download] [Up]
//
// cnvwrap.m
// Program for automatically generating degenerate (convenience)
// methods for methods with optional parameters
//
// Written by Carl Lindberg
// Copyright (c) 1994,1995,1996 by Carl Lindberg.
// Version 1.3 All rights reserved.
// This notice may not be removed from this source code.
//
// This object is included in the MiscKit by permission from the author
// and its use is governed by the MiscKit license, found in the file
// "LICENSE.rtf" in the MiscKit distribution. Please refer to that file
// for a list of all applicable permissions and restrictions.
//
//
// This program takes method declarations - with default values attached
// to optional parameters - and outputs all possible degenerate methods
// along with their header lines.
//
//
// Command-line options:
//
// -i filename Specify input file. Reads from stdin if this
// option not used.
// -m filename Output method implementations to named file. Adds
// '.m' to filename if not there already. Outputs to
// stdout if this option is not used.
// -h filename Output method headers to named file. Adds '.h' to
// filename if not there already. Outputs to stdout if
// this option is not used.
// -f filename Just like -h filename.h -m filename.m. Automatically
// adds .h and .m extensions to filename, and outputs
// headers and methods, respectively, to the two files.
// -e {h|m|hm} Excludes the {header|implementation|both} of the full
// method from being output. For example, -em will print
// out all headers, but will not print the implementation
// of the full method (normally, it is printed with a
// blank body).
// -a Appends to output files instead of overwriting.
// -H Help
//
//
//
// SYNTAX FOR INPUT LINE(S):
//
// Input lines are basically the same as header lines, except that
// default values to parameters are put inside square (or squiggly)
// brackets and tacked directly onto the end of that parameters.
// Also, no semicolon at the end.
//
//
// 1) Leave at least one space/tab after the "-" or "+".
// 2) ONE line per method - don't break up a long method into 2 or more
// lines.
// 3) Default values signify an optional parameter. These should be
// between square ("[<default value>]") or squiggly ("{<default value>"})
// brackets tacked on the end of a parameter - NO SPACES before the open
// bracket.
// The 'default value' may contain any character (including whitespaces)
// EXCEPT a corresponding right bracket character -- that is, if you use
// square brackets to define the default value, the default value itself
// cannot contain a right square bracket. Even then, you are allowed to
// have corresponding right bracket character(s) so long as no whitespaces
// come _after_ it. Ex:
//
// Legal: with:(char *)[[sender stringValue]]
// with:(char *){[sender stringValue]}
// with:(char *)[[[sender selectedCell]stringValue]]
// with:(char *){[[sender selectedCell] stringValue]} //use squiggly braces
// Illegal: with:(char *)[[sender stringValue] ]
// with:(char *)[[[sender selectedCell] stringValue]]
//
// This was as flexible as I could figure out - it's a limitation
// of the regular expression used to search. If this still isn't
// enough, than you could simply put in a word like FOOBAR as the
// default value, and then search-and-replace FOOBAR with the real
// default value on the output.
//
// 4) Every variable must be typecast - even if it's an id, please put
// (id) as the typecast. A method return type is optional, though.
// 5) If the first parameter - really the method name - is optional,
// then *every* parameter must be optional too, or else you will
// get duplicate methods output.
//
//
// The program works by first parsing each line, and splitting it
// up into four lists, each list containing some needed portion of
// each parameter. Then the program goes into a second main loop
// which works up the needed text using the values in the lists.
//
// For the input:
//
// - (int)spotOfStr:(const char *)str occurrenceNum:(int)n[0] caseSensitive:(BOOL)sense[YES]
//
// the four lists would end up so:
//
//
// paramtypeList paramList paramvalList defaultList
// (whole param minus any (param name (variable (default
// default value) only) name) name)
// -------------------------------- -------------- ------------ -----------
// Param 1: (int)spotOfStr:(const char *)str spotOfStr: str [blank]
// Param 2: occurrenceNum:(int)n occurrenceNum: n 0
// Param 3: caseSensitive:(BOOL)sense caseSensitive: sense YES
//
//
// There should be (number of optional parameters)**2 possible method
// combinations. The exception is that if the first "parameter"
// (spotOfStr in the example above) is optional, only one more possible
// method is added -- the one where every parameter is left out. You
// can't leave the first parameter out and include others, obviously.
// Therefore, if the first parameter is optional, *every* parameter should
// be optional -- else duplicate methods will be output.
//
//
//
//
// You may thank the piles of convenience methods in MiscString for
// prompting me to write this program. :-) Enjoy.
//
// Version 1.1 June 7, 1995
// - fixed bug where having '(' or '[' in the default value section would
// confuse program
// - added -e {h|m|hm} switch to exclude outputting of the last possible
// header or method. This lets you code the full method first, then
// just output the headers/code for the convenience methods only.
// Version 1.2 July 8, 1995
// - changed the regular expression search to allow more possibilities of
// default section values.
// Version 1.3 Sept 16, 1996
// - changed the regular expression search to allow use of { ... } as well
// as [ ... ] for default value sections.
// - changed output format of implementations slightly (the closing '}' is
// now on its own line).
#include <appkit/appkit.h>
#include <misckit/MiscString.h>
void dohelp();
void usage();
void freestuff();
// These are needed to be declared outside main() for freestuff()
id mainstr, headstring, specstring, hName, mName, iName, returnstr, mfilestr,
hfilestr, defaultList, paramList, paramtypeList, paramvalList;
void main(int argc, char **argv)
{
char ch;
BOOL isOptArray[50]; //ith slot YES if param #i is optional
BOOL append = NO; //append to given filenames, or overwrite?
BOOL outputFinalHeader = YES;
BOOL outputFinalMethod = YES;
int i, j, numposs, numparam, thenum, len, spot2, spot3, startposs;
FILE *infile, *moutfile, *houtfile;
id tmp;
// Initialize some stuff
mainstr = [MiscString new];
specstring= [MiscString new]; //special; used if first param is optional
headstring= [MiscString new]; //for building up the header line
hfilestr = [MiscString new]; //for storing method headers
mfilestr = [MiscString new]; //for storing method definitions
returnstr = [MiscString new]; //intermediate string, used for making
//the return value
iName = [MiscString new]; //strings to hold filenames
mName = [MiscString new];
hName = [MiscString new];
//for "caseSensitive:(BOOL)sense[YES]":
defaultList = [List new]; //for storing the default values (eg "YES")
paramList = [List new]; //for storing the parameter name (eg "caseSensitive:")
paramvalList = [List new]; //for storing the variable name (eg "sense")
paramtypeList = [List new]; //for storing whole parameter w/ typecasts
// (eg "caseSensitive:(BOOL)sense")
// These are defaults
infile = stdin;
houtfile = stdout;
moutfile = stdout;
append = NO;
re_set_syntax(RE_NO_BK_PARENS|RE_NO_BK_VBAR);
// Process arguments
while ((ch = getopt(argc, argv, "Ham:h:f:i:e:")) != EOF)
switch((char)ch) {
case '?':
usage(argv[0]);
case 'H':
dohelp(argv[0]);
freestuff();
exit(0);
case 'a':
append = YES;
break;
case 'i':
[iName setStringValue:optarg];
break;
case 'm':
[mName setStringValue:optarg];
[mName addExtensionIfNeeded:"m"];
break;
case 'h':
[hName setStringValue:optarg];
[hName addExtensionIfNeeded:"h"];
break;
case 'f':
[hName setStringValue:optarg];
[hName cat:".h"];
[mName setStringValue:optarg];
[mName cat:".m"];
break;
case 'e':
if (index(optarg,'h')) outputFinalHeader = NO;
if (index(optarg,'m')) outputFinalMethod = NO;
break;
default:
usage(argv[0]);
exit(2);
}
if ([iName length]) {
infile = fopen([iName stringValue], "r+");
if (!infile) {
fprintf(stderr,"ERROR: could not open '%s'\n",[iName stringValue]);
freestuff();
exit(1);
}
}
if ([hName length]) {
houtfile = fopen([hName stringValue], (append)? "a+":"w+");
if (!houtfile) {
fprintf(stderr,"ERROR: could not open '%s'\n",[hName stringValue]);
freestuff();
exit(1);
}
}
if ([mName length]) {
moutfile = fopen([mName stringValue], (append)? "a+":"w+");
if (!moutfile) {
fprintf(stderr,"ERROR: could not open '%s'\n",[mName stringValue]);
freestuff();
exit(1);
}
}
// Repeat for every method read in... the || [mainstr length] is there
// if the last line of input does not have a newline at the end -
// this loop only terminates if at EOF *and* no string was read in.
while ([mainstr fgets:infile keepNewline:NO] != EOF || [mainstr length]) {
if ([mainstr length] < 5) continue; //break out if a blank line
numparam = 0;
numposs = 1;
startposs = 0;
// Isolate each parameter and process it - the regular expression is
// what expects to see a cast for each variable.
for (i=0;(spot2 = [mainstr spotOfRegex:"[^\t\n :]+[:][(][^)]*[)][^ \t\n[{]+(([[][^]]*[]]|{[^}]*})[^ \t\n]*)?"
occurrenceNum:i length:&len]) >= 0;i++)
{
// The above parsing may not have worked if the method return type had
// spaces in it, such as (const char *). This checks to see if that
// happened and sets 'spot2' and 'len' to the corrected values.
if ((i == 0) && ((spot3 = [mainstr spotOfRegex:"[(][^)]*[)]"]) < spot2)) {
len += (spot2-spot3);
spot2 = spot3;
}
// Now get the whole parameter in a string.
tmp = [mainstr midFrom:spot2 length:len];
// Add the parameter name (sans type and variable name) to its list.
// If the method has a return type, this must be stripped out too -
// thus the check on the first character.
if ([tmp charAt:0] == '(') {
[paramList addObject:[tmp midFrom:[tmp spotOf:')']+1 to:[tmp spotOf:':']]];
}
else {
[paramList addObject:[tmp midFrom:0 to:[tmp spotOf:':']]];
}
numparam++;
if (![tmp endcmp:"]"] || ![tmp endcmp:"}"]) { // It is an optional param
id tmp2 = [tmp midFrom:[tmp spotOf:':'] to:[tmp length]];
int spot = [tmp spotOf:':'] + [tmp2 spotOfChars:"[{"]; // Find beginning of default value
isOptArray[i] = YES;
// Get the whole parameter minus default value and add it to list
[paramtypeList addObject:[tmp midFrom:0 to:spot-1]];
// Get the default value and add it to its list
[defaultList addObject:[tmp midFrom:spot+1 to:[tmp length]-2]];
// Get the variable name and add it to its list
//[paramvalList addObject:[tmp midFrom:[tmp rspotOf:')']+1 to:spot-1]];
[paramvalList addObject:[tmp2 midFrom:[tmp2 spotOf:')']+1 to:[tmp2 spotOfChars:"{["]-1]];
[tmp2 free];
// If the first parameter is optional, it is handled much differently.
// Instead of doubling the number of potential methods, only one more
// is added. This is handled by starting the second main loop at -1
// instead of 0 (startposs). Also, since in the rest of the methods
// the first parameter must be there, its slot in the isOptArray should
// be NO, as it really is not optional. 'specstring' holds the header
// for the extra method incurred -- which is just the name (and return
// type if there) of the parameter minus the colon. 'specstring' will
// be used only in the -1th iteration of the second main loop.
if (i==0) {
[specstring setStringValue:[[tmp left:[tmp spotOf:':']] stringValueAndFree]];
[specstring insert:" "];
startposs = -1;
isOptArray[0] = NO;
}
else { // Not the first param, so double # of possible methods.
numposs *= 2;
}
// Free tmp here... under the else below, it is added to one of the lists
// and is therefore freed then.
[tmp free];
}
else { // Not an optional parameter, doesn't have [XXX] at end
isOptArray[i] = NO;
[defaultList addObject:[MiscString new]]; // Add a dummy string to keep place
[paramtypeList addObject:tmp];
[paramvalList addObject:[tmp midFrom:[tmp rspotOf:')']+1 to:[tmp length]-1]];
}
} //done parsing the input line
/* //debugging with parsing...
for (i=0;k<numparam;k++) {
printf("Full param: %s\n",[[paramtypeList objectAt:i] stringValue]);
printf("Optional? %s\n",(isOptArray[i])? "YES":"NO");
printf("Default: '%s'\n",[[defaultList objectAt:i] stringValue]);
printf("Value: %s\n",[[paramvalList objectAt:i] stringValue]);
printf("Name: %s\n",[[paramList objectAt:i] stringValue]);
}
*/
// Okay, now output stuff. There should be numposs methods to do.
// 'startposs' is -1 if the first param is optional and the one
// extra method is there to do, 0 normally.
for (i=startposs;i<numposs;i++) {
[headstring setStringValue:[[mainstr wordNum:0] stringValueAndFree]]; //"-" or "+"
[returnstr setStringValue:"{ return [self"];
thenum = 1;
// Build up the header and return for each method. hfilestr holds
// the method headers, mfilestr holds the method name, and returnstr
// holds the return value which gets added to mfilestr at the end of
// the inner loop.
for (j=0;j<numparam;j++) {
// Add the method name to the return value. The argument will
// either be the variable name or the default
[returnstr addChar:' '];
[returnstr concatenate:[paramList objectAt:j]];
// Check to see if the current parameter deserves to be added
// in this pass. If it does, then add it to the header,
// and add the variable name to returnstr. Otherwise, just add
// the default value to returnstr.
// The special case of i==-1 should not do this part - that is handled
// elsewhere. If the current parameter is not optional then it should
// always be added in.
// Otherwise, the third condition determines whether a parameter is
// included in this pass. 'thenum' starts as 1 every iteration of i,
// meaning the value of that condition for the first parameter will
// be 0, then 1, then 0, then 1... as i goes up. 'thenum' gets doubled
// each optional parameter, so for the second optional parameter the
// condition will be 0, 0, 1, 1, 0, 0, 1, 1... as i goes up. The third
// will be 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, ... and so on. Since i represents
// the total number of combinations, this will account for every possible
// combination.
if ((i>=0) && (!(isOptArray[j]) || ((i % (thenum*2)) >= thenum))) {
[headstring addChar:' '];
[headstring concatenate:[paramtypeList objectAt:j]];
[returnstr concatenate:[paramvalList objectAt:j]]; //the variable name
}
else {
[returnstr concatenate:[defaultList objectAt:j]]; //the default value
}
if (isOptArray[j]) thenum *= 2;
}
// Special case handling for i==-1. Only the else portion of the above
// will be executed, so only the returnstr is done. specstring, set in
// the parsing loop, holds the header needed by hfilestr and mfilestr.
// So, just add it to them.
if (i==-1) {
[headstring concatenate:specstring];
}
[returnstr cat:"];\n}"];
if ((i <(numposs-1)) || outputFinalHeader) {
[hfilestr concatenate:headstring];
[hfilestr cat:";\n"];
}
if ((i < (numposs-1)) || outputFinalMethod) {
[mfilestr concatenate:headstring];
}
// If we are at the last possible method (i == numposs-1) we don't want
// to put a method body in - it would just call itself and be an infinite
// loop. I just decided to put in a blank method body - you will at least
// get a warning from the compiler if you forget to take this out or implement
// it. At least if you use -Wall.
// Otherwise, returnstr holds the entire method body, so add that to mfilestr.
if (i < (numposs-1)) //don't put in return for richest method
[mfilestr catFromFormat:"\n%s\n",[returnstr stringValue]];
else if (outputFinalMethod)
[mfilestr cat:"\n{\n}\n\n"];
}
[hfilestr addChar:'\n']; // Leave a blank line between sets of headers
[mfilestr cat:"\n\n\n"]; // Leave three blank lines between sets of
// convenience methods.
// Empty out all the lists to start over with the next input line
[defaultList freeObjects];
[paramList freeObjects];
[paramvalList freeObjects];
[paramtypeList freeObjects];
}
// Output the info... yeah, it only gets output at the end instead of
// as it's processed, but that's the price to pay if you want headers
// grouped together instead of interspersed with the actual methods
// when both outputs go to stdout.
fprintf(houtfile,"%s\n\n",[hfilestr stringValue]);
fprintf(moutfile,"%s\n",[mfilestr stringValue]);
// And clean up...
if ([hName length]) fclose(houtfile);
if ([mName length]) fclose(moutfile);
if ([iName length]) fclose(infile);
freestuff();
}
void freestuff()
{
[hName free];
[mName free];
[iName free];
[specstring free];
[headstring free];
[returnstr free];
[mfilestr free];
[hfilestr free];
[[defaultList freeObjects] free];
[[paramList freeObjects] free];
[[paramvalList freeObjects] free];
[[paramtypeList freeObjects] free];
[mainstr free];
}
void usage(char *progname)
{
fprintf(stderr,"\nusage: %s [-Ha] [-i inputfilename]\n",progname);
fprintf(stderr," [-h hfilename -m mfilename | -f basefilename]\n");
fprintf(stderr," [-e {h|m|hm}]\n\n");
fprintf(stderr," %s -H for more verbose help.\n\n",progname);
freestuff();
exit(2);
}
void dohelp(char *progname)
{
printf("usage: %s [-Ha] [-i filename] [-e {h|m|hm}]\n",progname);
printf(" [-h hfilename -m mfilename | -f basefilename]\n\n");
printf(" This program takes method headers with specified default values for\n");
printf(" optional parameters, and outputs both headers and methods for\n");
printf(" all possible degenerate (convenience) methods.\n");
printf(" Reads from stdin if infile not specified, and outputs headers\n");
printf(" and/or methods to stdout if destination is not specified.\n\n");
printf(" -H this help\n");
printf(" -a append to named output files; don't overwrite\n");
printf(" -i take input from named file\n");
printf(" -h/-m output headers/methods to named file. Adds '.h'/'.m' to\n");
printf(" filename if not there already.\n");
printf(" -f same as -h basefilename.h -m basefilename.m\n");
printf(" -e {h|m|hm} Excludes final {header|method|both} from being output.\n");
printf("\n");
printf(" INPUT SYNTAX: (one line per method)\n");
printf(" - (int)spotOf:(char)aChar caseSensitive:(BOOL)sense[YES]\n");
printf(" ^ ^ ^__________________________^ ^\n");
printf(" | | | |__NO space\n");
printf(" SPACE | CAST each parameter--even if id\n");
printf(" Method cast is optional\n");
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.