The Second Iteration

of the EEL Compiler

 

This report presents the different stages in the revamping of the EEL compiler. A similar structure and format as the original report has been followed.

Motivation II

As stated in the v2.0 report, the EEL compiler was generating code that made use of powerful support functions at the framework level. As such, it can be thought as programming to a micro-code layer that performs most of the functionality. For example, a line of eel of the form:

R1: always in(lsr.*.act, "A", "B", "C");

would have resulted in a piece of code similar to the following:

bool R1::Validate(Form* form) {

static vector<string> invar_0;

invar_0.push_back( "A" );

invar_0.push_back( "B" );

invar_0.push_back( "C" );

return rf_in(form, "lsr.*.act", 0, 0, invar_0);

}

The support function rf_in(...), in turn, would have had to expand the lsr.*.act field into all its constituent rows and perform the check. As a matter of fact, all support functions need to be prepared to deal with repeating fields. This is not only a loss of efficiency (timewise only), but more importantly a loss of granularity and flexibility. A better approach is therefore necessary.

Inception II

With a little bit more of 'adaptive' experience under my belt, I felt confident that I could change the behavior just enough to produce the appropriate code in little time. Since the parsing was working fine, all that remained was to modify/add traversals and visitors. That seemed doable. Also, since I did have the support functions' "micro-code", I sort of knew what the generated code should look like. So I moved on...

Ellaboration II

The first consideration here was the practicality of the approach. Undoing the 'inlining' of all these support function and somewhat replicating the code at each rule imposes a space overhead that you need to deal with. Fortunately enough, this validation system's bottleneck is speed, not space (of which we have lots). Furthermore, the previous version of the EEL compiler (v1.0) follows this approach and, while the memory requirements are higher, is still doable.

Planning II

There are two major issues to solve for handling repeating fields:

  1. get the generated code to create the appropriate control structure (ie. loops, indices, etc)
  2. weave this control structure with the generation of the normal commands

While special handling of combinations of repeating fields and externalvalidate() commands were not identified at this stage as being problematic, they ended up being pretty much the third item for the system to handle, as they present special needs.

Construction II

The whole point of redoing the code-generation phase of the EEL compiler is so that we can do away with the support functions. In reality, we are NOT aiming at getting rid of them completely, since they are our link to the abstract framework layer and thus our contract with the edit engine. What we are targeting, though, is being able to move as much functionality (and thus flexibility) from the support functions into the rules' code themselves. With this in mind, there are a number of issues we need to address:

Two simple cases

To exemplify the goals a little bit better, here's a very simple example. I will work with two eel rules, one with and one without repeating fields (external validations will be handled later). The rules follow:

R1: always in(lsr.a); // non-repeating

R2: always in(lsr.*.b); // multiple lsr.x.b rows (lsr.0.b, lsr.1.b, lsr.2.b, etc)

The code that we would like to see generated for the above two looks like:

  1. // R1:always in(lsr.a,"A","B");
  2. bool R1::Validate(Form* form) {
    1. ClearIndices();
    2. static vector<string> invar_0;
    3. invar_0.push_back( "A" );
    4. invar_0.push_back( "B" );
    5. if ( ! rf_in(form, "lsr.a", 0, 0, invar_0) ) rf_processerror();
    6. return Status();
  3. }
  4. // R2:always in(lsr.*.b,"A","B");
  5. bool R2::Validate(Form* form) {
    1. ClearIndices();
    2. static vector<string> invar_0;
    3. invar_0.push_back( "A" );
    4. invar_0.push_back( "B" );
    5. int idx[1];
    6. char buff[100];
    7. string section_0( "lsr" );
    8. for (idx[0]=0; idx[0] < form->GetRowCount( section_0 ); idx[0]++) {
      1. sprintf(buff, "lsr.%i.b", idx[0]);
      2. string rvn_0( buff );
      3. if ( ! rf_in(form, rvn_0, 0, 0, invar_0) )

        rf_processerror(idx, 1);

    9. }
    10. return Status();
  6. }

Lines 3.1 and 7.1 take care of cleaning up the rule's state before starting the validation. Lines 3.2-3.4 and 7.2-7.4 load the invar_0 auxiliary variable which will be used in the rf_in() call. These are basically identical as before. Line 3.5, on the other hand, presents a little bit of difference with the way we were doing it before. Now we actually check the return of the in function and call rf_processerror() if we fail. Notice also that we now have an expicit return statement and we use the Status() accessor to know wheter we have failed or not (all of this is plumbing for the case when we have multiple checks in any one validation process)

The R2 case is more interesting. There we have a [very simple] repeating field case. Lines 7.5 and 7.6 define the auxiliary loop and working buffer, respectively. Note the size of the idx array, since we only have one level of nesting. Line 7.7 defines the first (and only in this example) section name variable which we use in 7.8 to find out the number of rows we have in this section. 7.8 also defines the loop (notice we use the first element of the index vector to loop through, this is convenient as we will see later). 9.1 and 9.2 create the rvn_0 variable, which will change depending on the row we are currently on. 9.3 does the actual check (note that invar_0 had been loaded upon entering this function and, since it doesn't change on every iteration, we do not need to refresh it -this will change when we do external validations-). Finally, note that 9.4 calls rf_processerror() but it now passes the index vector so that the function knows which row has failed (there might be certain rows that fail and certain that don't, so we need this information)

External Validation (and all the problems it brings)

An external validation check is just what it name implies, namely a call to a (potentially remote) database to check some value in a certain table. These validations are extremely expensive in terms of execution time, since they are totally IO-bound and, furthermore, their IO is non-local (ie. gateways need to be crossed, parameters needs to be marshaled and unmarshaled, mainframe sessions need to be contacted, etc).

If we were to tread external validations as any other kind of 'support' function, the runtime penalty we would be imposing on the system would be simply too much. Needless to say, the only time we need special consideration is when we have BOTH repeating fields AND external validations (ie. the problem we are trying to optimize is collapsing many external connections from the same rule into one). If we have just one validation then we're all set. However, many times we have repeating fields and external validation. Things of the sort:

R5: always externalvalidate(tbhit, lsr.*.ftr);

which in English might mean "do a table hit for each feature (ftr) in the lsr form and make sure the feature specified is a valid one". The brute force approach (which incidentally works perfectly well for CPU-bound cases) would yield (in pseudocode, of course :-) :

...

for (idx[0]=0; idx[0] < form->GetRowCount( section_0 ); idx[0]++) {

fieldname = "lsr." + int_to_string(idx[0]) + ".ftr";

if ( ! rf_extval(form, "tbhit", fieldname) ) rf_processerror(idx, 1);

}

...

Now, if we have 10 features, we would be doing a remote connection every time and would thus take a long amount of time to complete. The idea is therefore to bulk all the fields together, and send them once, have the back-end process it, and when the results are receive demodulate them and call rf_processerror(...) for those that failed. One way of doing this is decoupling setting-up and execution. Therefore, we decide that rf_extval(..) would simply collect all the fields that need be checked without acting on them, whereas a new function, rf_doextval() will actually execute all of them at once, and handle error-setting once the results are received.

The above code would thus look like:

...

for (...) {

fieldname = ...

if ( ! rf_extval(form, "tbhit", fieldname) ) ; // we do nothing

}

rf_doextval();

...

With this solution, we can optimize the communication overhead. Note that we keep the if (...) syntax even though we do nothing in the action part. This is pure convenience to keep the code generator the way it is (getting rid of the if is easy, but adding control logic to handle multiple conditions linked by and or or is not so easy! :-)

Changes to the Class Structure

These were minimal. They are noted in the code that follows:

// ****************************************************************
//  eelc.cd - Class Dictionary for EEL 
//  ---------------------------------------------------------------
//  Copyright (c) 1997 - GTE Laboratories Incorporated
//                       40 Sylvan Road
//                       Waltham, MA 02254
//  All rights reserved.
//  ---------------------------------------------------------------
//  this file defines the class structure of an eel program. it is
//  used by the Demeter/Java system to generate a hierarchy of Java
//  classes on which we later execute traversals. note that the
//  different classes are annotated with specific syntax to facili-
//  tate parsing by the system. please see 
//    http://www.ccs.neu.edu/home/lieber
//  for more information on Demeter/Java
//  ---------------------------------------------------------------
//  date      who       what
//  10-30-97  lblando   created
//  11-11-97  lblando   added minimum changes (new visitor) to 
//                      support full-blown code generation
// ****************************************************************
        
(@
  import java.util.*;
  import java.io.*;
@)

// an entire eel program is just a list (possibly empty) of
// issue declarations
CompilationUnit         = IssueList .
IssueList               ~ {IssueDeclaration} .

// an issue declaration starts with "issue", is followed by
// a string (the name), an optional integer, and the body.
IssueDeclaration        = "issue" <name> String
                                [ <num>  Integer ]
                                         IssueBody .

// an issue body is enclosed in braces and contains one
// metaset and a list of rulesets
IssueBody               = "{"  <metaset>  MetaSet
                               <sets>     RuleSetList "}" .

// a metaset starts with "meta", is enclosed in braces, and
// contains a list of metasetrules.
MetaSet                 = "meta" MetaSetBody .
MetaSetBody             = "{" <rules> MetaRuleList "}" .

// a ruleset starts with "set" and has a name, then
// a brace and a list of rules followed by another brace
RuleSetList             ~ {RuleSet} .
RuleSet                 = "set"  <name> Ident
                          "{"   <rules> RuleList  "}" .

// metarules cannot be spawned, have a name, then an IfCmd (the
// only one possible) and then the do list.
MetaRuleList            ~ {MetaSetRule} .
MetaSetRule             = <name> Ident ":" 
                          <cmd>  IfCmd
                          "do" "(" <setlist>  NameList ")" ";" .

// simple rules can either be spawnable or not (i resorted to this
// admitedly involved design) after i unsuccessfully tried to parse
// the _optional_ "p" in front of _every_ rule.
RuleList                ~ {Rule} .
Rule                    : RuleSetRule | SpawnableRuleSetRule .
//                           *common* <name> Ident ":" <cmd> Command ";" .
// while it would make sense to leave the above line in, demjava
// complains when JavaCC processes grammar.jj. so we need to manually
// flatten the class graph in this part and replicate the above line
// in the subclasses.
                             
// as we see, the only difference between these next two is the 'p' 
// tag in front of the threaded ones...
// (notice how we replicate the information that more nicely fits in the
//  superclass)
RuleSetRule             =     <name> Ident ":" <cmd> Command ";" .
SpawnableRuleSetRule    = "p" <name> Ident ":" <cmd> Command ";" .

// we only have two types of commands at the moment. always and if. 
// always means that the expression must evaluate to true all the
// time and if means that, should the precondition evaluate to true,
// then the postcondition must also evaluate to true. if the 
// precondition evaluates to false, then the result of the rule is
// true (ie. did not trigger). 
Command                 : IfCmd 
                        | AlwaysCmd .
IfCmd                   = "if"      <pre_exp>  CondExpression
                          "then"    <post_exp> CondExpression . 
AlwaysCmd               = "always"  <exp>      CondExpression .

// adding parenthesized expressions. note that parentheses are not
// needed when we have one function only, i.e.:
//   if empty(lsr.act) then required(lsc);
// but MUST be present whenever we have boolean operators, ie:
//   if ( empty(lsr.act) and notempty(lsr.cc) ) then required(lsc);
ParenExpression         = "(" Expression ")" .
Expression              = CondOrExpression .
CondOrExpression        ~ CondAndExpression { "or"  CondAndExpression } .
CondAndExpression       ~ CondExpression    { "and" CondExpression    } .

// when we get to the bottom-level, an expression is either a 
// simplecondition (ie: one or two fields required), a multivalued
// condition (ie: a field and a list of values), or a parenexp to
// allow for nested parenthesized expressions.
CondExpression          : SimpleCondition 
                        | MultiValuedCondition 
                        | ParenExpression .

// a simplecondition is either SingleValued (ie: one field), or
// a comparison condition (two fields)
SimpleCondition         : SingleValuedCondition 
                        | ComparisonCondition .
// LRB                  | ExtValCondition .

// there are two types of multivaluedconditions: the externalvalidate
// and everything else.
MultiValuedCondition    : FieldAndListCondition | ExtValCondition .
// LRB                    InCond | NotinCond LengthCond ...

// 'everything else' is in(), notin(), or length(). 
// LRB
FieldAndListCondition   : InCond | NotinCond | LengthCond
                           *common* "(" <field> Field  ","
                                        <vlist> StaticValueList ")" .

// the ones that only require one field are empty(), notempty(), etc
SingleValuedCondition   : EmptyCond 
                        | NotemptyCond 
                        | RequiredCond 
                        | ProhibitedCond 
                        | IstimeCond 
                        | IsdateCond 
                        | IsdatetimeCond 
                        | IstimerangeCond 
                           *common* "(" <field> Field ")" .

// to parse the comparison conditions we need to use the lookahead feature of
// JavaCC-Demeter/Java, since they have infix notation. furthermore, i need to
// use the symbolic version of the lookahead (instead of a fixed offset) since
// fields can be arbitrarily long.
ComparisonCondition     :   *lookahead* (@_EQCond() @) EQCond 
                        | *lookahead* (@_NEQCond()@) NEQCond 
                        | *lookahead* (@_GTCond() @) GTCond 
                        | *lookahead* (@_LTCond() @) LTCond 
                        | *lookahead* (@_GEQCond()@) GEQCond 
                        | *lookahead* (@_LEQCond()@) LEQCond .

// the definition of an externalvalidate condition is straightforward
ExtValCondition         = ExtValCond "(" <id>    Ident "," 
                                         <flist> FormFieldList ")" .

// all the terminals go here. notice the definition of the comparison
// functions and how they all start with the same form. that is why we
// need the lookahead there.
LengthCond              = "length" .
ExtValCond              = "externalvalidate" .
InCond                  = "in" .   
NotinCond               = "notin" .
EmptyCond               = "empty" .
NotemptyCond            = "notempty" .
RequiredCond            = "required" .
ProhibitedCond          = "prohibited" .
IstimeCond              = "istime" .
IstimerangeCond         = "istimerange" .
IsdateCond              = "isdate" .
IsdatetimeCond          = "isdatetime" .
EQCond                  = <field1> Field "==" <field2> Field .
NEQCond                 = <field1> Field "!=" <field2> Field .
GTCond                  = <field1> Field ">"  <field2> Field .
LTCond                  = <field1> Field "<"  <field2> Field .
GEQCond                 = <field1> Field ">=" <field2> Field .
LEQCond                 = <field1> Field "<=" <field2> Field .

// defining a field was interesting, since fields are dotted lists of
// Idents, which would possible contain numbers, stars (*), and/or
// indices. we need a fixed lookahead to determine between a FormField
// (ie. lsr.blah.blah) and a StaticValue (ie, "hello", 4, 12121). 
Field                   : *lookahead* (@3@) FormField 
                        | *lookahead* (@3@) StaticValue .

// a formfield has a name and might have indices.
FormField               = <name> FieldName [ <idx>  Indices ] .

// an index must have a starting position but the end position is optional
Indices                 = "[" <first> Integer [".." <second> Integer ] "]" .

// a fieldname must have at least one part, and possibly more after a '.'
FieldName               ~ FieldNamePart { "." FieldNamePart } .

// each part of a fieldname is either an ident, a number, or a star
FieldNamePart           : FieldNamePartIdent 
                        | FieldNamePartIndex 
                        | FieldNamePartStar .

// the next two are trivial...
FieldNamePartIdent      = <part_name>  Ident .
FieldNamePartStar       = "*" .

// for numbers in the naming, we require the prescence of the '#'
// character in front of the number. for instance, the lna value of the
// second row of the service details section in the resale form is
// denoted by rsl.sd.1.lna. however, in eel we require that this be
// written as: rsl.sd.#1.lna). this is a very minor change to eel that
// makes the parser simpler (note: as of now, there are NO rules that use
// specific row numbers in them, and their use is not foreseen)
FieldNamePartIndex      = "#" <part_index> Integer .

// a nonempty list of FormFields, comma-separated
FormFieldList           ~ FormField { "," FormField } .

// a comma-separated, nonempty, list of Idents
NameList                ~ Name { "," Name } .
Name                    = <name> Ident .

// staticvalues are used in both fieldandlistconditions and
// comparisonconditions. we define a small hierarchy here to
// correctly parse them.
StaticValueList         ~ StaticValue { "," StaticValue } .
StaticValue             : StaticValueInt | StaticValueString .
StaticValueInt          = <value> int .
StaticValueString       = <value> String .

// END OF CLASS STRUCTURE
// ********************************************************************
// the following section describes the behavior-dependant classes
// they are included here for simplicity.
// ********************************************************************

// the visitor superclass. all others extend eelvisitor. traversals
// are defined in terms of an eel visitor.
// 11-11-97 - refined the semantics of each of the buffers as follows:
//  - buffer:  the running buffer where we put our commands as we move
//    along the parse tree.
//  - varbuff: the variables buffer where we put variable declarations
//    or definitions, loading, etc. this buffer gets placed right 
//    after the member function (ie. Validate()) has started.
//  - repbuff: repetition buffer. used to create/load/set variables
//    on EACH iteration when repeating fields are present. this buffer
//    gets inserted after the last loop has been opened but before the
//    code for the command is placed. critical in the generation of
//    code for repeating fields.
EELVisitor              : RuleConstructorVisitor 
                        | HeaderVisitor 
                        | ExpressionVisitor 
                        | ValidateVisitor 
                        | CommandVisitor 
                        | MetaValidateVisitor 
                        | ParserVisitor
                        | RepeatingFieldsVisitor
                            *common*  <buffer>  String 
                                      <varbuff> String
                                      <vardecl> String  
                                      <repbuff> String .

// the expression visitor is the one that does most of the complicated
// work when generating code for an expression. it contains state variables
// to maintain the indices (if present) so that code generation can use 
// them later in the traversal.
ExpressionVisitor       = "EV" <idx1> int <idx2> int <repeatingmaxlevel> int .

// commandvisitor knows how to generate code of the two commands (if and
// always). it uses the result of the expressionvisitor to generate the
// whole expression.
CommandVisitor          = "CV" <ev> ExpressionVisitor .

// the validatevisitor, on the other hand, uses a commandvisitor to fill in
// the body of the member function, but knows how to generate the 'shell'
ValidateVisitor         = "VV" <cmd> CommandVisitor .

// these one is the ValidateVisitor for metarules. notice we reuse the
// other two (command and expression). this _might_ be a good point in
// favor of the "use many visitors vs. one" argument.
MetaValidateVisitor     = "MVV" <cmd> CommandVisitor.   

// this one just knows how to do the constructors, notice that in this case,
// there's only one visitor for both simple rules and meta rules, and the 
// visitor itself knows what to do for each. (this, on the other hand, would
// be an example of the "use only one visitor instead of many" approach :-)
RuleConstructorVisitor  = "RCV" .

// same argument as above applies for the visitor that generates the header
// file. it is one and knows how to 'switch' between them
HeaderVisitor           = "HV"  .

// 11-11-97 this visitor is used to prepare each command for the repeating 
// fields
RepeatingFieldsVisitor  = "RFV" <levels>   int 
                                <maxlevel> int .

// with the parservisitor i used yet another approach and had just one 
// visitor to do everything (both header and cpp files). this visitor is,
// admitedly, simpler than the ones before (although more tedious to write)
ParserVisitor           = "PV"  <header> String 
                                <body>   String .

// the class that starts it all... :-)
Main = .
// ****************************************************************

Changes to the Behavior

These were not at all minimal. The behavior file, heavily commented, follows (search for 11-11-97 for comments on the changes):

// ****************************************************************
//  eelc.beh - Behavior Description for EEL compiler
//  ---------------------------------------------------------------
//  Copyright (c) 1997 - GTE Laboratories Incorporated
//                       40 Sylvan Road
//                       Waltham, MA 02254
//  All rights reserved.
//  ---------------------------------------------------------------
//  this file describes the different traversals used as well as
//  the behavior of the visitor objects, which cooperate to gene-
//  rate the output code. the language is Demeter/Java. for more
//  information about Demeter/Java please consult:
//   http://www.ccs.neu.edu/home/lieber
//  ---------------------------------------------------------------
//  date      who       what
//  10-30-97  lblando   created
//  11-12-97  lblando   redid to generate execution code and not
//                      rely on complex support functions
//  11-20-97  lblando   included optimizations to load vectors on
//                      construction and not at runtime
// ****************************************************************

// once we've parsed an .eel file, we get a huge object graph. the root of this
// graph is the CompilationUnit object. i'll define all the traversals starting from
// here. note that traversals is the only behavior we give this class.
CompilationUnit {
 // the allRules traversal is used to do the header file. we go to every rule object
 // in the graph but no further (since we don't need to for the class declarations) 
 traversal allRules(EELVisitor)         
   { to {MetaSetRule, SpawnableRuleSetRule, RuleSetRule};  }

 // when we are doing the constructor's code, we need to differentiate between
 // metarules and normal rules. constrMeta() and constrOther() are two traversals
 // that do just that. constrMeta() goes all the way down to the Name class, but
 // making sure we've gone through the MetaSetRule object (that is, we are in a 
 // meta rule) we need to get down to the Name class beacause, for meta rules, we
 // need to generate the "meta_list.push_back(<<Name>>);" lines in the constructor.
 // for the rest of the rules, on the other hand, we do not need to go that further
 // and constrOther stops at the SpawnableRuleSetRule or RuleSetRule objects. 
 traversal constrMeta(EELVisitor)       
   { via MetaSetRule to Name; }
 traversal constrOther(EELVisitor)      
   { to {SpawnableRuleSetRule, RuleSetRule}; }

 // viaRules and viaMetaRules are used in the generation of the Validate() and 
 // MetaCheck() functions. note that we carry three visitors in our traversals
 // to perform the work. the same argument for separating metarule vs. other rule
 // traversals as before applies here as well.
 traversal viaRules(ValidateVisitor,CommandVisitor,ExpressionVisitor) 
   { bypassing MetaSetRule to *; }
 traversal viaMetaRules(MetaValidateVisitor,CommandVisitor,ExpressionVisitor) 
   { bypassing Rule to *; }
}

// toStaticValues is used to load up the vector of values for 'in' and 'notin'
// commands. used for putting all the loading in the constructor 
Rule {
 traversal toStaticValues(ValidateVisitor,CommandVisitor,ExpressionVisitor)
   { to {StaticValueInt,StaticValueString}; }
}
MetaSetRule {
 traversal toStaticValues(ValidateVisitor,CommandVisitor,ExpressionVisitor)
   { to {StaticValueInt,StaticValueString}; }
}


// we put some basic behavior in the abstract visitor superclass.
EELVisitor {
  init   (@ buffer = ""; @)
  // the following two lines are here because of a glitch in Demeter/Java
  // (version 0.6.3) that requires empty bodies for superclass visitors in
  // order to execute the traversal properly.
  before {CompilationUnit,RuleSet,IssueDeclaration,MetaSetRule,RuleSetRule,
          SpawnableRuleSetRule,Name,FieldNamePartIndex,FieldNamePartIdent,
          FieldNamePartStar,FieldName,Command} (@  @)
  after  {CompilationUnit,RuleSet,IssueDeclaration,MetaSetRule,RuleSetRule,
          SpawnableRuleSetRule,Name,FieldNamePartIndex,FieldNamePartIdent, 
          FieldNamePartStar,FieldName,Command} (@  @)

  // a simple auxiliary function to help all visitors pretty print the 
  // code. note i use a default of 3 spaces per identation level
  public String tabs(int i) (@
     String buf = new String();
     for (int a=0; a < i*3; a++) buf += " ";
     return buf;
  @)
}

// HeaderVisitor --------------------------------------------------------------
// this visitor's responsibility is the creation of the .h file
HeaderVisitor {
  // auxiliary function to share code... :-)
  void common_part(String name)
  (@
     buffer += "class " + name + " : public Rule {\n";
     buffer += " public:\n";
     buffer += "   " + name + "();\n";
     buffer += "   virtual bool Validate(Form*);\n";
   @)

  // if we are in a meta rule, then we need to declare MetaCheck() and
  // the meta_list member variable
  before MetaSetRule (@
     common_part(host.get_name().toString());
     buffer += "   virtual bool MetaCheck(Form*);\n";
     buffer += " private:\n";
     buffer += "   static list<string> meta_list;\n";
     // find all the local variables and define them
     ValidateVisitor   vv = new ValidateVisitor();
     CommandVisitor    cv = new CommandVisitor();
     ExpressionVisitor ev = new ExpressionVisitor();
     vv.set_cmd( cv );
     cv.set_ev( ev );
     host.toStaticValues(vv,cv,ev);
     Vector localvars = ev.get_localvars();
     for(int i = 0; i < localvars.size(); i++) {
        String varname = (String)localvars.elementAt(i);
        buffer += "   static vector<string> " + varname + ";\n";
     }
     buffer += "};\n";
   @)

  // on any other rule, we just need Validate()...
  before {RuleSetRule, SpawnableRuleSetRule}  (@
     common_part(host.get_name().toString());
     // find all the local variables and define them
     ValidateVisitor   vv = new ValidateVisitor();
     CommandVisitor    cv = new CommandVisitor();
     ExpressionVisitor ev = new ExpressionVisitor();
     vv.set_cmd( cv );
     cv.set_ev( ev );
     host.toStaticValues(vv,cv,ev);
     Vector localvars = ev.get_localvars();
     if ( localvars.size() > 0 )
        buffer += " private: \n";
     for(int i = 0; i < localvars.size(); i++) {
        String varname = (String)localvars.elementAt(i);
        buffer += "   static vector<string> " + varname + ";\n";
     }
     buffer += "};\n";
   @)
}



// RuleConstructorVisitor ------------------------------------------
// used to create the code for all the constructors 
RuleConstructorVisitor {
  (@ 
     ValidateVisitor vv;
     CommandVisitor  cv;
     ExpressionVisitor ev;
   @)
  init (@ 
     vv = new ValidateVisitor();
     cv = new CommandVisitor();
     ev = new ExpressionVisitor();
     vv.set_cmd( cv );
     cv.set_ev( ev );
  @)

  void common_part(String name, Rule host) (@
     // find all local variables, define them
     host.toStaticValues(vv,cv,ev);
     Vector localvars = ev.get_localvars();
     for(int i = 0; i < localvars.size(); i++)
       buffer += "vector<string> " + name + "::" + (String)localvars.elementAt(i) + ";\n";     
     buffer += name + "::" + name + "() : Rule() {\n";
     buffer += "   Name(\"" + name + "\");\n";
     // this will come loaded with all the ".push_back" commands
     // needed to load the variable(s)
     String varbuff = cv.get_varbuff();
     if ( varbuff != "" ) buffer += varbuff;
   @)

  void common_part2(String name, MetaSetRule host) (@
     // same as above for metarules (todo: abstract away)
     host.toStaticValues(vv,cv,ev);
     Vector localvars = ev.get_localvars();
     for(int i = 0; i < localvars.size(); i++)
       buffer += "vector<string> " + name + "::" + (String)localvars.elementAt(i) + ";\n";     
     buffer += name + "::" + name + "() : Rule() {\n";
     buffer += "   Name(\"" + name + "\");\n";
     String varbuff = cv.get_varbuff();
     if ( varbuff != "" ) buffer += varbuff;
   @)

  // for a simple rule, common_part(...) is all we need!
  before RuleSetRule (@
     common_part(host.get_name().toString(), host);
     buffer += "}\n";
   @)

  // for spawnable rules, we need to set the spawn state variable
  // to true, so we do it here.
  before SpawnableRuleSetRule (@
     common_part(host.get_name().toString(), host);
     buffer += "   Spawn(true);\n";
     buffer += "}\n";
   @)

  // when we are entering a meta rule, we start just like any other rule,
  // and then we set the Meta() flag. note that we do not 'close' the function
  // since we still have not loaded the meta_list variable (because we haven't
  // traversed it yet!)
  before MetaSetRule (@
     buffer += "list<string> " + host.get_name().toString() + "::meta_list;\n";
     common_part2(host.get_name().toString(), host);
     buffer += "   Meta(true);\n";
   @)
  
  // when we are exiting a meta rule, we know that the meta_list variable is
  // loaded so we can call SetList() and close out the function.
  after MetaSetRule (@
     buffer += "   SetList(&meta_list);\n";
     buffer += "}\n";
   @)

  // we will only get to a name object when we are traversing metarules (note: this
  // is not a good approach, i know, but is here to show how NOT to do it :-) in 
  // other words, we know (here) that the only time we will get to a Name object is
  // in the traversal that does meta rules ONLY. we can then just output the code
  // necessary for the meta case and not do any checks. but were this visitor used
  // with some other traversal, the whole thing might fall apart!. maybe this is a
  // point in favor of the notion of making traversals and visitors self-contained
  // (inside a visitor) instead of splitting them, since they seem to be somewhat
  // tightly coupled.
  before Name (@
     buffer += "   meta_list.push_back( \"" + host.get_name().toString() + "\" );\n";
   @)
}



// ValidateVisitor ---------------------------------------------------------------
// this handles the creation of the Validate() function. it is not used with the
// metarules. (once again, the visitors and traversals, tigthly coupled in behavior
// yet not in Demeter :-)
ValidateVisitor {
  // 11-11-97 - as part of the overall revamping of the compiler, we now must know
  // if we are dealing with an externalvalidate() rule or not, for if we are, we
  // need to call rf_doextval(); just before we return (optimization). this boolean
  // variable keeps track of just that.
  (@ boolean in_extval; @)

  // once we get to a rule, we do another little traversal from here to
  // come up with the .eel representation of this rule *only* and we put it
  // as a comment just before we generate the c++ code 
  before {RuleSetRule,SpawnableRuleSetRule} (@
     java.io.StringWriter sw = new java.io.StringWriter();
     java.io.PrintWriter  pw = new java.io.PrintWriter( sw );
     PrintVisitor         pv = new PrintVisitor( pw );
     host.universal_trv0( pv );
     buffer += "// " + sw + "\n";
     buffer += "bool " + host.get_name().toString() + "::Validate(Form* form) {\n";
     buffer += "   ClearIndices();\n"; // 11-11-97 set this rule as 'clean'
     in_extval = false;                // 11-11-97 and reset this flag as well
   @)

   // 11-11-97 - signal that we are in a rule that has an externalvalidate() in it
   before ExtValCondition (@ in_extval = true; @)

  //closing a rule is simple, just add a brace 
  after {RuleSetRule,SpawnableRuleSetRule} (@
     buffer += "}\n\n";
   @)

  // the command visitor (cmd) is clever enough to always return bool so all we
  // need to do at this level is return that value (note that we do so AFTER the
  // traversal has gone 'inside' the command. also, note that before we output
  // the command code, we need to check if there are any variable initialization
  // code present and, if so, we place it before the command code.
  after {AlwaysCmd,IfCmd} (@
     //String vars = cmd.get_varbuff();
     //if ( vars != "" ) buffer += vars;
     buffer += cmd.get_buffer();
     if ( in_extval ) buffer += "   rf_doextval(form);\n"; // 11-11-97 - added this
     buffer += "   return Status();\n";
   @)  
}

// MetaValidateVisitor --------------------------------------------------------
// serves the same function as ValidateVisitor but is used on meta rules only
MetaValidateVisitor {
  // 11-11-97 - see comments in ValidateVisitor about in_extval 
  (@ boolean in_extval; String host_name; @)

  // once we get to a rule, we do another little traversal from here to
  // come up with the .eel representation of this rule *only* and we put it
  // as a comment just before we generate the c++ code 
  before MetaSetRule (@
    java.io.StringWriter sw = new java.io.StringWriter();
    java.io.PrintWriter  pw = new java.io.PrintWriter( sw );
    PrintVisitor         pv = new PrintVisitor( pw );
    host.universal_trv0( pv );
    buffer += "// " + sw + "\n";
    host_name = host.get_name().toString();
    buffer += "bool " + host_name + "::MetaCheck(Form* form) {\n";
    buffer += "   ClearIndices();\n"; // 11-11-97 - added
    in_extval = false;                // 11-11-97 - added
  @)

  // 11-11-97 - added to keep track (really only for consistency and 
  // symmetry, since in reality we'll never have a meta-rule with an
  // externalvalidate() command!! )
  // if we do, however, we need to print this to the user since it
  // is an error in the eel and needs to be corrected
  before ExtValCondition (@ 
    in_extval = true; 
    System.out.println("  --> FATAL ERROR: externalvalidate() command found in a meta");
    System.out.println("  --> rule. This is not allowed. Please remove and recompile.");
    System.out.println("  --> The rule is: " + host_name);
    System.out.println("  --> (compilation will proceed to try and detect more errors)");
  @)

  // 11-18-97 we do not allow repeating fields in meta rules also, so we
  // need to check here for this case as well
  before FieldNamePartStar (@
    System.out.println("  --> FATAL ERROR: repeating field (starred) found in a meta");
    System.out.println("  --> rule. This is not allowed. Please remove and recompile.");
    System.out.println("  --> The rule is: " + host_name);
    System.out.println("  --> (compilation will proceed to try and detect more errors)");
  @)
  
  // an if command is tricky, since we need to gain control after the first 'leg'
  // of the if (precondition) has been traversed. at this point we close the
  // MetaCheck() function and start the Validate() one (notice we clear the
  // commandvisitor's buffer before moving on, so we get a clear postcondition
  // later). also note the conditional inclusion of [optionally present] variable
  // definition code before the command code.
  after -> IfCmd,pre_exp,CondExpression (@
    //String vars = cmd.get_varbuff();
    //if ( vars != "" )  buffer += vars;
    buffer += cmd.get_buffer();
    buffer += "      return true;\n";
    buffer += "   return false;\n";
    buffer += "}\n";
    cmd.set_buffer( "" );
    cmd.set_varbuff( "" );
    buffer += "bool " + host_name + "::Validate(Form* form) {\n";
    buffer += "   ClearIndices();\n";  // 11-11-97 - set rule as clean
  @)

  // leaving a meta rule means generating the command code for the 'then' 
  // part of an if command (which we have already traversed). so we check
  // to see if we have any local variables to initialize and then we put 
  // the command code there.
  after MetaSetRule (@
    //String vars = cmd.get_varbuff();
    //if ( vars != "" ) buffer += vars;
    buffer += cmd.get_buffer();
    if ( in_extval ) buffer += "   rf_doextval(form);\n"; // 11-11-97 added
    buffer += "   return Status();\n";
    buffer += "}\n\n";
  @)
}

// RepeatingFieldsVisitor -----------------------------------------------
// 11-11-97 - added this visitor to do a small traversal from the node
// we are currently at. notice i could have used universal_trv0 to carry
// this visitor around (it would have worked just fine), but i've defined
// a couple of specific traversals so that we only go where we need to and
// nothing more (hoping this will speed up the process)
//
// this visitor is in charge of filling in all the control structure
// when repeating fields (i.e. starred) fields are present.
RepeatingFieldsVisitor {
  // several local/private data members are necessary
  (@
     Vector index_names;
     String current_index_name;
     Vector temp_vec;
     boolean dot_flag;
   @)

  // on initialization, create the structures and set count to 0
  init                     (@ 
    levels = 0; 
    current_index_name = new String();  
    index_names = new Vector();
  @)

  // as we enter a command, make sure we reset the count of what's
  // the maximum level of repeating fields ANY field in the cmd has
  before Command           (@ 
    maxlevel = 0; 
  @)

  // on entering a field name, clear out all the variables and make
  // space for a new vector of values...
  before FieldName         (@ 
    levels = 0;
    dot_flag = false;
    current_index_name = "";
    temp_vec = new Vector();
  @)

  // we've got to a star, check to see if we have this element (we
  // should not) and then insert the current prefix into that slot
  // clear the running count of the name and update other vars
  before FieldNamePartStar (@ 
    try {
      String p = (String)(temp_vec.elementAt(levels));
    }
    catch (ArrayIndexOutOfBoundsException e) {
      temp_vec.insertElementAt((Object)(new String(current_index_name)), levels);
    }
    levels++;     
    dot_flag = false;
    current_index_name = "";
  @)

  // we are exiting a field name, this is the time to update the maxlevel
  // variable. notice that if we have indeed found a field that has more
  // nesting levels than any previous one in this current command, then 
  // we need to use THAT vector of section names to get the row counts for
  // the nested for iterations, so we replace the ENTIRE vector and not just
  // add the 'extra' sections to the end of the current one!
  after  FieldName         (@ 
    if (levels > maxlevel) { 
      maxlevel = levels; 
      index_names = temp_vec; // always use the longest field to find counts...
    }
  @)

  // we are in a part, so simply add it to the running name
  before FieldNamePartIdent (@
    if (dot_flag) current_index_name += ".";
    dot_flag = true;
    current_index_name += host.get_part_name().toString();
  @)

  // accessor for outside classes to use
  public String get_section_name(int i) (@ 
    if (i < index_names.size()) 
      return (String)index_names.elementAt(i); 
    else
      return new String();
  @)

}

// traversals for repeating fields -----------------------------------
// the following two traversals are required for the Command and
// FieldName classes (as said above, i could've used the universal
// traversal to carry the RepeatingFieldsVisitor but i think these two
// (smaller) traversals will be faster). we do it twice, once from 
// the command object and once we get to a field. (this is like a 
// 'lookahead' of the object graph! :-)
Command {
  traversal repeatingFields(EELVisitor)
    { to {FieldNamePartIdent,FieldNamePartIndex,FieldNamePartStar}; }
}
FieldName {
  traversal repeatingFields(EELVisitor)
    { to {FieldNamePartIdent,FieldNamePartIndex,FieldNamePartStar}; }
}

// CommandVisitor ------------------------------------------------------------
// handles the traversal at the command level. since there are
// only two commands: 'if' and 'always', it's job is very simple. is knows how
// to handle metarules by not adding logic in between the pre and post conditions 
CommandVisitor {
  (@  
     boolean in_metarule = false, is_repeating = false; 
     RepeatingFieldsVisitor rfv;
     String aux_buffer;
     boolean in_extval;
   @)

  // in order to handle metarules, we need to make sure we know when we are
  // processing one of them. the flag 'in_metarule' will tell us so 
  before MetaSetRule (@ in_metarule = true;  @)
  after  MetaSetRule (@ in_metarule = false; @)

  // 11-11-97 we need to keep track of this now, as externalvalidate()s
  // are tricky because of the special optimization they need
  before ExtValCondition (@ in_extval = true; @)

  // before starting any command, we make sure to clear our own buffer as
  // well as the buffer of the expression visitor we contain. we also
  // use the repeating fields visitor to check this command and set the
  // repetingmaxlevel in the expression visitor and the is_repeating flag
  before {AlwaysCmd,IfCmd} (@   
     in_extval = false;
     buffer = ""; varbuff = ""; 
     ev.set_buffer("");  ev.set_varbuff("");
     rfv = new RepeatingFieldsVisitor();
     host.repeatingFields( rfv );          
     is_repeating = (rfv.get_maxlevel() != 0);
     ev.set_repeatingmaxlevel( rfv.get_maxlevel() );
  @)
  
  // an always command is very simple, since the boolean result will
  // already be present in the result of the expression visitor, so 
  // we simply set our buffer to that. 
  // 11-11-97 - added code to open and close the loops that we need
  // in case we are in the prescence of repeating fields. also note
  // that there's special handling of the externalvalidate() case since
  // we do not call rf_processerror() with it (for externalvalidate
  // calls are buffered and then only once only to reduce communication
  // overhead -see elsewhere-)
  after AlwaysCmd (@   
     varbuff += ev.get_varbuff(); 
     if ( is_repeating )  PrepareLoops();
     buffer += ev.get_repbuff();
     buffer += tabs(rfv.get_maxlevel()+1);
     buffer += "if ( ! " + ev.get_buffer() + " ) ";
     if (!in_extval) {
        if ( is_repeating ) buffer += "rf_processerror((int**)&idx, " + rfv.get_maxlevel() + ");\n";   
        else                buffer += "rf_processerror();\n";
     }
     else buffer += ";\n"; // 11-11-97 simply close rf_extval(..) lines...
     if ( is_repeating ) CloseLoops();
  @)
  
  // the if command is a little bit more complex in that we need to add
  // some logic in between the pre and post_conditions. for this reason
  // we wait until the traversal has finished the precondition and add
  // the glue logic there. notice we only add the logic if we are not
  // inside a metasetrule. 
  // 11-11-97 - for if commands we need to prepare the loops here and
  // close them after we've traversed the post-condition, but the 
  // logic is exactly the same.
  after -> IfCmd,pre_exp,CondExpression (@ 
     varbuff += ev.get_varbuff();
     if ( is_repeating ) PrepareLoops();
     if (!in_metarule) aux_buffer  = tabs(rfv.get_maxlevel()+1) + "if ( " + ev.get_buffer() + " ) \n";
     else                  buffer  = tabs(rfv.get_maxlevel()+1) + "if ( " + ev.get_buffer() + " ) \n";
     ev.set_buffer("");
     ev.set_varbuff("");
  @)

  // after the postcondition has been generated, we add it to our buffer
  // and add some trailing logic to make the whole command a valid c++
  // one of the form 'A ? B : true'. note that we do not add logic if
  // we are on a meta rule, since these have the logic split into two
  // different functions (MetaCheck() and Validate()) instead of one
  // 11-11-97 - see comments above
  after -> IfCmd,post_exp,CondExpression (@ 
     varbuff += ev.get_varbuff();
     if (!in_metarule)
        buffer  += ev.get_repbuff() + aux_buffer + tabs(rfv.get_maxlevel()+1);
     buffer += "   if ( ! " + ev.get_buffer() + " ) ";
     if (!in_extval) {
        if ( is_repeating ) buffer += "rf_processerror((int**)&idx, " + rfv.get_maxlevel() + ");\n";
        else                buffer += "rf_processerror();\n";
     }
     else buffer += " ;\n"; // don't do anything after the if, it's moot anyways
     if ( is_repeating ) CloseLoops();
  @)

  // 11-11-97 - this piece of code prepares the loops necessary when dealing
  // with repeating fields. it makes use of the repeating fields visitor that
  // has been loaded before we get here.
  public void PrepareLoops() (@
     // idx will be used as loop index. it's a vector because we have nested loops
     buffer += "   int idx[" + (new Integer(rfv.get_maxlevel())).toString() + "];\n";
     // buff will be used to store the repeating field name, that is, to form a
     // field name with the current valude of the idx entry that we're dealing with
     buffer += "   char buff[100];\n";
     // section will hold the current nesting level's prefix so that we can get the
     // number of rows that are present in the db. there's one per level, of course
     buffer += "   string section_0( \"" + rfv.get_section_name(0) + "\" );\n";
     for(int i=0; i < rfv.get_maxlevel(); i++) {
        String var = new String( "idx[" );
        var += (new Integer(i)).toString() + "]";
        buffer += tabs(i+1) + 
                  "for (" + var + "=0; " + var + " < form->GetRowCount( section_" + 
                  (new Integer(i)).toString() + " ); " + var + "++) {\n";
        if ( i < rfv.get_maxlevel()-1 ) {
          buffer += tabs(i+2) + "sprintf(buff, \"%s.%i." + rfv.get_section_name(i+1) + "\", section_" 
                              + (new Integer(i)).toString() + ".c_str(), idx[" + (new Integer(i)).toString() + "]);\n";
          buffer += tabs(i+2) + "string section_" + (new Integer(i+1)).toString() + "( buff );\n";
        }
     }
  @)

  // 11-11-97 - closeloops() is trivial, just put the appropriate amount of }s,
  // properly indented ! :-) 
  public void CloseLoops() (@
     for(int i=0; i < rfv.get_maxlevel(); i++) {
        buffer += tabs(rfv.get_maxlevel() - i) + "}\n";
     }
  @)

}

// ExpressionVisitor --------------------------------------------------------
// this is the lowest level (and hardest worker) visitor
// in the chain. its responsibility is to generate code for lower level
// expressions. each expression needs to evaluate to a boolean once finished.
// the expression visitor handles or's and's, parenthesis, and other niceties 
// 11-11-97 - as a result of the overhaul, this visitor has changed a bit. 
// we need to keep count of the repeating fields part, and generate code
// that's different for repeating and non-repeating fields. furthermore, we
// have to handle the externalvalidate() cases...
// we have added a new component, namely repbuff to the list of members. this
// buffer will hold any kind of code that needs to go at the innermost
// level of nesting (ie. inside all the 'for' loops). this is necessary so 
// that we can reuse whatever we had before and simply add a little bit of
// code to create variables and things like that
ExpressionVisitor {
  (@  
      boolean flag, condorflag, condandflag, svl_flag, in_extval; 
      int local_var_count;
      String current_var_name;
      String evstr;
      boolean is_repeating;
      int repeating_level;
      Vector repeating_parts;
      String current_repeating_part;
      int rvn_count;
      boolean clear_notdone;
      Vector localvars;
   @)

  Vector get_localvars() (@ return localvars; @)

  // make sure that we reset our or/and flags for the 
  // second expression in an 'if' command 
  before -> IfCmd, post_exp,CondExpression  (@    condorflag = condandflag = false;    @)

  // clean up the flags upon entering a command 
  before Command  (@ 
     repbuff = ""; rvn_count = 0; 
     local_var_count = 0; condorflag = condandflag = false;   
     localvars = new Vector();
  @)

  // wrap any parenthesized expression with "()"
  before ParenExpression  (@ buffer += "( "; @)
  after  ParenExpression  (@ buffer += " )"; @)

  //when we get to an OR node, we clean the or flag 
  before CondOrExpression (@ condorflag = false;   @)

  // on an AND node, we check the or flag and only add an '||' if it is
  // true (it will be the second time). we also clean the and flag 
  before CondAndExpression  (@      
      if ( condorflag ) buffer += " || ";
      condorflag = true; condandflag = false;
   @)

  // when we get to the condexpression, we check the and flag and only if
  // is true we put '&&' on the buffer (this to eliminate leading '&& a && b')
  // we also note that we have not reached an static-value and set the and 
  // flag to true 
  before CondExpression (@
      svl_flag = false;
      if ( condandflag )  buffer += " && ";
      condandflag = true;
      // use 'current_var_name' as a flag for SVL and Field to know where to go... 
      current_var_name = "";
      clear_notdone = true; // 11-11-97 need to maintain this for extvals+repeating
 @)

  // fieldname generation is a little tricky, since Demeter/Java does not
  // support primitive identifiers with dots in them (and eel identifiers have
  // dots in them). a single field is, therefore, a bunch of small parts which
  // we need to parse and output as a single unit (enclosed by quotes) 
  // 11-11-97 things are not so simple anymore. we first need to find out if
  // this guy is a repeating field or not. if it isn't, we still need to
  // buffer it away if we're dealing with an externalvalidate() (so that we
  // can add it to the vector later). if it is a repeating field, then we
  // create the support structures necessary to decompose it in its different
  // subsections as we traverse and reset the counts...
  before FieldName (@ 
    flag = false; 
    RepeatingFieldsVisitor rfv = new RepeatingFieldsVisitor();
    host.repeatingFields( rfv );
    is_repeating = rfv.get_maxlevel() != 0;
    if ( !is_repeating ) {
       if ( !in_extval ) buffer += "\""; 
       else            { evstr = new String(); 
                         evstr  += "\""; }
    }
    else {
       current_repeating_part = new String();
       repeating_parts = new Vector();
       repeating_level = 0;
    }         
  @)
  
  // close the quotes and exit... 
  // 11-11-97 - i wish! for a non-repeating field and non-extval command 
  // all we need to do is close the quotes, as it says above. if we are
  // in an extval command, on the other hand, we need to add evstr to the
  // current vector of strings, after having made sure that we've cleared
  // the vector (clearnotdone flag). remember that repbuff will be inside
  // all the loops so we need to be careful in not overflowing the vector!
  // for repeating fields, on the other hand, we need to to a bunch of
  // stuff: for starters, we need to create a new variable name to hold
  // the 'instantiated' variable name (with the current loop index) and
  // we need to generate code to load this variable with its value. we
  // also need to make sure that we handle extvals appropriately...
  after  FieldName (@ 
     if ( !is_repeating ) {
       if ( !in_extval ) buffer += "\""; 
       else {
         // non-repeating / extval case: clear the vector if not already done
         // and add evstr to it.
         evstr += "\"";
         if (clear_notdone) {
           repbuff += tabs(repeatingmaxlevel+1) + "vector<string> " + current_var_name + ";\n";
           clear_notdone = false;
         }
         repbuff += tabs( repeatingmaxlevel+1 ) + current_var_name + ".push_back( " + evstr + " );\n";
         evstr = "";
       }
     }
     else {
        // repeating case: create variable name based on count...
        String repeating_var_name = new String();
        repeating_var_name = "rvn_" + (new Integer(rvn_count)).toString();
        rvn_count++;
        // ...and load it with the different sections (form an sprintf command)
        repbuff += tabs( repeatingmaxlevel+1 ) + "sprintf(buff, \"";
        for (int i=0; i < repeating_parts.size(); i++) {
          repbuff += (String)repeating_parts.elementAt(i) + "%i";
        }
        repbuff += current_repeating_part + "\", ";
        for (int j=0; j < repeating_parts.size(); j++) {
          repbuff += "idx[" + (new Integer(j)).toString() + "]";
          if ( j < (repeating_parts.size()-1) ) repbuff += ", ";
        }
        repbuff += ");\n";
        // ... the sprintf() is finished, buff is ready. now create the
        // variable
        repbuff += tabs( repeatingmaxlevel+1 ) + "string " + repeating_var_name + "( buff );\n";
        // and see if this is an extval or not. if it is, we need to put the var in a vector
        // but if it isn't, we just need to output that varname to the main buffer!
        if (in_extval) {
           if (clear_notdone) {
             repbuff += tabs(repeatingmaxlevel+1) + "vector<string> " + current_var_name + ";\n";
             clear_notdone = false;
           }
           repbuff += tabs(repeatingmaxlevel+1 ) + current_var_name + ".push_back( " +repeating_var_name+ " );\n";
        }
        else buffer += repeating_var_name;
    }
  @)

  // only add a dot if we are on the second+ part of the name. note how
  // we differentiate between externalvalidate rules and others.
  // 11-11-97 - the problem now is 'where' to add the dot to! :-)
  before FieldNamePart (@ 
    if ( flag ) {
      if ( !is_repeating ) {
        if ( !in_extval ) buffer += "."; 
        else              evstr  += ".";
      }
      else                 current_repeating_part += ".";
    }
  @)

  // once we get to an 'Ident' leaf, we add it to the buffer and set the
  // flag to true to signal that we can add dots after this one if we
  // have to 
  // 11-11-97 same problem as to 'where' to add the data to as before
  before FieldNamePartIdent (@
    if (!flag) flag = true;
    if ( !is_repeating ) {
      if ( !in_extval )  buffer += host.get_part_name().toString();
      else               evstr  += host.get_part_name().toString();
    }   
    else current_repeating_part += host.get_part_name().toString();
   @)

  // if we've gotten to an index part (ie. lsr.ccna.#1.blah) then we need to
  // convert the index (number) to string and add it to the buffer 
  // 11-11-97 same problem as to 'where' to add the data to as before
  before FieldNamePartIndex (@
      StringBuffer b = new StringBuffer();
      b.append( host.get_part_index() );
      if (!is_repeating ) {
        if ( !in_extval ) buffer += b;
        else              evstr  += b;
      }
      else                current_repeating_part += b;
   @)

  // a star (repeating field) is simply passed 'as is' 
  // 11-11-97 - not anymore it isn't! :-) once we get here, we store 
  // current_repeating_part (ie. whatever we had of the fieldname from 
  // the last star until this one) into a new place in the vector of
  // repeating parts. this will be later used to form the loops. note
  // that we clear the running name (current_repeating_part) so that
  // we start clean from now on with the following section (if any)
  before FieldNamePartStar (@ 
     repeating_parts.insertElementAt(new String( current_repeating_part ), repeating_level);
     current_repeating_part = "";
     repeating_level++;
  @)

  // if we are in a form field, then we might have indices to denote a 
  // substring, in this case, we need to initialize the index values to
  // zero. if we are in an externalvalidate() expression, then we need
  // to add a ',' since we can have many formfields 
  before FormField  (@
    idx1 = 0; idx2 = 0;
  @)

  // after we have traversed an index section, we need to load the
  // visitor's idx1 and idx2 fields to remember this value for 
  // when the traversal exits this particular field. notice we only
  // load the idx2 field if it's there. otherwise it's a copy of
  // idx1 
  after Indices  (@
    idx1 = host.get_first().intValue();
    idx2 = idx1;
    if ( host.get_second() != null )
      idx2 = host.get_second().intValue();
   @)

  // when we exit the field, we need to add the indices. we do this
  // all the time except when we have a "static value" field (that is,
  // an immediate value such as "AA") or we are generating code for an
  // externalvalidate() function 
  after Field (@
    if (!svl_flag && !in_extval) {
      Integer i1 = new Integer( idx1 );
      Integer i2 = new Integer( idx2 );
      buffer += ", " + i1.toString() + ", " + i2.toString();
    }
  @)
 
  // if we are entering a multivaluedcondition, we need to create
  // a new local variable to put all the values there. so we do the
  // variable creation here (in current_var name) using the local_var_count
  // counter. we also add the variable declaration to the variable buffer
  before MultiValuedCondition (@   
    current_var_name = new String();
    current_var_name = "invar_" + new Integer( local_var_count ).toString();
    // varbuff += "   static vector<string> " + current_var_name + ";\n"; 
    localvars.addElement( new String( current_var_name ) );
  @)

  // notice we only increment the local_var_count upon exiting the 
  // multivalued condition, so that we know we have traversed the 
  // rule with the same value for the variable as was used to define the
  // local variable.
  after MultiValuedCondition  (@ local_var_count++; @)

  // generating code for the leaves is simple, this is just the prefix, though
  before EmptyCond        (@  buffer += "rf_empty(form, ";          @)
  before NotemptyCond     (@  buffer += "rf_notempty(form, ";       @)
  before IstimerangeCond  (@  buffer += "rf_istimerange(form, ";    @)
  before IstimeCond       (@  buffer += "rf_istime(form, ";         @)
  before IsdateCond       (@  buffer += "rf_isdate(form, ";         @)
  before IsdatetimeCond   (@  buffer += "rf_isdatetime(form, ";     @)
  before RequiredCond     (@  buffer += "rf_req(form, ";            @)
  before ProhibitedCond   (@  buffer += "rf_pro(form, ";            @)
  before InCond           (@  buffer += "rf_in(form, ";             @)
  before NotinCond        (@  buffer += "rf_notin(form,  ";         @)

  // length() is a little tricky
  before LengthCond       (@  
    buffer += "rf_length(form, "; 
    // length is an special case. even though it IS a multivalued condition for
    // convenience in parsing, we do not want to generate the vector<string>
    // so all we need to do here is flag that we are not generating it and
    // also clear the varbuff (which at this point is already loaded with the
    // value of the new variable name)
    current_var_name = ""; // flag that we have no variable
    set_varbuff( "" );     // clear varbuff 
    local_var_count--;     // decrement this (note: there's an inconsistent state
                           // since this statement executes until the increment 
                           // of the local_var_count happens again (upon exit from
                           // MultiValuedCondition). this violation of the invariant
                           // it harmless in this case
  @) 

  // some of the above we just close out with a ")" ...
  after  {EmptyCond, NotemptyCond, IstimeCond, IsdateCond, 
          IstimerangeCond, IsdatetimeCond, RequiredCond, ProhibitedCond} (@  
     buffer += ")";                       
  @)

  // some others, though, need to be apended with the name of the
  // local variable that we are using to load the multiple values
  after  {InCond, NotinCond, LengthCond} (@  
     buffer += current_var_name +")"; 
  @)

  // some housekeeping, to put commas where they're needed...
  // LRB  MultiValuedCondition below
  after  -> FieldAndListCondition,field,Field  (@ buffer += ", "; @)

  // control the svl_flag invariant
  before StaticValueList  (@ svl_flag = false; @)
  after  StaticValueList  (@ svl_flag = false; @)

  // when we get to a static value, in addition to knowing if we need
  // to add a ',' or not, we need to know if we are loading a local variable
  // or not. we do the later with the current_var_name != "" check. depending
  // on this we either add to buffer (ie. command) or varbuff (ie. variable
  // initialization)
  after StaticValueString (@  
      if ( svl_flag && current_var_name == "" ) buffer += ", ";
      svl_flag = true;
      if ( current_var_name == "" ) buffer += "\"" + host.get_value() + "\""; 
      else varbuff += "   " + current_var_name + ".push_back( \"" + host.get_value() + "\" );\n";
  @)
  after StaticValueInt (@  
      if ( svl_flag && current_var_name == "" ) buffer += ", ";
      svl_flag = true;
      Integer i = new Integer( host.get_value() );
      if ( current_var_name == "" ) buffer += i.toString();
      else varbuff += "   " + current_var_name + ".push_back( \"" + i.toString() + "\" );\n";
   @)

  // the comparison conditions are rather simple, since they use support
  // functions (to handle repeating fields). note the use of edge methods
  // to introduce the proper punctuation.
  before EQCond   (@ buffer += "rf_equal(form, ";  @)
  before NEQCond  (@ buffer += "!rf_equal(form, "; @)
  before GTCond   (@ buffer += "!rf_lteq(form, ";  @)
  before LTCond   (@ buffer += "!rf_gteq(form, ";  @)
  before GEQCond  (@ buffer += "rf_gteq(form, ";   @)
  before LEQCond  (@ buffer += "rf_lteq(form, ";   @)
  after  -> *,field1,Field (@ buffer += ", ";   @)
  after  -> *,field2,Field (@ buffer +=  ")";   @)

  // when we get to an extval condition we set the appropriate flag
  // to true. we of course unset it upon exiting. on exit, the
  // current_var_name contains the name of the local variable we've
  // loaded with the name of the fields. notice the edge method to
  // insert the id value at the right time.
  before ExtValCondition (@ 
    in_extval = true; 
    buffer += "rf_extval(form, \""; 
  @)
  after  ExtValCondition (@ 
    in_extval = false; 
    buffer += ", " + current_var_name + ")"; 
  @)
  after -> ExtValCondition,id,Ident (@ buffer += dest.toString() + "\""; @)
}

// ParserVisitor ----------------------------------------------------------
// this one takes care of creating the Parser utility class, which does all
// the rule creation and provides 'meta' information about the rules
ParserVisitor {
  (@ 
      Vector issue_names, 
             issue_metarulenames, 
             issue_rulesetnames, 
             issue_rulesetrulenames;
      int issue_count, ruleset_count, rule_count, metarule_count;
   @)
  
  // make sure we initialize everything
  init (@
     issue_count = ruleset_count = rule_count = metarule_count = 0;
     issue_names            = new Vector();
     issue_metarulenames    = new Vector();
     issue_rulesetnames     = new Vector();
     issue_rulesetrulenames = new Vector();
  @)

  // when we get to an issue declaration, reset all the counts and
  // add one element to all the vectors to 'make space' for this issue
  before IssueDeclaration (@
     ruleset_count = rule_count = metarule_count = 0;
     issue_names.addElement(new String( host.get_name() ));
     issue_metarulenames.addElement(new Vector());
     issue_rulesetnames.addElement(new Vector());
     issue_rulesetrulenames.addElement(new Vector());
  @)
  
  // once we're done with this issue, get ready for the next one...
  after IssueDeclaration (@
     issue_count++;
  @)
  
  // when we get to a meta rule, add its name
  before MetaSetRule (@
     Vector v = (Vector)issue_metarulenames.elementAt( issue_count );
     v.addElement(new String( host.get_name().toString()));
  @)

  // and count it once we're done
  after MetaSetRule (@
     metarule_count++;
  @)

  // for a ruleset, we need to create a new vector and also set the name  
  before RuleSet (@
     rule_count = 0;
     Vector v = (Vector)issue_rulesetnames.elementAt( issue_count );
     v.addElement(new String( host.get_name().toString() ));
     v = (Vector)issue_rulesetrulenames.elementAt( issue_count );
     v.addElement(new Vector());
  @)

  // and count it once we're done...
  after RuleSet (@
     ruleset_count++;
  @)

  // for rules, just add them to the appropriate vector...  
  before {RuleSetRule,SpawnableRuleSetRule} (@
     Vector v1 = (Vector)issue_rulesetrulenames.elementAt( issue_count );
     Vector v2 = (Vector)v1.elementAt( ruleset_count );
     v2.addElement(new String( host.get_name().toString()));
  @)
  
  // and count them, as usual...
  after {RuleSetRule,SpawnableRuleSetRule} (@
     rule_count++;
  @)
  
  // once we are done with the entire program, we start loading the
  // 'body' and 'header' string members from the vectors we've loaded
  // during the traversal
  after CompilationUnit (@

     // the header is trivial, as it is static.
     header = "#include <gtestl.h>\n\n"
            +  "class Rule;\n\n"
            +  "class Parser {\n" 
            +  "  public:\n"
            +  "    static int    NumOfIssues();\n"
            +  "    static int    NumOfCheckersInIssue(const string&);\n"
            +  "    static string IssueName(int);\n"
            +  "    static int    NumOfMetaRulesInIssue(const string&);\n"
            +  "    static string RulesetName(const string&, int);\n"
            +  "    static Rule*  CreateMetaRule(const string&, int);\n"
            +  "    static Rule*  CreateRule(const string&, int, int);\n"
            +  "    static int    NumOfRulesetsInIssue(const string&);\n"
            +  "    static int    NumOfRulesInSet(const string&, int);\n"
            +  "  private:\n"
            +  "    static int    find_issue_index(const string&);\n"
            +  "};\n";

     // the body is a different story, since we need to iterate through
     // vectors, create switch statements, etc...
     int i_issue, i_ruleset, i_rule;
     Vector v;
     body = "#include <parser.h>\n" 
          + "#include <gterules.h>\n\n";

     body += "int Parser::NumOfIssues() { return " + issue_count + "; }\n\n";

     body += "int Parser::NumOfCheckersInIssue(const string& a) { return 1; }\n\n";

     body += "string Parser::IssueName(int i) {\n"
          +  "   switch (i) {\n";
     for (i_issue = 0; i_issue < issue_names.size(); i_issue++) 
        body += "   case " + i_issue + ": return \"" + (String)issue_names.elementAt(i_issue) + "\";\n";
     body += "   }\n"
          +  "   return \"\";\n"
          +  "}\n\n";

     body += "int Parser::find_issue_index(const string& issueName) {\n"
          +  "   for (int i=0; i < NumOfIssues(); i++)\n"
          +  "      if ( IssueName(i) == issueName ) return i;\n"
          +  "   return -1;\n"
          +  "}\n\n";

     body += "int Parser::NumOfMetaRulesInIssue(const string& iname) {\n"
          +  " int i = find_issue_index( iname );\n"
          +  " if ( i != -1 ) {\n"
          +  "   switch( i ) {\n";
     for (i_issue = 0; i_issue < issue_names.size(); i_issue++) {
       v = (Vector)issue_metarulenames.elementAt( i_issue );
       body += "   case " + i_issue + ": return " + v.size() + ";\n";
     }
     body += "   }\n"
          +  " }\n"
          +  " return 0;\n"
          +  "}\n\n";     

     body += "string Parser::RulesetName(const string& iname, int rsetidx) {\n"
          +  "   int i = find_issue_index( iname );\n"
          +  "   if ( i != -1 ) {\n"
          +  "      switch ( i ) {\n";
     for (i_issue = 0; i_issue < issue_names.size(); i_issue++) {
        v = (Vector)issue_rulesetnames.elementAt( i_issue );
        body += "      case " + i_issue + ":\n"
             +  "         switch( rsetidx ) {\n";
        for (i_ruleset = 0; i_ruleset < v.size(); i_ruleset++) {
           body += "         case " + i_ruleset + ": return \"" + (String)v.elementAt(i_ruleset) + "\";\n";
        }
        body += "         }\n";
     }
     body += "      }\n"
          +  "   }\n"
          +  "   return \"\";\n"
          +  "}\n\n";            

     body += "Rule* Parser::CreateMetaRule(const string& iname, int mruleidx) {\n"
          +  "   int i = find_issue_index( iname );\n"
          +  "   if ( i != -1 ) {\n"
          +  "      switch ( i ) {\n";
     for (i_issue = 0; i_issue < issue_names.size(); i_issue++) {
        v = (Vector)issue_metarulenames.elementAt( i_issue );
        body += "      case " + i_issue + ":\n"
             +  "         switch( mruleidx ) {\n";
        for (i_ruleset = 0; i_ruleset < v.size(); i_ruleset++) {
           body += "         case " + i_ruleset + ": return new " + (String)v.elementAt(i_ruleset) + "();\n";
        }
        body += "         }\n";
     }
     body += "      }\n"
          +  "   }\n"
          +  "   return 0;\n"
          +  "}\n\n";            

     body += "Rule* Parser::CreateRule(const string& iname, int rsetidx, int ruleidx) {\n"
          +  "   int i = find_issue_index( iname );\n"
          +  "   if ( i != -1 ) {\n"
          +  "      switch ( i ) {\n";
     for (i_issue = 0; i_issue < issue_names.size(); i_issue++) {
        v = (Vector)issue_rulesetrulenames.elementAt( i_issue );
        body += "      case " + i_issue + ":\n"
             +  "         switch( rsetidx ) {\n";
        for (i_ruleset = 0; i_ruleset < v.size(); i_ruleset++) {
           body += "         case " + i_ruleset + ":\n"
                +  "            switch ( ruleidx ) {\n";
           Vector v2 = (Vector)v.elementAt(i_ruleset);
           for (i_rule = 0; i_rule < v2.size(); i_rule++) {
              body += "             case " + i_rule + ": return new " + (String)v2.elementAt(i_rule) + "();\n";
           }
           body += "            }\n";
        }
        body += "         }\n";
     }
     body += "      }\n"
          +  "   }\n"
          +  "   return 0;\n"
          +  "}\n\n";            

     body += "int Parser::NumOfRulesetsInIssue(const string& iname) {\n"
          +  "   int i = find_issue_index(iname);\n"
          +  "   if ( i != -1 ) {\n"
          +  "      switch ( i ) {\n";
     for (i_issue = 0; i_issue < issue_count; i_issue++) {
        v = (Vector)issue_rulesetnames.elementAt(i_issue);
        body += "      case " + i_issue + ": return " + v.size() + ";\n";
     }
     body += "      }\n"
          +  "   }\n"
          +  "   return 0;\n"
          +  "}\n\n";

     body += "int Parser::NumOfRulesInSet(const string& iname, int rsetidx) {\n"
          +  "   int i = find_issue_index( iname );\n"
          +  "   if ( i != -1 ) {\n"
          +  "      switch ( i ) {\n";
     for (i_issue = 0; i_issue < issue_names.size(); i_issue++) {
        v = (Vector)issue_rulesetrulenames.elementAt( i_issue );
        body += "      case " + i_issue + ":\n"
             +  "         switch( rsetidx ) {\n";
        for (i_ruleset = 0; i_ruleset < v.size(); i_ruleset++) {
           Vector v2 = (Vector)v.elementAt(i_ruleset);
           body += "         case " + i_ruleset + ": return " + v2.size() + ";\n";
        }
        body += "         }\n";
     }
     body += "      }\n"
          +  "   }\n"
          +  "   return 0;\n"
          +  "}\n\n";            
  @)
}



// the main class. simply start up the work...
Main {

 // an auxiliary function to generate copyright info and stuff
 static public String header(String file_name, String desc)
 (@
   String buffer = new String();
   String fname = file_name.replace( '.', '_' );
   buffer += "//=======================================================================\n";
   buffer += "// " + file_name + "\n";
   buffer += "//   - " + desc + "\n";
   buffer += "//-----------------------------------------------------------------------\n";
   buffer += Main.copyright();
   buffer += "//-----------------------------------------------------------------------\n";
   buffer += "// this is an automatically generated file. it was generated on:\n";
   Date date = new Date();
   buffer += "//  " + date.toString() + "\n";
   buffer += "//=======================================================================\n";
   buffer += "#ifndef _" + fname + "_\n";
   buffer += "#define _" + fname + "_\n";
   buffer += "\n";
   return buffer;
 @)

 // another one for the end of the a file
 static public String footer(String file_name)
 (@
   String buffer = new String();
   buffer += "#endif /* " + file_name + " */\n";
   return buffer;
 @)

 // actually, this is the one that does the copyright! :-)
 static public String copyright()
 (@
   String buffer = new String();
   buffer += "// (C) 1997 - GTE Laboratories Incorporated\n";
   buffer += "//            40 Sylvan Road, MS40\n";
   buffer += "//            Waltham, MA 02254\n";
   buffer += "// * All rights reserved *\n";
   buffer += "//-----------------------------------------------------------------------\n";
   buffer += "// Generator created by Luis Blando using Demeter/Java AOOP              \n";
   buffer += "// Comments, suggestions, complaints to lblando@gte.com                  \n";
   return buffer;
 @)

 // and this one saves all the strings to the files. 
 static public void my_save(String fname, String fcontents) 
 (@
    System.out.println("  saving " + fname);
    File file;
    try {
       file  = new File( fname );
    }
    catch (NullPointerException e) {
       System.out.println("Error: Cannot write to: " + fname);
       return;
    }
    FileOutputStream fStr;
    PrintWriter pStr;
    try {
       fStr = new FileOutputStream( file );
       pStr = new PrintWriter( fStr );
       pStr.println( fcontents );
       pStr.flush();
       fStr.close();
    }
    catch (IOException e) {
       System.out.println("Error: could not save file " + fname);
       return;
    }
 @)

 // main function
 (@
   static public void main(String args[]) throws Exception 
   {
     // some banner stuff
     System.out.println("EEL2CPP Compiler v2.1 - (c) 1997 - GTE Labs");
     System.out.println("(Done using Demeter/Java by Luis Blando)");
     System.out.println("-------------------------------------------");

     // parse from stdin. TODO: add reading from file
     System.out.println("Parsing the EEL file, creating object graph");
     CompilationUnit pgm = CompilationUnit.parse(System.in);


     // create all the class declarations...
     HeaderVisitor hv = new HeaderVisitor();
     System.out.println("Forming the class declarations...");
     pgm.allRules( hv );

     // now do the constructors. notice how we use the same
     // visitor in two different traversals
     RuleConstructorVisitor rcv = new RuleConstructorVisitor();
     System.out.println("Forming all the rule constructors...");
     pgm.constrMeta ( rcv );
     pgm.constrOther( rcv );

     // create all the visitors needed for Validate()...
     ValidateVisitor   vv = new ValidateVisitor();
     CommandVisitor    cv = new CommandVisitor();
     ExpressionVisitor ev = new ExpressionVisitor();
     // link them together
     vv.set_cmd( cv );
     cv.set_ev ( ev );
     System.out.println("Doing Validate() functions...");
     // do all the validates
     pgm.viaRules(vv,cv,ev);

     // now repeat it for metarules
     MetaValidateVisitor mvv = new MetaValidateVisitor();
     // note we reuse cv and ev.
     mvv.set_cmd( cv );
     System.out.println("Doing Metarules validates...");
     pgm.viaMetaRules( mvv, cv, ev );
     
     // now traverse again to do the parser class. note that
     // we reuse the traversal    
     ParserVisitor parser = new ParserVisitor();
     pgm.allRules( parser );

     // once we've finished traversing, collect all the info
     // into four buffers...
     String constructors = rcv.get_buffer();
     String bodies       = mvv.get_buffer() + vv.get_buffer();
     String gterules_h = Main.header( "gterules.h", "class declarations for rule objects" ) 
                       + "#include <rules.h>\n"
                       + hv.get_buffer()
                       + Main.footer( "gterules.h" );

     String gterules_cpp = Main.header( "gterules.cpp", "class definitions for rule objects" )
                       + "#include <gterules.h>\n\n"
                       + "// constructors -----------------------------------------\n"
                       + constructors 
                       + "\n// Validate/MetaCheck functions -----------------------\n"
                       + bodies
                       + Main.footer( "gterules.cpp" );

     String parser_h = Main.header( "parser.h", "declaration of parser utility class" )
                       + parser.get_header() 
                       + Main.footer( "parser.h" );

     String parser_cpp = Main.header( "parser.cpp", "definitions of parser utilities" )
                       + parser.get_body()
                       + Main.footer( "parser.cpp" );


     // create the output files... yeah, i know, the filenames are hard-coded! :-)
     System.out.println("Creating output files...");
     Main.my_save("parser.h",     parser_h);
     Main.my_save("parser.cpp",   parser_cpp);
     Main.my_save("gterules.h",   gterules_h);
     Main.my_save("gterules.cpp", gterules_cpp);
     System.out.println("Done!");
   }
 @)
}
// ************************************************************************************

What it all boils down to

After all this work, here's what happens when you feed a simple file to the compiler. The file follows:

// sample test file to show repeating fields
issue "1" 1 {
  meta {
  }
  set lsr_set {
   R1:  if in(lsr.*.a.*.d.*.c.*.e[2..3], "A", "B") 
        then (required(lsr.*.b) 
              and notin(A.*.B.*.C.*.D.*.E.*.F.*.G.*.H.*.I[3], "1", "2"));
   R2:  always in(lsr.a, "A", "B", "C");
   R10: if (empty(lsr.act)) 
        then externalvalidate(tbhit, lsr.b.*.c);
   R11: if (empty(lsr.*.act)) 
        then (externalvalidate(tbhit, A.*.B, A.*.C) 
        and externalvalidate(tbhit, a.*.b.*.c.*.d.*.e));
  }
}

The .cpp file generated looks like: (note: this is the unoptimized version)

//=======================================================================
// gterules.cpp
//   - class definitions for rule objects
//-----------------------------------------------------------------------
// (C) 1997 - GTE Laboratories Incorporated
//            40 Sylvan Road, MS40
//            Waltham, MA 02254
// * All rights reserved *
//-----------------------------------------------------------------------
// Generator created by Luis Blando using Demeter/Java AOOP              
// Comments, suggestions, complaints to lblando@gte.com                  
//-----------------------------------------------------------------------
// this is an automatically generated file. it was generated on:
//  Thu Nov 13 16:13:36 EST 1997
//=======================================================================
#ifndef _gterules_cpp_
#define _gterules_cpp_
#include <gterules.h>
// constructors -----------------------------------------
R1::R1() : Rule() {
   Name("R1");
}
R2::R2() : Rule() {
   Name("R2");
}
R10::R10() : Rule() {
   Name("R10");
}
R11::R11() : Rule() {
   Name("R11");
}
// Validate/MetaCheck functions -----------------------
// R1:if in(lsr.*.a.*.d.*.c.*.e[2..3],"A","B")then(required(lsr.*.b)and notin(A.*.B.*.C.*.D.*.E.*.F.*.G.*.H.*.I[3],"1","2"));
bool R1::Validate(Form* form) {
   ClearIndices();
   static vector<string> invar_0;
   invar_0.push_back( "A" );
   invar_0.push_back( "B" );
   static vector<string> invar_1;
   invar_1.push_back( "1" );
   invar_1.push_back( "2" );
   int idx[8];
   char buff[100];
   string section_0( "A" );
   for (idx[0]=0; idx[0] < form->GetRowCount( section_0 ); idx[0]++) {
      sprintf(buff, "%s.%i.B", section_0.c_str(), idx[0]);
      string section_1( buff );
      for (idx[1]=0; idx[1] < form->GetRowCount( section_1 ); idx[1]++) {
         sprintf(buff, "%s.%i.C", section_1.c_str(), idx[1]);
         string section_2( buff );
         for (idx[2]=0; idx[2] < form->GetRowCount( section_2 ); idx[2]++) {
            sprintf(buff, "%s.%i.D", section_2.c_str(), idx[2]);
            string section_3( buff );
            for (idx[3]=0; idx[3] < form->GetRowCount( section_3 ); idx[3]++) {
               sprintf(buff, "%s.%i.E", section_3.c_str(), idx[3]);
               string section_4( buff );
               for (idx[4]=0; idx[4] < form->GetRowCount( section_4 ); idx[4]++) {
                  sprintf(buff, "%s.%i.F", section_4.c_str(), idx[4]);
                  string section_5( buff );
                  for (idx[5]=0; idx[5] < form->GetRowCount( section_5 ); idx[5]++) {
                     sprintf(buff, "%s.%i.G", section_5.c_str(), idx[5]);
                     string section_6( buff );
                     for (idx[6]=0; idx[6] < form->GetRowCount( section_6 ); idx[6]++) {
                        sprintf(buff, "%s.%i.H", section_6.c_str(), idx[6]);
                        string section_7( buff );
                        for (idx[7]=0; idx[7] < form->GetRowCount( section_7 ); idx[7]++) {
                           sprintf(buff, "lsr.%i.a.%i.d.%i.c.%i.e", idx[0], idx[1], idx[2], idx[3]);
                           string rvn_0( buff );
                           sprintf(buff, "lsr.%i.b", idx[0]);
                           string rvn_1( buff );
                           sprintf(buff, "A.%i.B.%i.C.%i.D.%i.E.%i.F.%i.G.%i.H.%i.I", idx[0], idx[1], idx[2], idx[3], idx[4], idx[5], idx[6], idx[7]);
                           string rvn_2( buff );
                           if ( rf_in(form, rvn_0, 2, 3, invar_0) ) 
                              if ( ! ( rf_req(form, rvn_1, 0, 0) && rf_notin(form,  rvn_2, 3, 3, invar_1) ) ) rf_processerror(idx, 8);
                        }
                     }
                  }
               }
            }
         }
      }
   }
   return Status();
}
// R2:always in(lsr.a,"A","B","C");
bool R2::Validate(Form* form) {
   ClearIndices();
   static vector<string> invar_0;
   invar_0.push_back( "A" );
   invar_0.push_back( "B" );
   invar_0.push_back( "C" );
   if ( ! rf_in(form, "lsr.a", 0, 0, invar_0) ) rf_processerror();
   return Status();
}
// R10:if(empty(lsr.act))then externalvalidate(tbhit,lsr.b.*.c);
bool R10::Validate(Form* form) {
   ClearIndices();
   static vector<string> invar_0;
   int idx[1];
   char buff[100];
   string section_0( "lsr.b" );
   for (idx[0]=0; idx[0] < form->GetRowCount( section_0 ); idx[0]++) {
      sprintf(buff, "lsr.b.%i.c", idx[0]);
      string rvn_0( buff );
      invar_0.push_back( rvn_0 );
      if ( ( rf_empty(form, "lsr.act", 0, 0) ) ) 
         if ( ! rf_extval(form, "tbhit", invar_0) )  ;
   }
   rf_doextval();
   return Status();
}
// R11:if(empty(lsr.*.act))then(externalvalidate(tbhit,A.*.B,A.*.C)and externalvalidate(tbhit,a.*.b.*.c.*.d.*.e));
bool R11::Validate(Form* form) {
   ClearIndices();
   static vector<string> invar_0;
   static vector<string> invar_1;
   int idx[4];
   char buff[100];
   string section_0( "a" );
   for (idx[0]=0; idx[0] < form->GetRowCount( section_0 ); idx[0]++) {
      sprintf(buff, "%s.%i.b", section_0.c_str(), idx[0]);
      string section_1( buff );
      for (idx[1]=0; idx[1] < form->GetRowCount( section_1 ); idx[1]++) {
         sprintf(buff, "%s.%i.c", section_1.c_str(), idx[1]);
         string section_2( buff );
         for (idx[2]=0; idx[2] < form->GetRowCount( section_2 ); idx[2]++) {
            sprintf(buff, "%s.%i.d", section_2.c_str(), idx[2]);
            string section_3( buff );
            for (idx[3]=0; idx[3] < form->GetRowCount( section_3 ); idx[3]++) {
               sprintf(buff, "lsr.%i.act", idx[0]);
               string rvn_0( buff );
               sprintf(buff, "A.%i.B", idx[0]);
               string rvn_1( buff );
               invar_0.push_back( rvn_1 );
               sprintf(buff, "A.%i.C", idx[0]);
               string rvn_2( buff );
               invar_0.push_back( rvn_2 );
               sprintf(buff, "a.%i.b.%i.c.%i.d.%i.e", idx[0], idx[1], idx[2], idx[3]);
               string rvn_3( buff );
               invar_1.push_back( rvn_3 );
               if ( ( rf_empty(form, rvn_0, 0, 0) ) ) 
                  if ( ! ( rf_extval(form, "tbhit", invar_0) && rf_extval(form, "tbhit", invar_1) ) )  ;
            }
         }
      }
   }
   rf_doextval();
   return Status();
}
#endif /* gterules.cpp */

One Last Optimization

As shown in the above resulting .cpp file, the static vector variables (that hold potential values for 'in' and 'notin' commands are loaded each time Validate() is called. This is clearly inefficient. We could put the creation and loading of these variables in the class constructor. This is exactly what I did. As a matter of fact, the eelc.cd and eelc.beh files shown above do not generate the paragraph shown previously, but rather the one that follows (the output from the compiler, as well as the header and implementation files are shown):

Notice the creation of member variables invar_X in the rules that have in() and notin() functions. It is important to note that rule R11 has an externalvalidate() function. External validations also use a vector of strings. These, however, change on every iteration (when paired with repeating field edits, as is the case with R11). For this reason, invar_0 in R11 is not a member variable and is created and loaded at the innermost level of looping (so that we put the correct values in there).

Yes, some other optimizations are possible... But for those, I'll rely in the real compiler (C++ to machine code) ! :-)

Concluding Remarks II

It took me about three more days of work to add this new functionality (not counting the work needed to reformulate the support functions). Taking into account the scope of the changes, it is an incredibly short amount of time. Code size did grow substantially. This is partly because of the naive counting mechanism I am using (wc -l) and partly because I tend to write spaced code. No matter how you look at it, the length and much more importantly, complexity of the Demeter/Java solution is substantially less than its Lex/Yacc counterpart.