cenotelie / hime

Apache License 2.0
27 stars 4 forks source link

NullPointerException at fr.cenotelie.hime.redist.parsers.GSS.hasEdge after changing positions in grammar #78

Closed spipnl closed 2 years ago

spipnl commented 2 years ago

Hi,

I have a simple grammar to parse a string of alternating PING and PONG terminals. The grammar looks like this:

grammar PingPong
{
  options
  {
    Axiom = "s"; // the top variable for this grammar
    Separator = "SEPARATOR"; // the terminal that represent white space
    ParserType = "RNGLR";
    Runtime = "Java";
    AccessModifier = "Public";
    Namespace = "pingpong";
  }
  terminals
  {
    WHITE_SPACE -> U+0020 | U+0009 | U+000B | U+000C ;
    SEPARATOR   -> WHITE_SPACE+;
    PING       -> 'PING';
    PONG       -> 'PONG';
  }
  rules
  {
    s           -> ping
                | pong;
    ping        -> pong PING @OnPongPing
                | PING @OnPing;
    pong        -> ping PONG @OnPingPong
                | PONG @OnPong;
  }
}

I have generated the lexer and parser and created a simple evaluator to count the number of pings and pongs for the given input.

public class PingPongEvaluator extends PingPongParser.Actions implements Evaluator {
    public static final String KEY_PINGS = "pings";
    public static final String KEY_PONGS = "pongs";
    private double pings = 0;
    private double pongs = 0;

    @Override
    public void onPongPing(Symbol head, SemanticBody body) {
        pings++;
    }

    @Override
    public void onPing(Symbol head, SemanticBody body) {
        pings++;
    }

    @Override
    public void onPingPong(Symbol head, SemanticBody body) {
        pongs++;
    }

    @Override
    public void onPong(Symbol head, SemanticBody body) {
        pongs++;
    }

    @Override
    public Map<String, Double> getMetrics() {
        return Map.ofEntries(
                Map.entry(KEY_PINGS, pings),
                Map.entry(KEY_PONGS, pongs)
        );
    }
}

To test the lexer, parser and evaluator, I have created a simple unit test which works fine:

    @Test
    void pingPongPingPongPing() throws InitializationException {
        PingPongLexer lexer = new PingPongLexer("PINGPONGPINGPONGPING");
        PingPongEvaluator evaluator = new PingPongEvaluator();
        PingPongParser parser = new PingPongParser(lexer, evaluator);
        parser.parse();
        Map<String, Double> metrics = evaluator.getMetrics();
        assertEquals(3.0, metrics.get(PingPongEvaluator.KEY_PINGS).doubleValue());
        assertEquals(2.0, metrics.get(PingPongEvaluator.KEY_PONGS).doubleValue());
    }

When I swap the positions of the terminal and non-terminal in the grammar (PING pong instead of pong PING):

...
  rules
  {
    s           -> ping
                | pong;
    ping        -> PING pong @OnPongPing
                | PING @OnPing;
    pong        -> PONG ping @OnPingPong
                | PONG @OnPong;
  }
...

I get an exception:

java.lang.NullPointerException
    at fr.cenotelie.hime.redist.parsers.GSS.hasEdge(GSS.java:157)
    at fr.cenotelie.hime.redist.parsers.RNGLRParser.executeReduction(RNGLRParser.java:647)
    at fr.cenotelie.hime.redist.parsers.RNGLRParser.executeReduction(RNGLRParser.java:621)
    at fr.cenotelie.hime.redist.parsers.RNGLRParser.reducer(RNGLRParser.java:601)
    at fr.cenotelie.hime.redist.parsers.RNGLRParser.parse(RNGLRParser.java:566)
    at pingpong.calculator.PingPongMetricCalculatorTest.pingPongPingPongPing(PingPongMetricCalculatorTest.java:86)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

Process finished with exit code 255

This only happens when the input is four or more terminals long. I have tried multiple versions of Hime including the latest (3.5.1). Do you have any idea what could be wrong? Thanks!

woutersl commented 2 years ago

Thank you for reporting this. Indeed, there seems to be a bug in the Java runtime impacting all versions.

woutersl commented 2 years ago

Technical note: The GSS.hasEdge method mistakenly uses the GSS generation data for nodes instead of edges, as is the case in the .Net and Rust runtime.

woutersl commented 2 years ago

This issue is fixed on master. The fix will be backported to publish a version 3.5.2 of the Java runtime.

spipnl commented 2 years ago

Thanks for the quick response! I have just tested it and it's working 👍

woutersl commented 2 years ago

Version 3.5.2 of the Java runtime that includes this fix has been published. It will be replicated into maven central in a few minutes.

spipnl commented 2 years ago

I didn't know where to find the build status of the maven package. The link in the readme leads to a 404 (https://dev.azure.com/lwouters/cenotelie/_build/latest?definitionId=6&branchName=master), so I used jitpack.io in my pom to run the latest version directly from Github and verified it that way.