一个基于ANTLR 4的布尔表达式语句解释器的实现

时间:2022-04-18 21:54:02

Reference

The Definitive ANTLR 4 Reference, 2nd Edition.

0 Features

labeled grammar definition, i.e. # eq;

Visitorpattern.

1 The Grammar

grammar RuleSet;

prog:   stmt+ ;

stmt: expr '=' expr NEWLINE     #eq
    |expr '<' expr NEWLINE              #lt
    |expr '<=' expr NEWLINE             # le
    |expr '>' expr NEWLINE              # gt
    |expr '>=' expr NEWLINE             # ge
    ;

expr:   expr op=('*'|'/') expr      # MulDiv
        |   expr op=('+'|'-') expr          # AddSub
        |   INT                 # int
        |   '(' expr ')'                # parens
        ;

MUL :   '*' ;         // assigns token name to '*' used above in grammar
DIV :   '/' ;
ADD :   '+' ;
SUB :   '-' ;
NEWLINE : [\r\n]+ ;
INT :   [0-9]+ ;         // match integers
WS  :   [ \t]+ -> skip ; // toss out whitespace

2 Play around with ANTLR 4

export CLASSPATH=".:./antlr-4.5.1-complete.jar:$CLASSPATH"

# generate listner
#java -jar ./antlr-4.5.1-complete.jar RuleSet.g4

# generate visitor
java -jar ./antlr-4.5.1-complete.jar -no-listener -visitor RuleSet.g4

3 The Interpreter

Put the generate java files in pakage com.spike.antlr4.rs.

3.1 The Visitor

    package com.spike.antlr4.rs;

import com.spike.antlr4.rs.RuleSetParser.ExprContext;

public class EvalVisitor extends RuleSetBaseVisitor<Integer> {
    private boolean result = false;

    public boolean getResult() {
        return result;
    }

    public void setResult(boolean result) {
        this.result = result;
    }

    /** = */
    @Override
    public Integer visitEq(RuleSetParser.EqContext ctx) {
        // Integer result = visitChildren(ctx);

        ExprContext left = ctx.expr(0);
        ExprContext right = ctx.expr(1);
        int result = visit(left) - visit(right);
        if (result == 0) {
            this.setResult(true);
        } else {
            this.setResult(false);
        }
        return result;
    }

    /** <= */
    @Override
    public Integer visitLe(RuleSetParser.LeContext ctx) {
        // Integer result = visitChildren(ctx);
        ExprContext left = ctx.expr(0);
        ExprContext right = ctx.expr(1);
        int result = visit(left) - visit(right);
        if (result <= 0) {
            this.setResult(true);
        } else {
            this.setResult(false);
        }

        return result;
    }

    /** < */
    @Override
    public Integer visitLt(RuleSetParser.LtContext ctx) {
        ExprContext left = ctx.expr(0);
        ExprContext right = ctx.expr(1);
        int result = visit(left) - visit(right);
        if (result < 0) {
            this.setResult(true);
        } else {
            this.setResult(false);
        }
        return result;
    }

    /** >= */
    @Override
    public Integer visitGe(RuleSetParser.GeContext ctx) {
        ExprContext left = ctx.expr(0);
        ExprContext right = ctx.expr(1);
        int result = visit(left) - visit(right);
        if (result >= 0) {
            this.setResult(true);
        } else {
            this.setResult(false);
        }
        return result;
    }

    /** > */
    @Override
    public Integer visitGt(RuleSetParser.GtContext ctx) {
        ExprContext left = ctx.expr(0);
        ExprContext right = ctx.expr(1);
        int result = visit(left) - visit(right);
        if (result > 0) {
            this.setResult(true);
        } else {
            this.setResult(false);
        }
        return result;
    }

    /** INT */
    @Override
    public Integer visitInt(RuleSetParser.IntContext ctx) {
        return Integer.valueOf(ctx.INT().getText());
    }

    /** expr op=('*'|'/') expr */
    @Override
    public Integer visitMulDiv(RuleSetParser.MulDivContext ctx) {
        int left = visit(ctx.expr(0)); // get value of left subexpression
        int right = visit(ctx.expr(1)); // get value of right subexpression
        if (ctx.op.getType() == RuleSetParser.MUL)
            return left * right;
        return left / right; // must be DIV
    }

    /** expr op=('+'|'-') expr */
    @Override
    public Integer visitAddSub(RuleSetParser.AddSubContext ctx) {
        int left = visit(ctx.expr(0)); // get value of left subexpression
        int right = visit(ctx.expr(1)); // get value of right subexpression
        if (ctx.op.getType() == RuleSetParser.ADD)
            return left + right;
        return left - right; // must be SUB
    }

    /** '(' expr ')' */
    @Override
    public Integer visitParens(RuleSetParser.ParensContext ctx) {
        return visit(ctx.expr()); // return child expr's value
    }

}

3.2 The Main Entrance

    package com.spike.antlr4.rs;

import java.util.ArrayList;
import java.util.List;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;

/**
 * The Interpreter
 *
 * @author zhoujiagen<br/>
 *         Aug 26, 2015 1:20:34 AM
 */
public class Calc {
    static String NEWLINE = "\r\n";

    public static void main(String[] args) {
        System.out.println(eval("123=100+12+11" + NEWLINE, "124 + 125 = 123 + 126" + NEWLINE, "345 <= 346 + 347 + 348"
                + NEWLINE));
    }

    static List<Boolean> eval(String... stmts) {
        if (stmts != null && stmts.length > 0) {
            List<Boolean> result = new ArrayList<Boolean>();

            for (String stmt : stmts) {
                result.add(eval(stmt));
            }

            return result;
        }

        return null;
    }

    static boolean eval(String stmt) {
        boolean result = false;

        if (stmt != null && stmt.length() > 0) {
            ANTLRInputStream input = new ANTLRInputStream(stmt);
            RuleSetLexer lexer = new RuleSetLexer(input);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            RuleSetParser parser = new RuleSetParser(tokens);
            ParseTree tree = parser.stmt(); // parse

            EvalVisitor eval = new EvalVisitor();
            System.out.println(eval.visit(tree));

            // get the final result
            result = eval.getResult();
        }

        return result;
    }

}