Делайте в то же время и в то же время, используя ANTLR

Ранее я создал этот вопрос, спрашивая, как создавать операторы if/else, используя ANTLR 4. Я получил отличный ответ, который также показал, как выполнять циклы while. Я реализовал это на своем языке, и теперь я пытаюсь создать цикл do-while, используя почти те же принципы.

Мой синтаксис для циклов while выглядит следующим образом:

count is 0
while count is less than 10
  count+
  if count not equals 10
    write " " + count + ": Getting there..."
  else if count equals 10
    write count + ": The end!"
  end if
end while

И это то, что я хотел бы для циклов do-while:

count is 0
do
  count+
  write "count is " + count
  if count equals 10
    write "The end!"
  end if
while count is less than 10

Я проверил это, и они оба работают, однако, не одновременно. Ниже моя грамматика (извините за публикацию всего этого, но я думаю, что это необходимо).

Если мои токены WHILE и END_WHILE выше моих токенов DO_WHILE и DO_WHILE_CONDITION, то цикл while работает. Однако, если я переключаю их, мой цикл do-while работает. Если я изменю токен DO_WHILE_CONDITION на что-то другое, кроме while, то оба варианта будут работать.

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

//////////////////////////////////
// PARSER
//////////////////////////////////

program
 : block EOF
 ;

block
 : (statement (NEW_LINE+ | EOF))*
 ;

statement
 : assignment
 | if_statement
 | while_statement
 | until_statement
 | do_while_statement
 | write
 ;

assignment
 : ID ASSIGN expression # expressionAssignment
 | ID PLUS              # incrementAssignment
 | ID MINUS             # decrementAssignment
 ;

if_statement
 : IF condition_block (ELSE_IF condition_block)* (ELSE NEW_LINE statement_block)? END_IF
 ;

condition_block
 : expression NEW_LINE statement_block
 ;

statement_block
 : block
 ;

while_statement
 : WHILE expression NEW_LINE statement_block END_WHILE
 ;

until_statement
 : UNTIL expression NEW_LINE statement_block END_UNTIL
 ;

do_while_statement
 : DO_WHILE NEW_LINE statement_block DO_WHILE_CONDITION expression
 ;

expression
 : atom                                             # atomExpression
 | expression PLUS expression                       # plusExpression
 | expression MINUS expression                      # minusExpression
 | expression MULTIPLY expression                   # multiplicationExpression
 | expression DIVIDE expression                     # divisionExpression
 | expression PLUS                                  # incrementExpression
 | expression MINUS                                 # decrementExpression
 | expression AND expression                        # andExpression
 | expression OR expression                         # orExpression
 | expression EQUALS expression                     # equalityExpression
 | expression NOT_EQUALS expression                 # notEqualityExpression
 | expression LESS_THAN expression                  # lessThanExpression
 | expression NOT_LESS_THAN expression              # notLessThanExpression
 | expression GREATER_THAN expression               # greaterThanExpression
 | expression NOT_GREATER_THAN expression           # notGreaterThanExpression
 | expression GREATER_THAN_OR_EQUAL expression      # greaterThanOrEqualExpression
 | expression LESS_THAN_OR_EQUAL expression         # lessThanOrEqualExpression
 ;

atom
 : INT                              # integerAtom
 | FLOAT                            # floatAtom
 | BOOLEAN                          # boolAtom
 | ID                               # idAtom
 | STRING                           # stringAtom
 | OPEN_PAR expression CLOSE_PAR    # expressionAtom
 ;

write
 : WRITE expression
 ;

////////////////////////////////// 
// LEXER
//////////////////////////////////

PLUS                        : '+';
MINUS                       : '-';
MULTIPLY                    : '*';
DIVIDE                      : '/';

ASSIGN                      : 'is';
OPEN_CURLY                  : '{';
CLOSE_CURLY                 : '}';
OPEN_PAR                    : '(';
CLOSE_PAR                   : ')';
COLON                       : ':';
NEW_LINE                    : '\r'? '\n';

IF                          : 'if';
ELSE_IF                     : 'else if';
ELSE                        : 'else';
END_IF                      : 'end if';

WHILE                       : 'while';
END_WHILE                   : 'end while';

UNTIL                       : 'until';
END_UNTIL                   : 'end until';

DO_WHILE                    : 'do';
DO_WHILE_CONDITION          : 'while';

EQUALS                      : 'equals';
NOT_EQUALS                  : 'not equals';
LESS_THAN                   : 'is less than';
NOT_LESS_THAN               : 'is not less than';
GREATER_THAN                : 'is greater than';
NOT_GREATER_THAN            : 'is not greater than';
GREATER_THAN_OR_EQUAL       : 'is greater than or equals';
LESS_THAN_OR_EQUAL          : 'is less than or equals';
WRITE                       : 'write';

AND                         : 'and';
OR                          : 'or';
NOT                         : 'not';

BOOLEAN
 : 'TRUE' | 'true' | 'YES' | 'yes'
 | 'FALSE' | 'false' | 'NO' | 'no'
 ;

INT
 : (PLUS | MINUS)? NUMBER+
 ;

FLOAT
 : (PLUS | MINUS)? NUMBER+ ('.' | ',') (NUMBER+)?
 | (PLUS | MINUS)? (NUMBER+)? ('.' | ',') NUMBER+
 ;

NUMBER
 : '0'..'9'
 ;

STRING
 : '"' ( '\\"' | ~["] )* '"'
 ;

ID
 : ('a'..'z' | 'A'..'Z' | '0'..'9')+
 ;

WHITESPACE
 : [ \t]+ -> skip
 ;

COMMENT
 : ( ';;' .*? ';;' | ';' ~[\r\n]* ) -> skip
 ;  

person simonbs    schedule 29.03.2013    source источник


Ответы (1)


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

Это означает, что в вашем случае правило DO_WHILE_CONDITION:

WHILE                       : 'while';
...
DO_WHILE_CONDITION          : 'while';

никогда не будет соответствовать.

Кроме того, обычно не рекомендуется «приклеивать» ключевые слова друг к другу с помощью пробелов. Рассмотрим, когда ввод "end  if" (2 пробела). Лучше создайте 2 токена: END и IF и используйте их в правилах парсера.

Попробуйте что-то вроде этого:

program
 : block
 ; 

block
 : NEW_LINE* (statement (NEW_LINE+ | EOF))*
 ;

statement
 : assignment
 | if_statement
 | while_statement
 | until_statement
 | do_while_statement
 | write
 ;

assignment
 : ID IS expression # expressionAssignment
 | ID PLUS          # incrementAssignment
 | ID MINUS         # decrementAssignment
 ;

if_statement
 : IF condition_block (ELSE IF condition_block)* (ELSE NEW_LINE statement_block)? END IF
 ;

condition_block
 : expression NEW_LINE statement_block
 ;

statement_block
 : block
 ;

while_statement
 : WHILE expression NEW_LINE statement_block END WHILE
 ;

until_statement
 : UNTIL expression NEW_LINE statement_block END UNTIL
 ;

do_while_statement
 : DO NEW_LINE statement_block WHILE expression
 ;

// Added unary expressions instead of combining them in the lexer.
expression
 : atom                                            # atomExpression
 | MINUS expression                                # unaryMinusExpression
 | PLUS expression                                 # unaryPlusExpression
 | expression PLUS expression                      # plusExpression
 | expression MINUS expression                     # minusExpression
 | expression MULTIPLY expression                  # multiplicationExpression
 | expression DIVIDE expression                    # divisionExpression
 | expression PLUS                                 # incrementExpression
 | expression MINUS                                # decrementExpression
 | expression AND expression                       # andExpression
 | expression OR expression                        # orExpression
 | expression EQUALS expression                    # equalityExpression
 | expression NOT EQUALS expression                # notEqualityExpression
 | expression IS LESS THAN expression              # lessThanExpression
 | expression IS NOT LESS THAN expression          # notLessThanExpression
 | expression IS GREATER THAN expression           # greaterThanExpression
 | expression IS NOT GREATER THAN expression       # notGreaterThanExpression
 | expression IS GREATER THAN OR EQUALS expression # greaterThanOrEqualExpression
 | expression IS LESS THAN OR EQUALS expression    # lessThanOrEqualExpression
 ;

atom
 : INT                              # integerAtom
 | FLOAT                            # floatAtom
 | bool                             # boolAtom
 | ID                               # idAtom
 | STRING                           # stringAtom
 | OPEN_PAR expression CLOSE_PAR    # expressionAtom
 ;

write
 : WRITE expression
 ;

// By making this a parser rule, you needn't inspect the lexer rule 
// to see if it's true or false.
bool
 : TRUE
 | FALSE
 ;

////////////////////////////////// 
// LEXER
//////////////////////////////////

PLUS                        : '+';
MINUS                       : '-';
MULTIPLY                    : '*';
DIVIDE                      : '/';

OPEN_CURLY                  : '{';
CLOSE_CURLY                 : '}';
OPEN_PAR                    : '(';
CLOSE_PAR                   : ')';
COLON                       : ':';
NEW_LINE                    : '\r'? '\n';

IF                          : 'if';
ELSE                        : 'else';
END                         : 'end';
WHILE                       : 'while';
UNTIL                       : 'until';
DO                          : 'do';
EQUALS                      : 'equals';
NOT                         : 'not';
IS                          : 'is';
LESS                        : 'less';
THAN                        : 'than';
GREATER                     : 'greater';
WRITE                       : 'write';
AND                         : 'and';
OR                          : 'or';

TRUE  : 'TRUE'  | 'true'  | 'YES' | 'yes';
FALSE : 'FALSE' | 'false' | 'NO'  | 'no';

INT
 : DIGIT+
 ;

// (DIGIT+)? is the same as: DIGIT*
FLOAT
 : DIGIT+ [.,] DIGIT*
 | DIGIT* [.,] DIGIT+
 ;

// If a rule can never become a token on its own (an INT will always 
// be created instead of a DIGIT), mark it as a 'fragment'.
fragment DIGIT
 : [0-9]
 ;

// Added support for escaped backslashes.
STRING
 : '"' ( '\\"' | '\\\\' | ~["\\] )* '"'
 ;

// Can it start with a digit? Maybe this is better: [a-zA-Z] [a-zA-Z0-9]*
ID
 : [a-zA-Z0-9]+
 ;

WHITESPACE
 : [ \t]+ -> skip
 ;

COMMENT
 : ( ';;' .*? ';;' | ';' ~[\r\n]* ) -> skip
 ;  

Какой синтаксический анализатор обе конструкции while создает без проблем. Также обратите внимание, что я внес небольшие коррективы в вашу грамматику (см. встроенные комментарии). Унарное выражение является важным, иначе 1-2 будет размечено как 2 токена INT, которые не могут быть сопоставлены с expression в синтаксическом анализаторе!

person Bart Kiers    schedule 29.03.2013
comment
Спасибо! Это решило проблему, и это действительно отличные исправления в грамматике. Я не думал о необходимости разбивать токены, использующие пробел, на несколько токенов, а также унарные выражения. Я очень рад, что ты уловил это. Я не совсем уверен, что именно решило проблему while/do-while. Использовался ли один и тот же токен для обоих выражений? - person simonbs; 29.03.2013
comment
@simonbs, правило синтаксического анализатора do-while использовало токен DO_WHILE_CONDITION, но этот токен никогда не будет создан лексером, поскольку правило WHILE соответствует тем же символам, и оно было помещено перед DO_WHILE_CONDITION. - person Bart Kiers; 29.03.2013
comment
Хорошо, я вижу. Спасибо! В примечании я обнаружил, что правила должны были быть program : block; block : NEW_LINE* (statement (NEW_LINE+ | EOF))* Имея EOF дважды, он ожидал этого дважды, если ввод не заканчивался новой строкой. - person simonbs; 29.03.2013
comment
@simonbs, я соответствующим образом изменил свой ответ (в таких случаях не стесняйтесь редактировать мой ответ!). - person Bart Kiers; 29.03.2013