Extending simple ANTLR grammar to support input variables
Я все еще в поисках действительно простого языка, и теперь я знаю, что его нет. Так что я пишу один сам, используя ANTLR3.
Я нашел действительно хороший пример в этом ответе:
Exp.g:
grammar Exp;
eval returns [double value]
: exp=additionExp {$value = $exp.value;}
;
additionExp returns [double value]
: m1=multiplyExp {$value = $m1.value;}
( '+' m2=multiplyExp {$value += $m2.value;}
| '-' m2=multiplyExp {$value -= $m2.value;}
)*
;
multiplyExp returns [double value]
: a1=atomExp {$value = $a1.value;}
( '*' a2=atomExp {$value *= $a2.value;}
| '/' a2=atomExp {$value /= $a2.value;}
)*
;
atomExp returns [double value]
: n=Number {$value = Double.parseDouble($n.text);}
| '(' exp=additionExp ')' {$value = $exp.value;}
;
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
WS
: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
;
Java-код:
public Double evaluate(String string, Map<String, Double> input) throws RecognitionException {
ANTLRStringStream in = new ANTLRStringStream(string);
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
return new ExpParser(tokens).eval();
}
Используя этот грамматик ANTLR, я могу оценить такие выражения, как
(12+14)/2
и получить 13 в результате.
Теперь единственное, чего не хватает в моем сценарии использования, это способ вставить в него простые двойные переменные, чтобы я мог оценить следующее, указав {"A": 12.0, "B":14.0} в качестве входной карты:
(A+B)/2
Есть идеи?
2 ответа
Вы могли бы создать Map<String, Double> memory
в вашем парсере и введите Identifier
в вашей грамматике:
Identifier
: ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*
;
Тогда ваш atomExp
Правило парсера будет выглядеть так:
atomExp returns [double value]
: n=Number {$value = Double.parseDouble($n.text);}
| i=Identifier {$value = memory.get($i.text);} // <- added!
| '(' exp=additionExp ')' {$value = $exp.value;}
;
Вот небольшая (полная) демонстрация:
grammar Exp;
@parser::members {
private java.util.HashMap<String, Double> memory = new java.util.HashMap<String, Double>();
public static Double eval(String expression) throws Exception {
return eval(expression, new java.util.HashMap<String, Double>());
}
public static Double eval(String expression, java.util.Map<String, Double> vars) throws Exception {
ANTLRStringStream in = new ANTLRStringStream(expression);
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
parser.memory.putAll(vars);
return parser.parse();
}
}
parse returns [double value]
: exp=additionExp {$value = $exp.value;}
;
additionExp returns [double value]
: m1=multiplyExp {$value = $m1.value;}
( '+' m2=multiplyExp {$value += $m2.value;}
| '-' m2=multiplyExp {$value -= $m2.value;}
)*
;
multiplyExp returns [double value]
: a1=atomExp {$value = $a1.value;}
( '*' a2=atomExp {$value *= $a2.value;}
| '/' a2=atomExp {$value /= $a2.value;}
)*
;
atomExp returns [double value]
: n=Number {$value = Double.parseDouble($n.text);}
| i=Identifier {$value = memory.get($i.text);}
| '(' exp=additionExp ')' {$value = $exp.value;}
;
Identifier
: ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*
;
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
WS
: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
;
И теперь нет необходимости создавать экземпляр анализатора / лексера самостоятельно, вы можете просто сделать:
import org.antlr.runtime.*;
import java.util.*;
public class ANTLRDemo {
public static void main(String[] args) throws Exception {
Map<String, Double> vars = new HashMap<String, Double>();
vars.put("two", 2.0);
vars.put("pi", Math.PI);
System.out.println(ExpParser.eval("two * pi", vars));
}
}
который будет производить:
6.283185307179586
Удачи!
Бах, нашел время, чтобы реализовать это, так что с таким же успехом можно опубликовать это, хотя я был поражен ударом:)
В приведенной ниже грамматике я реализовал формат назначения переменных, который вы хотели сделать.
grammar Exp;
eval returns [double value]
scope
{
java.util.Hashtable varMap;
}
@init
{
$eval::varMap = new java.util.Hashtable();
}
: exp=additionExp {$value = $exp.value;}
| varList
;
additionExp returns [double value]
: m1=multiplyExp {$value = $m1.value;}
( '+' m2=multiplyExp {$value += $m2.value;}
| '-' m2=multiplyExp {$value -= $m2.value;}
)*
;
multiplyExp returns [double value]
: a1=atomExp {$value = $a1.value;}
( '*' a2=atomExp {$value *= $a2.value;}
| '/' a2=atomExp {$value /= $a2.value;}
)*
;
atomExp returns [double value]
: n=Number {$value = Double.parseDouble($n.text);}
| v=ID {$value = $eval::varMap.get($v);}
| '(' exp=additionExp ')' {$value = $exp.value;}
;
varList
:
OPEN_BRACE assignVar (COMMA assignVar)+ CLOSE_BRACE
;
assignVar
:QUOTE var=ID n=Number QUOTE COLON { $eval::varMap.put($var, $n); }
;
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
WS
: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
;
fragment LETTER: LOWER | UPPER;
fragment LOWER: 'a'..'z';
fragment UPPER: 'A'..'Z';
OPEN_BRACE
: '{'
;
CLOSE_BRACE
: '}'
;
COLON : ';';
COMMA : ',';
QUOTE : '"';
ID
: LETTER*;