diff --git a/core/src/main/java/io/parsingdata/metal/data/ParseGraph.java b/core/src/main/java/io/parsingdata/metal/data/ParseGraph.java index 61995f60..30b7c8d4 100644 --- a/core/src/main/java/io/parsingdata/metal/data/ParseGraph.java +++ b/core/src/main/java/io/parsingdata/metal/data/ParseGraph.java @@ -35,6 +35,7 @@ public class ParseGraph extends ImmutableObject implements ParseItem { public final ParseGraph tail; public final boolean branched; public final Token definition; + public final int scopeDepth; public final long size; public static final Token NONE = new Token("NONE", null) { @@ -47,52 +48,61 @@ public class ParseGraph extends ImmutableObject implements ParseItem { private ParseGraph(final Token definition) { head = null; tail = null; - branched = false; this.definition = checkNotNull(definition, "definition"); + branched = false; + scopeDepth = 0; size = 0; } - private ParseGraph(final ParseItem head, final ParseGraph tail, final Token definition, final boolean branched) { + private ParseGraph(final ParseItem head, final ParseGraph tail, final Token definition, final boolean branched, final int scopeDepth) { this.head = checkNotNull(head, "head"); this.tail = checkNotNull(tail, "tail"); - this.branched = branched; this.definition = checkNotNull(definition, "definition"); + this.branched = branched; + this.scopeDepth = scopeDepth; size = tail.size + 1; } private ParseGraph(final ParseItem head, final ParseGraph tail, final Token definition) { - this(head, tail, definition, false); + this(head, tail, definition, false, 0); } protected ParseGraph add(final ParseValue head) { if (branched) { - return new ParseGraph(this.head.asGraph().add(head), tail, definition, true); + return new ParseGraph(this.head.asGraph().add(head), tail, definition, true, scopeDepth); } return new ParseGraph(head, this, definition); } protected ParseGraph add(final ParseReference parseReference) { if (branched) { - return new ParseGraph(head.asGraph().add(parseReference), tail, definition, true); + return new ParseGraph(head.asGraph().add(parseReference), tail, definition, true, scopeDepth); } return new ParseGraph(parseReference, this, definition); } protected ParseGraph addBranch(final Token definition) { if (branched) { - return new ParseGraph(head.asGraph().addBranch(definition), tail, this.definition, true); + return new ParseGraph(head.asGraph().addBranch(definition), tail, this.definition, true, definition.isScopeDelimiter() ? scopeDepth + 1 : scopeDepth); } - return new ParseGraph(new ParseGraph(definition), this, this.definition, true); + return new ParseGraph(new ParseGraph(definition), this, this.definition, true, definition.isScopeDelimiter() ? 1 : 0); } - protected ParseGraph closeBranch() { + protected ParseGraph closeBranch(final Token token) { if (!branched) { throw new IllegalStateException("Cannot close branch that is not open."); } + final int newScopeDepth = token.isScopeDelimiter() ? scopeDepth - 1 : scopeDepth; if (head.asGraph().branched) { - return new ParseGraph(head.asGraph().closeBranch(), tail, definition, true); + return new ParseGraph(head.asGraph().closeBranch(token), tail, definition, true, newScopeDepth); + } + if (!head.getDefinition().equals(token)) { + throw new IllegalStateException("Cannot close branch with token that does not match its head token."); + } + if (newScopeDepth != 0) { + throw new IllegalStateException("Cannot close parse graph that has a non zero scopeDepth."); } - return new ParseGraph(head, tail, definition, false); + return new ParseGraph(head, tail, definition); } public boolean isEmpty() { return size == 0; } @@ -131,7 +141,7 @@ public String toString() { if (isEmpty()) { return "pg(terminator:" + definition.getClass().getSimpleName() + ")"; } - return "pg(" + head + "," + tail + "," + branched + ")"; + return "pg(" + head + "," + tail + "," + branched + "," + scopeDepth + ")"; } @Override @@ -140,6 +150,7 @@ public boolean equals(final Object obj) { && Objects.equals(head, ((ParseGraph)obj).head) && Objects.equals(tail, ((ParseGraph)obj).tail) && Objects.equals(branched, ((ParseGraph)obj).branched) + && Objects.equals(scopeDepth, ((ParseGraph)obj).scopeDepth) && Objects.equals(definition, ((ParseGraph)obj).definition); // The size field is excluded from equals() and hashCode() because it is cached data. } diff --git a/core/src/main/java/io/parsingdata/metal/data/ParseState.java b/core/src/main/java/io/parsingdata/metal/data/ParseState.java index b5397cfd..0c368070 100644 --- a/core/src/main/java/io/parsingdata/metal/data/ParseState.java +++ b/core/src/main/java/io/parsingdata/metal/data/ParseState.java @@ -42,20 +42,18 @@ public class ParseState extends ImmutableObject { public final Source source; public final ImmutableList> iterations; public final ImmutableList references; - public final int scopeDepth; - public ParseState(final ParseGraph order, final ParseValueCache cache, final Source source, final BigInteger offset, final ImmutableList> iterations, final ImmutableList references, final int scopeDepth) { + public ParseState(final ParseGraph order, final ParseValueCache cache, final Source source, final BigInteger offset, final ImmutableList> iterations, final ImmutableList references) { this.order = checkNotNull(order, "order"); this.cache = checkNotNull(cache, "cache"); this.source = checkNotNull(source, "source"); this.offset = checkNotNegative(offset, "offset"); this.iterations = checkNotNull(iterations, "iterations"); this.references = checkNotNull(references, "references"); - this.scopeDepth = scopeDepth; } public static ParseState createFromByteStream(final ByteStream input, final BigInteger offset) { - return new ParseState(ParseGraph.EMPTY, new ParseValueCache(), new ByteStreamSource(input), offset, new ImmutableList<>(), new ImmutableList<>(), 0); + return new ParseState(ParseGraph.EMPTY, new ParseValueCache(), new ByteStreamSource(input), offset, new ImmutableList<>(), new ImmutableList<>()); } public static ParseState createFromByteStream(final ByteStream input) { @@ -63,42 +61,42 @@ public static ParseState createFromByteStream(final ByteStream input) { } public ParseState addBranch(final Token token) { - return new ParseState(order.addBranch(token), cache, source, offset, token.isIterable() ? iterations.add(new ImmutablePair<>(token, ZERO)) : iterations, references, token.isScopeDelimiter() ? scopeDepth + 1 : scopeDepth); + return new ParseState(order.addBranch(token), cache, source, offset, token.isIterable() ? iterations.add(new ImmutablePair<>(token, ZERO)) : iterations, references); } public ParseState closeBranch(final Token token) { if (token.isIterable() && !iterations.head.left.equals(token)) { throw new IllegalStateException(format("Cannot close branch for iterable token %s. Current iteration state is for token %s.", token.name, iterations.head.left.name)); } - return new ParseState(order.closeBranch(), cache, source, offset, token.isIterable() ? iterations.tail : iterations, references, token.isScopeDelimiter() ? scopeDepth - 1 : scopeDepth); + return new ParseState(order.closeBranch(token), cache, source, offset, token.isIterable() ? iterations.tail : iterations, references); } public ParseState add(final ParseReference parseReference) { - return new ParseState(order, cache, source, offset, iterations, references.add(parseReference), scopeDepth); + return new ParseState(order, cache, source, offset, iterations, references.add(parseReference)); } public ParseState add(final ParseValue parseValue) { - return new ParseState(order.add(parseValue), cache.add(parseValue), source, offset, iterations, references, scopeDepth); + return new ParseState(order.add(parseValue), cache.add(parseValue), source, offset, iterations, references); } public ParseState createCycle(final ParseReference parseReference) { - return new ParseState(order.add(parseReference), cache, source, offset, iterations, references, scopeDepth); + return new ParseState(order.add(parseReference), cache, source, offset, iterations, references); } public ParseState iterate() { - return new ParseState(order, cache, source, offset, iterations.tail.add(new ImmutablePair<>(iterations.head.left, iterations.head.right.add(ONE))), references, scopeDepth); + return new ParseState(order, cache, source, offset, iterations.tail.add(new ImmutablePair<>(iterations.head.left, iterations.head.right.add(ONE))), references); } public Optional seek(final BigInteger newOffset) { - return newOffset.compareTo(ZERO) >= 0 ? Optional.of(new ParseState(order, cache, source, newOffset, iterations, references, scopeDepth)) : Optional.empty(); + return newOffset.compareTo(ZERO) >= 0 ? Optional.of(new ParseState(order, cache, source, newOffset, iterations, references)) : Optional.empty(); } public ParseState withOrder(final ParseGraph order) { - return new ParseState(order, NO_CACHE, source, offset, iterations, references, scopeDepth); + return new ParseState(order, NO_CACHE, source, offset, iterations, references); } public ParseState withSource(final Source source) { - return new ParseState(order, cache, source, ZERO, iterations, references, scopeDepth); + return new ParseState(order, cache, source, ZERO, iterations, references); } public Optional slice(final BigInteger length) { @@ -109,7 +107,7 @@ public Optional slice(final BigInteger length) { public String toString() { final String iterationsString = iterations.isEmpty() ? "" : ";iterations:" + iterations; final String referencesString = references.isEmpty() ? "" : ";references:" + references; - return getClass().getSimpleName() + "(source:" + source + ";offset:" + offset + ";order:" + order + iterationsString + referencesString + ";scopeDepth:" + scopeDepth + ";" + cache + ")"; + return getClass().getSimpleName() + "(source:" + source + ";offset:" + offset + ";order:" + order + iterationsString + referencesString + ";" + cache + ")"; } @Override @@ -120,13 +118,12 @@ public boolean equals(final Object obj) { && Objects.equals(offset, ((ParseState)obj).offset) && Objects.equals(source, ((ParseState)obj).source) && Objects.equals(iterations, ((ParseState)obj).iterations) - && Objects.equals(references, ((ParseState)obj).references) - && Objects.equals(scopeDepth, ((ParseState)obj).scopeDepth); + && Objects.equals(references, ((ParseState)obj).references); } @Override public int immutableHashCode() { - return Objects.hash(getClass(), order, cache, offset, source, iterations, references, scopeDepth); + return Objects.hash(getClass(), order, cache, offset, source, iterations, references); } } diff --git a/core/src/main/java/io/parsingdata/metal/expression/value/reference/Ref.java b/core/src/main/java/io/parsingdata/metal/expression/value/reference/Ref.java index f0ba8ab2..68dd0d05 100644 --- a/core/src/main/java/io/parsingdata/metal/expression/value/reference/Ref.java +++ b/core/src/main/java/io/parsingdata/metal/expression/value/reference/Ref.java @@ -88,7 +88,7 @@ public static class NameRef extends Ref { protected ImmutableList evalImpl(final ParseState parseState, final int limit, final int requestedScope) { return Optional.of(parseState.cache) .filter(p -> references.size == 1) - .filter(p -> requestedScope >= parseState.scopeDepth) + .filter(p -> requestedScope >= parseState.order.scopeDepth) .flatMap(p -> p.find(references.head, limit)) .orElseGet(() -> super.evalImpl(parseState, limit, requestedScope)); } @@ -124,7 +124,7 @@ public DefinitionRef withScope(final SingleValueExpression scope) { @Override public ImmutableList eval(final ParseState parseState, final Encoding encoding) { - final int requestedScope = scope == null ? parseState.scopeDepth : scope.evalSingle(parseState, encoding) + final int requestedScope = scope == null ? parseState.order.scopeDepth : scope.evalSingle(parseState, encoding) .filter(sizeValue -> !sizeValue.equals(NOT_A_VALUE) && sizeValue.asNumeric().compareTo(ZERO) >= 0) .orElseThrow(() -> new IllegalArgumentException("Argument scopeSize must evaluate to a positive, countable value.")).asNumeric().intValueExact(); if (limit == null) { @@ -136,7 +136,7 @@ public ImmutableList eval(final ParseState parseState, final Encoding enc } protected ImmutableList evalImpl(final ParseState parseState, final int limit, final int requestedScope) { - return wrap(getAllValues(parseState.order, parseValue -> toList(references).stream().anyMatch(ref -> predicate.test(parseValue, ref)), limit, requestedScope, parseState.scopeDepth), new ImmutableList()).computeResult(); + return wrap(getAllValues(parseState.order, parseValue -> toList(references).stream().anyMatch(ref -> predicate.test(parseValue, ref)), limit, requestedScope, parseState.order.scopeDepth), new ImmutableList()).computeResult(); } static List toList(final ImmutableList allValues) { diff --git a/core/src/main/java/io/parsingdata/metal/token/Tie.java b/core/src/main/java/io/parsingdata/metal/token/Tie.java index 7ea68856..2e600c4b 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Tie.java +++ b/core/src/main/java/io/parsingdata/metal/token/Tie.java @@ -68,7 +68,7 @@ protected Optional parseImpl(final Environment environment) { private Trampoline> iterate(final Environment environment, final ImmutableList values, final int index, final ParseState returnParseState) { if (values.isEmpty()) { - return complete(() -> success(new ParseState(environment.parseState.closeBranch(this).order, environment.parseState.cache, returnParseState.source, returnParseState.offset, returnParseState.iterations, returnParseState.references, returnParseState.scopeDepth))); + return complete(() -> success(new ParseState(environment.parseState.closeBranch(this).order, environment.parseState.cache, returnParseState.source, returnParseState.offset, returnParseState.iterations, returnParseState.references))); } if (values.head.equals(NOT_A_VALUE)) { return complete(Util::failure); diff --git a/core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java b/core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java index 51363a8f..28762dd3 100644 --- a/core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java +++ b/core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java @@ -25,9 +25,7 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static io.parsingdata.metal.Shorthand.TRUE; import static io.parsingdata.metal.Shorthand.con; @@ -68,6 +66,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -220,7 +219,7 @@ public class AutoEqualityTest { private static final List> PARSE_ITEMS = List.of(() -> CLOSED_BRANCHED_GRAPH, () -> ParseGraph.EMPTY, () -> GRAPH_WITH_REFERENCE, () -> createFromByteStream(DUMMY_STREAM).add(PARSE_VALUE).order, () -> createFromByteStream(DUMMY_STREAM).add(PARSE_VALUE).add(PARSE_VALUE).order, () -> BRANCHED_GRAPH); private static final List> BYTE_STREAMS = List.of(() -> new InMemoryByteStream(new byte[] { 1, 2 }), () -> DUMMY_STREAM); private static final List> BIG_INTEGERS = List.of(() -> ONE, () -> BigInteger.valueOf(3)); - private static final List> PARSE_STATES = List.of(() -> createFromByteStream(DUMMY_STREAM), () -> createFromByteStream(DUMMY_STREAM, ONE), () -> new ParseState(GRAPH_WITH_REFERENCE, NO_CACHE, DUMMY_BYTE_STREAM_SOURCE, TEN, new ImmutableList<>(), new ImmutableList<>(), 0)); + private static final List> PARSE_STATES = List.of(() -> createFromByteStream(DUMMY_STREAM), () -> createFromByteStream(DUMMY_STREAM, ONE), () -> new ParseState(GRAPH_WITH_REFERENCE, NO_CACHE, DUMMY_BYTE_STREAM_SOURCE, TEN, new ImmutableList<>(), new ImmutableList<>())); private static final List> PARSE_VALUE_CACHES = List.of(() -> NO_CACHE, ParseValueCache::new, () -> new ParseValueCache().add(PARSE_VALUE), () -> new ParseValueCache().add(PARSE_VALUE).add(PARSE_VALUE)); private static final List> IMMUTABLE_LISTS = List.of(ImmutableList::new, () -> ImmutableList.create("TEST"), () -> ImmutableList.create(1), () -> ImmutableList.create(1).add(2)); private static final List> BOOLEANS = List.of(() -> true, () -> false); @@ -262,7 +261,7 @@ private static Map, List>> buildMap() { public static Stream data() throws IllegalAccessException, InvocationTargetException, InstantiationException { final Set> classes = findClasses().filter(not(CLASSES_TO_IGNORE::contains)).collect(toSet()); classes.removeAll(CLASSES_TO_TEST); - assertEquals(Set.of(), classes, "Please add missing class to the CLASSES_TO_TEST or CLASSES_TO_IGNORE constant."); + Assertions.assertEquals(Set.of(), classes, "Please add missing class to the CLASSES_TO_TEST or CLASSES_TO_IGNORE constant."); return generateObjectArrays(CLASSES_TO_TEST).stream(); } @@ -437,4 +436,11 @@ public void basicNoHashCollisions(final Object object, final Object same, final } } + private static void assertEquals(final Object o, final Object object) { + Assertions.assertEquals(o, object, String.format("Objects should be equal:\n%s\n%s\n", o, object)); + } + private static void assertNotEquals(final Object o, final Object object) { + Assertions.assertNotEquals(o, object, String.format("Objects should not be equal:\n%s\n%s\n", o, object)); + } + } diff --git a/core/src/test/java/io/parsingdata/metal/ToStringTest.java b/core/src/test/java/io/parsingdata/metal/ToStringTest.java index 5f827f0d..85992632 100644 --- a/core/src/test/java/io/parsingdata/metal/ToStringTest.java +++ b/core/src/test/java/io/parsingdata/metal/ToStringTest.java @@ -174,15 +174,15 @@ public void encoding() { @Test public void data() { final ParseState parseState = stream(1, 2); - final String parseStateString = "ParseState(source:ByteStreamSource(InMemoryByteStream(2));offset:0;order:pg(EMPTY);scopeDepth:0;cache:size=0)"; + final String parseStateString = "ParseState(source:ByteStreamSource(InMemoryByteStream(2));offset:0;order:pg(EMPTY);cache:size=0)"; assertEquals(parseStateString, parseState.toString()); final ParseState parseStateWithIterations = parseState.addBranch(rep(def("a",1))).iterate(); - final String parseStateWithIterationsString = "ParseState(source:ByteStreamSource(InMemoryByteStream(2));offset:0;order:pg(pg(terminator:Rep),pg(EMPTY),true);iterations:>Rep(Def(a,Const(0x01)))->1;scopeDepth:1;cache:size=0)"; + final String parseStateWithIterationsString = "ParseState(source:ByteStreamSource(InMemoryByteStream(2));offset:0;order:pg(pg(terminator:Rep),pg(EMPTY),true,1);iterations:>Rep(Def(a,Const(0x01)))->1;cache:size=0)"; assertEquals(parseStateWithIterationsString, parseStateWithIterations.toString()); final ParseState parseStateWithoutCache = parseStateWithIterations.withOrder(parseStateWithIterations.order); - final String parseStateWithoutCacheString = "ParseState(source:ByteStreamSource(InMemoryByteStream(2));offset:0;order:pg(pg(terminator:Rep),pg(EMPTY),true);iterations:>Rep(Def(a,Const(0x01)))->1;scopeDepth:1;no-cache)"; + final String parseStateWithoutCacheString = "ParseState(source:ByteStreamSource(InMemoryByteStream(2));offset:0;order:pg(pg(terminator:Rep),pg(EMPTY),true,1);iterations:>Rep(Def(a,Const(0x01)))->1;no-cache)"; assertEquals(parseStateWithoutCacheString, parseStateWithoutCache.toString()); final Optional result = Optional.of(parseState); @@ -222,10 +222,10 @@ public void toStringOnParseStateCollections() { assertFalse(parseState.toString().contains(";iterations:")); assertFalse(parseState.toString().contains(";references:")); final ImmutableList> iterationsList = ImmutableList.create(new ImmutablePair<>(t(), BigInteger.ZERO)); - final ParseState parseStateWithIteration = new ParseState(parseState.order, parseState.cache, parseState.source, parseState.offset, iterationsList, new ImmutableList<>(), 0); + final ParseState parseStateWithIteration = new ParseState(parseState.order, parseState.cache, parseState.source, parseState.offset, iterationsList, new ImmutableList<>()); assertTrue(parseStateWithIteration.toString().contains(";iterations:" + iterationsList.toString())); final ImmutableList referencesList = ImmutableList.create(new ParseReference(BigInteger.ZERO, parseState.source, t())); - final ParseState parseStateWithReference = new ParseState(parseState.order, parseState.cache, parseState.source, parseState.offset, new ImmutableList<>(), referencesList, 0); + final ParseState parseStateWithReference = new ParseState(parseState.order, parseState.cache, parseState.source, parseState.offset, new ImmutableList<>(), referencesList); assertTrue(parseStateWithReference.toString().contains(";references:" + referencesList.toString())); } diff --git a/core/src/test/java/io/parsingdata/metal/data/DataExpressionSourceTest.java b/core/src/test/java/io/parsingdata/metal/data/DataExpressionSourceTest.java index b69df9ad..1d9c1b04 100644 --- a/core/src/test/java/io/parsingdata/metal/data/DataExpressionSourceTest.java +++ b/core/src/test/java/io/parsingdata/metal/data/DataExpressionSourceTest.java @@ -73,8 +73,8 @@ private Optional setupResult() { @Test public void createSliceFromParseValue() { final ParseValue value = setupValue(); - assertTrue(value.slice().source.isAvailable(ZERO, BigInteger.valueOf(4))); - assertFalse(value.slice().source.isAvailable(ZERO, BigInteger.valueOf(5))); + assertTrue(value.slice().source.isAvailable(ZERO, valueOf(4))); + assertFalse(value.slice().source.isAvailable(ZERO, valueOf(5))); } @Test @@ -82,7 +82,7 @@ public void indexOutOfBounds() { final Optional result = setupResult(); final DataExpressionSource source = new DataExpressionSource(ref("a"), 1, result.get(), enc()); - final Exception e = Assertions.assertThrows(IllegalStateException.class, () -> source.getData(ZERO, BigInteger.valueOf(4))); + final Exception e = Assertions.assertThrows(IllegalStateException.class, () -> source.getData(ZERO, valueOf(4))); assertEquals("ValueExpression dataExpression yields 1 result(s) (expected at least 2).", e.getMessage()); } diff --git a/core/src/test/java/io/parsingdata/metal/data/ParseGraphTest.java b/core/src/test/java/io/parsingdata/metal/data/ParseGraphTest.java index e0c2fe8b..07726ddc 100644 --- a/core/src/test/java/io/parsingdata/metal/data/ParseGraphTest.java +++ b/core/src/test/java/io/parsingdata/metal/data/ParseGraphTest.java @@ -23,9 +23,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static io.parsingdata.metal.Shorthand.scope; import static io.parsingdata.metal.Shorthand.seq; import static io.parsingdata.metal.data.ParseGraph.EMPTY; import static io.parsingdata.metal.data.ParseGraph.NONE; @@ -34,31 +36,43 @@ import static io.parsingdata.metal.util.EnvironmentFactory.env; import static io.parsingdata.metal.util.ParseStateFactory.stream; import static io.parsingdata.metal.util.TokenDefinitions.any; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import io.parsingdata.metal.AutoEqualityTest; +import io.parsingdata.metal.encoding.Encoding; import io.parsingdata.metal.token.Token; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class ParseGraphTest { - private final Token t = any("t"); - - private final ParseGraph pg; - private final ParseGraph pgc; - private final ParseGraph pgl; - private final Token aDef; - private final ParseValue a; - private final ParseValue b; - private final ParseValue c; - private final ParseValue d; - private final ParseValue e; - private final ParseValue f; - private final ParseValue g; - private final ParseValue h; - - public ParseGraphTest() { + private static final Token t = any("t"); + private static final Token s = seq("scopeDelimiter", t, t); + + private static ParseGraph pg; + private static ParseGraph pgc; + private static ParseGraph pgl; + private static Token aDef; + private static ParseValue a; + private static ParseValue b; + private static ParseValue c; + private static ParseValue d; + private static ParseValue e; + private static ParseValue f; + private static ParseValue g; + private static ParseValue h; + + @BeforeAll + public static void setup() { aDef = any("a"); Token token = seq(aDef, any("empty"), @@ -84,21 +98,21 @@ public ParseGraphTest() { pgl = makeLongGraph(); } - private ParseGraph makeSimpleGraph() { + private static ParseGraph makeSimpleGraph() { return ParseGraph .EMPTY - .add(a) // [a] - .add(b) // [b] - .addBranch(t) // +---+ - .add(c) // | [c] - .addBranch(t) // | +---+ - .add(d) // | | [d] - .add(e) // | | [e] - .closeBranch() // | +---+ - .add(f) // | [f] - .closeBranch() // +---+ - .add(g) // [g] - .add(h); // [h] + .add(a) // [a] + .add(b) // [b] + .addBranch(t) // +---+ + .add(c) // | [c] + .addBranch(t) // | +---+ + .add(d) // | | [d] + .add(e) // | | [e] + .closeBranch(t) // | +---+ + .add(f) // | [f] + .closeBranch(t) // +---+ + .add(g) // [g] + .add(h); // [h] } @Test @@ -123,14 +137,14 @@ public void simple() { assertEquals(a, pg.tail.tail.tail.tail.head); } - private ParseGraph makeCycleGraph() { + private static ParseGraph makeCycleGraph() { return ParseGraph .EMPTY .add(a) .addBranch(t) .add(b) .add(new ParseReference(a.slice().offset, a.slice().source, aDef)) - .closeBranch(); + .closeBranch(t); } @Test @@ -145,25 +159,25 @@ public void cycle() { assertEquals(a, pgc.tail.head); } - private ParseGraph makeLongGraph() { + private static ParseGraph makeLongGraph() { return ParseGraph .EMPTY .add(a) .addBranch(t) .addBranch(t) .add(b) - .closeBranch() + .closeBranch(t) .addBranch(t) - .closeBranch() + .closeBranch(t) .add(c) .addBranch(t) .add(d) - .closeBranch() - .closeBranch() + .closeBranch(t) + .closeBranch(t) .add(e) .addBranch(t) .add(f) - .closeBranch(); + .closeBranch(t); } @Test @@ -191,17 +205,17 @@ public void testLong() { @Test public void testSimpleToString() { - assertThat(pg.toString(), is("pg(pval(h:0x68),pg(pval(g:0x67),pg(pg(pval(f:0x66),pg(pg(pval(e:0x65),pg(pval(d:0x64),pg(terminator:Def),false),false),pg(pval(c:0x63),pg(terminator:Def),false),false),false),pg(pval(b:0x62),pg(pval(a:0x61),pg(EMPTY),false),false),false),false),false)")); + assertThat(pg.toString(), is("pg(pval(h:0x68),pg(pval(g:0x67),pg(pg(pval(f:0x66),pg(pg(pval(e:0x65),pg(pval(d:0x64),pg(terminator:Def),false,0),false,0),pg(pval(c:0x63),pg(terminator:Def),false,0),false,0),false,0),pg(pval(b:0x62),pg(pval(a:0x61),pg(EMPTY),false,0),false,0),false,0),false,0),false,0)")); } @Test public void testCycleToString() { - assertThat(pgc.toString(), is("pg(pg(pref(@0),pg(pval(b:0x62),pg(terminator:Def),false),false),pg(pval(a:0x61),pg(EMPTY),false),false)")); + assertThat(pgc.toString(), is("pg(pg(pref(@0),pg(pval(b:0x62),pg(terminator:Def),false,0),false,0),pg(pval(a:0x61),pg(EMPTY),false,0),false,0)")); } @Test public void testLongToString() { - assertThat(pgl.toString(), is("pg(pg(pval(f:0x66),pg(terminator:Def),false),pg(pval(e:0x65),pg(pg(pg(pval(d:0x64),pg(terminator:Def),false),pg(pval(c:0x63),pg(pg(terminator:Def),pg(pg(pval(b:0x62),pg(terminator:Def),false),pg(terminator:Def),false),false),false),false),pg(pval(a:0x61),pg(EMPTY),false),false),false),false)")); + assertThat(pgl.toString(), is("pg(pg(pval(f:0x66),pg(terminator:Def),false,0),pg(pval(e:0x65),pg(pg(pg(pval(d:0x64),pg(terminator:Def),false,0),pg(pval(c:0x63),pg(pg(terminator:Def),pg(pg(pval(b:0x62),pg(terminator:Def),false,0),pg(terminator:Def),false,0),false,0),false,0),false,0),pg(pval(a:0x61),pg(EMPTY),false,0),false,0),false,0),false,0)")); } @Test @@ -213,7 +227,7 @@ public void testNone() { @Test public void testCloseNotBranched() { - final Exception e = assertThrows(IllegalStateException.class, EMPTY::closeBranch); + final Exception e = assertThrows(IllegalStateException.class, () -> EMPTY.closeBranch(t)); assertEquals("Cannot close branch that is not open.", e.getMessage()); } @@ -236,4 +250,130 @@ public void testCurrent() { assertFalse(EMPTY.addBranch(NONE).current().isPresent()); } + public static Stream scopeDepthTest() { + return Stream.of( + // Add branches with and without scope delimited tokens. + arguments(0, EMPTY), + arguments(0, EMPTY.add(a)), + arguments(0, EMPTY.addBranch(t)), + arguments(1, EMPTY.addBranch(s)), + arguments(1, EMPTY.addBranch(t).addBranch(s)), + arguments(1, EMPTY.addBranch(s).addBranch(t)), + arguments(2, EMPTY.addBranch(s).addBranch(s)), + arguments(2, EMPTY.addBranch(s).addBranch(s).addBranch(t)), + arguments(2, EMPTY.addBranch(s).addBranch(t).addBranch(s)), + arguments(2, EMPTY.addBranch(t).addBranch(s).addBranch(s)), + arguments(3, EMPTY.addBranch(s).addBranch(s).addBranch(s)), + + // Close branches with and without scope delimited tokens. + arguments(2, EMPTY.addBranch(s).addBranch(s).addBranch(t).closeBranch(t)), + arguments(1, EMPTY.addBranch(s).addBranch(t).addBranch(s).closeBranch(s)), + arguments(2, EMPTY.addBranch(s).addBranch(s).addBranch(s).closeBranch(s)), + arguments(0, EMPTY.addBranch(t).addBranch(s).addBranch(s).closeBranch(s).closeBranch(s)), + arguments(1, EMPTY.addBranch(s).addBranch(s).addBranch(s).closeBranch(s).closeBranch(s)), + arguments(0, EMPTY.addBranch(t).addBranch(s).addBranch(s).closeBranch(s).closeBranch(s).closeBranch(t)), + arguments(0, EMPTY.addBranch(s).addBranch(s).addBranch(s).closeBranch(s).closeBranch(s).closeBranch(s)), + + // A previously closed branch should not interfere with the scopeDepth when adding branches. + arguments(0, EMPTY.addBranch(t).closeBranch(t).addBranch(t)), + arguments(1, EMPTY.addBranch(t).closeBranch(t).addBranch(s)), + arguments(1, EMPTY.addBranch(t).closeBranch(t).addBranch(t).addBranch(s)), + arguments(1, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(t)), + arguments(2, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(s)), + arguments(2, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(s).addBranch(t)), + arguments(2, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(t).addBranch(s)), + arguments(2, EMPTY.addBranch(t).closeBranch(t).addBranch(t).addBranch(s).addBranch(s)), + arguments(3, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(s).addBranch(s)), + + // A previously closed branch should not interfere with the scopeDepth when closing branches. + arguments(0, EMPTY.addBranch(t).closeBranch(t).addBranch(t)), + arguments(0, EMPTY.addBranch(t).closeBranch(t).addBranch(s).closeBranch(s)), + arguments(0, EMPTY.addBranch(t).closeBranch(t).addBranch(t).addBranch(s).closeBranch(s)), + arguments(1, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(t).closeBranch(t)), + arguments(1, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(s).closeBranch(s)), + arguments(2, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(s).addBranch(t).closeBranch(t)), + arguments(1, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(t).addBranch(s).closeBranch(s)), + arguments(1, EMPTY.addBranch(t).closeBranch(t).addBranch(t).addBranch(s).addBranch(s).closeBranch(s)), + arguments(2, EMPTY.addBranch(t).closeBranch(t).addBranch(s).addBranch(s).addBranch(s).closeBranch(s)), + + // Adding values should not interfere with the scopeDepth. + arguments(0, EMPTY), + arguments(0, EMPTY.add(a)), + arguments(0, EMPTY.add(a).addBranch(t).add(a)), + arguments(1, EMPTY.add(a).addBranch(s).add(a)), + arguments(1, EMPTY.add(a).addBranch(t).add(a).addBranch(s).add(a)), + arguments(1, EMPTY.add(a).addBranch(s).add(a).addBranch(t).add(a)), + arguments(2, EMPTY.add(a).addBranch(s).add(a).addBranch(s).add(a)), + arguments(2, EMPTY.add(a).addBranch(s).add(a).addBranch(s).add(a).addBranch(t).add(a)), + arguments(2, EMPTY.add(a).addBranch(s).add(a).addBranch(t).add(a).addBranch(s).add(a)), + arguments(2, EMPTY.add(a).addBranch(t).add(a).addBranch(s).add(a).addBranch(s).add(a)), + arguments(3, EMPTY.add(a).addBranch(s).add(a).addBranch(s).add(a).addBranch(s).add(a)), + arguments(0, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(t).add(a)), + arguments(1, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(s).add(a)), + arguments(1, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(t).add(a).addBranch(s).add(a)), + arguments(1, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(s).add(a).addBranch(t).add(a)), + arguments(2, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(s).add(a).addBranch(s).add(a)), + arguments(2, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(s).add(a).addBranch(s).add(a).addBranch(t).add(a)), + arguments(2, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(s).add(a).addBranch(t).add(a).addBranch(s).add(a)), + arguments(2, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(t).add(a).addBranch(s).add(a).addBranch(s).add(a)), + arguments(3, EMPTY.add(a).addBranch(t).add(a).closeBranch(t).add(a).addBranch(s).add(a).addBranch(s).add(a).addBranch(s).add(a)) + ); + } + + @ParameterizedTest + @MethodSource + public void scopeDepthTest(final int scopeDepth, final ParseGraph graph) { + assertEquals(scopeDepth, graph.scopeDepth); + } + + public static Stream closeBranchExceptionTest() { + final TestToken token = new TestToken("Test", null); + return Stream.of( + arguments("Cannot close branch that is not open.", EMPTY, null), + arguments("Cannot close branch that is not open.", EMPTY.add(a), t), + arguments("Cannot close branch that is not open.", EMPTY.addBranch(t).closeBranch(t), t), + arguments("Cannot close branch that is not open.", EMPTY.addBranch(t).closeBranch(t), s), + + arguments("Cannot close branch with token that does not match its head token.", EMPTY.addBranch(t), s), + arguments("Cannot close branch with token that does not match its head token.", EMPTY.addBranch(s), t), + arguments("Cannot close branch with token that does not match its head token.", EMPTY.addBranch(t).addBranch(s), t), + arguments("Cannot close branch with token that does not match its head token.", EMPTY.addBranch(s).addBranch(t), s), + arguments("Cannot close branch with token that does not match its head token.", EMPTY.addBranch(t).addBranch(s).closeBranch(s), s), + arguments("Cannot close branch with token that does not match its head token.", EMPTY.addBranch(s).addBranch(t).closeBranch(t), t), + + arguments("Cannot close parse graph that has a non zero scopeDepth.", EMPTY.addBranch(token.setScopeDelimiter(true)), token.setScopeDelimiter(false)) + ); + } + + @ParameterizedTest + @MethodSource + public void closeBranchExceptionTest(final String errorMessage, final ParseGraph graph, final Token token) { + final Exception e = assertThrows(IllegalStateException.class, () -> graph.closeBranch(token)); + assertEquals(errorMessage, e.getMessage()); + } + + private static class TestToken extends Token { + + private boolean isScopeDelimiter; + + protected TestToken(String name, Encoding encoding) { + super(name, encoding); + } + + @Override + protected Optional parseImpl(Environment environment) { + return Optional.empty(); + } + @Override public String toString() { return "Test"; } + + @Override + public boolean isScopeDelimiter() { + return isScopeDelimiter; + } + public Token setScopeDelimiter(final boolean isScopeDelimiter) { + this.isScopeDelimiter = isScopeDelimiter; + return this; + } + } + } diff --git a/core/src/test/java/io/parsingdata/metal/data/ParseValueCacheTest.java b/core/src/test/java/io/parsingdata/metal/data/ParseValueCacheTest.java index bf196fe4..937ade63 100644 --- a/core/src/test/java/io/parsingdata/metal/data/ParseValueCacheTest.java +++ b/core/src/test/java/io/parsingdata/metal/data/ParseValueCacheTest.java @@ -1,5 +1,7 @@ package io.parsingdata.metal.data; +import static io.parsingdata.metal.Shorthand.seq; +import static io.parsingdata.metal.util.TokenDefinitions.any; import static java.math.BigInteger.ZERO; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -42,6 +44,7 @@ class ParseValueCacheTest { private static ParseValue pv3; private static Token pv2Definition; private static Token pv3Definition; + private static ParseGraph parseGraph; @BeforeAll public static void setup() { @@ -54,6 +57,11 @@ public static void setup() { pv3 = parseValue("second.second.name", pv3Definition); final ParseValue pvother3 = parseValue("other.subname"); parseValueCache = new ParseValueCache().add(pv1).add(pvother).add(pv2).add(pvother2).add(pv3).add(pvother3); + + // Build a parseGraph with a scopeDepth of 2. + final Token t = any("t"); + final Token s = seq("scopeDelimiter", t, t); + parseGraph = ParseGraph.EMPTY.addBranch(s).addBranch(s); } public static Stream findTest() { @@ -150,7 +158,7 @@ public static Stream cacheUsageTest() { arguments("multi definitionRef", ref(pv2Definition, pv3Definition), false), arguments("multi definitionRef with limit", ref(con(1), pv2Definition, pv3Definition), false), - // Requested scope is smaller than the scopeDepth of the ParseState. + // Requested scope is smaller than the scopeDepth of the ParseGraph. arguments("scoped nameRef", scope(ref("second.name"), con(1)), false), arguments("scoped nameRef with limit", scope(ref(con(1), "second.name"), con(1)), false), arguments("scoped multi nameRef", scope(ref("second.name", "first.name"), con(1)), false), @@ -160,7 +168,7 @@ public static Stream cacheUsageTest() { arguments("scoped multi definitionRef", scope(ref(pv2Definition, pv3Definition), con(1)), false), arguments("scoped multi definitionRef with limit", scope(ref(con(1), pv2Definition, pv3Definition), con(1)), false), - // Requested scope matches or exceeds the scopeDepth of the ParseState. + // Requested scope matches or exceeds the scopeDepth of the ParseGraph. arguments("matching scoped nameRef", scope(ref("second.name"), con(2)), true), arguments("matching scoped nameRef with limit", scope(ref(con(1), "second.name"), con(2)), true), arguments("matching scoped multi nameRef", scope(ref("second.name", "first.name"), con(2)), false), @@ -175,9 +183,9 @@ public static Stream cacheUsageTest() { @ParameterizedTest(name="{2} - {0}") @MethodSource public void cacheUsageTest(final String testName, final ValueExpression expression, final boolean shouldUseCache) { - final ParseState parseState = new ParseState(ParseGraph.EMPTY, parseValueCache, createFromBytes(new byte[0]).source, ZERO, new ImmutableList<>(), new ImmutableList<>(), 2); + final ParseState parseState = new ParseState(parseGraph, parseValueCache, createFromBytes(new byte[0]).source, ZERO, new ImmutableList<>(), new ImmutableList<>()); final ImmutableList eval = expression.eval(parseState, enc()); - // The parseState is empty, while the cache if filled. + // Only the cache is filled with the values we are referring to. // That means, if result is not empty, the cache was used. assertEquals(shouldUseCache, !eval.isEmpty()); } diff --git a/core/src/test/java/io/parsingdata/metal/data/SelectionTest.java b/core/src/test/java/io/parsingdata/metal/data/SelectionTest.java index 0871b75e..d0ba8be8 100644 --- a/core/src/test/java/io/parsingdata/metal/data/SelectionTest.java +++ b/core/src/test/java/io/parsingdata/metal/data/SelectionTest.java @@ -24,6 +24,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static io.parsingdata.metal.Shorthand.rep; +import static io.parsingdata.metal.data.ParseGraph.EMPTY; +import static io.parsingdata.metal.data.Selection.NO_LIMIT; import static io.parsingdata.metal.data.Selection.findItemAtOffset; import static io.parsingdata.metal.data.Selection.getAllValues; import static io.parsingdata.metal.util.EncodingFactory.enc; @@ -54,13 +56,13 @@ private Source createSource() { @Test public void findItemAtOffsetTest() { assertEquals("the_one", - findItemAtOffset(ImmutableList.create(ParseGraph.EMPTY.add(new ParseValue("two", any("a"), Slice.createFromSource(source, BigInteger.valueOf(2), BigInteger.valueOf(2)).get(), enc())) - .add(new ParseValue("zero", any("a"), Slice.createFromSource(source, ZERO, BigInteger.valueOf(2)).get(), enc())) - .add(new ParseValue("the_one", any("a"), Slice.createFromSource(source, ONE, BigInteger.valueOf(2)).get(), enc()))), ZERO, source).computeResult().get().asGraph().head.asValue().name); + findItemAtOffset(ImmutableList.create(EMPTY.add(new ParseValue("two", any("a"), Slice.createFromSource(source, BigInteger.valueOf(2), BigInteger.valueOf(2)).get(), enc())) + .add(new ParseValue("zero", any("a"), Slice.createFromSource(source, ZERO, BigInteger.valueOf(2)).get(), enc())) + .add(new ParseValue("the_one", any("a"), Slice.createFromSource(source, ONE, BigInteger.valueOf(2)).get(), enc()))), ZERO, source).computeResult().get().asGraph().head.asValue().name); assertEquals("zero", findItemAtOffset(ImmutableList.create(new ParseValue("zero", any("a"), Slice.createFromSource(source, ZERO, BigInteger.valueOf(2)).get(), enc())) - .add(new ParseValue("offsetMatchOtherSource", any("a"), Slice.createFromSource(otherSource, ZERO, BigInteger.valueOf(2)).get(), enc())) - .add(new ParseValue("otherOffsetMatchSource", any("a"), Slice.createFromSource(source, ONE, BigInteger.valueOf(2)).get(), enc())), ZERO, source).computeResult().get().asValue().name); + .add(new ParseValue("offsetMatchOtherSource", any("a"), Slice.createFromSource(otherSource, ZERO, BigInteger.valueOf(2)).get(), enc())) + .add(new ParseValue("otherOffsetMatchSource", any("a"), Slice.createFromSource(source, ONE, BigInteger.valueOf(2)).get(), enc())), ZERO, source).computeResult().get().asValue().name); } @Test @@ -72,4 +74,9 @@ public void limit() { } } + @Test + public void emptyGraph() { + assertEquals(0, getAllValues(EMPTY, (value) -> true, NO_LIMIT, 2, 0).size); + } + }