Решение левого рекурсии - PullRequest
5 голосов
/ 02 января 2012

Я пытаюсь разобрать язык с помощью ANTLR, который может содержать следующий синтаксис:

someVariable, somVariable.someMember, functionCall(param).someMember,  foo.bar.baz(bjork).buffalo().xyzzy

Это грамматика ANTLR, которую я до сих пор придумал, и access_operation выдает ошибку

The following sets of rules are mutually left-recursive [access_operation, expression]

grammar Test;

options { 
  output=AST;
  ASTLabelType=CommonTree; 
}

tokens {
  LHS;
  RHS;
  CALL;
  PARAMS;
}

start   
  :  body? EOF
  ;

body
  : expression (',' expression)*
  ;

expression
  : function -> ^(CALL)
  | access_operation
  | atom
  ;

access_operation
  : (expression -> ^(LHS)) '.'! (expression -> ^(RHS))
  ;

function
  : (IDENT '(' body? ')') -> ^(IDENT PARAMS?) 
  ;         

atom
  : IDENT
  | NUMBER
  ;

fragment LETTER : ('a'..'z' | 'A'..'Z');
fragment DIGIT  : '0'..'9';

IDENT    : (LETTER)+ ;
NUMBER   : (DIGIT)+ ;
SPACE    : (' ' | '\t' | '\r' | '\n') { $channel=HIDDEN; };

До сих пор я мог управлять рефакторингом правила access_operation в '.' expression, которое генерирует AST, где узел access_operation содержит только правую сторону операции.

Вместо этого я ищу что-то вроде этого:

enter image description here

Как решить проблему левой рекурсии в этом случае?

1 Ответ

7 голосов
/ 02 января 2012

Под "неправильным AST" я сделаю полуобразованное предположение, что для ввода, подобного "foo.bar.baz", вы получите AST, где foo - корень с bar как ребенок, у которого, в свою очередь, baz в детстве, который является листом в АСТ. Вы можете захотеть изменить это. Но я бы не стал использовать AST на вашем месте: я бы держал AST как можно более плоским:

    foo
   / | \
  /  |  \
bar baz  ...

Таким образом, оценка намного проще: вы просто смотрите вверх foo, а затем идете слева направо через его дочерние элементы.

Небольшая демонстрация:

grammar Test;

options { 
  output=AST;
  ASTLabelType=CommonTree; 
}

tokens {
  BODY;
  ACCESS;
  CALL;
  PARAMS;
}

start   
 : body EOF -> body
 ;

body
 : expression (',' expression)* -> ^(BODY expression+)
 ;

expression
 : atom
 ;         

atom
 : NUMBER
 | (IDENT -> IDENT) ( tail       -> ^(IDENT tail)
                    | call tail? -> ^(CALL IDENT call tail?)
                    )?
 ;

tail
 : (access)+
 ;

access
 : ('.' IDENT -> ^(ACCESS IDENT)) (call -> ^(CALL IDENT call))?
 ;

call
 : '(' (expression (',' expression)*)? ')' -> ^(PARAMS expression*)
 ;

IDENT  : LETTER+;
NUMBER : DIGIT+;
SPACE  : (' ' | '\t' | '\r' | '\n') {$channel=HIDDEN;};

fragment LETTER : ('a'..'z' | 'A'..'Z');
fragment DIGIT  : '0'..'9';

, который можно проверить с помощью:

import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;
import org.antlr.stringtemplate.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String src = "someVariable, somVariable.someMember, functionCall(param).someMember, " + 
        "foo.bar.baz(bjork).buffalo().xyzzy";
    TestLexer lexer = new TestLexer(new ANTLRStringStream(src));
    TestParser parser = new TestParser(new CommonTokenStream(lexer));
    CommonTree tree = (CommonTree)parser.start().getTree();
    DOTTreeGenerator gen = new DOTTreeGenerator();
    StringTemplate st = gen.toDOT(tree);
    System.out.println(st);
  }
}

Выход Main соответствует следующему AST:

enter image description here

EDIT

И поскольку вы указали, что вашей конечной целью является не оценка входных данных, а то, что вам скорее нужно согласовать структуру AST с каким-либо сторонним API 3 rd , вот грамматика, которая создаст подобный AST Вы указали в отредактированном вопросе:

grammar Test;

options { 
  output=AST;
  ASTLabelType=CommonTree; 
}

tokens {
  BODY;
  ACCESS_OP;
  CALL;
  PARAMS;
  LHS;
  RHS;
}

start   
 : body EOF -> body
 ;

body
 : expression (',' expression)* -> ^(BODY expression+)
 ;

expression
 : atom
 ;         

atom
 : NUMBER
 | (ID -> ID) ( ('(' params ')' -> ^(CALL ID params)) 
                ('.' expression -> ^(ACCESS_OP ^(LHS ^(CALL ID params)) ^(RHS expression)))?
              | '.' expression  -> ^(ACCESS_OP ^(LHS ID) ^(RHS expression))
              )?
 ;

params
 : (expression (',' expression)*)? -> ^(PARAMS expression*)
 ;

ID     : LETTER+;
NUMBER : DIGIT+;
SPACE  : (' ' | '\t' | '\r' | '\n') {$channel=HIDDEN;};

fragment LETTER : ('a'..'z' | 'A'..'Z');
fragment DIGIT  : '0'..'9';

, который создает следующий AST, если вы запускаете класс Main:

enter image description here

Правило atom может быть немного устрашающим, но вы не можете его укорачивать, так как левый ID должен быть доступен для большинства альтернатив. ANTLRWorks помогает визуализировать альтернативные пути, которые может принять это правило:

enter image description here

, что означает atom может быть любой из 5 следующих альтернатив (с соответствующими им AST):

+----------------------+--------------------------------------------------------+
| alternative          | generated AST                                          |
+----------------------+--------------------------------------------------------+
| NUMBER               | NUMBER                                                 |
| ID                   | ID                                                     |
| ID params            | ^(CALL ID params)                                      |
| ID params expression | ^(ACCESS_OP ^(LHS ^(CALL ID params)) ^(RHS expression))|
| ID expression        | ^(ACCESS_OP ^(LHS ID) ^(RHS expression)                |
+----------------------+--------------------------------------------------------+
...