Forráskód Böngészése

WIP: grammar tests

Ranides Atterwim 2 éve
szülő
commit
d0f343d115

+ 4 - 2
assira.grammar/src/main/java/net/ranides/assira/g4/G4Selector.java

@@ -1,5 +1,6 @@
 package net.ranides.assira.g4;
 
+import net.ranides.assira.collection.lists.ListUtils;
 import net.ranides.assira.functional.Functions.Function2;
 import net.ranides.assira.functional.Functions.Function3;
 import net.ranides.assira.g4.visitors.G4NodeVisitor;
@@ -402,12 +403,13 @@ public abstract class G4Selector<V> {
 
         @Override
         public R aggregate(G4Node tree) throws NoSuchElementException {
-            throw new UnsupportedOperationException("Mapping selector does not implement #aggregate method");
+            V out = G4Selector.this.aggregate(tree);
+            return out!=null ? transform.apply(out) : null;
         }
 
         @Override
         public List<R> list(G4Node tree) {
-            throw new UnsupportedOperationException("Mapping selector does not implement #list method");
+            return ListUtils.map(G4Selector.this.list(tree), transform);
         }
 
         @Override

+ 1 - 1
assira.grammar/src/main/java/net/ranides/assira/g4/G4SelectorBuilder.java

@@ -30,7 +30,7 @@ import java.util.function.Supplier;
  * one {@link G4Selector} which can be used to traverse grammar trees and generate results by using defined mappings.
  *
  * Implementation note: selectors can be chained, and transformed. We use something similar to flyweight pattern and
- * store shared state inside builder.
+ * store root state inside builder.
  *
  * @param <V>
  */

+ 42 - 0
assira.grammar/src/main/java/net/ranides/assira/g4/G4Support.java

@@ -0,0 +1,42 @@
+package net.ranides.assira.g4;
+
+import lombok.experimental.UtilityClass;
+import net.ranides.assira.text.StrAppender;
+import net.ranides.assira.text.StrBuilder;
+
+@UtilityClass
+public class G4Support {
+
+    public static String printTree(G4Node node) {
+        return printTree(new StrBuilder(), node).toString();
+    }
+
+    public static <T extends Appendable> T printTree(T output, G4Node node) {
+        printTree0(new StrAppender(output), "", node);
+        return output;
+    }
+
+    private static <T extends Appendable> void printTree0(StrAppender output, String indent, G4Node node) {
+        output.append(indent).printf("%s = '%s'", node.name(), node.text()).endl();
+        node.children().forEach(n -> {
+            printTree0(output, indent+"   ", n);
+        });
+    }
+
+    public static String printPath(G4Node node) {
+        return printPath(new StrBuilder(), node).toString();
+    }
+
+    public static <T extends Appendable> T printPath(T output, G4Node node) {
+        printPath0(new StrAppender(output), node);
+        return output;
+    }
+
+    private static void printPath0(StrAppender output, G4Node node) {
+        output.printf("%s = '%s'", node.path(), node.text()).endl();
+        node.children().forEach(n -> {
+            printPath0(output, n);
+        });
+    }
+
+}

+ 1 - 40
assira.grammar/src/main/java/net/ranides/assira/g4/impl/antlr/BasicParser.java

@@ -7,11 +7,6 @@ import net.ranides.assira.g4.G4Parser;
 import org.antlr.runtime.RecognitionException;
 import org.antlr.runtime.tree.CommonTree;
 import org.antlr.v4.analysis.LeftRecursiveRuleAltInfo;
-import org.antlr.v4.runtime.CharStreams;
-import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.LexerInterpreter;
-import org.antlr.v4.runtime.ParserInterpreter;
-import org.antlr.v4.runtime.ParserRuleContext;
 import org.antlr.v4.runtime.misc.Pair;
 import org.antlr.v4.tool.Alternative;
 import org.antlr.v4.tool.Grammar;
@@ -130,41 +125,7 @@ public class BasicParser implements G4Parser {
 
     @Override
     public NodeForRule parse(String rule, String text) {
-        return new Shared(rule, text).root;
+        return new BasicRoot(this, rule, text).root;
     }
 
-    class Shared {
-
-        protected final LexerInterpreter lexer;
-        protected final CommonTokenStream tokens;
-        protected final ParserInterpreter interpreter;
-        protected final ParserRuleContext tree;
-        protected final NodeForRule root;
-
-        public Shared(String rule, String text) {
-            this.lexer = grammar.createLexerInterpreter(CharStreams.fromString(text));
-            this.tokens = new CommonTokenStream(lexer);
-            this.interpreter = grammar.createGrammarParserInterpreter(tokens);
-
-            Rule foundRule = grammar.getRule(rule);
-            if(foundRule == null) {
-                throw new IllegalArgumentException("Unknown rule: " + rule);
-            }
-            this.tree = interpreter.parse(foundRule.index);
-            this.root = new NodeForRule(this, null, this.tree);
-        }
-
-        protected Grammar grammar() {
-            return BasicParser.this.grammar;
-        }
-
-        protected CrossMap<Integer, Integer, String> branchLabels() {
-            return BasicParser.this.branchLabels;
-        }
-
-        protected CrossMap<String, String, Integer> expressionLabels() {
-            return BasicParser.this.expressionLabels;
-        }
-
-    }
 }

+ 33 - 0
assira.grammar/src/main/java/net/ranides/assira/g4/impl/antlr/BasicRoot.java

@@ -0,0 +1,33 @@
+package net.ranides.assira.g4.impl.antlr;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.LexerInterpreter;
+import org.antlr.v4.runtime.ParserInterpreter;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.tool.Rule;
+
+class BasicRoot {
+
+    protected final BasicParser parser;
+    protected final LexerInterpreter lexer;
+    protected final CommonTokenStream tokens;
+    protected final ParserInterpreter interpreter;
+    protected final ParserRuleContext tree;
+    protected final NodeForRule root;
+
+    public BasicRoot(BasicParser parser, String rule, String text) {
+        this.parser = parser;
+        this.lexer = parser.grammar.createLexerInterpreter(CharStreams.fromString(text));
+        this.tokens = new CommonTokenStream(lexer);
+        this.interpreter = parser.grammar.createGrammarParserInterpreter(tokens);
+
+        Rule foundRule = parser.grammar.getRule(rule);
+        if (foundRule == null) {
+            throw new IllegalArgumentException("Unknown rule: " + rule);
+        }
+        this.tree = interpreter.parse(foundRule.index);
+        this.root = new NodeForRule(this, null, this.tree);
+    }
+
+}

+ 16 - 12
assira.grammar/src/main/java/net/ranides/assira/g4/impl/antlr/NodeAbstract.java

@@ -2,7 +2,6 @@ package net.ranides.assira.g4.impl.antlr;
 
 import net.ranides.assira.collection.query.CQuery;
 import net.ranides.assira.g4.G4Node;
-import net.ranides.assira.math.MathUtils;
 import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.RuleNode;
 import org.antlr.v4.runtime.tree.TerminalNode;
@@ -13,12 +12,12 @@ import java.util.Optional;
 
 abstract class NodeAbstract implements G4Node {
 
-    protected final BasicParser.Shared shared;
+    protected final BasicRoot root;
 
     protected final NodeAbstract parent;
 
-    public NodeAbstract(BasicParser.Shared shared, NodeAbstract parent) {
-        this.shared = shared;
+    public NodeAbstract(BasicRoot root, NodeAbstract parent) {
+        this.root = root;
         this.parent = parent;
     }
 
@@ -26,7 +25,7 @@ abstract class NodeAbstract implements G4Node {
 
     @Override
     public final G4Node root() {
-        return shared.root;
+        return root.root;
     }
 
     @Override
@@ -44,22 +43,22 @@ abstract class NodeAbstract implements G4Node {
         if (index >= 0 && index < node().getChildCount()) {
             return wrap(node().getChild(index));
         } else {
-            throw new NoSuchElementException("No child [" + index + "] inside " + path());
+            throw new NoSuchElementException("No child [" + index + "] inside " + this);
         }
     }
 
     @Override
     public G4Node child(String name) {
         return children(name)
-            .single(() -> new NoSuchElementException("More than one child '" + name + "' inside " + path()))
-            .orElseThrow(() -> new NoSuchElementException("No child '" + name + "' inside " + path()));
+            .single(() -> new NoSuchElementException("More than one child '" + name + "' inside " + this))
+            .orElseThrow(() -> new NoSuchElementException("No child '" + name + "' inside " + this));
     }
 
     @Override
     public G4Node label(String label) {
-        Integer index = shared.expressionLabels().get(name(), label);
+        Integer index = root.parser.expressionLabels.get(name(), label);
         if(index == null) {
-            throw new NoSuchElementException("No label '" + label + "' inside " + path());
+            throw new NoSuchElementException("No label '" + label + "' inside '" + this + "'");
         }
         return child(index);
     }
@@ -81,9 +80,9 @@ abstract class NodeAbstract implements G4Node {
 
     protected final G4Node wrap(ParseTree tree) {
         if(tree instanceof RuleNode) {
-            return new NodeForRule(shared, this, (RuleNode) tree);
+            return new NodeForRule(root, this, (RuleNode) tree);
         } else {
-            return new NodeForTerm(shared, this, (TerminalNode) tree);
+            return new NodeForTerm(root, this, (TerminalNode) tree);
         }
     }
 
@@ -147,4 +146,9 @@ abstract class NodeAbstract implements G4Node {
     public int hashCode() {
         return Objects.hash(node());
     }
+
+    @Override
+    public String toString() {
+        return path() + qualifier() + "=" + text();
+    }
 }

+ 4 - 4
assira.grammar/src/main/java/net/ranides/assira/g4/impl/antlr/NodeForRule.java

@@ -11,8 +11,8 @@ class NodeForRule extends NodeAbstract implements G4Node {
 
     private final RuleNode node;
 
-    public NodeForRule(BasicParser.Shared shared, NodeAbstract parent, RuleNode node) {
-        super(shared, parent);
+    public NodeForRule(BasicRoot root, NodeAbstract parent, RuleNode node) {
+        super(root, parent);
         this.node = node;
     }
 
@@ -29,7 +29,7 @@ class NodeForRule extends NodeAbstract implements G4Node {
     @Override
     public String shortname() {
         int ruleIndex = node.getRuleContext().getRuleIndex();
-        return shared.interpreter.getRuleNames()[ruleIndex];
+        return root.interpreter.getRuleNames()[ruleIndex];
     }
 
     @Override
@@ -37,7 +37,7 @@ class NodeForRule extends NodeAbstract implements G4Node {
         int ruleIndex = node.getRuleContext().getRuleIndex();
         int ruleAlt = node.getRuleContext().getAltNumber();
 
-        String label = shared.branchLabels().get(ruleIndex, ruleAlt);
+        String label = root.parser.branchLabels.get(ruleIndex, ruleAlt);
         return label != null ?
             NodeSupport.QUALIFIER_NAME + label :
             NodeSupport.QUALIFIER_INDEX + ruleAlt;

+ 3 - 3
assira.grammar/src/main/java/net/ranides/assira/g4/impl/antlr/NodeForTerm.java

@@ -9,8 +9,8 @@ class NodeForTerm extends NodeAbstract implements G4Node {
 
     private final TerminalNode node;
 
-    public NodeForTerm(BasicParser.Shared shared, NodeAbstract parent, TerminalNode node) {
-        super(shared, parent);
+    public NodeForTerm(BasicRoot root, NodeAbstract parent, TerminalNode node) {
+        super(root, parent);
         this.node = node;
     }
 
@@ -26,7 +26,7 @@ class NodeForTerm extends NodeAbstract implements G4Node {
 
     @Override
     public String shortname() {
-        return shared.lexer.getVocabulary().getSymbolicName(node.getSymbol().getType());
+        return root.lexer.getVocabulary().getSymbolicName(node.getSymbol().getType());
     }
 
     @Override

+ 55 - 32
assira.grammar/src/test/java/net/ranides/assira/g4/G4ParserTest.java

@@ -1,7 +1,6 @@
 package net.ranides.assira.g4;
 
 import net.ranides.assira.io.ResourceUtils;
-import net.ranides.assira.text.StrBuilder;
 import org.junit.Test;
 
 import java.io.IOException;
@@ -12,6 +11,53 @@ import static org.junit.Assert.assertEquals;
 
 public class G4ParserTest {
 
+    // @todo G4Parser should have "coverage mode" for each rule and rule branch
+
+    @Test
+    public void testLabels() throws IOException {
+        G4Parser parser = G4Parser.newInstance(ResourceUtils.text("LabelTest.g4"));
+
+        G4Node expr1 = parser.parse("manyLabels", "! ( 2 3 + 4 + 5 6 !");
+        assertEquals("2", expr1.label("num1").text());
+        assertEquals("3", expr1.label("num2").text());
+        assertEquals("5", expr1.label("num4").text());
+        assertEquals("6", expr1.label("num5").text());
+
+        G4Node expr2 = parser.parse("functionCall", "hello(1,2,3,4)");
+        assertEquals("1,2,3,4", expr2.label("args").text());
+
+        testLabelExpression(parser, "withLabels");
+        testLabelExpression(parser, "withoutLabels");
+        testLabelExpression(parser, "withoutLabelsRecursive");
+
+    }
+
+    private void testLabelExpression(G4Parser parser, String rule) {
+
+        G4Node expr2 = parser.parse(rule, "-5");
+        assertEquals("5", expr2.label("a").text());
+        assertThrows(NoSuchElementException.class, () -> {
+            expr2.label("b");
+        });
+
+        G4Node expr3 = parser.parse(rule, "(1+5) & 8");
+        assertEquals("(1+5)", expr3.label("a").text());
+        assertEquals("8", expr3.label("b").text());
+
+        G4Node expr4 = parser.parse(rule, "(1+5) * 8");
+        assertEquals("(1+5)", expr4.label("b").text());
+        assertEquals("8", expr4.label("a").text());
+
+        G4Node expr5 = parser.parse(rule, "(1+5) + 8");
+        assertEquals("(1+5)", expr5.label("x").text());
+        assertEquals("8", expr5.label("y").text());
+
+        G4Node expr6 = parser.parse(rule, "? 1 2 3");
+        assertEquals("1", expr6.label("p").text());
+        assertEquals("2", expr6.label("q").text());
+        assertEquals("3", expr6.label("r").text());
+    }
+
     @Test
     public void selector() throws IOException {
         G4Parser parser = G4Parser.newInstance(ResourceUtils.text("Calculator.g4"));
@@ -53,8 +99,8 @@ public class G4ParserTest {
             .prepare();
 
 
-        assertEquals(6, selectExpression.aggregate(parser.parse("expression", "4 + 6 / 3")).intValue());
-        assertEquals(30, selectExpression.aggregate(parser.parse("expression", "(4 + 6) * 3")).intValue());
+        assertEquals(9, selectExpression.aggregate(parser.parse("expression", "6 + 9 / 3")).intValue());
+        assertEquals(5, selectExpression.aggregate(parser.parse("expression", "(6 + 9) / 3")).intValue());
     }
 
     @Test
@@ -64,45 +110,22 @@ public class G4ParserTest {
         G4Node tree1 = parser.parse("withoutLabels", "(4 + 2) * 3 ");
         G4Node tree2 = parser.parse("expression", "(4 + 2) * 3 ");
 
-        System.out.println(names(tree1) );
-        System.out.printf("%n%n--------------%n%n");
-        System.out.println(names(tree2) );
+        System.out.println(G4Support.printTree(tree1) );
+        System.out.println("--------------");
+        System.out.println(G4Support.printTree(tree2) );
     }
 
     @Test
     public void printPaths() throws IOException {
         G4Parser g4 = G4Parser.newInstance(ResourceUtils.text("Calculator.g4"));
 
-
         G4Node tree1 = g4.parse("withoutLabels", "(4 + 2) * 3 ");
         G4Node tree2 = g4.parse("expression", "(4 + 2) * 3 ");
 
-        System.out.println(paths(tree1) );
-        System.out.printf("%n%n--------------%n%n");
-        System.out.println(paths(tree2) );
-    }
-
-    static String names(G4Node node) {
-        return names(new StrBuilder(), "", node).toString();
+        System.out.println(G4Support.printPath(tree1) );
+        System.out.println("--------------");
+        System.out.println(G4Support.printPath(tree2) );
     }
 
-    static StrBuilder names(StrBuilder writer, String indent, G4Node node) {
-        writer.printf("%s%s = '%s'%n", indent, node.name(), node.text());
-        node.children().forEach(n -> {
-            names(writer, indent+"   ", n);
-        });
-        return writer;
-    }
-
-    static String paths(G4Node node) {
-        return paths(new StrBuilder(), node).toString();
-    }
 
-    static StrBuilder paths(StrBuilder writer, G4Node node) {
-        writer.printf("%s = '%s'%n", node.path(), node.text());
-        node.children().forEach(n -> {
-            paths(writer, n);
-        });
-        return writer;
-    }
 }

+ 80 - 0
assira.grammar/src/test/resources/LabelTest.g4

@@ -0,0 +1,80 @@
+grammar LabelTest;
+
+
+manyLabels
+    : SYMBOL_FACT SYMBOL_LB num1=number num2=number SYMBOL_ADD number SYMBOL_ADD num4=number num5=number SYMBOL_FACT
+    ;
+
+withLabels
+    : functionCall                              #branch_call
+    | operatorNeg a=withLabels                  #branch_pref
+    | withLabels operatorFact                   #branch_suff
+    | SYMBOL_LB withLabels SYMBOL_RB            #branch_bracket
+    | a=withLabels operatorAnd b=withLabels     #branch_1
+    | b=withLabels operatorMul a=withLabels     #branch_2
+    | x=withLabels operatorAdd y=withLabels     #branch_3
+    | number                                    #branch_number
+    | SYMBOL_ASK p=number q=number r=number     #branch_ask
+    ;
+
+withoutLabels
+    : functionCall
+    | operatorNeg a=withLabels
+    | withLabels operatorFact
+    | SYMBOL_LB withLabels SYMBOL_RB
+    | a=withLabels operatorAnd b=withLabels
+    | b=withLabels operatorMul a=withLabels
+    | x=withLabels operatorAdd y=withLabels
+    | number
+    | SYMBOL_ASK p=number q=number r=number
+    ;
+
+withoutLabelsRecursive
+    : functionCall
+    | operatorNeg a=withoutLabelsRecursive
+    | withoutLabelsRecursive operatorFact
+    | SYMBOL_LB withoutLabelsRecursive SYMBOL_RB
+    | a=withoutLabelsRecursive operatorAnd b=withoutLabelsRecursive
+    | b=withoutLabelsRecursive operatorMul a=withoutLabelsRecursive
+    | x=withoutLabelsRecursive operatorAdd y=withoutLabelsRecursive
+    | number
+    | SYMBOL_ASK p=number q=number r=number
+    ;
+
+functionCall
+    : NAME SYMBOL_LB args=functionArguments SYMBOL_RB
+    ;
+
+functionArguments
+    : functionArgument (SYMBOL_COM functionArgument)*
+    ;
+
+functionArgument
+    : withLabels
+    ;
+
+operatorAnd     : SYMBOL_AND;
+operatorMul     : SYMBOL_MUL;
+operatorAdd     : SYMBOL_ADD;
+operatorNeg     : SYMBOL_NEG;
+operatorFact    : SYMBOL_FACT;
+
+number : NUMBER_INT;
+
+NUMBER_INT : [0-9]+ ;
+
+NAME : [a-z]+ ;
+
+SYMBOL_LB : '(' ;
+SYMBOL_RB : ')' ;
+SYMBOL_COM : ',' ;
+
+SYMBOL_FACT: '!' ;
+SYMBOL_AND : '&' ;
+SYMBOL_MUL : '*' ;
+SYMBOL_ADD : '+' ;
+SYMBOL_NEG : '-' ;
+
+SYMBOL_ASK : '?' ;
+
+WS :  [ \t\r\n]+ -> skip ;