首页 > 解决方案 > ANTLR4 在访客不工作时调用下一步

问题描述

我的语法已经完成,现在想实现功能。但我得到它不起作用,我不明白如何在 functionExpr 中评估应该调用哪个命令。作为我正在使用的测试表达式

  isSet(value, test) and isSet(value, foo)

目前我不知道如何评估单个 CommandIsSet 函数并在那里评估变量类型。为了解决这个问题,顺序应该是

  1. 访问程序
  2. visitAndExpr
  3. visitFunctionExpr (for first isSet(value, test)
  4. 访问命令集
  5. 访问变量参数
  6. 访问值类型
  7. visitFunctionExpr (对于第二个 isSet(value, foo)
  8. 访问命令集
  9. 访问变量参数
  10. 访问值类型

目前我停留在第 3 步,如何确定它是一个 commandIsSet 我需要使用 ctx.getText() 并检查它是否以 isSet 开头?

我已经从 SO 中的@BartKiers 找到了一个很好的答案, 那里我使用了值对象。

这是我的语法

grammar FEL;

prog: expr+ EOF;
expr:
             statement                          #StatementExpr
             |NOT expr                          #NotExpr
             | expr AND expr                 #AndExpr
             | expr (OR | XOR) expr        #OrExpr
             | function                           #FunctionExpr
             | LPAREN expr RPAREN       #ParenExpr
             | writeCommand                 #WriteExpr
 ;

writeCommand: setCommand | setIfCommand;
statement: ID '=' getCommand  NEWLINE         #Assign;
setCommand: 'set' LPAREN variableType '|' parameter RPAREN SEMI;
setIfCommand: 'setIf' LPAREN variableType '|' expr '?' parameter ':' parameter RPAREN SEMI;

getCommand:         getFieldValue                   #FieldValue
                            | getInstanceAttribValue     #InstanceAttribValue
                            | getFormAttribValue          #FormAttributeValue
                            | getMandatorAttribValue   #MandatorAttributeValue
                            ;

getFieldValue: 'getFieldValue' LPAREN instanceID=ID COMMA fieldname=ID RPAREN;
getInstanceAttribValue: 'getInstanceAttrib' LPAREN instanceId=ID COMMA moduleId=ID COMMA attribname=ID RPAREN;
getFormAttribValue: 'getFormAttrib' LPAREN formId=ID COMMA moduleId=ID COMMA attribname=ID RPAREN;
getMandatorAttribValue: 'getMandatorAttrib' LPAREN mandator=ID COMMA moduleId=ID COMMA attribname=ID RPAREN;

twoParameterList: parameter '|' parameter;
parameter:
            variableType #VariableParameter
            | constType  #ConstParameter
            ;
pdixFuncton:ID;
constType:
                ID #ID_Without
                | '"'  ID '"' #ID_WITH
                ;
variableType:
                    valueType                      #ValueTyp
                    |instanceType                #InstanceTyp
                    |formType                      #FormTyp
                    |bufferType                    #BufferTyp
                    |instanceAttribType       #InstanceAttribTyp
                    |formAttribType             #FormAttribTyp
                    |mandatorAttribType     #ManatorTyp
                    ;
valueType:'value' COMMA parameter (COMMA functionParameter)?;
instanceType: 'instance' COMMA instanceParameter;
formType: 'form' COMMA formParameter;
bufferType: 'buffer' COMMA parameter;
instanceParameter: 'instanceId'
                                | 'instanceKey'
                                | 'firstpenId'
                                | 'lastpenId'
                                | 'lastUpdate'
                                | 'started'
                                ;
formParameter: 'formId'
                            |'formKey'
                            |'lastUpdate'
                            ;
functionParameter: 'lastPen'
                                | 'fieldGroup'
                                | ' fieldType'
                                | 'fieldSource'
                                | 'updateId'
                                | 'sessionId'
                                | 'icrConfidence'
                                | 'icrRecognition'
                                |  'lastUpdate';

instanceAttribType:('instattrib' | 'instanceattrib') COMMA attributeType;
formAttribType:'formattrib' COMMA attributeType;
mandatorAttribType: 'mandatorattrib' COMMA attributeType;
attributeType:ID '#' ID;

 function:
                commandIsSet                                    #IsSet
                | commandIsEmpty                            #IsEmpty
                | commandIsEqual                               #IsEqual
                |commandIsNumLessEqual                  #IsLessEqual
                |commandIsNumLess                           #IsNumLess
                |commandIsNumGreaterEqual            #IsNumGreaterEqual
                |commandIsNumGreater                    #IsNumGreater
                |commandIsNumEqual                       #IsNumEqual
                |commandIsLess                               #IsLess
                |commandIsLessEqual                       #IsLessEqual
                |commandIsGreater                           #IsGreater
                |commandIsGreaterEqual                  #IsGreaterEqual
                |commandMatches                             #Matches
                |commandContains                            #Contains
                |commandEndsWith                            #EndsWith
                |commandStartsWith                          #StartsWith
                ;
commandIsSet: IS_SET LPAREN parameter RPAREN;
commandIsEmpty: IS_EMPTY LPAREN parameter RPAREN;
commandIsEqual: IS_EQUAL LPAREN twoParameterList RPAREN;
commandStartsWith: 'startsWith' LPAREN twoParameterList RPAREN;
commandEndsWith: 'endsWith' LPAREN twoParameterList RPAREN;
commandContains: 'contains' LPAREN twoParameterList RPAREN;
commandMatches: 'maches' LPAREN twoParameterList RPAREN;
commandIsLess: 'isLess' LPAREN twoParameterList RPAREN;
commandIsLessEqual: 'isLessEqual' LPAREN twoParameterList RPAREN;
commandIsGreater: 'isGreater' LPAREN twoParameterList RPAREN;
commandIsGreaterEqual: 'isGreaterEqual' LPAREN twoParameterList RPAREN;
commandIsNumEqual: 'isNumEqual' LPAREN twoParameterList RPAREN;
commandIsNumGreater: 'isNumGreater' LPAREN twoParameterList RPAREN;
commandIsNumGreaterEqual: 'isNumGreaterEqual' LPAREN twoParameterList RPAREN;
commandIsNumLess: 'isNumLess' LPAREN twoParameterList RPAREN;
commandIsNumLessEqual: 'isNumLessEqual' LPAREN twoParameterList RPAREN;

/*
stringFunctionType:
    a=substringStrFunction
    |   a=cutStrFunction
    |   a=replaceStrFunction
    |   a=reformatDateStrFunction
    |   a=translateStrFunction
    |   a=fillStrFunction
    |   a=concatStrFunction
    |   a=justifyStrFunction
    |   a=ifElseStrFunction
    |   a=tokenStrFunction
    |   a=toLowerFunction
    |   a=toUpperFunction
    |   a=trimFunction
;
*/

LPAREN : '(';
RPAREN : ')';
LBRACE : '{';
RBRACE : '}';
LBRACK : '[';
RBRACK : ']';
SEMI : ';';
COMMA : ',';
DOT : '.';
ASSIGN : '=';
GT : '>';
LT : '<';
BANG : '!';
TILDE : '~';
QUESTION : '?';
COLON : ':';
EQUAL : '==';
LE : '<=';
GE : '>=';
NOTEQUAL : '!=';
AND : 'and';
OR : 'or';
XOR :'xor';
NOT :'not'  ;
INC : '++';
DEC : '--';
ADD : '+';
SUB : '-';
MUL : '*';
DIV : '/';

INT: [0-9]+;
NEWLINE: '\r'? '\n';
IS_SET:'isSet';
IS_EMPTY:'isEmpty';
IS_EQUAL:'isEqual';
WS: (' '|'\t' | '\n' | '\r' )+ -> skip;

ID
:   JavaLetter JavaLetterOrDigit*
;

fragment
JavaLetter
:   [a-zA-Z$_] // these are the "java letters" below 0xFF
|   // covers all characters above 0xFF which are not a surrogate
    ~[\u0000-\u00FF\uD800-\uDBFF]
    {Character.isJavaIdentifierStart(_input.LA(-1))}?
|   // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF
    [\uD800-\uDBFF] [\uDC00-\uDFFF]
    {Character.isJavaIdentifierStart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1)))}?
;

fragment
JavaLetterOrDigit
:   [a-zA-Z0-9$_] // these are the "java letters or digits" below 0xFF
|   // covers all characters above 0xFF which are not a surrogate
    ~[\u0000-\u00FF\uD800-\uDBFF]
    {Character.isJavaIdentifierPart(_input.LA(-1))}?
|   // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF
    [\uD800-\uDBFF] [\uDC00-\uDFFF]
    {Character.isJavaIdentifierPart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1)))}?
;
fragment DoubleQuote: '"' ;   // Hard to read otherwise.

这是我的带有测试表达式的访问者实现

public class FELCustomVisitor extends FELBaseVisitor<Value> {

// used to compare floating point numbers
public static final double SMALL_VALUE = 0.00000000001;

// store variables (there's only one global scope!)
private Map<String, Value> memory = new HashMap<String, Value>();

public static void main(String[] args) {
    String expression = "isSet(value, test) and isSet(value, foo)";
    FELLexer lexer = new FELLexer(new ANTLRInputStream(expression));
    FELParser parser = new FELParser(new CommonTokenStream(lexer));
    ParseTree tree = parser.prog();
    Value answer = new FELCustomVisitor().visit(tree);
    System.out.printf("answer is %s", answer.asBoolean());
}


@Override
public Value visitProg(FELParser.ProgContext ctx) {
    System.out.println("called prog " + ctx.getText());
    return this.visitChildren(ctx);
}

@Override
public Value visitAndExpr(FELParser.AndExprContext ctx) {
    Value left = this.visit(ctx.expr(0));
    Value right = this.visit(ctx.expr(2));
    System.out.println("working on and expression: left == " + left.asBoolean() + " right is " + right.asBoolean());
    return new Value(left.asBoolean() && right.asBoolean());
}

@Override
public Value visitFunctionExpr(FELParser.FunctionExprContext ctx) {
    System.out.println("called function expresssion " + ctx.getText());
    FELParser.FunctionContext function = ctx.function();

    return null;
}


@Override
public Value visitCommandIsSet(FELParser.CommandIsSetContext ctx) {
    //here I need to extract the inner variable type with this i can evaluate the return value

    return super.visitCommandIsSet(ctx);
}

@Override
public Value visitVariableParameter(FELParser.VariableParameterContext ctx) {
    return super.visitVariableParameter(ctx);
}
@Override
public Value visitValueTyp(FELParser.ValueTypContext ctx) {
    return super.visitValueTyp(ctx);
}

标签: javaantlr4

解决方案


此方法中的部分this.visit(ctx.expr(2))是错误的:

@Override
public Value visitAndExpr(FELParser.AndExprContext ctx) {
    Value left = this.visit(ctx.expr(0));
    Value right = this.visit(ctx.expr(2));
    System.out.println("working on and expression: left == " + left.asBoolean() + " right is " + right.asBoolean());
    return new Value(left.asBoolean() && right.asBoolean());
}

有 2 个子节点的索引为 0 和 1。

你让你的访问者有点太复杂了。从您想要评估的最简单的表达式开始,例如isSet(value, test),然后从树的底部到它的根,并实现这些visit...()方法。在这种情况下isSet(value, test),将是规则:

@Override
public Value visitID_Without(FELParser.ID_WithoutContext ctx) {
    // ID #ID_Without
}

@Override
public Value visitCommandIsSet(FELParser.CommandIsSetContext ctx) {
    // commandIsSet: IS_SET LPAREN parameter RPAREN;
}

@Override
public Value visitProg(FELParser.ProgContext ctx) {
    // expr+ EOF
}

当它起作用时,将and表达式添加到它以支持isSet(value, test) and isSet(value, foo)

@Override
public Value visitID_Without(FELParser.ID_WithoutContext ctx) {
    // ID #ID_Without
}

@Override
public Value visitCommandIsSet(FELParser.CommandIsSetContext ctx) {
    // commandIsSet: IS_SET LPAREN parameter RPAREN;
}

@Override
public Value visitAndExpr(FELParser.AndExprContext ctx) {
    // expr AND expr
}

@Override
public Value visitProg(FELParser.ProgContext ctx) {
    // expr+ EOF
}

等等

一个小的工作示例如下所示:

public class FELCustomVisitor extends FELBaseVisitor<Value> {

    private final Map<String, Value> memory;

    public FELCustomVisitor(Map<String, Value> memory) {
        this.memory = memory;
    }

    @Override
    public Value visitID_Without(FELParser.ID_WithoutContext ctx) {
        // ID #ID_Without
        return this.memory.getOrDefault(ctx.ID().getText(), Value.NULL);
    }

    @Override
    public Value visitCommandIsSet(FELParser.CommandIsSetContext ctx) {
        // commandIsSet: IS_SET LPAREN parameter RPAREN;
        Value param = this.visit(ctx.parameter());
        return param == Value.NULL ? Value.FALSE : Value.TRUE;
    }

    @Override
    public Value visitAndExpr(FELParser.AndExprContext ctx) {
        // expr AND expr
        Value lhs = this.visit(ctx.expr(0));
        Value rhs = this.visit(ctx.expr(1));
        return lhs.asBoolean() && rhs.asBoolean() ? Value.TRUE : Value.FALSE;
    }

    @Override
    public Value visitProg(FELParser.ProgContext ctx) {
        // expr+ EOF
        Value result = Value.NULL;
        for (FELParser.ExprContext expr : ctx.expr()) {
            result = this.visit(expr);
        }
        return result;
    }

    public static void main(String[] args) {
        String[] expressions = {
                "isSet(value, test)",
                "isSet(value, foo)",
                "isSet(value, fooooooo)",
                "isSet(value, test) and isSet(value, foo)",
                "isSet(value, nope) and isSet(value, test) and isSet(value, foo)"
        };

        for (String expression  : expressions) {
            FELLexer lexer = new FELLexer(CharStreams.fromString(expression));
            FELParser parser = new FELParser(new CommonTokenStream(lexer));
            ParseTree tree = parser.prog();
            Map<String, Value> memory = new HashMap<>();
            memory.put("test", new Value(42));
            memory.put("foo", new Value("bar"));
            Value answer = new FELCustomVisitor(memory).visit(tree);
            System.out.printf("%s = %s%n", expression, answer);
        }
    }
}

class Value {

    public static final Value TRUE = new Value(true);
    public static final Value FALSE = new Value(false);
    public static final Value NULL = new Value(null);

    private final Object value;

    public Value(Object value) {
        this.value = value;
    }

    public Boolean asBoolean() {
        return (Boolean) this.value;
    }

    @Override
    public String toString() {
        return String.format("Value{type: %s, value: %s}",
                this.value == null ? "null" : this.value.getClass().getSimpleName(), this.value);
    }
}

运行它时,您会看到以下打印到您的控制台:

isSet(value, test) = Value{type: Boolean, value: true}
isSet(value, foo) = Value{type: Boolean, value: true}
isSet(value, fooooooo) = Value{type: Boolean, value: false}
isSet(value, test) and isSet(value, foo) = Value{type: Boolean, value: true}
isSet(value, nope) and isSet(value, test) and isSet(value, foo) = Value{type: Boolean, value: false}

编辑

[...]因此将调用visitVariableParameter,我如何区分不同的选项并调用正确的访问方法[...]

您不需要覆盖该方法。假设您要实现isSet(instance, instanceId),那么您所要做的就是覆盖visitInstanceType(...)访问者。每当this.visit(ctx.parameter());调用代码中的某个位置时,它都会自动确保parameter调用 的子级。

您只需将此方法添加到访问者:

@Override
public Value visitInstanceType(FELParser.InstanceTypeContext ctx) {
    // 'instance' COMMA instanceParameter
    Value value = new Value(String.format("TODO: instance, `%s`", ctx.instanceParameter().getText()));
    System.out.println("We're in visitInstanceType(...), returning: " + value);
    return value;
}

现在在评估时"isSet(instance, instanceId)",您将看到上述方法被正确调用。


推荐阅读