How to embed actions within every match of a one-or-more ( rule+ ) or zero-or-more ( rule* ) rule in ANTLR?

Yeah, well, the particular task is very simple if you know the solution. But when you are just getting started with ANTLR it is not that obvious what kind of things you can do within a grammar, and what you can not. I looked around the web for a while, but it was difficult for me to find a solution, so I will share it here.

The following example is an excerpt from the project I am currently working on. It uses Kyle Yetter’s Ruby target, so do not be confused, the code within the embedded actions is Ruby.

The grammar rules involved into this are shown in the code snippet below. I left out entityName because it is not really interesting for the problem, it just says something like “One capital Letter and then just letters, digits and underscores” and has no embedded actions.

entityDef returns [ entity ]
  : 'entity' entityName '{' {
      entity = Entity.new( $entityName.text,
                           $entityName.start.line )
    }
      attribDef+
    '}'
  ;
 
attribDef returns [ attrib ]
  : ID ':' TYPE {
     attrib = Attribute.new( $ID.text,
                             $TYPE.text.downcase.to_sym,
                             $ID.line )
    }
  ;

The first rule entityDef recognizes an entity definition and creates an entity object with the extracted data. The second rule attribDef does the same with an attribute definition. Within the entityDef there could be several matches of attribDef, so you see attribDef+ there on line 6, which means one-or-more attribute definitions. By the way, just to get an idea of what this parser should parse:

entity User {
  first_name: String
  last_name: String
  age: Integer
}

So, now to the problem. How the hell do you get all matched attribute definitions into the entity ? It is really simple, but not quite obvious to someone who is getting started with ANTLR:

entityDef returns [ entity ]
  : 'entity' entityName '{' {
      entity = Entity.new( $entityName.text,
                           $entityName.start.line)
    }
      (attrib = attribDef { entity << attrib } )+
    '}'
  ;

Line 6 in the preceding code snippet is the interesting one. Just put everything into parentheses, fetch the rule return value into an alias ( attrib = attribDef ), directly embed your code (  {entity << attrib} ), and put the one-or-more symbol ( + ) behind the parantheses. Your done, the embedded code will be called on every match of attribDef and the entity will collect all its attributes this way.

Well, that’s it with my first english article containing something useful. I would appreciate any kind of comments, praise, criticism, whatever, do not be shy!