Примітка : ця відповідь стосується ANTLR3 ! Якщо ви шукаєте приклад ANTLR4 , то це запитання демонструє, як створити простий синтаксичний аналізатор та оцінювач за допомогою ANTLR4 .
Ви спочатку створюєте граматику. Нижче наведена невелика граматика, яку ви можете використовувати для оцінки виразів, побудованих за допомогою 4 основних математичних операторів: +, -, * та /. Ви також можете групувати вирази, використовуючи дужки.
Зауважте, що ця граматика є просто дуже базовою: вона не обробляє одинарні оператори (мінус у: -1 + 9) або десяткові цифри на зразок .99 (без провідного числа), щоб назвати лише два недоліки. Це лише приклад, над яким можна працювати.
Ось вміст граматичного файлу Exp.g :
grammar Exp;
/* This will be the entry point of our parser. */
eval
: additionExp
;
/* Addition and subtraction have the lowest precedence. */
additionExp
: multiplyExp
( '+' multiplyExp
| '-' multiplyExp
)*
;
/* Multiplication and division have a higher precedence. */
multiplyExp
: atomExp
( '*' atomExp
| '/' atomExp
)*
;
/* An expression atom is the smallest part of an expression: a number. Or
when we encounter parenthesis, we're making a recursive call back to the
rule 'additionExp'. As you can see, an 'atomExp' has the highest precedence. */
atomExp
: Number
| '(' additionExp ')'
;
/* A number: can be an integer value, or a decimal value */
Number
: ('0'..'9')+ ('.' ('0'..'9')+)?
;
/* We're going to ignore all white space characters */
WS
: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
;
(Правила парсера починаються з малої літери, а лексерові правила починаються з великої літери)
Створивши граматику, ви захочете створити з неї аналізатор і лексери. Завантажте банку ANTLR і зберігайте його в тому самому каталозі, що і ваш граматичний файл.
Виконайте таку команду в оболонці / командному рядку:
java -cp antlr-3.2.jar org.antlr.Tool Exp.g
Він не повинен створювати жодного повідомлення про помилку, і тепер файли ExpLexer.java , ExpParser.java та Exp.tokens мають бути створені.
Щоб побачити, чи все працює правильно, створіть цей тестовий клас:
import org.antlr.runtime.*;
public class ANTLRDemo {
public static void main(String[] args) throws Exception {
ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
parser.eval();
}
}
і складіть його:
// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java
// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java
а потім запустіть його:
// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo
// Windows
java -cp .;antlr-3.2.jar ANTLRDemo
Якщо все піде добре, на консоль нічого не друкується. Це означає, що аналізатор не знайшов жодної помилки. Коли ви перейдете "12*(5-6)"
на нього, "12*(5-6"
а потім перекомпілюєте його та запустите, слід надрукувати наступне:
line 0:-1 mismatched input '<EOF>' expecting ')'
Гаразд, тепер ми хочемо додати трохи граматичного коду Java до граматики, щоб парсер насправді зробив щось корисне. Додавання код може бути зроблений шляхом розміщення {
і }
всередині вашої граматики з деяким простим кодом Java всередині нього.
Але спочатку: всі правила парсера у граматичному файлі повинні повертати примітивне подвійне значення. Це можна зробити, додавши returns [double value]
після кожного правила:
grammar Exp;
eval returns [double value]
: additionExp
;
additionExp returns [double value]
: multiplyExp
( '+' multiplyExp
| '-' multiplyExp
)*
;
// ...
що потребує мало пояснень: очікується, що кожне правило поверне подвійне значення. Тепер, щоб "взаємодіяти" із значенням повернення double value
(яке НЕ знаходиться в простому блоці коду Java {...}
) з коду, всередині коду, вам потрібно буде додати знак долара перед value
:
grammar Exp;
/* This will be the entry point of our parser. */
eval returns [double value]
: additionExp { /* plain code block! */ System.out.println("value equals: "+$value); }
;
// ...
Ось граматика, але тепер із доданим кодом Java:
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;}
;
і оскільки наше eval
правило тепер повертає дубль, змініть ANTLRDemo.java на це:
import org.antlr.runtime.*;
public class ANTLRDemo {
public static void main(String[] args) throws Exception {
ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);
System.out.println(parser.eval()); // print the value
}
}
Знову (повторно) генеруйте свіжий лексер і аналізатор з вашої граматики (1), компілюйте всі класи (2) і запустіть ANTLRDemo (3):
// *nix/MacOS
java -cp antlr-3.2.jar org.antlr.Tool Exp.g // 1
javac -cp .:antlr-3.2.jar ANTLRDemo.java // 2
java -cp .:antlr-3.2.jar ANTLRDemo // 3
// Windows
java -cp antlr-3.2.jar org.antlr.Tool Exp.g // 1
javac -cp .;antlr-3.2.jar ANTLRDemo.java // 2
java -cp .;antlr-3.2.jar ANTLRDemo // 3
і тепер ви побачите результат виразу, 12*(5-6)
надрукованого на консолі!
Знову ж таки: це дуже коротке пояснення. Я закликаю вас переглядати вікі ANTLR та читати деякі підручники та / або трохи пограти з тим, що я щойно опублікував.
Удачі!
Редагувати:
У цій публікації показано, як розширити приклад вище, щоб Map<String, Double>
можна було вказати а, що містить змінні в наданому виразі.
Щоб цей код працював з поточною версією Antlr (червень 2014 р.), Мені потрібно було внести кілька змін. ANTLRStringStream
потрібно було стати ANTLRInputStream
, повернене значення, яке потрібно змінити з parser.eval()
на parser.eval().value
, і мені потрібно було видалити WS
пункт в кінці, тому що такі значення атрибутів, як $channel
такі, більше не дозволяється відображатися в лексерових діях.