ANTLR - с учетом неполной грамматики - PullRequest
2 голосов
/ 28 ноября 2011

Я использую ANTLR для разбора строк математических выражений и пометки их с помощью MathML.

Прямо сейчас у меня есть грамматика ниже.Теперь у меня есть три вопроса:

  1. Грамматика допускает полные выражения, такие как 2*(3+4).Я также хочу разрешить неполные выражения, например 2*(3+.Будучи полным новичком в ANTLR, я понятия не имею, как этого добиться.Пожалуйста, укажите мне правильный документ или приведите пример.
  2. Расположение правила квадратного корня sqrt среди атомарных элементов, кажется, работает, но я почти уверен, что оно должно быть где-то в правиле exponent?Или это должно быть?
  3. Если я хочу расширить эту грамматику, чтобы фактически выполнить вычисления, могу ли я как-то использовать ее повторно или мне нужно скопировать и вставить?

Любые другие комментарииили предложения по моей грамматике также приветствуются, так как мой общий опыт работы с ANTLR теперь составляет около четырех часов.

grammar Expr;

parse returns [String value]
    :   stat+ {$value = $stat.value;}
    ;

stat returns [String value]
    :   exponent NEWLINE {$value = "<math>" + $exponent.value + "</math>";}
    |   NEWLINE
    ;

exponent returns [String value]
    :   e=expr {$value = $e.value;}
        (   '^' e=expr {$value = "<msup><mrow>" + $value + "</mrow><mrow>" + $e.value + "</mrow></msup>";}
        )*
    ;

expr returns [String value]
    :   e=multExpr {$value = $e.value;}
        (   '+' e=multExpr {$value += "<mo>+</mo>" + $e.value;}
        |   '-' e=multExpr {$value += "<mo>-</mo>" + $e.value;}
        )*
    ;

multExpr returns [String value]
    :   e=atom {$value = $e.value;} 
        (   '*' e=atom {$value += "<mo>*</mo>" + $e.value;}
        |   '/' e=atom {$value += "<mo>/</mo>" + $e.value;}
        )*
    ; 

atom returns [String value]
    :   INT {$value = "<mn>" + $INT.text + "</mn>";}
    |   '-' e=atom {$value = "<mo>-</mo>" + $e.value;}
    |   'sqrt[' exponent ']' {$value = "<msqrt><mrow>" + $exponent.value + "</mrow></msqrt>";}
    |   '(' exponent ')' {$value = "<mo>(</mo>" + $exponent.value + "<mo>)</mo>";}
    ;

INT :   '0'..'9'+ ;
NEWLINE:'\r'? '\n' ;
WS  :   (' '|'\t')+ {skip();} ;

1 Ответ

3 голосов
/ 29 ноября 2011

Сначала несколько замечаний о вашей грамматике:

  • вы должны присвоить правилам уникальные метки для левой и правой сторон (e1=atom ('*' e2=atom ...);
  • вы, вероятно, захотите создать отдельные токены sqrt и [ вместо 1 единственного sqrt[, иначе ввод, такой как "sqrt [ 9 ]" (пробел между sqrt и [), не будет обрабатываться должным образом ;
  • унарный минус обычно имеет более низкий приоритет, чем возведение в степень.

rickythefox писал:

Кажется, что расположение правила квадратного корня sqrt среди атомщиков работает, но я почти уверен, что оно должно быть где-то в правиле экспоненты? Или это должно?

Нет, все в порядке: оно должно иметь самый высокий приоритет. Говоря о приоритетности, обычная таблица приоритетов (от низшей к высшей) в вашем случае будет:

  • сложение и вычитание;
  • умножение и деление;
  • одинарный минус;
  • экспоненцирование;
  • выражения в скобках (включая вызовы функций, например sqrt[...]).

rickythefox писал:

Грамматика допускает полные выражения, такие как 2 * (3 + 4). Я хочу также разрешить неполные выражения, например, 2 * (3+. Будучи новичком в ANTLR, я понятия не имею, как этого добиться. Пожалуйста, укажите мне нужный документ или приведите пример.

Это сложно.

Я действительно вижу только один способ: внутри вашего правила статистики вы сначала заставляете анализатор смотреть вперед в потоке токенов, чтобы проверить, действительно ли равен и expr впереди. Это можно сделать с помощью синтаксического предиката . Как только синтаксический анализатор уверен, что есть expr, только тогда проанализируйте указанное выражение. Если нет expr, попробуйте сопоставить NEWLINE, а если нет NEWLINE, просто используйте один токен, отличный от NEWLINE (который должен быть частью неполное выражение!). (я опубликую небольшую демонстрацию ниже)

rickythefox писал:

Если я хочу расширить эту грамматику, чтобы фактически выполнить вычисления, могу ли я как-то использовать ее повторно или мне нужно скопировать и вставить?

Правила синтаксического анализатора ANTLR могут возвращать более одного объекта. Конечно, это не совсем так, поскольку методы Java (в сущности, это правило синтаксического анализатора) могут возвращать только один объект. Правило парсера возвращает объект, который содержит ссылки на несколько объектов. Так что вы могли бы сделать:

stat returns [String str, double num]
  :  ...
  ;

Демонстрация

Принимая во внимание все мои подсказки, небольшая рабочая демонстрация может выглядеть так:

grammar Expr;

parse returns [String str, double num]
@init{$str = "";}
  :  (stat 
     {
       $str += $stat.str;
       $num = $stat.num;
       if(!Double.isNaN($num)) {
         System.out.println($stat.text.trim() + " = " + $num);
       }
     })+
  ;

stat returns [String str, double num]
  : (expr)=> expr NEWLINE      {$str = "<math>" + $expr.str + "</math>"; $num = $expr.num;}
  |          NEWLINE           {$str = ""; $num = Double.NaN;}
  |          ~NEWLINE          {$str = ""; $num = Double.NaN; System.err.println("Ignoring: " + $text);}
  ;

expr returns [String str, double num]
  :  e1=multExpr       {$str = $e1.str; $num = $e1.num;}
     ( '+' e2=multExpr {$str += "<mo>+</mo>" + $e2.str; $num += $e2.num;}
     | '-' e2=multExpr {$str += "<mo>-</mo>" + $e2.str; $num -= $e2.num;}
     )*
  ;

multExpr returns [String str, double num]
  :  e1=unaryExpr       {$str = $e1.str; $num = $e1.num;} 
     ( '*' e2=unaryExpr {$str += "<mo>*</mo>" + $e2.str; $num *= $e2.num;}
     | '/' e2=unaryExpr {$str += "<mo>/</mo>" + $e2.str; $num /= $e2.num;}
     )*
  ; 

unaryExpr returns [String str, double num]
  :  '-' e=expExpr {$str = "<mo>-</mo>" + $e.str; $num = -1 * $e.num;}
  |  e=expExpr     {$str = $e.str; $num = $e.num;}
  ;

expExpr returns [String str, double num]
  :  e1=atom       {$str = $e1.str; $num = $e1.num;}
     ( '^' e2=atom {$str = "<msup><mrow>" + $str + "</mrow><mrow>" + $e2.str + "</mrow></msup>"; $num = Math.pow($num, $e2.num);}
     )*
  ;

atom returns [String str, double num]
  :  INT                 {$str = "<mn>" + $INT.text + "</mn>"; $num = Double.valueOf($INT.text);}
  |  'sqrt' '[' expr ']' {$str = "<msqrt><mrow>" + $expr.str + "</mrow></msqrt>"; $num = Math.sqrt($expr.num);}
  |  '(' expr ')'        {$str = "<mo>(</mo>" + $expr.str + "<mo>)</mo>"; $num = $expr.num;}
  ;

INT     : '0'..'9'+;
NEWLINE : '\r'? '\n';
WS      : (' '|'\t')+ {skip();};

(обратите внимание, что (...)=> - это так называемый синтаксический предикат )

Вы можете проверить синтаксический анализатор, сгенерированный из приведенной выше грамматики, следующим классом:

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String src =
        "sqrt [ 9 ] \n" +  
        "1+2*3      \n" + 
        "2*(3+      \n" +
        "2*(3+42)^2 \n";
    ExprLexer lexer = new ExprLexer(new ANTLRStringStream(src));
    ExprParser parser = new ExprParser(new CommonTokenStream(lexer));
    ExprParser.parse_return returnValue = parser.parse();
    String mathML = returnValue.str;
    double eval = returnValue.num;
    // ...
  }
}

И если вы теперь запустите класс выше, вы увидите, что ввод

sqrt [ 9 ]
1+2*3
2*(3+
2*(3+42)^2

выдаст следующий вывод:

sqrt[9] = 3.0
1+2*3 = 7.0
Ignoring: 2
Ignoring: *
Ignoring: (
Ignoring: 3
Ignoring: +
2*(3+42)^2 = 4050.0
...