开发者

Using antlr to parse a | separated file

So I think this should be easy, but I'm having a tough time with it. I'm trying to parse a | delimited file, and any line that doesn't start with a | is a comment. I guess I don't understand how comments work. It always errors out on a comment line. This is a legacy file, so there's no changing it. Here's my grammar.

grammar Route;

@header {
package org.benheath.codegeneration;
}

@lexer::header {
package org.benheath.codegeneration;
}

file: line+;
line: route+ '\n';
route: ('|' elt) {System.out.println("element: [" + $elt.text + "]");} ;
elt: (ELEMENT)*;

COMMENT: ~'|' .* '\n' ;
ELEMENT: ('a'..'z'|'A'..'Z'|'0'..'9'|'*'|'_'|'@'|'#') ;
WS: (' '|'\t')开发者_StackOverflow社区 {$channel=HIDDEN;} ; // ignore whitespace

Data:

! a comment
Another comment
| a | abc | b | def | ...


A grammar for that would look like this:

parse
  :  line* EOF   
  ;

line
  :  ( comment | values ) ( NL | EOF )
  ;

comment
  :  ELEMENT+
  ;

values
  :  PIPE ( ELEMENT PIPE )+
  ;

PIPE
  :  '|'    
  ;

ELEMENT
  :  ('a'..'z')+
  ;

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

WS
  :  (' '|'\t') {$channel=HIDDEN;} 
  ;

And to test it, you just need to sprinkle a bit of code in your grammar like this:

grammar Route;

@members {
  List<List<String>> values = new ArrayList<List<String>>();
}

parse
  :  line* EOF   
  ;

line
  :  ( comment | v=values {values.add($v.line);} ) ( NL | EOF )
  ;

comment
  :  ELEMENT+
  ;

values returns [List<String> line]
@init {line = new ArrayList<String>();}
  :  PIPE ( e=ELEMENT {line.add($e.text);} PIPE )*
  ;

PIPE
  :  '|'    
  ;

ELEMENT
  :  ('a'..'z')+
  ;

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

WS
  :  (' '|'\t') {$channel=HIDDEN;} 
  ;

Now generate a lexer/parser by invoking:

java -cp antlr-3.2.jar org.antlr.Tool Route.g

create a class RouteTest.java:

import org.antlr.runtime.*;
import java.util.List;

public class RouteTest {
  public static void main(String[] args) throws Exception {
    String data = 
        "a comment\n"+
        "| xxxxx | y |     zzz   |\n"+
        "another comment\n"+
        "| a | abc | b | def |";
    ANTLRStringStream in = new ANTLRStringStream(data);
    RouteLexer lexer = new RouteLexer(in);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    RouteParser parser = new RouteParser(tokens);
    parser.parse();
    for(List<String> line : parser.values) {
      System.out.println(line);
    }
  }
}

Compile all source files:

javac -cp antlr-3.2.jar *.java

and run the class RouteTest:

// Windows
java -cp .;antlr-3.2.jar RouteTest

// *nix/MacOS
java -cp .:antlr-3.2.jar RouteTest

If all goes well, you see this printed to your console:

[xxxxx, y, zzz]
[a, abc, b, def]

Edit: note that I simplified it a bit by only allowing lower case letters, you can always expand the set of course.


It's a nice idea to use ANTLR for a job like this, although I do think it's overkill. For example, it would be very easy to (in pseudo-code):

for each line in file:
if line begins with '|':
    fields = /|\s*([a-z]+)\s*/g

Edit: Well, you can't express the distinction between comments and lines lexically, because there is nothing lexical that distinguishes them. A hint to get you in one workable direction.

line: comment | fields;
comment: NONBAR+ (BAR|NONBAR+) '\n';
fields = (BAR NONBAR)+;


This seems to work, I swear I tried it. Changing comment to lower case switched it to the parser vs the lexer, I still don't get it.

grammar Route;

@header {
    package org.benheath.codegeneration;
}

@lexer::header {
    package org.benheath.codegeneration;
}

file: (line|comment)+;
line: route+ '\n';
route: ('|' elt) {System.out.println("element: [" + $elt.text + "]");} ;
elt: (ELEMENT)*;

comment : ~'|' .* '\n';

ELEMENT: ('a'..'z'|'A'..'Z'|'0'..'9'|'*'|'_'|'@'|'#') ;
WS: (' '|'\t') {$channel=HIDDEN;} ; // ignore whitespace
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜