Skip to content
Advertisement

ANTLR4 Return values from a function

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()

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.

Advertisement