• No results found

Adding Actions to Compute Polynomial Values

To actually compute the value of a polynomial, we add actions to the SORCERER grammar. As with the ANTLR only version, we use a few trigger functions to store and retrieve register values. Assigning the result of evaluating a polynomial is done in rule assign as follows:

assign

: <<float r;>>

#( ASSIGN id:ID poly>[r] ) <<

store(id->getText(), r);

printf("storing %f in %s\n", r, id->getText()); >>

;

We define a local variable, r, with the init-action (first action of a rule or subrule) and place the result of poly into it. We call our trigger function store() with the register and the result value and print it out. The position of this action is important: It must be done after the reference to poly so that poly’s value is computed before the printf tries to use it.

Computing the value of the polynomial trees is straightforward (one of the design goals of our AST, remember?):

poly > [float r]

: <<float p1,p2;>>

#( MULT poly>[p1] poly>[p2] ) <<r = p1*p2;>> | <<float p1,p2;>>

#( ADD poly>[p1] poly>[p2] ) <<r = p1+p2;>> | <<float p1,p2;>>

#( EXP poly>[p1] poly>[p2] ) <<r = pow(p1,p2);>>

| id:ID <<r = value(id->getText());>> | f:FLOAT <<r = atof(f->getText());>> ;

Rule poly returns the floating point result as a return value. Starting with the simplest alternatives, you will note that for a floating point tree node, we simply compute the floating point value (atof()) of the text found on the input stream for that token. For an identifier (register) node, we call our trigger function value() with the register identifier and return the result. The other alternatives of poly take the results of the two operands and perform the appropriate operation. Again, the actions must appear after the calls to the operands so that both results are available before being used.

Because rule assign will be called from the ANTLR grammar, no main program is required or specified in the SORCERER description. The entire specification is

#header << #include <math.h> #include <stdio.h> #include <string.h> #include "tokens.h" #include "AToken.h"

typedef ANTLRCommonToken ANTLRToken; #include "AST.h"

typedef AST SORAST; >>

class EvalPoly { <<

protected:

virtual float value(char *r) = 0;

virtual void store(char *r, float v) = 0; >>

assign

: <<float r;>>

#( ASSIGN id:ID poly>[r] ) << store(id->getText(), r); printf("storing %f in %s\n", r, id->getText()); >> ; poly > [float r] : <<float p1,p2;>>

#( MULT poly>[p1] poly>[p2] ) <<r = p1*p2;>> | <<float p1,p2;>>

#( ADD poly>[p1] poly>[p2] ) <<r = p1+p2;>> | <<float p1,p2;>>

Constructing and Walking ASTs

| id:ID <<r = value(id->getText());>> | f:FLOAT <<r = atof(f->getText());>> ;

}

Now the question is, how do we link our SORCERER tree-walker into our AST- constructing ANTLR grammar? First, we need to include the tree walker definition:

#header <<

// must be visible to all generated files; hence, must // be in #header action

#include "AToken.h"

typedef ANTLRCommonToken ANTLRToken; #include "MyEvalPoly.h"

>>

Second, we need to associate labels with the assignment and addition operator tokens so that they may be referenced in our SORCERER description.

#token ASSIGN "=" #token ADD "\+"

Third, and finally, we need to place a call in the assign ANTLR rule to invoke the SORCERER assign rule (which will evaluate the polynomial):

assign

: ID "="^ poly ";"!

<<walker.assign((SORASTBase **)&#0);>> ;

where our tree walker is defined as a member variable of our parser:

class PolyParser { << protected: MyEvalPoly walker; >> ... }

The action invokes the assign rule of the tree walker we have declared. The cast is required because SORCERER generates tree-walking functions that take generic SORASTBase tree pointers not ASTBase pointers (the type of #0).

The augmented ANTLR description is

typedef ANTLRCommonToken ANTLRToken; #include "MyEvalPoly.h" >> << #include "PBlackBox.h" #include "DLGLexer.h" main() {

ParserBlackBox<DLGLexer, PolyParser, ANTLRToken> p(stdin); ASTBase *root = NULL;

p.parser()->interp(&root); }

>>

#token "[\ \t]+" <<skip();>>

#token "\n" <<skip(); newline();>> #token ID "[a-z]" #token FLOAT "[0-9]+ { . [0-9]+ }" #token EXP "^" #token ASSIGN "=" #token ADD "\+" #token MULT class PolyParser { << protected: MyEvalPoly walker; >> interp! : ( a:assign )+ ; assign : ID "="^ poly ";"! <<walker.assign((SORASTBase **)&#0);>> ; poly : term ( "\+"^ term )* ;

Constructing and Walking ASTs term:bigterm | reg { "^"^ exp } ; bigterm! : c:coefficient ( r:reg ( "^" e:exp

<<#0 = #(#[MULT,"MULT"], #c, #(#[EXP,"EXP"], #r, #e));>> | <<#0 = #(#[MULT,"MULT"], #c, #r);>> ) | <<#0 = #c;>> ) ; coefficient : FLOAT ; reg : ID ; exp : reg | FLOAT ; }

The previous makefile created with

genmk -CC -class PolyParser -project poly -trees poly.g

for testing the AST construction can be modified for use with a SORCERER phase in the following ways:

MAKE variables for SORCERER support code and binaries are added:

SOR_H = $(PCCTS)/sorcerer/h SOR_LIB = $(PCCTS)/sorcerer/lib SOR = $(PCCTS)/sorcerer/sor

CFLAGS variable is changed to add the SORCERER directory:

SRC variable is changed to add the SORCERER phase and support file: SRC = poly.cpp \ PolyParser.cpp \ $(ANTLR_H)/AParser.cpp $(ANTLR_H)/DLexerBase.cpp \ $(ANTLR_H)/ASTBase.cpp $(ANTLR_H)/PCCTSAST.cpp \ $(ANTLR_H)/ATokenBuffer.cpp $(SCAN).cpp \ eval.cpp EvalPoly.cpp \ $(SOR_LIB)/STreeParser.cpp

OBJ variable is changed in a similar way.

Target poly.o is changed to depend on the SORCERER phase include file:

poly.o : $(TOKENS) $(SCAN).h poly.cpp EvalPoly.h $(CCC) -c $(CFLAGS) -o poly.o poly.cpp

Finally, we add targets for all the SORCERER output and support code:

EvalPoly.o : EvalPoly.cpp

$(CCC) -c $(CFLAGS) EvalPoly.cpp eval.o : eval.cpp

$(CCC) -c $(CFLAGS) eval.cpp

eval.cpp EvalPoly.cpp EvalPoly.h : eval.sor $(SOR) -CPP eval.sor

STreeParser.o : $(SOR_LIB)/STreeParser.cpp $(CCC) -o STreeParser.o -c $(CFLAGS) \ $(SOR_LIB)/STreeParser.cpp