Jelajahi Sumber

WIP: grammar helper

Ranides Atterwim 2 tahun lalu
induk
melakukan
9a0dc3351d

+ 68 - 0
assira.grammar/pom.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>assira.project</artifactId>
+        <groupId>net.ranides</groupId>
+        <version>2.8.4-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>assira.grammar</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${project.groupId}:${project.artifactId}</name>
+    <description>assira: ANTLR4 interpreter</description>
+
+    <properties>
+        <assira.junit.debug>false</assira.junit.debug>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>net.ranides</groupId>
+            <artifactId>assira.commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.ranides</groupId>
+            <artifactId>assira.core</artifactId>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>net.ranides</groupId>
+            <artifactId>assira.junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>antlr4-runtime</artifactId>
+            <version>4.13.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>antlr4</artifactId>
+            <version>4.13.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>annotations</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+
+    </dependencies>
+
+</project>

+ 17 - 0
assira.grammar/src/main/java/net/ranides/assira/g4/G4Node.java

@@ -0,0 +1,17 @@
+package net.ranides.assira.g4;
+
+import java.util.List;
+
+public interface G4Node {
+
+    G4Node root();
+
+    String name();
+
+    String alt();
+
+    String value();
+
+    List<G4Node> children();
+
+}

+ 14 - 0
assira.grammar/src/main/java/net/ranides/assira/g4/G4Parser.java

@@ -0,0 +1,14 @@
+package net.ranides.assira.g4;
+
+
+import net.ranides.assira.g4.antlr.BasicParser;
+
+public interface G4Parser {
+
+    static G4Parser newInstance(String grammar) {
+        return new BasicParser(grammar);
+    }
+
+    G4Node parse(String rule, String text);
+
+}

+ 74 - 0
assira.grammar/src/main/java/net/ranides/assira/g4/antlr/BasicParser.java

@@ -0,0 +1,74 @@
+package net.ranides.assira.g4.antlr;
+
+
+import net.ranides.assira.collection.maps.CrossMap;
+import net.ranides.assira.collection.maps.OpenCrossMap;
+import net.ranides.assira.g4.G4Parser;
+import org.antlr.runtime.RecognitionException;
+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.Grammar;
+import org.antlr.v4.tool.Rule;
+import org.antlr.v4.tool.ast.AltAST;
+
+import java.util.List;
+import java.util.Map;
+
+public class BasicParser implements G4Parser {
+
+    protected final Grammar grammar;
+
+    public BasicParser(String grammar) {
+        try {
+            this.grammar = new Grammar(grammar);
+        } catch (RecognitionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public NodeForRule parse(String rule, String text) {
+        return new Shared(grammar, rule, text).root;
+    }
+
+    static class Shared {
+
+        protected final Grammar grammar;
+        protected final LexerInterpreter lexer;
+        protected final CommonTokenStream tokens;
+        protected final ParserInterpreter parser;
+        protected final ParserRuleContext tree;
+        protected final NodeForRule root;
+        protected final CrossMap<Integer, Integer, String> labels;
+
+        public Shared(Grammar grammar, String rule, String text) {
+            this.grammar = grammar;
+            this.lexer = grammar.createLexerInterpreter(CharStreams.fromString(text));
+            this.tokens = new CommonTokenStream(lexer);
+            this.parser = grammar.createGrammarParserInterpreter(tokens);
+
+            this.tree = parser.parse(grammar.getRule(rule).index);
+            this.root = new NodeForRule(this, this.tree);
+
+            this.labels = new OpenCrossMap<>();
+
+            // tricky fragment: we deduce alternative labels for every rule
+
+            for(Rule currentRule : grammar.rules.values()) {
+                Map<String, List<Pair<Integer, AltAST>>> altLabels = currentRule.getAltLabels();
+                if(altLabels != null) {
+                    for(List<Pair<Integer, AltAST>> list : altLabels.values()) {
+                        for(Pair<Integer, AltAST> pair : list) {
+                            labels.put(currentRule.index, pair.a, pair.b.altLabel.toString());
+                        }
+                    }
+                }
+            }
+
+        }
+
+    }
+}

+ 34 - 0
assira.grammar/src/main/java/net/ranides/assira/g4/antlr/BasicSupport.java

@@ -0,0 +1,34 @@
+package net.ranides.assira.g4.antlr;
+
+import lombok.experimental.UtilityClass;
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.misc.Interval;
+import org.antlr.v4.runtime.tree.ParseTree;
+
+@UtilityClass
+class BasicSupport {
+
+    public static String getFullText(ParseTree node) {
+        if(node instanceof ParserRuleContext) {
+            ParserRuleContext ctx = (ParserRuleContext) node;
+            if(hasValidInterval(ctx)) {
+                return ctx.getStart().getInputStream().getText(asInterval(ctx));
+            }
+        }
+        return node.getText();
+    }
+
+    private static boolean hasValidInterval(ParserRuleContext context) {
+        return context.start != null
+            && context.start.getStartIndex() >= 0
+            && context.stop != null
+            && context.stop.getStopIndex() > context.start.getStartIndex();
+    }
+
+    private static Interval asInterval(ParserRuleContext context) {
+        return new Interval(context.start.getStartIndex(), context.stop.getStopIndex());
+    }
+
+
+
+}

+ 67 - 0
assira.grammar/src/main/java/net/ranides/assira/g4/antlr/NodeForRule.java

@@ -0,0 +1,67 @@
+package net.ranides.assira.g4.antlr;
+
+import net.ranides.assira.collection.lists.VirtualList;
+import net.ranides.assira.g4.G4Node;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.RuleNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import java.util.List;
+
+class NodeForRule implements G4Node {
+
+    protected final BasicParser.Shared shared;
+
+    protected final RuleNode node;
+
+    public NodeForRule(BasicParser.Shared shared, RuleNode node) {
+        this.shared = shared;
+        this.node = node;
+    }
+
+    @Override
+    public G4Node root() {
+        return shared.root;
+    }
+
+    @Override
+    public String name() {
+        int ruleIndex = node.getRuleContext().getRuleIndex();
+        return shared.parser.getRuleNames()[ruleIndex];
+    }
+
+    @Override
+    public String alt() {
+        int ruleIndex = node.getRuleContext().getRuleIndex();
+        int ruleAlt = node.getRuleContext().getAltNumber();
+        return shared.labels.get(ruleIndex, ruleAlt);
+    }
+
+    @Override
+    public String value() {
+        return BasicSupport.getFullText(node);
+    }
+
+    @Override
+    public List<G4Node> children() {
+        return VirtualList.of(node.getChildCount(), i -> {
+            ParseTree c = node.getChild(i);
+            if(c instanceof RuleNode) {
+                return new NodeForRule(shared, (RuleNode) c);
+            } else {
+                return new NodeForTerm(shared, (TerminalNode) c);
+            }
+        });
+    }
+
+    @Override
+    public String toString() {
+        String n = name();
+        String a = alt();
+        if(a != null) {
+            return n + "<" + a + ">";
+        } else {
+            return n;
+        }
+    }
+}

+ 46 - 0
assira.grammar/src/main/java/net/ranides/assira/g4/antlr/NodeForTerm.java

@@ -0,0 +1,46 @@
+package net.ranides.assira.g4.antlr;
+
+import net.ranides.assira.g4.G4Node;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+import java.util.Collections;
+import java.util.List;
+
+class NodeForTerm implements G4Node {
+
+    protected final BasicParser.Shared shared;
+
+    protected final TerminalNode node;
+
+    public NodeForTerm(BasicParser.Shared shared, TerminalNode node) {
+        this.shared = shared;
+        this.node = node;
+    }
+
+    @Override
+    public G4Node root() {
+        return shared.root;
+    }
+
+    @Override
+    public String name() {
+        return shared.lexer.getVocabulary().getSymbolicName(node.getSymbol().getType());
+    }
+
+    @Override
+    public String alt() {
+        return null;
+    }
+
+    @Override
+    public String value() {
+        return node.getText();
+    }
+
+    @Override
+    public List<G4Node> children() {
+        return Collections.emptyList();
+    }
+
+
+}

+ 36 - 0
assira.grammar/src/test/java/net/ranides/assira/g4/G4ParserTest.java

@@ -0,0 +1,36 @@
+package net.ranides.assira.g4;
+
+import net.ranides.assira.io.ResourceUtils;
+import org.antlr.runtime.RecognitionException;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class G4ParserTest {
+
+    @Test
+    public void simple() throws IOException {
+        String calc = ResourceUtils.text("Calculator.g4");
+        G4Parser g4 = G4Parser.newInstance(calc);
+
+//        G4Node tree1 = g4.parse("expression", "(4 + 2) * 3 -- 3*9! / log(2+7, 6+5)");
+        G4Node tree2 = g4.parse("expression", "(4 + 2) * 3 ");
+
+//        System.out.printf("%s = %s%n", tree1.name(), tree1.value());
+//        System.out.printf("%s = %s%n", tree2.name(), tree2.value());
+
+//        dump("1  > ", tree1);
+        dump("2  > ", tree2);
+    }
+
+    static void dump(String indent, G4Node node) {
+        if(node.alt() != null) {
+            System.out.printf("%s%s<%s> = %s%n", indent, node.name(), node.alt(), node.value());
+        } else {
+            System.out.printf("%s%s = %s%n", indent, node.name(), node.value());
+        }
+        node.children().forEach(n -> {
+            dump(indent+"   ", n);
+        });
+    }
+}

+ 84 - 0
assira.grammar/src/test/resources/Calculator.g4

@@ -0,0 +1,84 @@
+grammar Calculator;
+
+expression
+    : functionCall                      #exprǂcall
+    | operatorPrefix expression         #exprǂpref
+    | expression operatorSuffix         #exprǂsuff
+    | SYMBOL_LB expression SYMBOL_RB    #exprǂbracket
+    | expression operator1 expression   #exprǂ1
+    | expression operator2 expression   #exprǂ2
+    | expression operator3 expression   #exprǂ3
+    | number                            #exprǂnumber
+    ;
+
+functionCall
+    : NAME SYMBOL_LB functionArguments SYMBOL_RB
+    ;
+
+functionArguments
+    : expression (SYMBOL_COM expression)*
+    ;
+
+operatorPrefix
+    : operatorNegate
+    ;
+
+operatorSuffix
+    : operatorFactorial
+    ;
+
+operator1
+    : operatorPow
+    | operatorOr
+    | operatorAnd
+    ;
+
+operator2
+    : operatorMul
+    | operatorDiv
+    ;
+
+operator3
+    : operatorAdd
+    | operatorSub
+    ;
+
+operatorNegate : SYMBOL_SUB;
+operatorFactorial : SYMBOL_FACT;
+operatorPow : SYMBOL_POW;
+operatorOr : SYMBOL_OR;
+operatorAnd : SYMBOL_AND;
+operatorMul : SYMBOL_MUL;
+operatorDiv : SYMBOL_DIV;
+operatorAdd : SYMBOL_ADD;
+operatorSub : SYMBOL_SUB;
+
+number
+    : numberInt
+    | numberReal
+    ;
+
+numberInt: NUMBER_INT;
+
+numberReal: NUMBER_REAL;
+
+NUMBER_REAL : [0-9]* '.' [0-9]*;
+
+NUMBER_INT : [0-9]+ ;
+
+NAME : [a-z]+ ;
+
+SYMBOL_LB : '(' ;
+SYMBOL_RB : ')' ;
+SYMBOL_COM : ',' ;
+
+SYMBOL_FACT: '!' ;
+SYMBOL_POW : '^' ;
+SYMBOL_OR : '|' ;
+SYMBOL_AND : '&' ;
+SYMBOL_MUL : '*' ;
+SYMBOL_DIV : '/' ;
+SYMBOL_ADD : '+' ;
+SYMBOL_SUB : '-' ;
+
+WS :  [ \t\r\n]+ -> skip ;

+ 1 - 0
pom.xml

@@ -80,6 +80,7 @@
                 <module>assira.commons</module>
                 <module>assira.enterprise</module>
                 <module>assira.xls</module>
+                <module>assira.grammar</module>
             </modules>
         </profile>
         <profile>