Im making a programming language as a project at my University and I’ve run into a problem with adding a return statement in functions.
This is my grammar:
grammar LCT; program : statement* ; statement : assignStatement | reassignment | output | returnStatement | ifStatement | forStatement | functionDeclaration | functionCall ; assignStatement : Var Identifier '=' expr | Var Identifier ; reassignment : Identifier '=' expr ; output : Print LeftParen expr RightParen ; expr : variable # VariableExpr | expr '++' # PostIncrementExpr | expr '--' # PostDecrementExpr | '++' expr # PreIncrementExpr | '--' expr # PreDecrementExpr | '!' expr # NotExpr | expr op=Power expr # PowerExpr | expr op=(LessEqual|MoreEqual|LessThan|MoreThan) expr # RelationalExpr | expr op=(Multiply|Divide|Modulo) expr # MultiplicativeExpr | expr op=(Plus|Minus) expr # AdditiveExpr | expr op=(Equal|NotEqual) expr # EqualExpr | expr AND expr # AndExpr | expr OR expr # OrExpr | functionCall # FunctionReturn ; variable : (Int | Float) # NumberVariable | Identifier # IdentifierVariable | String # StringVariable | (True | False) # BooleanVariable ; functionDeclaration : Function identifier '(' arguments? ')' statementBlock ; functionCall : identifier '(' arguments? ')' ; identifier : Identifier | functionName ; arguments : expr (',' expr)* ; functionName : 'test' ; returnStatement : Return LeftParen expr RightParen ; statementBlock : statement+ End ; forStatement : For forCondition statementBlock ; forCondition : loopCount=expr 'times' ; ifStatement : If conditionBlock (Else If conditionBlock)* (Else Then statementBlock)? ; conditionBlock : '('expr')' Then statementBlock ; // Tokens fragment Newline: ('r' 'n'? | 'n'); Multi_comment: '#-' .*? '-#' -> skip; //Doesn't work without skip Single_comment: '#' ~[r|n]* -> skip; // //Reserved keywords Function: 'function'; Break: 'break'; For: 'loop'; If: 'if'; Else: 'else'; Square_root: 'sqrt'; OR: 'OR'; // Instead of || AND: 'AND'; // Instead of && True: 'true'; False: 'false'; Return: 'return'; Var: 'var'; Print: 'output'; End: 'end'; Then: 'then'; LeftParen: '('; RightParen: ')'; LeftBracket: '['; RightBracket: ']'; LeftBrace: '{'; RightBrace: '}'; Colon: ':'; Semicolon: ';'; Comma: ','; //Operators Plus: '+'; Minus: '-'; Multiply: '*'; Divide: '/'; Power: '^'; Modulo: '%'; LessThan: '<'; MoreThan: '>'; Assign: '='; LessEqual: '<='; MoreEqual: '>='; NotEqual: '!='; Not: '!'; Equal: '=='; String: '"' (~('n' | '"'))* '"'; Int: '0' | '-'?[1-9][0-9]*; Float: [0.9]*[.]?[0.9]+; Identifier: [a-zA-Z_] [a-zA-Z0-9_]*; Whitespace: [ ntr]+ -> skip;
And here is the custom visitor
package LCTlang.statements; import LCTlang.LCTFunctionCall; import LCTlang.LCTBaseVisitor; import LCTlang.LCTParser; import LCTlang.Value; import java.beans.Expression; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class StatementVisitor extends LCTBaseVisitor<Value> { private final Map<String, Value> memory = new HashMap<String, Value>(); private final Map<String, LCTFunctionCall> functions = new HashMap<String, LCTFunctionCall>(); /* Start of all Statements * Start of all Statements * Start of all Statements*/ @Override public Value visitAssignStatement(LCTParser.AssignStatementContext ctx) { if (ctx.getText().contains("=")) { String id = ctx.Identifier().getText(); Value value = this.visit(ctx.expr()); return memory.put(id, value); } else { String id = ctx.Identifier().getText(); Value value = Value.VOID; return memory.put(id, value); } } @Override public Value visitReassignment(LCTParser.ReassignmentContext ctx) { String id = ctx.Identifier().getText(); if (memory.containsKey(id)) { Value value = this.visit(ctx.expr()); return memory.replace(id, value); } else throw new RuntimeException("no such variable: " + id); } @Override public Value visitForStatement(LCTParser.ForStatementContext ctx) { String endCheck = ctx.statementBlock().getText(); if (!endCheck.substring(endCheck.length() - 3).contains("end")) throw new RuntimeException("Missing end to encapsulate the loop"); Value loopCount = this.visit(ctx.forCondition().loopCount); /* Value firstVal = this.visit(ctx.forCondition().startExpr); Value secondVal = this.visit(ctx.forCondition().endExpr); double i; if (firstVal.asDouble() < secondVal.asDouble()) { for (i = firstVal.asDouble() ; i < secondVal.asDouble(); i++){ this.visit(ctx.statementBlock()); } } else if (firstVal.asDouble() > secondVal.asDouble()) { for (i = firstVal.asDouble() ; i < secondVal.asDouble(); i--){ this.visit(ctx.statementBlock()); } }*/ for (int i = 0; i < loopCount.asDouble(); i++){ this.visit(ctx.statementBlock()); } return Value.VOID; } @Override public Value visitIfStatement(LCTParser.IfStatementContext ctx) { List<LCTParser.ConditionBlockContext> conditions = ctx.conditionBlock(); boolean evaluatedBlock = false; for(LCTParser.ConditionBlockContext condition : conditions) { Value evaluated = this.visit(condition.expr()); if(evaluated.asBoolean()) { evaluatedBlock = true; // evaluate this block whose expr==true this.visit(condition.statementBlock()); break; } } if(!evaluatedBlock && ctx.statementBlock() != null) { // evaluate the else-stat_block (if present == not null) this.visit(ctx.statementBlock()); } return Value.VOID; } @Override public Value visitFunctionDeclaration(LCTParser.FunctionDeclarationContext ctx) { String id = ctx.identifier().getText(); String[] arguments = null; if (ctx.arguments() != null) { arguments = ctx.arguments().getText().split(","); for (String arg: arguments) { Value value = Value.VOID; memory.put(arg, value); } } LCTFunctionCall funcCall = new LCTFunctionCall(ctx.statementBlock(), arguments); functions.put(id, funcCall); return Value.VOID; } @Override public Value visitFunctionCall(LCTParser.FunctionCallContext ctx) { ArrayList<Value> values = new ArrayList<Value>(); int i = 0; String id = ctx.identifier().getText(); LCTFunctionCall funcCall = functions.get(id); if (funcCall.getArguments() != null) { if (ctx.arguments() == null) throw new RuntimeException("Missing arguments in function call for: " + id); for (LCTParser.ExprContext expr : ctx.arguments().expr()){ values.add(this.visit(expr)); } for (String arg: funcCall.getArguments()) { memory.replace(arg, values.get(i)); i++; } } this.visit(funcCall.getStatements()); return Value.VOID; } /* Start of all Variables * Start of all Variables * Start of all Variables*/ @Override public Value visitVariableExpr(LCTParser.VariableExprContext ctx) { Value value = this.visit(ctx.variable()); return value; } @Override public Value visitIdentifierVariable(LCTParser.IdentifierVariableContext ctx) { String id = ctx.getText(); Value value = memory.get(id); if(value == null) { throw new RuntimeException("no such variable: " + id); } return value; } @Override public Value visitStringVariable(LCTParser.StringVariableContext ctx) { String str = ctx.getText(); // strip quotes str = str.substring(1, str.length() - 1).replace("""", """); return new Value(str); } @Override public Value visitNumberVariable(LCTParser.NumberVariableContext ctx) { return new Value(Double.valueOf(ctx.getText())); } @Override public Value visitBooleanVariable(LCTParser.BooleanVariableContext ctx) { return new Value(Boolean.valueOf(ctx.getText())); } /* Start of all Expr * Start of all Expr * Start of all Expr*/ @Override public Value visitPostIncrementExpr(LCTParser.PostIncrementExprContext ctx) { Value expression = this.visit(ctx.expr()); int i = 1; return new Value(expression.asDouble() + i); } @Override public Value visitPostDecrementExpr(LCTParser.PostDecrementExprContext ctx) { Value expression = this.visit(ctx.expr()); int i = 1; return new Value(expression.asDouble() - i); } @Override public Value visitPreIncrementExpr(LCTParser.PreIncrementExprContext ctx) { Value expression = this.visit(ctx.expr()); int i = 1; return new Value(i + expression.asDouble()); } @Override public Value visitPreDecrementExpr(LCTParser.PreDecrementExprContext ctx) { Value expression = this.visit(ctx.expr()); int i = -1; return new Value(i + expression.asDouble()); } @Override public Value visitPowerExpr(LCTParser.PowerExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); return new Value(Math.pow(left.asDouble(), right.asDouble())); } @Override public Value visitAdditiveExpr(LCTParser.AdditiveExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); switch (ctx.op.getType()) { case LCTParser.Plus: return left.isDouble() && right.isDouble() ? new Value(left.asDouble() + right.asDouble()) : new Value(left.asString() + right.asString()); case LCTParser.Minus: return new Value(left.asDouble() - right.asDouble()); default: throw new RuntimeException("unknown operator: " + LCTParser.tokenNames[ctx.op.getType()]); } } @Override public Value visitMultiplicativeExpr(LCTParser.MultiplicativeExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); switch (ctx.op.getType()) { case LCTParser.Multiply: return new Value(left.asDouble() * right.asDouble()); case LCTParser.Divide: if (left.asDouble() == 0 || right.asDouble() == 0) { throw new RuntimeException("Division with 0 is illegal"); //return left.asDouble() == 0 ? right : left; } return new Value(left.asDouble() / right.asDouble()); case LCTParser.Modulo: return new Value(left.asDouble() % right.asDouble()); default: throw new RuntimeException("unknown operator: " + LCTParser.tokenNames[ctx.op.getType()]); } } @Override public Value visitRelationalExpr(LCTParser.RelationalExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); switch (ctx.op.getType()) { case LCTParser.LessThan: return new Value(left.asDouble() < right.asDouble()); case LCTParser.LessEqual: return new Value(left.asDouble() <= right.asDouble()); case LCTParser.MoreThan: return new Value(left.asDouble() > right.asDouble()); case LCTParser.MoreEqual: return new Value(left.asDouble() >= right.asDouble()); default: throw new RuntimeException("unknown operator: " + LCTParser.tokenNames[ctx.op.getType()]); } } @Override public Value visitEqualExpr(LCTParser.EqualExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); switch (ctx.op.getType()) { case LCTParser.Equal: if (!left.isDouble() && !right.isDouble()){ return new Value(left.asString().equals(right.asString())); } else return new Value(Math.abs(left.asDouble() - right.asDouble()) < 0.00000000001); case LCTParser.NotEqual: if (!left.isDouble() && !right.isDouble()){ return new Value(!left.asString().equals(right.asString())); } else return new Value(Math.abs(left.asDouble() - right.asDouble()) >= 0.00000000001); default: throw new RuntimeException("unknown operator: " + LCTParser.tokenNames[ctx.op.getType()]); } } @Override public Value visitAndExpr(LCTParser.AndExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); return new Value(left.asBoolean() && right.asBoolean()); } @Override public Value visitOrExpr(LCTParser.OrExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); return new Value(left.asBoolean() || right.asBoolean()); } //OUTPUT @Override public Value visitOutput(LCTParser.OutputContext ctx) { if ((ctx.getText().contains("<missing '('>")) || (ctx.getText().contains("<missing ')'>"))) { throw new RuntimeException("Missing ( ) around output expression"); } Value value = this.visit(ctx.expr()); System.out.println(value); return value; } }
To help with transferring data I have made classes to carry values and function data.
package LCTlang; public class Value { public static Value VOID = new Value(new Object()); final Object value; public Value(Object value) { this.value = value; } public Boolean asBoolean() { return (Boolean)value; } public Double asDouble() { return (Double)value; } public String asString() { return String.valueOf(value); } public boolean isDouble() { return value instanceof Double; } @Override public int hashCode() { if(value == null) { return 0; } return this.value.hashCode(); } @Override public boolean equals(Object o) { if(value == o) { return true; } if(value == null || o == null || o.getClass() != value.getClass()) { return false; } Value that = (Value)o; return this.value.equals(that.value); } @Override public String toString() { return String.valueOf(value); } }
package LCTlang; public class LCTFunctionCall { LCTParser.StatementBlockContext statements; String[] arguments; public LCTFunctionCall(LCTParser.StatementBlockContext Statements, String[] Arguments){ this.statements = Statements; this.arguments = Arguments; } public LCTParser.StatementBlockContext getStatements() { return statements; } public String[] getArguments() { return arguments; } }
The problem I have atm is when I make a function call I want to get a value in return if the function has a return statement. Here is a code example with functions, the function call testFunc has the return statement.
function testFunc(x) return("Hello, " + x + " I'm the compiler") end function FizzBuzz(loopAmount) var print = "" var i = 0 var fizz = 3 var buzz = 5 loop loopAmount times i = i++ print = "" if ( i % fizz == 0 ) then print = print + "Fizz" end if ( i % buzz == 0 ) then print = print + "Buzz" end if ( print == "") then print = i end output(print) end end function main() output(testFunc("Bob")) end main()
Advertisement
Answer
The first issue I see is that there is no visitReturnStatement
in your Visitor class.
This method would need to be responsible for pulling the return value from the result of visiting its expr()
child and getting it’s value
.
The more interesting issue is that you might be anywhere within the functionDeclaration
‘s statementBlock
child sub-tree when the return
is encountered. However, whenever it’s encountered, it needs to “return” immediately from the function, without bothering with any more of the logic in the statementBlock
.
Off hand, a fairly simple solution it to use exception handling for this (even though it’s not really an exception, you need the “jump out of here up to the first place that’s ready to handle me” behavior that exceptions do well).
If you set up something like a FunctionReturnResultException
that has a Value
member in it’s definition, you could wrap your code to evaluate the statements in the statement block in visitFunctionCall
:
this.visit(funcCall.getStatements());
in a try catch something like
try { this.visit(funcCall.getStatements()); // What am I doing here? no return?? } catch (FunctionReturnResultException res) { // pull the value from your exception and return it. }
Your visitReturnStatement
method would contain something akin to:
throw new FunctionReturnResultException(this.visit(ctx.expr()));
——-
BTW, saw this in your code:
return memory.put(id, value);
You might want to double-check docs on HashMap.put(). I suspect this is not doing what you intend.