2012-10-15 10 views
5

Ich versuche, mit Jparsec meine recht einfache Grammatik zu definieren und zu verwenden, bin aber völlig verwirrt darüber, wie ich vorgehen soll. Ich weiß an dieser Stelle nicht, ob es mein unzureichendes Verständnis des Problembereichs oder die spärliche und wenig informative Dokumentation von jparsec ist. Oder beides.Verwechslung in jparsec

Ich habe einen grammer etwas wie folgt aus:

foo='abc' AND bar<>'def' OR (biz IN ['a', 'b', 'c'] AND NOT baz = 'foo') 

So können Sie es unterstützt Operatoren wie AND, OR, NOT, IN, =, <> sehen. Es unterstützt auch beliebig verschachtelte Klammern, um Vorrang zu geben.

Ich denke, dass ich mit Tokenizing ziemlich weit gekommen bin. Hier ist, was ich habe:

public final class NewParser { 
    // lexing 
    private static final Terminals OPERATORS = Terminals.operators("=", "OR", "AND", "NOT", "(", ")", "IN", "[", "]", ",", "<>"); 
    private static final Parser<?> WHITESPACE = Scanners.WHITESPACES; 
    private static final Parser<?> FIELD_NAME_TOKENIZER = Terminals.Identifier.TOKENIZER; 
    private static final Parser<?> QUOTED_STRING_TOKENIZER = Terminals.StringLiteral.SINGLE_QUOTE_TOKENIZER.or(Terminals.StringLiteral.DOUBLE_QUOTE_TOKENIZER); 
    private static final Parser<?> IGNORED = Parsers.or(Scanners.WHITESPACES).skipMany(); 
    private static final Parser<?> TOKENIZER = Parsers.or(OPERATORS.tokenizer(), WHITESPACE, FIELD_NAME_TOKENIZER, QUOTED_STRING_TOKENIZER).many(); 

    @Test 
    public void test_tokenizer() { 
     Object result = TOKENIZER.parse("foo='abc' AND bar<>'def' OR (biz IN ['a', 'b', 'c'] AND NOT baz = 'foo')"); 
     Assert.assertEquals("[foo, =, abc, null, AND, null, bar, <>, def, null, OR, null, (, biz, null, IN, null, [, a, ,, null, b, ,, null, c, ], null, AND, null, NOT, null, baz, null, =, null, foo,)]", result.toString()); 
    } 
} 

test_tokenizer Pässe, so Ich denke, es funktioniert OK.

Jetzt habe ich bereits eine Typhierarchie, die die Syntax darstellt. Zum Beispiel habe ich Klassen genannt Node, BinaryNode, FieldNode, LogicalAndNode, ConstantNode und so weiter. Und was ich versuche zu tun, ist eine Parser erstellen, die meine Tokens und spuckt eine Node. Und hier bleibe ich stecken.

Ich dachte, ich mit etwas wirklich einfach wie diese beginnen würde:

private static Parser<FieldNode> fieldNodeParser = 
    Parsers.sequence(FIELD_NAME_TOKENIZER) 
    .map(new Map<Object, FieldNode>() { 
     @Override 
     public FieldNode map(Object from) { 
      Fragment fragment = (Fragment)from; 
      return new FieldNode(fragment.text()); 
     } 
    }); 

Ich dachte, ich wäre in der Lage, dies zu tun:

public static Parser<Node> parser = fieldNodeParser.from(TOKENIZER); 

Aber das gibt mir einen Compiler-Fehler:

Es sieht also so aus, als ob meine Generika irgendwo hingeworfen werden, aber ich habe keine Ahnung wo oder wie ich das beheben kann. Ich bin mir nicht einmal sicher, ob ich das richtig mache. Kann mich jemand aufklären?

Antwort

6

Sie mischen zwei verschiedene Ebenen von "Parsern": String-Level-Parser aka. Scanner oder Lexer und Parser auf Token-Ebene. So implementiert JParsec die traditionelle Trennung von lexikalischer und syntaktischer Analyse.

Um Ihren Code sauber kompilieren zu lassen, können Sie am Ende der Parserdefinition einen Aufruf an die Methode .cast() hinzufügen, aber das wird Ihr Problem nicht beheben, da der nächste Fehler etwa cannot run a character-level parser at token level sein wird. Dieses Problem rührt von der Verwendung von .from() her, um Ihren Top-Level-Parser zu definieren, der implizit die Grenze zwischen den beiden Welten festlegt.

ist hier eine Arbeits Implementierung (und Unit-Tests) für Ihre Parser:

public class SampleTest { 


private static Parser<FieldNode> fieldNodeParser = Parsers.sequence(Terminals.fragment(Tokens.Tag.IDENTIFIER).map(new Map<String, FieldNode>() { 
      @Override 
      public FieldNode map(String from) { 
       String fragment = from; 
       return new FieldNode(fragment); 
      } 
     })).cast(); 

public static Parser<FieldNode> parser = fieldNodeParser.from(NewParser.TOKENIZER, Scanners.WHITESPACES); 


@Test 
public void test_tokenizer() { 
    Object result = Parsers.or(NewParser.TOKENIZER, Scanners.WHITESPACES.cast()).many().parse("foo='abc' AND bar<>'def' OR (biz IN ['a', 'b', 'c'] AND NOT baz = 'foo')"); 
    Assert.assertEquals("[foo, =, abc, null, AND, null, bar, <>, def, null, OR, null, (, biz, null, IN, null, [, a, ,, null, b, ,, null, c, ], null, AND, null, NOT, null, baz, null, =, null, foo,)]", result.toString()); 
} 

@Test 
public void test_parser() throws Exception { 
    FieldNode foo = parser.parse("foo"); 
    assertEquals(foo.text, "foo"); 
} 

public static final class NewParser { 
    // lexing 
    static final Terminals OPERATORS = Terminals.operators("=", "OR", "AND", "NOT", "(", ")", "IN", "[", "]", ",", "<>"); 
    static final Parser<String> FIELD_NAME_TOKENIZER = Terminals.Identifier.TOKENIZER.source(); 
    static final Parser<?> QUOTED_STRING_TOKENIZER = Terminals.StringLiteral.SINGLE_QUOTE_TOKENIZER.or(Terminals.StringLiteral.DOUBLE_QUOTE_TOKENIZER); 
    static final Terminals TERMINALS = Terminals.caseSensitive(new String[] { "=", "(", ")", "[", "]", ",", "<>" }, new String[] { "OR", "AND", "NOT", "IN" }); 
    static final Parser<?> TOKENIZER = Parsers.or(TERMINALS.tokenizer(), QUOTED_STRING_TOKENIZER); 
} 

private static class FieldNode { 
    final String text; 

    public FieldNode(String text) { 

     this.text = text; 
    } 
} 

}

Was ich geändert:

  • Ich verwende die Terminals.caseSensitive Methode ein Lexer erstellen nur für Terminals (Schlüsselwörter, Operatoren und Identifikatoren).Die Kennung LEXER verwendet implizit das eine nativ von jParsec vorgesehen (z. Terminals.IDENTIFIER),
  • Ich verwende den .from() Methode mit dem tokenizer und WHITESPACES als Trenner,
  • Die fieldNodeParser verwendet Terminals.fragment(...)Tokens und keine Zeichen zu analysieren.

Hoffnung, dass Arnaud

hilft