下面是两个简单的 ANTLR 语法,演示了两种评估逻辑和算术表达式混合的方法,然后是测试类及其输出。第一个语法采用您要求的形式,使用 ANTLR 确定表达式类型并确保表达式值兼容。另一种形式使用语法中的动作来确定表达式类型。我在这里提供它以进行比较。
我在两种语法中都对表达式求值进行了硬编码,以使示例更有用并显示它们按预期工作。请注意,除了您在下面的测试代码中看到的内容之外,我还没有测试过这些语法。此外,我只包括了演示感兴趣的概念所必需的运算符。添加其他人应该很容易。
要求 ANTLR 保持表达式类型不同需要告诉 ANTLR 当运算符对多个表达式类型有效时该怎么做。例如在下面的语法LogicRules
中,词法分析器标记LPAR
标记以 rule 开头的逻辑表达式compExpr
或以 开头的算术表达式的开头primaryExpr
。默认情况下,ANTLR 无法知道 input"(3..."
是逻辑表达式分组 ( "(3 + 3 == 6)"
) 的开头还是算术/数字表达式分组 ( "(3 + 3) == 6"
) 的开头。rule 中的句法谓词compExpr
用于帮助 ANTLR 区分这两种选择,实际上是告诉它等待和观察。
LogicRules.g — 让 ANTLR 规则区分布尔表达式和数字表达式
grammar LogicRules;
statement returns [boolean result]
: logicalExpr {$result = $logicalExpr.result;} EOF
;
logicalExpr returns [boolean result]
@init { $result = true;}
: lhs=compExpr {$result = $lhs.result;}
(AND rhs=compExpr
{$result = $result && $rhs.result;}
)*
;
compExpr returns [boolean result]
@init { $result = true;}
: (eqExpr) => eqExpr {$result = $eqExpr.result;}
//^^ Needs a syntactic predicate to differentiate between logical grouping and arithmetic grouping
| LPAR logicalExpr {$result = $logicalExpr.result;} RPAR
//^^ Only group logical expressions at this level.
;
eqExpr returns [boolean result]
@init {$result = true;}
: lhs=arithmeticExpr
(EQ rhs=arithmeticExpr
{$result = $result && $lhs.result == $rhs.result;}
)+
//^^ use +: a logical expression of arithmetic expressions
// requires a logical operation to produce a boolean value
;
arithmeticExpr returns [int result]
@init {$result = 0;}
: lhs=primaryExpr {$result += $lhs.result;}
(PLUS rhs=primaryExpr
{$result += $rhs.result;}
)*
;
primaryExpr returns [int result]
@init {$result = 0;}
: INT {$result = $INT.int;}
| LPAR arithmeticExpr RPAR {$result = $arithmeticExpr.result;}
//^^ Only group other numeric/arithmetic expressions at this level.
;
INT : ('0'..'9')+;
AND : '&&';
EQ : '==';
LPAR : '(';
RPAR : ')';
PLUS : '+';
WS : (' '|'\t'|'\r'|'\n')+ {skip();};
使用代码而不是 ANTLR 规则评估表达式的结果类型会导致如下文法。请注意,分组仅在一个级别完成,不需要句法谓词。整体语法保持相对简单。和前面的语法一样,这个语法在遇到无效的类型转换时会产生错误。
LogicEval.g — 让代码区分布尔表达式和数字表达式
grammar LogicEval;
@parser::members {
private static boolean toBoolean(Object obj){
if (obj instanceof Boolean){
return (Boolean)obj;
} else {
throw new RuntimeException("Cannot convert " + obj + " to boolean");
}
}
private static int toInt(Object obj){
if (obj instanceof Integer){
return (Integer)obj;
} else {
throw new RuntimeException("Cannot convert " + obj + " to integer");
}
}
}
statement returns [Object result]
: expr {$result = $expr.result;} EOF
;
expr returns [Object result]
: lhs=compExpr {$result = $lhs.result;}
(AND rhs=compExpr
{$result = toBoolean($result) && toBoolean($rhs.result);}
)*
;
compExpr returns [Object result]
@init {Object lhsResult = null;}
: lhs=arithmeticExpr {$result = lhsResult = $lhs.result;}
(EQ rhs=arithmeticExpr
{$result = toInt(lhsResult) == toInt($rhs.result);}
)*
;
arithmeticExpr returns [Object result]
: lhs=primaryExpr {$result = $lhs.result;}
(PLUS rhs=primaryExpr
{$result = toInt($result) + toInt($rhs.result);}
)*
;
primaryExpr returns [Object result]
: INT {$result = $INT.int;}
| LPAR expr RPAR {$result = $expr.result;}
;
INT : ('0'..'9')+;
AND : '&&';
EQ : '==';
LPAR : '(';
RPAR : ')';
PLUS : '+';
WS : (' '|'\t'|'\r'|'\n')+ {skip();};
LogicTest.java — 两种语法的测试代码
public class LogicTest {
public static void main(String[] args) {
test("1 + 2 == 3", Result.True);
test("1 + 2 == 4", Result.False);
test("1 + 2 == 3 && 1 + 2 == 4", Result.False);
test("1 + 2 == 3 && 4 + 5 == 9", Result.True);
test("(1 + 2) == 3 && (4 + 5 == 9)", Result.True);
test("1 + 2 == (3 && 4 + 5 == 9)", Result.Failure);
test("1 && 2", Result.Failure);
test("1 + 2", Result.Failure);
}
private static void test(String rawInput, Result expectedResult){
Result rulesResult = runRules(rawInput);
Result evalResult = runEval(rawInput);
System.out.println("---\n");
System.out.printf("**Input:** %s%n%n", rawInput);
System.out.printf("**Expected Result:** %s%n%n", expectedResult);
System.out.printf("**LogicRules Result:** %s%n%n", rulesResult);
System.out.printf("**LogicRules Passed?** %s%n%n", (rulesResult == expectedResult));
System.out.printf("**LogicEval Result:** %s%n%n", evalResult);
System.out.printf("**LogicEval Passed?** %s%n%n", (evalResult == expectedResult));
}
private static Result runRules(String rawInput){
CharStream input = new ANTLRStringStream(rawInput);
LogicRulesLexer lexer = new LogicRulesLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
LogicRulesParser parser = new LogicRulesParser(tokens);
boolean result;
try {
result = parser.statement();
} catch (Exception e) {
return Result.Failure;
}
if (lexer.getNumberOfSyntaxErrors() > 0 || parser.getNumberOfSyntaxErrors() > 0){
return Result.Failure;
}
return result ? Result.True : Result.False;
}
private static Result runEval(String rawInput){
CharStream input = new ANTLRStringStream(rawInput);
LogicEvalLexer lexer = new LogicEvalLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
LogicEvalParser parser = new LogicEvalParser(tokens);
Object result;
try {
result = parser.statement();
} catch (Exception e) {
return Result.Failure;
}
if (lexer.getNumberOfSyntaxErrors() > 0 || parser.getNumberOfSyntaxErrors() > 0){
return Result.Failure;
}
if (result instanceof Boolean){
return ((Boolean)result) ? Result.True : Result.False;
} else {
return Result.Failure; //Produced a valid result, but it wasn't a boolean.
}
}
private static enum Result {
True, False, Failure;
}
}
输出——所有测试通过
输入: 1 + 2 == 3
预期结果:真
逻辑规则结果:真
逻辑规则通过了吗?真的
LogicEval 结果:真
逻辑评估通过了吗?真的
输入: 1 + 2 == 4
预期结果:错误
逻辑规则结果:假
逻辑规则通过了吗?真的
LogicEval 结果:假
逻辑评估通过了吗?真的
输入: 1 + 2 == 3 && 1 + 2 == 4
预期结果:错误
逻辑规则结果:假
逻辑规则通过了吗?真的
LogicEval 结果:假
逻辑评估通过了吗?真的
输入: 1 + 2 == 3 && 4 + 5 == 9
预期结果:真
逻辑规则结果:真
逻辑规则通过了吗?真的
LogicEval 结果:真
逻辑评估通过了吗?真的
输入: (1 + 2) == 3 && (4 + 5 == 9)
预期结果:真
逻辑规则结果:真
逻辑规则通过了吗?真的
LogicEval 结果:真
逻辑评估通过了吗?真的
输入: 1 + 2 == (3 && 4 + 5 == 9)
预期结果:失败
LogicRules 结果:失败
逻辑规则通过了吗?真的
LogicEval 结果:失败
逻辑评估通过了吗?真的
输入: 1 && 2
预期结果:失败
LogicRules 结果:失败
逻辑规则通过了吗?真的
LogicEval 结果:失败
逻辑评估通过了吗?真的
输入: 1 + 2
预期结果:失败
LogicRules 结果:失败
逻辑规则通过了吗?真的
LogicEval 结果:失败
逻辑评估通过了吗?真的