The Gen
framework implements an Objective-C code generator that I built it while I was working on Statec (a state machine DSL
and class generator for Objective-C) and have now extracted into a separate library.
Gen
is not complete (although I think it models most of the useful parts of Objective-C) and certainly could do with some work
ironing out its kinks. But it works and I think it meets the criteria of being able to do useful work.
In practice Gen
provides a set of classes to model Objective-C concepts such as classes (GenClass
), protocols (GenProtocol
),
properties (GenProperty
), variables (GenVariable
), methods (GenMethod
), and so on. To create a class you create a GenClass
instance, add properties, methods, protocols, variables and so on. Then add it to a GenCompilationUnit
that knows how to write
out the corresponding .m
/.h
files.
At the method level code is inserted into the method body using a template string (essentially -stringWithFormat:
). In practice
attempts to model the structure of methods proved to be a somewhat tedious exercise for little reward and I found it easier to
work with strings. Though even here Gen
provides some helper methods to make it easier to do things like invoking GenMethod
's.
Here is a real example of using Gen
, taken from the source of Statec. This method creates the .m
/.h
files for the user-facing
state machine class. You can see the creation of a class with a private instance variable (i.e. the variable is defined in a class
extension) that implements a protocol and defines the methods of that protocol. In particular you can see how, when the method
tagged start
is being defined it looks up a method in the implementation class and uses the GenMethod
to create a call to it:
- (GenCompilationUnit *)generateUnit {
NSString *userClassName = [NSString stringWithFormat:@"%@%@Machine",
[[self machine] prefix],
[[self machine] name]];
GenCompilationUnit *unit = [[GenCompilationUnit alloc] initWithTag:@"user"
name:userClassName];
NSString *versionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
NSString *revNumber = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
NSMutableString *commentString = [NSMutableString string];
[commentString appendFormat:
@"// State machine %@ generated by Statec v%@(%@) on %@\n"
@"// Statec copyright (c) 2012 Matt Mower <[email protected]>\n"
@"// \n",
[[self machine] name],
versionString,
revNumber,
[NSDate date]
];
[unit setComment:commentString];
// Import the generated machine into the user machine
[[unit declarationImports] addObject:[[self implUnit] headerFileName]];
GenClass *userClass = [[GenClass alloc] initWithTag:@"user"
name:userClassName
baseClass:nil];
NSString *implVariableName = [NSString stringWithFormat:@"_%@Machine", [[[self machine] name] statecStringByLoweringFirstLetter]];
GenVariable *implVariable = [[GenVariable alloc] initWithTag:@"impl"
scope:GenInstanceScope|GenPrivateScope
name:implVariableName
type:[[[self implUnit] classWithTag:@"impl"] pointerType]];
[userClass addVariable:implVariable];
// The delegate protocol is what this class conforms to so stub its methods for the user to implement
GenProtocol *delegateProtocol = [[self implUnit] protocolWithTag:@"delegate"];
[userClass addProtocol:delegateProtocol];
GenMethod *setupMethod = [[GenMethod alloc] initWithTag:@"setup"
scope:GenInstanceScope|GenPrivateScope
returnType:@"void"
selectorFormat:@"setup%@Machine", [[self machine] name]];
[[setupMethod body] append:@"\t%@ = [[%@ alloc] init];\n"
@"\t[%@ setDelegate:self];",
[implVariable name],
[[[self implUnit] classWithTag:@"impl"] name],
[implVariable name]
];
[userClass addMethod:setupMethod];
// Create the initializer that will setup the machine
GenMethod *initializer = [[GenMethod alloc] initWithTag:@"init"
scope:GenInstanceScope
returnType:StatecTypeId
selector:@selector(init)];
[[initializer body] append:@"\tself = [super init];\n"
@"\tif( self ) {\n"
@"\t\t%@;\n"
@"\t}\n"
@"\treturn self;\n",
[setupMethod invocationWithReceiver:@"self"]
];
[initializer setIsDeclaredHere:NO];
[userClass addInitializer:initializer];
/*
For the users convenience we will sort the methods into utility methods,
non-final state methods, and final state methods.
*/
NSArray *methods = [[delegateProtocol methods] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return method_weight(obj1) - method_weight(obj2);
}];
for( GenMethod *method in methods ) {
GenMethod *stubMethod = [method mutableCopy];
if( [[method tag] isEqualToString:@"start"] ) {
GenMethod *startMethod = [[[self implUnit] principalClass] instanceMethodWithTag:@"start"];
[stubMethod setBody:[[GenStatementGroup alloc] initWithFormat:@"\t%@;", [startMethod invocationWithReceiver:[implVariable name]]]];
[stubMethod setIsDeclaredHere:YES];
} else {
[stubMethod setBody:[[GenStatementGroup alloc] initWithFormat:@"\t// Your code here"]];
[stubMethod setIsDeclaredHere:NO];
}
[userClass addMethod:stubMethod];
}
[unit addClass:userClass];
return unit;
}
I'm not sure if anyone else will find Gen
useful, it's not often that one needs to be able to dynamically generate Objective-C code. But,
if you do, and you use it, I'd be grateful to hear your feedback.