diff --git a/core/src/main/java/io/parsingdata/metal/Shorthand.java b/core/src/main/java/io/parsingdata/metal/Shorthand.java index 6fe55d71..6f29580a 100644 --- a/core/src/main/java/io/parsingdata/metal/Shorthand.java +++ b/core/src/main/java/io/parsingdata/metal/Shorthand.java @@ -46,9 +46,9 @@ import io.parsingdata.metal.expression.value.ConstantFactory; import io.parsingdata.metal.expression.value.Elvis; import io.parsingdata.metal.expression.value.Expand; +import io.parsingdata.metal.expression.value.FoldCat; import io.parsingdata.metal.expression.value.FoldLeft; import io.parsingdata.metal.expression.value.FoldRight; -import io.parsingdata.metal.expression.value.FoldCat; import io.parsingdata.metal.expression.value.Reverse; import io.parsingdata.metal.expression.value.UnaryValueExpression; import io.parsingdata.metal.expression.value.Value; @@ -62,6 +62,7 @@ import io.parsingdata.metal.expression.value.bitwise.ShiftLeft; import io.parsingdata.metal.expression.value.bitwise.ShiftRight; import io.parsingdata.metal.expression.value.reference.Count; +import io.parsingdata.metal.expression.value.reference.CurrentIteration; import io.parsingdata.metal.expression.value.reference.CurrentOffset; import io.parsingdata.metal.expression.value.reference.First; import io.parsingdata.metal.expression.value.reference.Last; @@ -89,6 +90,7 @@ public final class Shorthand { public static final Token EMPTY = def(EMPTY_NAME, 0L); public static final ValueExpression SELF = new Self(); public static final ValueExpression CURRENT_OFFSET = new CurrentOffset(); + public static final ValueExpression CURRENT_ITERATION = new CurrentIteration(); public static final Expression TRUE = new True(); private Shorthand() {} diff --git a/core/src/main/java/io/parsingdata/metal/data/ImmutablePair.java b/core/src/main/java/io/parsingdata/metal/data/ImmutablePair.java new file mode 100644 index 00000000..486bb17a --- /dev/null +++ b/core/src/main/java/io/parsingdata/metal/data/ImmutablePair.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2016 Netherlands Forensic Institute + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.parsingdata.metal.data; + +import static io.parsingdata.metal.Util.checkNotNull; + +import java.util.Objects; + +import io.parsingdata.metal.Util; + +public class ImmutablePair { + + public final L left; + public final R right; + + public ImmutablePair(final L left, final R right) { + this.left = checkNotNull(left, "left"); + this.right = checkNotNull(right, "right"); + } + + @Override + public String toString() { + return left + "=" + right; + } + + @Override + public boolean equals(final Object obj) { + return Util.notNullAndSameClass(this, obj) + && Objects.equals(left, ((ImmutablePair)obj).left) + && Objects.equals(right, ((ImmutablePair)obj).right); + } + + @Override + public int hashCode() { + return Objects.hash(getClass(), left, right); + } + +} 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 d8a796b6..e394b4d4 100644 --- a/core/src/main/java/io/parsingdata/metal/data/ParseState.java +++ b/core/src/main/java/io/parsingdata/metal/data/ParseState.java @@ -16,6 +16,7 @@ package io.parsingdata.metal.data; +import static java.math.BigInteger.ONE; import static java.math.BigInteger.ZERO; import static io.parsingdata.metal.Util.checkNotNegative; @@ -36,15 +37,17 @@ public class ParseState { public final ParseGraph order; public final BigInteger offset; public final Source source; + public final ImmutableList> iterations; - public ParseState(final ParseGraph order, final Source source, final BigInteger offset) { + public ParseState(final ParseGraph order, final Source source, final BigInteger offset, final ImmutableList> iterations) { this.order = checkNotNull(order, "order"); this.source = checkNotNull(source, "source"); this.offset = checkNotNegative(offset, "offset"); + this.iterations = checkNotNull(iterations, "iterations"); } public static ParseState createFromByteStream(final ByteStream input, final BigInteger offset) { - return new ParseState(ParseGraph.EMPTY, new ByteStreamSource(input), offset); + return new ParseState(ParseGraph.EMPTY, new ByteStreamSource(input), offset, new ImmutableList<>()); } public static ParseState createFromByteStream(final ByteStream input) { @@ -52,27 +55,34 @@ public static ParseState createFromByteStream(final ByteStream input) { } public ParseState addBranch(final Token token) { - return new ParseState(order.addBranch(token), source, offset); + return new ParseState(order.addBranch(token), source, offset, token.isIterable() ? iterations.add(new ImmutablePair<>(token, ZERO)) : iterations); } - public ParseState closeBranch() { - return new ParseState(order.closeBranch(), source, offset); + public ParseState closeBranch(final Token token) { + if (token.isIterable() && !iterations.head.left.equals(token)) { + throw new IllegalStateException(String.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(), source, offset, token.isIterable() ? iterations.tail : iterations); } public ParseState add(final ParseValue parseValue) { - return new ParseState(order.add(parseValue), source, offset); + return new ParseState(order.add(parseValue), source, offset, iterations); } public ParseState add(final ParseReference parseReference) { - return new ParseState(order.add(parseReference), source, offset); + return new ParseState(order.add(parseReference), source, offset, iterations); + } + + public ParseState iterate() { + return new ParseState(order, source, offset, iterations.tail.add(new ImmutablePair<>(iterations.head.left, iterations.head.right.add(ONE)))); } public Optional seek(final BigInteger newOffset) { - return newOffset.compareTo(ZERO) >= 0 ? Optional.of(new ParseState(order, source, newOffset)) : Optional.empty(); + return newOffset.compareTo(ZERO) >= 0 ? Optional.of(new ParseState(order, source, newOffset, iterations)) : Optional.empty(); } public ParseState source(final ValueExpression dataExpression, final int index, final ParseState parseState, final Encoding encoding) { - return new ParseState(order, new DataExpressionSource(dataExpression, index, parseState, encoding), ZERO); + return new ParseState(order, new DataExpressionSource(dataExpression, index, parseState, encoding), ZERO, iterations); } public Optional slice(final BigInteger length) { @@ -81,7 +91,8 @@ public Optional slice(final BigInteger length) { @Override public String toString() { - return getClass().getSimpleName() + "(source:" + source + ";offset:" + offset + ";order:" + order + ")"; + final String iterationString = iterations.isEmpty() ? "" : ";iterations:" + iterations.toString(); + return getClass().getSimpleName() + "(source:" + source + ";offset:" + offset + ";order:" + order + iterationString + ")"; } @Override @@ -89,12 +100,13 @@ public boolean equals(final Object obj) { return Util.notNullAndSameClass(this, obj) && Objects.equals(order, ((ParseState)obj).order) && Objects.equals(offset, ((ParseState)obj).offset) - && Objects.equals(source, ((ParseState)obj).source); + && Objects.equals(source, ((ParseState)obj).source) + && Objects.equals(iterations, ((ParseState)obj).iterations); } @Override public int hashCode() { - return Objects.hash(getClass(), order, offset, source); + return Objects.hash(getClass(), order, offset, source, iterations); } } diff --git a/core/src/main/java/io/parsingdata/metal/expression/value/reference/CurrentIteration.java b/core/src/main/java/io/parsingdata/metal/expression/value/reference/CurrentIteration.java new file mode 100644 index 00000000..543f159d --- /dev/null +++ b/core/src/main/java/io/parsingdata/metal/expression/value/reference/CurrentIteration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2018 Netherlands Forensic Institute + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.parsingdata.metal.expression.value.reference; + +import static io.parsingdata.metal.expression.value.ConstantFactory.createFromNumeric; + +import java.util.Optional; + +import io.parsingdata.metal.Util; +import io.parsingdata.metal.data.ImmutableList; +import io.parsingdata.metal.data.ParseState; +import io.parsingdata.metal.encoding.Encoding; +import io.parsingdata.metal.expression.value.Value; +import io.parsingdata.metal.expression.value.ValueExpression; +import io.parsingdata.metal.token.Rep; +import io.parsingdata.metal.token.RepN; +import io.parsingdata.metal.token.Token; +import io.parsingdata.metal.token.While; + +/** + * A {@link ValueExpression} that represents the 0-based current iteration in an + * iterable {@link Token} (when {@link Token#isIterable()} returns true, e.g. when + * inside a {@link Rep}, {@link RepN}) or {@link While}). + */ +public class CurrentIteration implements ValueExpression { + + @Override + public ImmutableList> eval(final ParseState parseState, final Encoding encoding) { + if (parseState.iterations.isEmpty()) { + return ImmutableList.create(Optional.empty()); + } + return ImmutableList.create(Optional.of(createFromNumeric(parseState.iterations.head.right, new Encoding()))); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + @Override + public boolean equals(final Object obj) { + return Util.notNullAndSameClass(this, obj); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + +} diff --git a/core/src/main/java/io/parsingdata/metal/token/Cho.java b/core/src/main/java/io/parsingdata/metal/token/Cho.java index a2426307..dc47e71e 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Cho.java +++ b/core/src/main/java/io/parsingdata/metal/token/Cho.java @@ -62,7 +62,7 @@ private Trampoline> iterate(final Environment environment, } return list.head .parse(environment) - .map(result -> complete(() -> success(result.closeBranch()))) + .map(result -> complete(() -> success(result.closeBranch(this)))) .orElseGet(() -> intermediate(() -> iterate(environment, list.tail))); } diff --git a/core/src/main/java/io/parsingdata/metal/token/IterableToken.java b/core/src/main/java/io/parsingdata/metal/token/IterableToken.java new file mode 100644 index 00000000..0ea8c014 --- /dev/null +++ b/core/src/main/java/io/parsingdata/metal/token/IterableToken.java @@ -0,0 +1,81 @@ +/* + * Copyright 2013-2018 Netherlands Forensic Institute + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.parsingdata.metal.token; + +import static io.parsingdata.metal.Trampoline.complete; +import static io.parsingdata.metal.Trampoline.intermediate; +import static io.parsingdata.metal.Util.checkNotNull; +import static io.parsingdata.metal.Util.success; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +import io.parsingdata.metal.Trampoline; +import io.parsingdata.metal.data.Environment; +import io.parsingdata.metal.data.ParseState; +import io.parsingdata.metal.encoding.Encoding; + +public abstract class IterableToken extends Token { + + public final Token token; + + IterableToken(final String name, final Token token, final Encoding encoding) { + super(name, encoding); + this.token = checkNotNull(token, "token"); + } + + protected final Optional parse(final Environment environment, final Predicate stopCondition, final Function> ifIterationFails) { + return iterate(environment.addBranch(this), stopCondition, ifIterationFails).computeResult(); + } + + /** + * Iteratively parse iterations of the token, given a stop condition and the logic how to handle a failed parse. + * + * @param environment the environment to apply the parse to + * @param stopCondition a function to determine when to stop the iteration + * @param ifIterationFails a function to determine how to handle a failed parse + * @return a trampolined {@code Optional} + */ + private Trampoline> iterate(final Environment environment, final Predicate stopCondition, final Function> ifIterationFails) { + if (stopCondition.test(environment)) { + return complete(() -> success(environment.parseState.closeBranch(this))); + } + return token + .parse(environment) + .map(nextParseState -> intermediate(() -> iterate(environment.withParseState(nextParseState.iterate()), stopCondition, ifIterationFails))) + .orElseGet(() -> complete(() -> ifIterationFails.apply(environment))); + } + + @Override + public boolean isIterable() { + return true; + } + + @Override + public boolean equals(final Object obj) { + return super.equals(obj) + && Objects.equals(token, ((IterableToken)obj).token); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), token); + } + +} diff --git a/core/src/main/java/io/parsingdata/metal/token/Post.java b/core/src/main/java/io/parsingdata/metal/token/Post.java index 8c5c4018..78d1f1d6 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Post.java +++ b/core/src/main/java/io/parsingdata/metal/token/Post.java @@ -55,7 +55,7 @@ public Post(final String name, final Token token, final Expression predicate, fi protected Optional parseImpl(final Environment environment) { return token .parse(environment.addBranch(this)) - .map(nextParseState -> predicate.eval(nextParseState, environment.encoding) ? success(nextParseState.closeBranch()) : failure()) + .map(nextParseState -> predicate.eval(nextParseState, environment.encoding) ? success(nextParseState.closeBranch(this)) : failure()) .orElseGet(Util::failure); } diff --git a/core/src/main/java/io/parsingdata/metal/token/Pre.java b/core/src/main/java/io/parsingdata/metal/token/Pre.java index 936d059e..12fc07a7 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Pre.java +++ b/core/src/main/java/io/parsingdata/metal/token/Pre.java @@ -59,7 +59,7 @@ protected Optional parseImpl(final Environment environment) { } return token .parse(environment.addBranch(this)) - .map(resultParseState -> success(resultParseState.closeBranch())) + .map(resultParseState -> success(resultParseState.closeBranch(this))) .orElseGet(Util::failure); } diff --git a/core/src/main/java/io/parsingdata/metal/token/Rep.java b/core/src/main/java/io/parsingdata/metal/token/Rep.java index 222a42a3..740e04a0 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Rep.java +++ b/core/src/main/java/io/parsingdata/metal/token/Rep.java @@ -16,15 +16,10 @@ package io.parsingdata.metal.token; -import static io.parsingdata.metal.Trampoline.complete; -import static io.parsingdata.metal.Trampoline.intermediate; -import static io.parsingdata.metal.Util.checkNotNull; import static io.parsingdata.metal.Util.success; -import java.util.Objects; import java.util.Optional; -import io.parsingdata.metal.Trampoline; import io.parsingdata.metal.data.Environment; import io.parsingdata.metal.data.ParseState; import io.parsingdata.metal.encoding.Encoding; @@ -38,25 +33,15 @@ * * @see RepN */ -public class Rep extends Token { - - public final Token token; +public class Rep extends IterableToken { public Rep(final String name, final Token token, final Encoding encoding) { - super(name, encoding); - this.token = checkNotNull(token, "token"); + super(name, token, encoding); } @Override - protected Optional parseImpl(final Environment environment) { - return iterate(environment.addBranch(this)).computeResult(); - } - - private Trampoline> iterate(final Environment environment) { - return token - .parse(environment) - .map(nextParseState -> intermediate(() -> iterate(environment.withParseState(nextParseState)))) - .orElseGet(() -> complete(() -> success(environment.parseState.closeBranch()))); + protected Optional parseImpl(Environment environment) { + return parse(environment, env -> false, env -> success(env.parseState.closeBranch(this))); } @Override @@ -64,15 +49,4 @@ public String toString() { return getClass().getSimpleName() + "(" + makeNameFragment() + token + ")"; } - @Override - public boolean equals(final Object obj) { - return super.equals(obj) - && Objects.equals(token, ((Rep)obj).token); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), token); - } - } diff --git a/core/src/main/java/io/parsingdata/metal/token/RepN.java b/core/src/main/java/io/parsingdata/metal/token/RepN.java index c8aa58cf..3a9b61fb 100644 --- a/core/src/main/java/io/parsingdata/metal/token/RepN.java +++ b/core/src/main/java/io/parsingdata/metal/token/RepN.java @@ -16,17 +16,13 @@ package io.parsingdata.metal.token; -import static io.parsingdata.metal.Trampoline.complete; -import static io.parsingdata.metal.Trampoline.intermediate; import static io.parsingdata.metal.Util.checkNotNull; import static io.parsingdata.metal.Util.failure; -import static io.parsingdata.metal.Util.success; +import java.math.BigInteger; import java.util.Objects; import java.util.Optional; -import io.parsingdata.metal.Trampoline; -import io.parsingdata.metal.Util; import io.parsingdata.metal.data.Environment; import io.parsingdata.metal.data.ImmutableList; import io.parsingdata.metal.data.ParseState; @@ -46,14 +42,12 @@ * @see Rep * @see ValueExpression */ -public class RepN extends Token { +public class RepN extends IterableToken { - public final Token token; public final ValueExpression n; public RepN(final String name, final Token token, final ValueExpression n, final Encoding encoding) { - super(name, encoding); - this.token = checkNotNull(token, "token"); + super(name, token, encoding); this.n = checkNotNull(n, "n"); } @@ -63,17 +57,8 @@ protected Optional parseImpl(final Environment environment) { if (counts.size != 1 || !counts.head.isPresent()) { return failure(); } - return iterate(environment.addBranch(this), counts.head.get().asNumeric().longValueExact()).computeResult(); - } - - private Trampoline> iterate(final Environment environment, final long count) { - if (count <= 0) { - return complete(() -> success(environment.parseState.closeBranch())); - } - return token - .parse(environment) - .map(nextParseState -> intermediate(() -> iterate(environment.withParseState(nextParseState), count - 1))) - .orElseGet(() -> complete(Util::failure)); + final BigInteger count = counts.head.get().asNumeric(); + return parse(environment, env -> env.parseState.iterations.head.right.compareTo(count) >= 0, env -> failure()); } @Override @@ -84,13 +69,12 @@ public String toString() { @Override public boolean equals(final Object obj) { return super.equals(obj) - && Objects.equals(token, ((RepN)obj).token) && Objects.equals(n, ((RepN)obj).n); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), token, n); + return Objects.hash(super.hashCode(), n); } } diff --git a/core/src/main/java/io/parsingdata/metal/token/Seq.java b/core/src/main/java/io/parsingdata/metal/token/Seq.java index efce4d9d..a943a7d7 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Seq.java +++ b/core/src/main/java/io/parsingdata/metal/token/Seq.java @@ -58,7 +58,7 @@ protected Optional parseImpl(final Environment environment) { private Trampoline> iterate(final Environment environment, final ImmutableList list) { if (list.isEmpty()) { - return complete(() -> success(environment.parseState.closeBranch())); + return complete(() -> success(environment.parseState.closeBranch(this))); } return list.head .parse(environment) diff --git a/core/src/main/java/io/parsingdata/metal/token/Sub.java b/core/src/main/java/io/parsingdata/metal/token/Sub.java index 90912a98..921efc23 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Sub.java +++ b/core/src/main/java/io/parsingdata/metal/token/Sub.java @@ -75,7 +75,7 @@ protected Optional parseImpl(final Environment environment) { private Trampoline> iterate(final Environment environment, final ImmutableList> addresses) { if (addresses.isEmpty()) { - return complete(() -> success(environment.parseState.closeBranch())); + return complete(() -> success(environment.parseState.closeBranch(this))); } return addresses.head .flatMap(address -> parse(environment, address.asNumeric())) 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 df8e0024..6a579426 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Tie.java +++ b/core/src/main/java/io/parsingdata/metal/token/Tie.java @@ -70,7 +70,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().order, returnParseState.source, returnParseState.offset))); + return complete(() -> success(new ParseState(environment.parseState.closeBranch(this).order, returnParseState.source, returnParseState.offset, returnParseState.iterations))); } return values.head .map(value -> token diff --git a/core/src/main/java/io/parsingdata/metal/token/Token.java b/core/src/main/java/io/parsingdata/metal/token/Token.java index 8e5054df..44c5bf62 100644 --- a/core/src/main/java/io/parsingdata/metal/token/Token.java +++ b/core/src/main/java/io/parsingdata/metal/token/Token.java @@ -79,6 +79,10 @@ public boolean isLocal() { return true; } + public boolean isIterable() { + return false; + } + public Token getCanonical(final ParseState parseState) { return this; } diff --git a/core/src/main/java/io/parsingdata/metal/token/While.java b/core/src/main/java/io/parsingdata/metal/token/While.java index 02841cb6..f3840bb6 100644 --- a/core/src/main/java/io/parsingdata/metal/token/While.java +++ b/core/src/main/java/io/parsingdata/metal/token/While.java @@ -17,16 +17,11 @@ package io.parsingdata.metal.token; import static io.parsingdata.metal.Shorthand.TRUE; -import static io.parsingdata.metal.Trampoline.complete; -import static io.parsingdata.metal.Trampoline.intermediate; -import static io.parsingdata.metal.Util.checkNotNull; -import static io.parsingdata.metal.Util.success; +import static io.parsingdata.metal.Util.failure; import java.util.Objects; import java.util.Optional; -import io.parsingdata.metal.Trampoline; -import io.parsingdata.metal.Util; import io.parsingdata.metal.data.Environment; import io.parsingdata.metal.data.ParseState; import io.parsingdata.metal.encoding.Encoding; @@ -45,30 +40,18 @@ * * @see Expression */ -public class While extends Token { +public class While extends IterableToken { - public final Token token; public final Expression predicate; public While(final String name, final Token token, final Expression predicate, final Encoding encoding) { - super(name, encoding); - this.token = checkNotNull(token, "token"); + super(name, token, encoding); this.predicate = predicate == null ? TRUE : predicate; } @Override - protected Optional parseImpl(final Environment environment) { - return iterate(environment.addBranch(this)).computeResult(); - } - - private Trampoline> iterate(final Environment environment) { - if (predicate.eval(environment.parseState, environment.encoding)) { - return token - .parse(environment) - .map(nextParseState -> intermediate(() -> iterate(environment.withParseState(nextParseState)))) - .orElseGet(() -> complete(Util::failure)); - } - return complete(() -> success(environment.parseState.closeBranch())); + protected Optional parseImpl(Environment environment) { + return super.parse(environment, env -> !predicate.eval(env.parseState, env.encoding), env -> failure()); } @Override @@ -79,13 +62,12 @@ public String toString() { @Override public boolean equals(final Object obj) { return super.equals(obj) - && Objects.equals(token, ((While)obj).token) && Objects.equals(predicate, ((While)obj).predicate); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), token, predicate); + return Objects.hash(super.hashCode(), predicate); } } diff --git a/core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java b/core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java index bec338a8..0e0a377e 100644 --- a/core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java +++ b/core/src/test/java/io/parsingdata/metal/AutoEqualityTest.java @@ -79,11 +79,11 @@ import io.parsingdata.metal.expression.comparison.LtNum; import io.parsingdata.metal.expression.value.Bytes; import io.parsingdata.metal.expression.value.Cat; -import io.parsingdata.metal.expression.value.FoldCat; import io.parsingdata.metal.expression.value.Const; import io.parsingdata.metal.expression.value.ConstantFactory; import io.parsingdata.metal.expression.value.Elvis; import io.parsingdata.metal.expression.value.Expand; +import io.parsingdata.metal.expression.value.FoldCat; import io.parsingdata.metal.expression.value.FoldLeft; import io.parsingdata.metal.expression.value.FoldRight; import io.parsingdata.metal.expression.value.Reverse; @@ -100,6 +100,7 @@ import io.parsingdata.metal.expression.value.bitwise.ShiftLeft; import io.parsingdata.metal.expression.value.bitwise.ShiftRight; import io.parsingdata.metal.expression.value.reference.Count; +import io.parsingdata.metal.expression.value.reference.CurrentIteration; import io.parsingdata.metal.expression.value.reference.CurrentOffset; import io.parsingdata.metal.expression.value.reference.First; import io.parsingdata.metal.expression.value.reference.Last; @@ -133,7 +134,7 @@ public class AutoEqualityTest { private static final ParseValue PARSE_VALUE = new ParseValue("a", any("a"), createFromBytes(new byte[]{1, 2}), enc()); private static final ParseGraph GRAPH_WITH_REFERENCE = createFromByteStream(DUMMY_STREAM).add(new ParseReference(ZERO, new ConstantSource(new byte[]{1, 2}), any("a"))).order; private static final ParseGraph BRANCHED_GRAPH = createFromByteStream(DUMMY_STREAM).addBranch(any("a")).order; - private static final ParseGraph CLOSED_BRANCHED_GRAPH = createFromByteStream(DUMMY_STREAM).addBranch(any("a")).closeBranch().order; + private static final ParseGraph CLOSED_BRANCHED_GRAPH = createFromByteStream(DUMMY_STREAM).addBranch(any("a")).closeBranch(any("a")).order; private static final List> STRINGS = Arrays.asList(() -> "a", () -> "b"); private static final List> ENCODINGS = Arrays.asList(EncodingFactory::enc, EncodingFactory::signed, EncodingFactory::le, () -> new Encoding(Charset.forName("UTF-8"))); @@ -152,7 +153,7 @@ public class AutoEqualityTest { private static final List> PARSE_ITEMS = Arrays.asList(() -> 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 = Arrays.asList(() -> new InMemoryByteStream(new byte[] { 1, 2 }), () -> DUMMY_STREAM); private static final List> BIG_INTEGERS = Arrays.asList(() -> ONE, () -> BigInteger.valueOf(3)); - private static final List> PARSE_STATES = Arrays.asList(() -> createFromByteStream(DUMMY_STREAM), () -> createFromByteStream(DUMMY_STREAM, ONE), () -> new ParseState(GRAPH_WITH_REFERENCE, DUMMY_BYTE_STREAM_SOURCE, TEN)); + private static final List> PARSE_STATES = Arrays.asList(() -> createFromByteStream(DUMMY_STREAM), () -> createFromByteStream(DUMMY_STREAM, ONE), () -> new ParseState(GRAPH_WITH_REFERENCE, DUMMY_BYTE_STREAM_SOURCE, TEN, new ImmutableList<>())); private static final List> IMMUTABLE_LISTS = Arrays.asList(ImmutableList::new, () -> ImmutableList.create("TEST"), () -> ImmutableList.create(1), () -> ImmutableList.create(1).add(2)); private static final Map>> mapping = buildMap(); @@ -191,7 +192,7 @@ public static Collection data() throws IllegalAccessException, Invocat And.class, Or.class, ShiftLeft.class, ShiftRight.class, Add.class, Div.class, Mod.class, Mul.class, io.parsingdata.metal.expression.value.arithmetic.Sub.class, Cat.class, Nth.class, Elvis.class, FoldLeft.class, FoldRight.class, Const.class, Expand.class, Bytes.class, CurrentOffset.class, - FoldCat.class, + FoldCat.class, CurrentIteration.class, // Expressions Eq.class, EqNum.class, EqStr.class, GtEqNum.class, GtNum.class, LtEqNum.class, LtNum.class, io.parsingdata.metal.expression.logical.And.class, io.parsingdata.metal.expression.logical.Or.class, diff --git a/core/src/test/java/io/parsingdata/metal/EqualityTest.java b/core/src/test/java/io/parsingdata/metal/EqualityTest.java index fa619a16..faf7aaa7 100644 --- a/core/src/test/java/io/parsingdata/metal/EqualityTest.java +++ b/core/src/test/java/io/parsingdata/metal/EqualityTest.java @@ -56,6 +56,7 @@ import io.parsingdata.metal.data.ConstantSource; import io.parsingdata.metal.data.ImmutableList; +import io.parsingdata.metal.data.ImmutablePair; import io.parsingdata.metal.data.ParseGraph; import io.parsingdata.metal.data.ParseState; import io.parsingdata.metal.data.ParseValue; @@ -155,7 +156,7 @@ public void multiConstructorTypes() { @Test public void immutableList() { - final ImmutableList object = ImmutableList.create("a"); + final ImmutableList object = ImmutableList.create("a"); assertFalse(object.equals(null)); assertTrue(object.equals(ImmutableList.create("a"))); assertTrue(object.equals(new ImmutableList<>().add("a"))); @@ -166,6 +167,20 @@ public void immutableList() { assertFalse(object.add("b").add("c").equals(ImmutableList.create("a").add("c").add("c"))); } + @Test + public void immutablePair() { + final ImmutablePair object = new ImmutablePair("a", ONE); + assertFalse(object.equals(null)); + assertTrue(object.equals(new ImmutablePair("a", ONE))); + assertEquals(object.hashCode(), new ImmutablePair("a", ONE).hashCode()); + assertFalse(object.equals("a")); + assertNotEquals(object.hashCode(), "a".hashCode()); + assertFalse(object.equals(new ImmutablePair("b", ONE))); + assertNotEquals(object.hashCode(), new ImmutablePair("b", ONE).hashCode()); + assertFalse(object.equals(new ImmutablePair("a", ZERO))); + assertNotEquals(object.hashCode(), new ImmutablePair("a", ZERO).hashCode()); + } + @Test public void parseGraph() { final ParseValue value = new ParseValue("a", any("a"), createFromBytes(new byte[]{1, 2}), enc()); @@ -173,8 +188,8 @@ public void parseGraph() { assertFalse(object.equals(null)); assertFalse(object.equals("a")); final ParseState parseState = createFromByteStream(DUMMY_STREAM); - assertNotEquals(parseState.addBranch(any("a")).add(value).add(value).closeBranch().addBranch(any("a")).order, parseState.addBranch(any("a")).closeBranch().addBranch(any("a")).order); - assertNotEquals(parseState.addBranch(any("a")).order, parseState.addBranch(any("a")).closeBranch().order); + assertNotEquals(parseState.addBranch(any("a")).add(value).add(value).closeBranch(any("a")).addBranch(any("a")).order, parseState.addBranch(any("a")).closeBranch(any("a")).addBranch(any("a")).order); + assertNotEquals(parseState.addBranch(any("a")).order, parseState.addBranch(any("a")).closeBranch(any("a")).order); assertNotEquals(parseState.addBranch(any("a")).order, parseState.addBranch(any("b")).order); } diff --git a/core/src/test/java/io/parsingdata/metal/ToStringTest.java b/core/src/test/java/io/parsingdata/metal/ToStringTest.java index 3bc8d4b6..ae8bfbd9 100644 --- a/core/src/test/java/io/parsingdata/metal/ToStringTest.java +++ b/core/src/test/java/io/parsingdata/metal/ToStringTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static io.parsingdata.metal.Shorthand.CURRENT_ITERATION; import static io.parsingdata.metal.Shorthand.CURRENT_OFFSET; import static io.parsingdata.metal.Shorthand.SELF; import static io.parsingdata.metal.Shorthand.add; @@ -121,7 +122,7 @@ private Token t() { } private ValueExpression v() { - return fold(foldLeft(foldRight(rev(bytes(neg(add(div(mod(mul(sub(cat(last(ref(n()))), first(nth(exp(ref(n()), con(1)), con(1)))), con(1)), cat(ref(n()), ref(t()))), add(SELF, add(offset(ref(n())), add(CURRENT_OFFSET, count(ref(n())))))), elvis(ref(n()), ref(n())))))), Shorthand::add, ref(n())), Shorthand::add), Shorthand::add, ref(n())); + return fold(foldLeft(foldRight(rev(bytes(neg(add(div(mod(mul(sub(cat(last(ref(n()))), first(nth(exp(ref(n()), con(1)), con(1)))), sub(CURRENT_ITERATION, con(1))), cat(ref(n()), ref(t()))), add(SELF, add(offset(ref(n())), add(CURRENT_OFFSET, count(ref(n())))))), elvis(ref(n()), ref(n())))))), Shorthand::add, ref(n())), Shorthand::add), Shorthand::add, ref(n())); } @Test @@ -148,6 +149,8 @@ public void specialExpressions() { assertEquals("Self", SELF.toString()); assertTrue(v().toString().contains("CurrentOffset")); assertEquals("CurrentOffset", CURRENT_OFFSET.toString()); + assertTrue(v().toString().contains("CurrentIteration")); + assertEquals("CurrentIteration", CURRENT_ITERATION.toString()); } @Test @@ -163,6 +166,9 @@ public void data() { final ParseState parseState = stream(1, 2); final String parseStateString = "ParseState(source:ByteStreamSource(InMemoryByteStream(2));offset:0;order:pg(EMPTY))"; 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)"; + assertEquals(parseStateWithIterationsString, parseStateWithIterations.toString()); final Optional result = Optional.of(parseState); assertEquals("Optional[" + parseState + "]", result.toString()); final ParseValue pv1 = new ParseValue("name", NONE, createFromBytes(new byte[]{1, 2}), enc()); diff --git a/core/src/test/java/io/parsingdata/metal/data/ParseStateTest.java b/core/src/test/java/io/parsingdata/metal/data/ParseStateTest.java new file mode 100644 index 00000000..d5e2edf4 --- /dev/null +++ b/core/src/test/java/io/parsingdata/metal/data/ParseStateTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2016 Netherlands Forensic Institute + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.parsingdata.metal.data; + +import static io.parsingdata.metal.Shorthand.rep; +import static io.parsingdata.metal.util.ParseStateFactory.stream; +import static io.parsingdata.metal.util.TokenDefinitions.any; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import io.parsingdata.metal.token.Token; + +public class ParseStateTest { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Test + public void closeParseStateWithWrongToken() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Cannot close branch for iterable token closeName. Current iteration state is for token openName."); + final Token open = rep("openName", any("a")); + final Token close = rep("closeName", any("a")); + stream().addBranch(open).closeBranch(close); + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/parsingdata/metal/expression/value/reference/CurrentIterationTest.java b/core/src/test/java/io/parsingdata/metal/expression/value/reference/CurrentIterationTest.java new file mode 100644 index 00000000..722ed24d --- /dev/null +++ b/core/src/test/java/io/parsingdata/metal/expression/value/reference/CurrentIterationTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013-2016 Netherlands Forensic Institute + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.parsingdata.metal.expression.value.reference; + +import static io.parsingdata.metal.Shorthand.CURRENT_ITERATION; +import static io.parsingdata.metal.Shorthand.add; +import static io.parsingdata.metal.Shorthand.con; +import static io.parsingdata.metal.Shorthand.def; +import static io.parsingdata.metal.Shorthand.eq; +import static io.parsingdata.metal.Shorthand.eqNum; +import static io.parsingdata.metal.Shorthand.not; +import static io.parsingdata.metal.Shorthand.nth; +import static io.parsingdata.metal.Shorthand.ref; +import static io.parsingdata.metal.Shorthand.rep; +import static io.parsingdata.metal.Shorthand.repn; +import static io.parsingdata.metal.Shorthand.seq; +import static io.parsingdata.metal.Shorthand.when; +import static io.parsingdata.metal.Shorthand.whl; +import static io.parsingdata.metal.util.EncodingFactory.enc; +import static io.parsingdata.metal.util.ParseStateFactory.stream; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.runners.Parameterized; + +import io.parsingdata.metal.token.Token; +import io.parsingdata.metal.util.ParameterizedParse; + +public class CurrentIterationTest extends ParameterizedParse { + + public static final Token VALUE_EQ_ITERATION = def("value", con(1), eq(CURRENT_ITERATION)); + public static final Token VALUE_NOT_EQ_ITERATION = def("value", con(1), not(eq(CURRENT_ITERATION))); + public static final Token VALUE_EQ_255 = def("value", con(1), eq(con(255))); + + @Parameterized.Parameters(name="{0} ({4})") + public static Collection data() { + return Arrays.asList(new Object[][] { + { "[0, 1, 2, 3, 255] rep(CURRENT_ITERATION), def(255)", seq(rep(VALUE_EQ_ITERATION), VALUE_EQ_255), stream(0, 1, 2, 3, 255), enc(), true }, + { "[0, 1, 2, 3] repn=4(CURRENT_ITERATION)", repn(VALUE_EQ_ITERATION, con(4)), stream(0, 1, 2, 3), enc(), true }, + { "[255, 0, 1, 2, 3, 255] def(255), while!=3(CURRENT_ITERATION), def (255)", seq(VALUE_EQ_255, whl(VALUE_EQ_ITERATION, not(eq(con(3)))), VALUE_EQ_255), stream(255, 0, 1, 2, 3, 255), enc(), true }, + { "[0, 0, 1, 2, 255, 1, 0, 1, 2, 255] repn=2(CURRENT_ITERATION, repn=3(CURRENT_ITERATION))", repn(seq(VALUE_EQ_ITERATION, repn(VALUE_EQ_ITERATION, con(3)), VALUE_EQ_255), con(2)), stream(0, 0, 1, 2, 255, 1, 0, 1, 2, 255), enc(), true }, + { "[0, 1] seq(CURRENT_ITERATION, ...)", seq(VALUE_EQ_ITERATION, VALUE_EQ_ITERATION), stream(0, 1), enc(), false }, + { "[0] CURRENT_ITERATION", VALUE_EQ_ITERATION, stream(0), enc(), false }, + { "[0, 1] seq(!CURRENT_ITERATION, ...)", seq(VALUE_NOT_EQ_ITERATION, VALUE_NOT_EQ_ITERATION), stream(0, 1), enc(), true }, + { "[0] !CURRENT_ITERATION", VALUE_NOT_EQ_ITERATION, stream(0), enc(), true }, + { "[0 | 0, 1 | 0, 0, 2 | 0, 0, 0, 3] rep(CURRENT_ITERATION)", rep(def("value", add(CURRENT_ITERATION, con(1)), eqNum(CURRENT_ITERATION))), stream(0, 0, 1, 0, 0, 2, 0, 0, 0, 3), enc(), true }, + { "[1, 1, 0, 1 | 0 | 1 | 3] repn=4(def(size)), repn=4(if(sizeRef(CURRENT_ITERATION) != 0, def(CURRENT_ITERATION)))", seq(repn(def("size", 1), con(4)), repn(when(def("value", con(1), eq(CURRENT_ITERATION)), not(eq(nth(ref("size"), CURRENT_ITERATION), con(0)))), con(4))), stream(1, 1, 0, 1, 0, 1, 3), enc(), true }, + }); + } + +}