Is it possible to evaluate IF statements in antlr4 using listeners rather than visitors in java? If so, could you suggest how?
I'm trying to make an interpreter for my language. I've used the generated listeners of antlr to implement it. I've already finished some semantic analysis and evaluation on expressions and now I don't know how to go about evaluating IF, WHILE, FOR, etc function calls.
My grammar in antlr4 excluding the lexical rules:
grammar CodeCraftGrammar;
@header{
package gen;
}
program
: constantStatement* mainFunction functionDeclaration* EOF
;
constantStatement
: ABSOLUTE dataType ID ASSIGN expression SEMI
;
variableDeclaration
: dataType ID
;
dataType
: INT
| FLOAT
| CHAR
| STRING
| BOOLEAN
;
functionDeclaration
: returnType ID parameterList block
;
returnType
: dataType #returnDataType
| DARKNESS #returnDarkness
;
parameterList
: LPAREN parameter? (COMMA parameter)* RPAREN
;
parameter
: dataType ID
;
block
: LBRACE statement* RBRACE
;
mainFunction
: DARKNESS MINE parameterList block
;
statement
: block #blockStatement
| variableDeclaration SEMI #varDec
| assignmentStatement SEMI #assign
| functionCallStatement SEMI #funcCall
| ifStatement #if
| whileStatement #while
| doWhileStatement #dowhile
| forStatement #for
| returnStatement #return
| CHOKE SEMI #choke
;
assignmentStatement
: ID ASSIGN expression
;
functionCallStatement
: ID actualParameters #funcCallID
| PRINT actualParameters #funcCallPrint
| PRINTLN actualParameters #funcCallPrintln
;
actualParameters
: LPAREN expression? (COMMA expression)* RPAREN
;
ifStatement
: WETHER condition block OTHERWISE block #ifelse
| WETHER condition block #ifonly
;
condition
: LPAREN expression RPAREN
;
whileStatement
: UNTIL condition block
;
doWhileStatement
: EXECUTE block UNTIL condition SEMI
;
forStatement
: AS LPAREN assignmentStatement SEMI expression SEMI assignmentStatement RPAREN block
;
returnStatement
: REPLY expression SEMI
;
expression
: '!' expression #bangExpr
| '(' expression ')' #parensExpr
| expression ('*'|'/'|'%') expression #multdivmodExpr
| expression ('+'|'-') expression #addminusExpr
| expression ('>='|'<='|'<'|'>') expression #relationalExpr
| expression ('=='|'!=') expression #equalityExpr
| expression '&&' expression #andExpr
| expression '||' expression #orExpr
| IntegerLiteral #intExpr
| BooleanLiteral #boolExpr
| FloatingPointLiteral #floatExpr
| StringLiteral #stringExpr
| CharacterLiteral #charExpr
| ID actualParameters #funcCallExpr
| ID #idExpr
| NULL #nullExpr
;
This is where the interpreter starts:
public Main(String code, String[] args,JTextArea ta)throws Exception {
this.ta =ta;
String inputFile = "src/sample_code.cc";
InputStream is=null;
if ( inputFile!=null ) {
is = new FileInputStream(inputFile);
}
//is = new StringBufferInputStream(code);
ANTLRInputStream input = new ANTLRInputStream(is);
CodeCraftGrammarLexer lexer = new CodeCraftGrammarLexer(input);
CodeCraftGrammarParser parser = new CodeCraftGrammarParser(new CommonTokenStream(lexer));
parser.setBuildParseTree(true);
ParseTree tree = parser.program();
List<String> ruleNames = Arrays.asList(parser.getRuleNames());
TreeViewer tv = new TreeViewer(ruleNames, tree);
ParseTreeWalker walker = new ParseTreeWalker();
FirstPass def = new FirstPass();
walker.walk(def, tree);
// create next phase and feed symbol table info from define to reference phase
SecondPass ref = new SecondPass(def.globals, def.scopes);
walker.walk(ref, tree);
}
This is the first pass in the parse tree where it defines first all the variables and functions:
public class FirstPass extends CodeCraftGrammarBaseListener{
ParseTreeProperty<Scope> scopes = new ParseTreeProperty<Scope>();
GlobalScope globals;
Scope currentScope; // define symbols in this scope
void saveScope(ParserRuleContext ctx, Scope s) { scopes.put(ctx, s); }
public FirstPass(){
}
public void enterProgram(@NotNull ProgramContext ctx) {
globals = new GlobalScope(null);
currentScope = globals;
}
public void exitProgram(@NotNull ProgramContext ctx) {
Main.displayScope(globals);
}
public void enterFunctionDeclaration(@NotNull FunctionDeclarationContext ctx) {
String name = ctx.ID().getText();
int typeTokenType = ctx.returnType().start.getType(); //UNSURE
Symbol.Type type = Main.getType(typeTokenType);
// push new scope by making new one that points to enclosing scope
FunctionSymbol function = new FunctionSymbol(name, type, currentScope);
GlobalScope temp = (GlobalScope) currentScope;
if(temp.symbols.containsKey(function.name)){
//multiple function declaration in same global scope
Main.error(ctx.ID().getSymbol(), "Function already declared: "+name);
}//UNSURE should i let it continue saving in symbol table
currentScope.define(function); // Define function in current scope
saveScope(ctx, function); // Push: set function's parent to current
currentScope = function; // Current scope is now function scope
if(currentScope.getClass()==FunctionSymbol.class)
currentScope = (FunctionSymbol)currentScope;
}
public void enterMainFunction(@NotNull MainFunctionContext ctx) {
String name = ctx.MINE().getText();
int typeTokenType = ctx.DARKNESS().getSymbol().getType(); //UNSURE
Symbol.Type type = Main.getType(typeTokenType);
// push new scope by making new one that points to enclosing scope
FunctionSymbol function = new FunctionSymbol(name, type, currentScope);
GlobalScope temp = (GlobalScope) currentScope;
if(temp.symbols.containsKey(function.name)){
//multiple function declaration in same global scope
Main.error(ctx.MINE().getSymbol(), "Main already declared: "+name);
}//UNSURE should i let it continue saving in symbol table
currentScope.define(function); // Define function in current scope
saveScope(ctx, function); // Push: set function's parent to current
currentScope = function; // Current scope is now function scope
if(currentScope.getClass()==FunctionSymbol.class)
currentScope = (FunctionSymbol)currentScope;
}
public void exitFunctionDeclaration(@NotNull FunctionDeclarationContext ctx) {
Main.displayScope(currentScope);
currentScope = currentScope.getEnclosingScope(); // pop scope
if(currentScope.getClass()==GlobalScope.class)
currentScope = (GlobalScope)currentScope;
}
public void exitMainFunction(@NotNull MainFunctionContext ctx) {
Main.displayScope(currentScope);
currentScope = currentScope.getEnclosingScope(); // pop scope
if(currentScope.getClass()==GlobalScope.class)
currentScope = (GlobalScope)currentScope;
}
public void enterBlock(CodeCraftGrammarParser.BlockContext ctx) {
// push new local scope
currentScope = new LocalScope(currentScope);
currentScope = (LocalScope)currentScope;
saveScope(ctx, currentScope);
}
public void exitBlock(CodeCraftGrammarParser.BlockContext ctx) {
Main.displayScope(currentScope);
currentScope = currentScope.getEnclosingScope(); // pop scope
if(currentScope.getClass()==FunctionSymbol.class)
currentScope = (FunctionSymbol)currentScope;
}
public void exitParameter(@NotNull ParameterContext ctx) {
FunctionSymbol temp = (FunctionSymbol) currentScope;
String name = ctx.ID().getText();
if(temp.arguments.containsKey(name)){
//multiple function declaration in same global scope
Main.error(ctx.ID().getSymbol(), "Parameter already declared: "+name);
}
defineVar(ctx.dataType(), ctx.ID().getSymbol(),false);
}
public void exitVariableDeclaration(@NotNull VariableDeclarationContext ctx) {
LocalScope temp = (LocalScope) currentScope;
String name = ctx.ID().getText();
if(temp.symbols.containsKey(name)){
//multiple function declaration in same global scope
Main.error(ctx.ID().getSymbol(), "Variable already declared: "+name);
}
defineVar(ctx.dataType(), ctx.ID().getSymbol(),false);
}
public void exitConstantStatement(@NotNull ConstantStatementContext ctx) {
GlobalScope temp = (GlobalScope) currentScope;
ConstantStatementContext ctx2 = ctx;
String name = ctx2.ID().getText();
if(temp.symbols.containsKey(name)){
//multiple function declaration in same global scope
Main.error(ctx2.ID().getSymbol(), "Constant already declared: "+name);
}
defineVar(ctx2.dataType(), ctx2.ID().getSymbol(),true);
}
void defineVar(CodeCraftGrammarParser.DataTypeContext typeCtx, Token nameToken, Boolean isConstant) {
int typeTokenType = typeCtx.start.getType(); //UNSURE
Symbol.Type type = Main.getType(typeTokenType);
VariableSymbol var = new VariableSymbol(nameToken.getText(), type, isConstant);
currentScope.define(var); // Define symbol in current scope
}
}
The second pass:
public class SecondPass extends CodeCraftGrammarBaseListener{
ParseTreeProperty<Scope> scopes;
GlobalScope globals;
Scope currentScope; // resolve symbols starting in this scope
JTextArea ta;
Stack<Symbol> stack = new Stack<Symbol>();
Symbol op1,op2,ans;
public SecondPass(GlobalScope globals, ParseTreeProperty<Scope> scopes) {
this.scopes = scopes;
this.globals = globals;
}
public void enterProgram(@NotNull ProgramContext ctx) {
currentScope = globals;
}
public void enterFunctionDeclaration(@NotNull FunctionDeclarationContext ctx) {
currentScope = scopes.get(ctx);
}
public void exitFunctionDeclaration(@NotNull FunctionDeclarationContext ctx) {
currentScope = currentScope.getEnclosingScope();
}
public void enterBlock(@NotNull BlockContext ctx) {
currentScope = scopes.get(ctx);
}
public void exitBlock(@NotNull BlockContext ctx) {
currentScope = currentScope.getEnclosingScope();
}
public void exitIf(@NotNull IfContext ctx) {
}
public void exitFuncCallID(@NotNull FuncCallIDContext ctx) {
String funcName = ctx.ID().getText();
Symbol meth = currentScope.resolve(funcName);
if ( meth==null ) {
Main.error(ctx.ID().getSymbol(), "no such function: "+funcName);
}
if ( meth instanceof VariableSymbol ) {
Main.error(ctx.ID().getSymbol(), funcName+" is not a function");
}
}
public void exitAssignmentStatement(AssignmentStatementContext ctx) {
String name = ctx.ID().getSymbol().getText();
Symbol var = currentScope.resolve(name);
if ( var==null ) {
Main.error(ctx.ID().getSymbol(), "no such variable: "+name);
}
if ( var instanceof FunctionSymbol ) {
Main.error(ctx.ID().getSymbol(), name+" is not a variable");
}
Symbol x = stack.pop();
System.out.println("==\n::assign value"+x.value + " assign type:" + x.type.toString());
if(var.type == x.type){
currentScope.resolve(name).value = x.value;
System.out.println("assignment name:"+name +" value: "+currentScope.resolve(name).value+" type:"+currentScope.resolve(name).type+"===\n");
}else if (var.type == Type.tFLOAT && x.type== Type.tINT){
currentScope.resolve(name).value = x.asFloat();
System.out.println("assignment name:"+name +" value: "+currentScope.resolve(name).value+" type:"+currentScope.resolve(name).type+"===\n");
}else{
}
}
public void exitFuncCallExpr(@NotNull FuncCallExprContext ctx) {
String funcName = ctx.ID().getText();
Symbol meth = currentScope.resolve(funcName);
if ( meth==null ) {
Main.error(ctx.ID().getSymbol(), "no such function: "+funcName);
}
if ( meth instanceof VariableSymbol ) {
Main.error(ctx.ID().getSymbol(), funcName+" is not a function");
}
//#WHAT THE HECK
}
public void exitBangExpr(@NotNull BangExprContext ctx) {
Symbol op1 = stack.pop();
Symbol ans=null;
String operator = ctx.getChild(0).getText();
if(op1.isBoolean()){
if (operator.equals("!")){
ans = new Symbol(Type.tBOOLEAN, !op1.asBoolean());
}
stack.push(ans);
}else{
TerminalNode tn = (TerminalNode) ctx.getChild(1);
String msg = "The operator " + operator +" is undefined for the argument type(s)" + op1;
Main.semanticError(tn.getSymbol(),msg);
}
}
public void exitOrExpr(@NotNull OrExprContext ctx) {
Symbol op1 = stack.pop();
Symbol op2 = stack.pop();
Symbol ans=null;
String operator = ctx.getChild(1).getText();
if(op1.isBoolean()&&op2.isBoolean()){
if (operator.equals("||")){
ans = new Symbol(Type.tBOOLEAN, op2.asBoolean() || op1.asBoolean());
}
stack.push(ans);
}else{
TerminalNode tn = (TerminalNode) ctx.getChild(1);
String msg = "The operator " + operator +" is undefined for the argument type(s)" + op2 +", "+ op1;
Main.semanticError(tn.getSymbol(),msg);
}
}
public void exitAndExpr(@NotNull AndExprContext ctx) {
Symbol op1 = stack.pop();
Symbol op2 = stack.pop();
Symbol ans=null;
String operator = ctx.getChild(1).getText();
if(op1.isBoolean()&&op2.isBoolean()){
if (operator.equals("&&")){
ans = new Symbol(Type.tBOOLEAN, op2.asBoolean() && op1.asBoolean());
}
stack.push(ans);
}else{
TerminalNode tn = (TerminalNode) ctx.getChild(1);
String msg = "The operator " + operator +" is undefined for the argument type(s)" + op2 +", "+ op1;
Main.semanticError(tn.getSymbol(),msg);
}
}
public void exitEqualityExpr(@NotNull EqualityExprContext ctx) {
Symbol op1 = stack.pop();
Symbol op2 = stack.pop();
Symbol ans=null;
String operator = ctx.getChild(1).getText();
if((op1.isFloat() || op1.isInt())&&(op2.isFloat() || op2.isInt())){
if (operator.equals("==")){
ans = new Symbol(Type.tBOOLEAN, op2.asFloat() == op1.asFloat());
}else if (operator.equals("!=")){
ans = new Symbol(Type.tBOOLEAN, op2.asFloat() != op1.asFloat());
}
stack.push(ans);
}else if(op1.isBoolean()&&op2.isBoolean()){
if (operator.equals("==")){
ans = new Symbol(Type.tBOOLEAN, op2.asBoolean() == op1.asBoolean());
}else if (operator.equals("!=")){
ans = new Symbol(Type.tBOOLEAN, op2.asBoolean() != op1.asBoolean());
}
stack.push(ans);
}else if(op1.isString()&&op2.isString()){
if (operator.equals("==")){
ans = new Symbol(Type.tBOOLEAN, op2.asString() == op1.asString());
}else if (operator.equals("!=")){
ans = new Symbol(Type.tBOOLEAN, op2.asString() != op1.asString());
}
stack.push(ans);
}else if(op1.isChar()&&op2.isChar()){
if (operator.equals("==")){
ans = new Symbol(Type.tBOOLEAN, op2.asChar() == op1.asChar());
}else if (operator.equals("!=")){
ans = new Symbol(Type.tBOOLEAN, op2.asChar() != op1.asChar());
}
stack.push(ans);
}else{
//error expression type mismatch
//The operator - is undefined for the argument type(s) java.lang.String, java.lang.String
TerminalNode tn = (TerminalNode) ctx.getChild(1);
String msg = "The operator " + operator +" is undefined for the argument type(s)" + op2 +", "+ op1;
Main.semanticError(tn.getSymbol(),msg);
}
}
public void exitRelationalExpr(@NotNull RelationalExprContext ctx) {
Symbol op1 = stack.pop();
Symbol op2 = stack.pop();
Symbol ans=null;
String operator = ctx.getChild(1).getText();
if((op1.isFloat() || op1.isInt())&&(op2.isFloat() || op2.isInt())){
if (operator.equals(">")){
ans = new Symbol(Type.tBOOLEAN, op2.asFloat() > op1.asFloat());
}else if (operator.equals("<")){
ans = new Symbol(Type.tBOOLEAN, op2.asFloat() < op1.asFloat());
}else if (operator.equals(">=")){
ans = new Symbol(Type.tBOOLEAN, op2.asFloat() >= op1.asFloat());
}else if (operator.equals("<=")){
ans = new Symbol(Type.tBOOLEAN, op2.asFloat() <= op1.asFloat());
}
stack.push(ans);
}else{
//error expression type mismatch
//The operator - is undefined for the argument type(s) java.lang.String, java.lang.String
TerminalNode tn = (TerminalNode) ctx.getChild(1);
String msg = "The operator " + operator +" is undefined for the argument type(s)" + op2 +", "+ op1;
Main.semanticError(tn.getSymbol(),msg);
}
}
public void exitMultdivmodExpr(MultdivmodExprContext ctx) {
Symbol op1 = stack.pop();
Symbol op2 = stack.pop();
Symbol ans=null;
String operator = ctx.getChild(1).getText();
if((op1.isFloat() || op1.isInt())&&(op2.isFloat() || op2.isInt())){
if(op1.asFloat()==0 && (operator.equals("/")||operator.equals("%"))){
//error div/mod by zero
TerminalNode tn = (TerminalNode) ctx.getChild(1);
String msg = "Arithmetic Error: " + operator +" by zero";
Main.semanticError(tn.getSymbol(),msg);
}else {
if(op1.isFloat()||op2.isFloat()){ //push as float
if (operator.equals("*")){
ans = new Symbol(Type.tFLOAT, op2.asFloat() * op1.asFloat());
}else if(operator.equals("/")){
ans = new Symbol(Type.tFLOAT, op2.asFloat() / op1.asFloat());
}else if(operator.equals("%")){
ans = new Symbol(Type.tFLOAT, op2.asFloat() % op1.asFloat());
}
}else{ //push as int
if (operator.equals("*")){
ans = new Symbol(Type.tINT, op2.asInt() * op1.asInt());
}else if(operator.equals("/")){
ans = new Symbol(Type.tINT, op2.asInt() / op1.asInt());
}else if(operator.equals("%")){
ans = new Symbol(Type.tINT, op2.asInt() % op1.asInt());
}
}
stack.push(ans);
}
}else{
//error expression type mismatch
TerminalNode tn = (TerminalNode) ctx.getChild(1);
String msg = "The operator " + operator +" is undefined for the argument type(s)" + op2 +", "+ op1;
Main.semanticError(tn.getSymbol(),msg);
}
}
public void exitAddminusExpr(AddminusExprContext ctx) {
Symbol op1 = stack.pop();
Symbol op2 = stack.pop();
Symbol ans;
String operator = ctx.getChild(1).getText();
if((op1.isFloat() || op1.isInt())&&(op2.isFloat() || op2.isInt())){
if(op1.isFloat()||op2.isFloat()){ //push as float
if (operator.equals("-")){
ans = new Symbol(Type.tFLOAT, op2.asFloat() - op1.asFloat());
}else{
ans = new Symbol(Type.tFLOAT, op2.asFloat() + op1.asFloat());
}
}else{ //push as int
if (operator.equals("-")){
ans = new Symbol(Type.tINT, op2.asInt() - op1.asInt());
}
else{
ans = new Symbol(Type.tINT, op2.asInt() + op1.asInt() );
}
}
stack.push(ans);
}else if(op1.isString() && op2.isString() && operator.equals("+")){
ans = new Symbol(Type.tSTRING, op2.asString() + op1.asString());
stack.push(ans);
}else{
//error expression type mismatch
//The operator - is undefined for the argument type(s) java.lang.String, java.lang.String
TerminalNode tn = (TerminalNode) ctx.getChild(1);
String msg = "The operator " + operator +" is undefined for the argument type(s)" + op2 +", "+ op1;
Main.semanticError(tn.getSymbol(),msg);
}
}
public void exitNullExpr(NullExprContext ctx) {
Symbol s = new Symbol(Type.tNULL,null);
stack.push(s);
}
public void exitIdExpr(@NotNull CodeCraftGrammarParser.IdExprContext ctx) {
String name = ctx.ID().getSymbol().getText();
Symbol var = currentScope.resolve(name);
if ( var==null ) {
Main.semanticError(ctx.ID().getSymbol(), "no such variable: "+name);
}else if ( var instanceof FunctionSymbol ) {
Main.semanticError(ctx.ID().getSymbol(), name+" is not a variable");
}else{
stack.push(var);
}
}
public void exitIntExpr(IntExprContext ctx) {
String valueInString = ctx.IntegerLiteral().getSymbol().getText();
Integer valueInInteger = Integer.valueOf(valueInString);
Symbol s = new Symbol(Type.tINT,valueInInteger);
stack.push(s);
}
public void exitFloatExpr(FloatExprContext ctx) {
String valueInString = ctx.FloatingPointLiteral().getSymbol().getText();
Float valueInFloat = Float.valueOf(valueInString);
Symbol s = new Symbol(Type.tFLOAT,valueInFloat);
stack.push(s);
}
public void exitBoolExpr(BoolExprContext ctx) {
String value = ctx.BooleanLiteral().getSymbol().getText();
Symbol s = null;
if("true".equals(value)){
s = new Symbol(Type.tBOOLEAN,true);
}else if("false".equals(value)){
s = new Symbol(Type.tBOOLEAN,false);
}
stack.push(s);
}
public void exitStringExpr(StringExprContext ctx) {
String value = ctx.StringLiteral().getSymbol().getText();
Symbol s;
if(value.length()==2){ //String is blank
s = new Symbol(Type.tSTRING,"");
}else{
s = new Symbol(Type.tSTRING,value.substring(1, value.length()-1));
}
stack.push(s);
}
public void exitCharExpr(CharExprContext ctx) {
String value = ctx.CharacterLiteral().getSymbol().getText();
Symbol s = new Symbol(Type.tCHAR,value.charAt(1));
stack.push(s);
}
}
It would be difficult to evaluate the IF statement with the listener interface, as with the listener interface, you are driven by the Antlr tree walker, which travels the AST tree in its owner order. However, in order to evaluate the IF statement, you need to control the order of the evaluation of the AST or part of the AST based on the evaluation result of the condition statement.
I think the visitor interface is more suitable for the evaluation of IF, For/While and function calls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With