Figure 17.2 illustrates how various components of a Jeeves-based translator relate to each other. Gray rectangles constitute the Jeeves framework.
The Jeeves framework supplies a driver program, jeeves, a template-parsing module, TemplateParser.pm, and a utility module for creating and accessing the syntax tree, AST.pm.
You supply a specification parser for a given application domain, such as OO_Schema.pm, a specification file (emp.om), and one or more templates, such as oo.tpl.
The driver starts by calling the parse
function of the specification parser. This calls the AST functions new, add_prop
, and add_prop_list
to convert all "relevant" data from the specification file to a tree of properties.
The driver then calls the template parser's parse function, which converts the given template file to an intermediate perl file. (Note the line Translated oo.tpl to oo.tpl.pl in the command-line invocation shown earlier.) The template contains variables along with looping and conditional constructs, all of which are more than adequately supported by Perl itself, so by converting the template to Perl code, we are able to leverage all of Perl's power. This is similar to early versions of C++ compilers (cfront), which simply converted C++ files to intermediate C files, thus taking advantage of the power, optimization features, and portability of existing C compilers.
Finally, the driver loads the intermediate file using require
, which is an eval in disguise. When evaluated, this code traverses the syntax tree and produces the required output files.
So what have we gained by this seemingly complicated arrangement? The input parser is written once to produce a standardized data structure. The template parser knows how to make this data structure available to the template and traverse it in a controlled fashion. The result is that you can write all kinds of templates while reusing the input parser.
This arrangement works very well in big projects. Someone with parsing experience writes the parser, and another who knows the application well writes templates. Others simply write different specifications and run the tool. Tomorrow, if you, an applications developer, write a template to automatically generate a Motif-based user interface from an object model, the others don't have to learn Motif to generate custom UIs for their object models.
The jeeves driver takes the name of a specification parser on the command line. This means that you can have a library of specification parsers for all kinds of problems and a library of templates corresponding to these parsers. The framework itself is independent of application domains.
The advantage of writing it in Perl is that no other language comes anywhere close to Perl's text-processing abilities. Besides, you can use modules such as Tk and IO::Socket within your templates.
Most of Jeeves is extremely simple; the only piece of code that warrants some attention is the template parser.
The following snippet shows a sample conversion of a template to the intermediate Perl file:
@foreach class_list Name: $class_name @foreach attr_list Attr: $attr_name, $attr_type @end @end
Stripped to its essence, the corresponding intermediate file looks like this:
$ROOT->visit(); foreach $class_list_i (@class_list) { $class_list_i->visit(); print "Name : $class_name\n"; foreach $attr_list_i (@attr_list) { $attr_list_i->visit(); print "Attr: $attr_name, $attr_type\n"; Ast->bye(); } Ast->bye(); }
Ast::visit converts all properties of the AST node being visited to global Perl variables in package main. The root node of the syntax tree is "visited" first, which results in the creation of one global variable, @class_list, since that is the sole property of the root node. @class_list consists of one or more AST nodes itself, and when one of them is visited, the properties class_name and attr_list become available as $class_name and @attr_list. This code has to account for the possibility that a given global variable already exists, either because of a similarly named property at an outer level of nesting or because one was defined by the template writer using an @perl directive. For this reason visit() keeps track of the old value of a variable if necessary; bye() restores it to the old value at the end of a @FOREACH block. This arrangement hence implements dynamic scoping.