Проблема JavaCC — сгенерированный код не находит все ошибки синтаксического анализа

Только начал с JavaCC. Но у меня странное поведение с ним. Я хочу проверить ввод в виде токенов (букв и цифр), которые объединены знаками (+, -, /) и могут содержать круглые скобки. Надеюсь понятно было :)

В основном методе есть строка, которая должна выдавать ошибку, потому что у нее одна открывающая и две закрывающие скобки, но я не получаю исключение синтаксического анализа --> Почему?

Кто-нибудь знает, почему я не получаю исключение?

Я боролся с левой рекурсией и конфликтами выбора при первой попытке, но мне удалось их преодолеть. Может, там я привнес проблему?!

О, и, возможно, мое решение не очень хорошее - игнорируйте этот факт... или лучше дайте совет ;-)

Файл: CodeParser.jj

 options {
   STATIC=false;
 }

 PARSER_BEGIN(CodeParser)

 package com.testing;

 import java.io.StringReader;
 import java.io.Reader;

 public class CodeParser {

     public CodeParser(String s) 
     {
         this((Reader)(new StringReader(s))); 

     }

     public static void main(String args[])
     {
         try
         {
               /** String has one open, but two closing parenthesis --> should produce parse error */
               String s = "A+BC+-(2XXL+A/-B))";
               CodeParser parser = new CodeParser(s);
               parser.expression();
         }
         catch(Exception e)
         {
               e.printStackTrace();
         }
     }
 }
 PARSER_END(CodeParser)

 TOKEN:
 {
  <code : ("-")?(["A"-"Z", "0"-"9"])+ >
  | <op : ("+"|"/") >
  | <not : ("-") >
  | <lparenthesis : ("(") >
  | <rparenthesis : (")") >
 }

 void expression() :
 {
 }
 {
  negated_expression() | parenthesis_expression() | LOOKAHEAD(2) operator_expression() | <code>
 }

 void negated_expression() :
 {
 }
 {
       <not>parenthesis_expression()
 }

 void parenthesis_expression() :
 {
 }
 {
        <lparenthesis>expression()<rparenthesis>
 }

 void operator_expression() :
 {
 }
 {
       <code><op>expression()
 }

Редактировать — 16.11.2009

Теперь я попробовал ANTLR.

Я изменил некоторые термины, чтобы лучше соответствовать моей предметной области. Я придумал следующий код (используя ответы на этом сайте), который, похоже, теперь работает:

grammar Code;

CODE    :   ('A'..'Z'|'0'..'9')+;
OP  :   '+'|'/';

start   :   terms EOF;
terms   :   term (OP term)*;
term    :   '-'? CODE
    |   '-'? '(' terms ')';

И кстати... ANTLRWORKS - отличный инструмент для отладки/визуализации! Мне очень помог.

Дополнительная информация
Вышеприведенный код соответствует таким вещам, как:

(-Z19+-Z07+((FV+((M005+(M272/M276))/((M278/M273/M642)+-M005)))/(FW+(M005+(M273/M278/M642)))))+(-Z19+-Z07+((FV+((M005+(M272/M276))/((M278/M273/M642/M651)+-M005)))/(FW+(M0))))

person Kai Mechel    schedule 12.11.2009    source источник
comment
Я не могу конкретно помочь, но JavaCC довольно старый и твердый. ANTLR (antlr.org) по-прежнему активно поддерживается и используется гораздо шире.   -  person skaffman    schedule 12.11.2009
comment
Также beust.com/weblog/archives/000145.html   -  person skaffman    schedule 12.11.2009
comment
Я думал, что JavaCC более прямолинеен, чем ANTLR. Я посмотрел на оба, но, возможно, мне стоит попробовать ANTLR еще раз.   -  person Kai Mechel    schedule 12.11.2009


Ответы (3)


То, что говорит кггрегори, является правильным ответом. Вы можете увидеть это, если создадите грамматику с параметром DEBUG_PARSER, а затем запустите ее:

$ javacc -debug_parser -output_directory=com/testing/ CodeParser.jj && javac com/testing/*.java && java -cp . com.testing.CodeParser
Java Compiler Compiler Version 5.0 (Parser Generator)
(type "javacc" with no arguments for help)
Reading from file CodeParser.jj . . .
File "TokenMgrError.java" is being rebuilt.
File "ParseException.java" is being rebuilt.
File "Token.java" is being rebuilt.
File "SimpleCharStream.java" is being rebuilt.
Parser generated successfully.
Call:   expression
  Call:   operator_expression
    Consumed token: <<code>: "A" at line 1 column 1>
    Consumed token: <<op>: "+" at line 1 column 2>
    Call:   expression
      Call:   operator_expression
        Consumed token: <<code>: "BC" at line 1 column 3>
        Consumed token: <<op>: "+" at line 1 column 5>
        Call:   expression
          Call:   negated_expression
            Consumed token: <"-" at line 1 column 6>
            Call:   parenthesis_expression
              Consumed token: <"(" at line 1 column 7>
              Call:   expression
                Call:   operator_expression
                  Consumed token: <<code>: "2XXL" at line 1 column 8>
                  Consumed token: <<op>: "+" at line 1 column 12>
                  Call:   expression
                    Call:   operator_expression
                      Consumed token: <<code>: "A" at line 1 column 13>
                      Consumed token: <<op>: "/" at line 1 column 14>
                      Call:   expression
                        Consumed token: <<code>: "-B" at line 1 column 15>
                      Return: expression
                    Return: operator_expression
                  Return: expression
                Return: operator_expression
              Return: expression
              Consumed token: <")" at line 1 column 17>
            Return: parenthesis_expression
          Return: negated_expression
        Return: expression
      Return: operator_expression
    Return: expression
  Return: operator_expression
Return: expression

Видеть, что? Последний потребляемый токен — это предпоследний символ — предпоследняя правая скобка.

Если вам нужно исключение, опять же, как сказал kgregory, вы можете добавить новое производство верхнего уровня под названием «файл» или «данные» или что-то еще и закончить его токеном. Таким образом, любые висячие скобки, подобные этому, вызовут ошибку. Вот грамматика, которая делает это:

options {
  STATIC=false;
}

PARSER_BEGIN(CodeParser)
package com.testing;

import java.io.StringReader;
import java.io.Reader;

public class CodeParser {

    public CodeParser(String s) 
    {
        this((Reader)(new StringReader(s))); 

    }

    public static void main(String args[])
    {
        try
        {
              /** String has one open, but two closing parenthesis --> should produce parse error */
              String s = "A+BC+-(2XXL+A/-B))";
              CodeParser parser = new CodeParser(s);
              parser.file();
        }
        catch(Exception e)
        {
              e.printStackTrace();
        }
    }
}
PARSER_END(CodeParser)

TOKEN:
{
        <code : ("-")?(["A"-"Z", "0"-"9"])+ >
        | <op : ("+"|"/") >
        | <not : ("-") >
        | <lparenthesis : ("(") >
        | <rparenthesis : (")") >
}

void file() : {} {
  expression() <EOF>
}
void expression() :
{
}
{
        negated_expression() | parenthesis_expression() | LOOKAHEAD(2) operator_expression() | <code>
}

void negated_expression() :
{
}
{
      <not>parenthesis_expression()
}

void parenthesis_expression() :
{
}
{
       <lparenthesis>expression()<rparenthesis>
}

void operator_expression() :
{
}
{
      <code><op>expression()
}

И пример запуска:

$ javacc -debug_parser -output_directory=com/testing/ CodeParser.jj && javac com/testing/*.java && java -cp . com.testing.CodeParser
Java Compiler Compiler Version 5.0 (Parser Generator)
(type "javacc" with no arguments for help)
Reading from file CodeParser.jj . . .
File "TokenMgrError.java" is being rebuilt.
File "ParseException.java" is being rebuilt.
File "Token.java" is being rebuilt.
File "SimpleCharStream.java" is being rebuilt.
Parser generated successfully.
Call:   file
  Call:   expression
    Call:   operator_expression
      Consumed token: <<code>: "A" at line 1 column 1>
      Consumed token: <<op>: "+" at line 1 column 2>
      Call:   expression
        Call:   operator_expression
          Consumed token: <<code>: "BC" at line 1 column 3>
          Consumed token: <<op>: "+" at line 1 column 5>
          Call:   expression
            Call:   negated_expression
              Consumed token: <"-" at line 1 column 6>
              Call:   parenthesis_expression
                Consumed token: <"(" at line 1 column 7>
                Call:   expression
                  Call:   operator_expression
                    Consumed token: <<code>: "2XXL" at line 1 column 8>
                    Consumed token: <<op>: "+" at line 1 column 12>
                    Call:   expression
                      Call:   operator_expression
                        Consumed token: <<code>: "A" at line 1 column 13>
                        Consumed token: <<op>: "/" at line 1 column 14>
                        Call:   expression
                          Consumed token: <<code>: "-B" at line 1 column 15>
                        Return: expression
                      Return: operator_expression
                    Return: expression
                  Return: operator_expression
                Return: expression
                Consumed token: <")" at line 1 column 17>
              Return: parenthesis_expression
            Return: negated_expression
          Return: expression
        Return: operator_expression
      Return: expression
    Return: operator_expression
  Return: expression
Return: file
com.testing.ParseException: Encountered " ")" ") "" at line 1, column 18.
Was expecting:
    <EOF> 

  at com.testing.CodeParser.generateParseException(CodeParser.java:354)
  at com.testing.CodeParser.jj_consume_token(CodeParser.java:238)
  at com.testing.CodeParser.file(CodeParser.java:34)
  at com.testing.CodeParser.main(CodeParser.java:22)

Вуаля! Исключение.

person tomcopeland    schedule 13.11.2009
comment
Я попробовал ANTLR и столкнулся с той же проблемой. С ANTLR я попробовал это решение (добавив EOF). Это решило проблему :-) Отлично! Спасибо вам всем! - person Kai Mechel; 16.11.2009

Из часто задаваемых вопросов по Java CC :

4.7 Я добавил спецификацию ПРОГНОЗ, и предупреждение исчезло; значит ли это, что я исправил проблему?

Нет. JavaCC не будет сообщать о предупреждениях о конфликте выбора, если вы используете спецификацию LOOKAHEAD. Отсутствие предупреждения не означает, что вы решили проблему правильно, это просто означает, что вы добавили спецификацию LOOKAHEAD.

Я бы начал с попытки избавиться от конфликта без использования просмотра вперед.

person rsp    schedule 12.11.2009
comment
Разве Lookahead не решает, какую вещь использовать? ‹code› и ‹code›‹op›выражение() начинаются с одного и того же токена. Итак, забегая вперед, нужно только выбрать правильную вещь... это называется «производство»?! :) - person Kai Mechel; 12.11.2009
comment
Использование просмотра вперед означает, что вы больше не получаете ошибку, а не то, что она действительно исправлена. Не могли бы вы использовать <code> (<op> expression() )?, чтобы исключить необходимость просмотра вперед? - person rsp; 12.11.2009
comment
В этом случае остаются две продукции, начинающиеся с ‹кода›. Без предпросмотра JavaCC все еще не может решить, какой из них выбрать, не прочитав еще один токен. Или я ошибаюсь, и вы имели в виду что-то другое? С уважением - Кай - person Kai Mechel; 12.11.2009
comment
после использования <code> синтаксический анализатор просматривает следующий токен, и если его <op>, он также использует выражение(). Упреждающий просмотр не требуется, так как <op> интерпретируется, когда <code> уже используется. Под ( xxx )? я имел в виду необязательное xxx. - person rsp; 13.11.2009
comment
Но основная проблема заключается в потреблении «кода». Для использования ‹кода› синтаксический анализатор должен решить, выбирает ли он ‹код› из выражения() или из оператора_выражения(). Это должна быть проблема, описанная на слайде 11 здесь: cs. sjsu.edu/~mak/lectures/CS153-091029.ppt Кроме того, я попробовал ваше решение, чтобы, возможно, оптимизировать все это... пойти другим путем... но я застрял. Прямо сейчас я смотрю на ANTLR. JavaCC работает, за исключением описанной выше проблемы. Вот почему я попробую ANTLR. Но JavaCC выглядел более удобным в использовании :) - person Kai Mechel; 13.11.2009
comment
Я думал о том, чтобы отказаться от всего | <code> из expression() в пользу <code> (<op> expression() )? в operator_expression(), который в этом случае можно было бы переименовать в code_expression(). Удачи в использовании Antlr. - person rsp; 13.11.2009

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

Если это так, то я подозреваю, что вы видите проблему, потому что синтаксический анализатор правильно сопоставляет выражение, а затем игнорирует последующий ввод. Я давно не использовал JavaCC, но iirc не выдавал ошибку из-за того, что не достиг конца потока.

Большинство грамматик имеют явное производство верхнего уровня для соответствия всему файлу, выглядящее примерно так (я уверен, что синтаксис неправильный, как я уже сказал, это было давно):

input : ( expression ) *

Или, возможно, есть токен EOF, который вы можете использовать, если хотите обработать только одно выражение.

person kdgregory    schedule 13.11.2009