2016-06-07 8 views
0

Ich versuche, ein semantisches Prädikat im Lexer zu verwenden, um einen Token vorauszusehen, aber irgendwie kann ich es nicht richtig machen. Hier ist, was ich habe:ANRLR4 lexer semantisches Prädikat Problem

Lexer Grammatik

lexer grammar TLLexer; 

DirStart 
    : { getCharPositionInLine() == 0 }? '#dir' 
    ; 

DirEnd 
    : { getCharPositionInLine() == 0 }? '#end' 
    ; 

Cont 
    : 'contents' [ \t]* -> mode(CNT) 
    ; 

WS 
    : [ \t]+ -> channel(HIDDEN) 
    ; 

NL 
    : '\r'? '\n' 
    ; 

mode CNT; 

CNT_DirEnd 
    : '#end' [ \t]* '\n'? 
     { System.out.println("--matched end--"); } 
    ; 

CNT_LastLine 
    : ~ '\n'* '\n' 
     { _input.LA(1) == CNT_DirEnd }? -> mode(DEFAULT_MODE) 
    ; 

CNT_Line 
    : ~ '\n'* '\n' 
    ; 

Parser Grammatik

parser grammar TLParser; 

options { tokenVocab = TLLexer; } 

dirs 
    : (dir 
     | NL 
    )* 
    ; 

dir 
    : DirStart Cont 
     contents 
     DirEnd 
    ; 

contents 
    : CNT_Line* CNT_LastLine 
    ; 

Im Wesentlichen jede Zeile in dem Zeug in dem CNT-Modus ist Freiform, aber es beginnt nie mit #end gefolgt von optionalen Leerzeichen. Grundsätzlich möchte ich das #end Tag im Standard-Lexer-Modus beibehalten.

Meine Testeingang ist wie folgt:

#dir contents 
..line.. 
#end 

Wenn ich dies in grun laufen bekomme ich folgende

$ grun TL dirs test.txt 
--matched end-- 
line 3:0 extraneous input '#end\n' expecting {CNT_LastLine, CNT_Line} 

So klar CNT_DirEnd abgestimmt wird, aber irgendwie das Prädikat nicht erkennt es .

Ich weiß, dass diese besondere Aufgabe kein semantisches Prädikat erfordert, aber das ist nur der Teil, der nicht funktioniert. Der eigentliche Parser, obwohl er ohne das Prädikat geschrieben werden kann, wird viel weniger sauber sein, wenn ich einfach die Übereinstimmung des Tags #end in den Modus CNT verschiebe.

Danke,
Kesha.

+0

Es scheint CNT_Line definiert ist als nicht übereinstimmend mit '..line ..' –

+0

@ThomasG Es stimmt überein, was Sie mit der Option -gui sehen können, oder wenn Sie DRUCK-Aktionen zu CNT_Line hinzufügen (dann druckt grun es dreimal, weil es nie aus der CNT-Modus) und CNT_LastLine (druckt es nie). –

Antwort

0

Ich denke, ich habe es herausgefunden. Das Element _input stellt die Zeichen der ursprünglichen Eingabe dar, also _input.LA gibt Zeichen zurück, nicht Lexer Token IDs (ist das der richtige Begriff?). In beiden Fällen haben die vom Lexer an den Parser zurückgegebenen Zahlen nichts mit den von _input.LA zurückgegebenen Werten zu tun, daher schlägt das Prädikat fehl, es sei denn, durch irgendeinen seltsamen Zufall ist der von _input.LA (1) zurückgegebene Zeichenwert gleich die Lexer-ID CNT_DirEnd.

I modifiziert, um die Lexer, wie unten gezeigt und jetzt funktioniert es, auch wenn es, wie ich es wäre nicht so elegant ist zu hoffen (vielleicht kennt jemand einen besseren Weg?)

lexer grammar TLLexer; 

@lexer::members { 
    private static final String END_DIR = "#end"; 

    private boolean isAtEndDir() { 
     StringBuilder sb = new StringBuilder(); 

     int n = 1; 
     int ic; 

     // read characters until EOF 
     while ((ic = _input.LA(n++)) != -1) { 
      char c = (char) ic; 
      // we're interested in the next line only 
      if (c == '\n') break; 
      if (c == '\r') continue; 
      sb.append(c); 
     } 

     // Does the line begin with #end ? 
     if (sb.indexOf(END_DIR) != 0) return false; 
     // Is the #end followed by whitespace only? 
     for (int i = END_DIR.length(); i < sb.length(); i++) { 
      switch (sb.charAt(i)) { 
      case ' ': 
      case '\t': 
       continue; 
      default: return false; 
      } 
     } 
     return true; 
    } 
} 

[skipped .. nothing changed in the default mode] 

mode CNT; 

/* removed CNT_DirEnd */ 

CNT_LastLine 
    : ~ '\n'* '\n' 
     { isAtEndDir() }? -> mode(DEFAULT_MODE) 
    ; 

CNT_Line 
    : ~ '\n'* '\n' 
    ;