Skip to content

mmower/Gen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Gen

An Objective-C Code Generator

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.

Example

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 <self@mattmower.com>\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;
}

Feedback

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.