From bdb505fce782741445c8c370f7141ca9f26dbc31 Mon Sep 17 00:00:00 2001 From: jbb01 <32650546+jbb01@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:01:58 +0200 Subject: [PATCH] fixup query --- .../eu/jonahbauer/json/query/JsonMath.java | 423 +++++++++++----- .../eu/jonahbauer/json/query/JsonQuery.java | 13 +- .../json/query/impl/ConcatGenerator.java | 26 +- .../json/query/impl/EmptyGenerator.java | 11 +- .../json/query/impl/FlatMappingGenerator.java | 19 +- .../jonahbauer/json/query/impl/Generator.java | 71 ++- .../json/query/impl/MappingGenerator.java | 18 +- .../json/query/impl/SingletonGenerator.java | 22 +- .../json/query/parser/JQParser.java | 99 ++-- .../parser/ast/JQAlternativeExpression.java | 95 ++-- .../ast/JQArrayConstructionExpression.java | 7 +- .../json/query/parser/ast/JQAsExpression.java | 153 +----- .../json/query/parser/ast/JQAssignment.java | 3 +- .../query/parser/ast/JQAssignmentCoerce.java | 3 +- .../query/parser/ast/JQAssignmentPipe.java | 4 +- .../query/parser/ast/JQBinaryExpression.java | 4 +- .../parser/ast/JQBooleanAndExpression.java | 6 +- .../parser/ast/JQBooleanOrExpression.java | 5 +- .../json/query/parser/ast/JQBuiltIn.java | 472 +++++++++++------- .../query/parser/ast/JQCommaExpression.java | 34 +- .../json/query/parser/ast/JQConstant.java | 7 +- .../json/query/parser/ast/JQExpression.java | 8 +- .../json/query/parser/ast/JQFilter.java | 4 +- .../json/query/parser/ast/JQFunction.java | 9 +- .../parser/ast/JQFunctionInvocation.java | 4 +- .../query/parser/ast/JQIndexExpression.java | 5 +- .../json/query/parser/ast/JQInvocable.java | 5 +- .../query/parser/ast/JQIterateExpression.java | 4 +- .../query/parser/ast/JQLocExpression.java | 7 +- .../json/query/parser/ast/JQNegation.java | 3 +- .../parser/ast/JQParenthesizedExpression.java | 4 +- .../query/parser/ast/JQPipeExpression.java | 3 +- .../json/query/parser/ast/JQProgram.java | 6 + .../parser/ast/JQRecursionExpression.java | 13 +- .../query/parser/ast/JQRootExpression.java | 5 +- .../query/parser/ast/JQSliceExpression.java | 19 +- .../parser/ast/JQStringInterpolation.java | 3 +- .../query/parser/ast/JQTryExpression.java | 87 ++-- .../parser/ast/JQVariableExpression.java | 6 +- .../eu/jonahbauer/json/query/util/Util.java | 29 +- 40 files changed, 980 insertions(+), 739 deletions(-) diff --git a/query/src/main/java/eu/jonahbauer/json/query/JsonMath.java b/query/src/main/java/eu/jonahbauer/json/query/JsonMath.java index f6b6259..58e5b90 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/JsonMath.java +++ b/query/src/main/java/eu/jonahbauer/json/query/JsonMath.java @@ -2,6 +2,7 @@ package eu.jonahbauer.json.query; import eu.jonahbauer.json.*; import eu.jonahbauer.json.exceptions.JsonParserException; +import eu.jonahbauer.json.query.impl.Generator; import eu.jonahbauer.json.query.parser.ast.*; import eu.jonahbauer.json.query.util.Util; import lombok.experimental.UtilityClass; @@ -9,11 +10,11 @@ import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.*; -import java.util.function.DoubleBinaryOperator; -import java.util.function.DoubleUnaryOperator; -import java.util.function.Function; -import java.util.function.Predicate; +import java.util.function.*; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -236,20 +237,20 @@ public class JsonMath { // // - public static @NotNull Stream<@Nullable JsonValue> error(@Nullable JsonValue value) { + public static @NotNull Generator<@Nullable JsonValue> error(@Nullable JsonValue value) { if (value != null) throw new JsonQueryUserException(value); - return Stream.empty(); + return Generator.empty(); } - public static @NotNull Stream<@Nullable JsonValue> halt() { + public static @NotNull Generator<@Nullable JsonValue> halt() { throw new JsonQueryHaltException(null, 0); } - public static @NotNull Stream<@Nullable JsonValue> halt(@Nullable JsonValue value) { + public static @NotNull Generator<@Nullable JsonValue> halt(@Nullable JsonValue value) { throw new JsonQueryHaltException(value, 5); } - public static @NotNull Stream<@Nullable JsonValue> halt(@Nullable JsonValue value, @Nullable JsonValue code) { + public static @NotNull Generator<@Nullable JsonValue> halt(@Nullable JsonValue value, @Nullable JsonValue code) { throw new JsonQueryHaltException(value, switch (code) { case JsonNumber(var number) -> (int) number; case null, default -> throw new JsonQueryException(STR."\{Util.type(value)} (\{Util.value(value)}) halt_error/1: number required"); @@ -258,11 +259,11 @@ public class JsonMath { // // - public static @NotNull Stream<@NotNull JsonValue> range(@Nullable JsonValue upto) { + public static @NotNull Generator<@NotNull JsonValue> range(@Nullable JsonValue upto) { return range(new JsonNumber(0), upto); } - public static @NotNull Stream<@NotNull JsonValue> range(@Nullable JsonValue from, @Nullable JsonValue upto) { + public static @NotNull Generator<@NotNull JsonValue> range(@Nullable JsonValue from, @Nullable JsonValue upto) { var min = switch (from) { case JsonNumber(var number) -> number; case null, default -> throw new JsonQueryException("Range bounds must be numeric."); @@ -271,81 +272,79 @@ public class JsonMath { case JsonNumber(var number) -> number; case null, default -> throw new JsonQueryException("Range bounds must be numeric."); }; - return IntStream.range((int) Math.ceil(min), (int) Math.ceil(max)).mapToObj(JsonNumber::new); + return Generator.from(IntStream.range((int) Math.ceil(min), (int) Math.ceil(max)).boxed()).map(JsonNumber::new); } - public static @NotNull Stream<@Nullable JsonValue> range(@Nullable JsonValue from, @Nullable JsonValue upto, @Nullable JsonValue by) { + public static @NotNull Generator<@Nullable JsonValue> range(@Nullable JsonValue from, @Nullable JsonValue upto, @Nullable JsonValue by) { if (compare(by, JsonNumber.ZERO) > 0) { - return Stream.iterate(from, i -> compare(i, upto) < 0, i -> add(i, by)); + return Generator.from(Stream.iterate(from, i -> compare(i, upto) < 0, i -> add(i, by))); } else if (compare(by, JsonNumber.ZERO) < 0) { - return Stream.iterate(from, i -> compare(i, upto) > 0, i -> add(i, by)); + return Generator.from(Stream.iterate(from, i -> compare(i, upto) > 0, i -> add(i, by))); } else { - return Stream.empty(); + return Generator.empty(); } } - public static @NotNull Stream<@Nullable JsonValue> limit(@NotNull Stream<@Nullable JsonValue> stream, @Nullable JsonValue limit) { + public static @NotNull Generator<@Nullable JsonValue> limit(@NotNull Generator<@Nullable JsonValue> stream, @Nullable JsonValue limit) { if (compare(limit, JsonNumber.ZERO) > 0) { - class State { - @Nullable JsonValue limit; - public State(@Nullable JsonValue limit) { this.limit = limit; } - } + return new Generator<>() { + private @Nullable JsonValue i = limit; - return stream.gather(Gatherer.ofSequential( - () -> new State(limit), - (state, value, downstream) -> { - state.limit = JsonMath.sub(state.limit, JsonNumber.ONE); - downstream.push(value); - return compare(state.limit, JsonNumber.ZERO) > 0; - } - )); + @Override + public boolean hasNext() { + return compare(i, JsonNumber.ZERO) > 0; + } + + @Override + public JsonValue next() { + if (compare(i, JsonNumber.ZERO) <= 0) throw new NoSuchElementException(); + i = sub(i, JsonNumber.ONE); + return stream.next(); + } + }; } else if (Objects.equals(limit, JsonNumber.ZERO)) { - return Stream.empty(); + return Generator.empty(); } else { return stream; } } - public static @NotNull Stream<@Nullable JsonValue> first(@NotNull Stream<@Nullable JsonValue> stream) { + public static @NotNull Generator<@Nullable JsonValue> first(@NotNull Generator<@Nullable JsonValue> stream) { return stream.limit(1); } - public static @NotNull Stream<@Nullable JsonValue> last(@NotNull Stream<@Nullable JsonValue> stream) { - class State { - boolean hasValue; - @Nullable JsonValue value; - } - return stream.gather(Gatherer.ofSequential( - State::new, - Gatherer.Integrator.ofGreedy((state, value, _) -> { - state.hasValue = true; - state.value = value; - return true; - }), - (state, downstream) -> { - if (state.hasValue) downstream.push(state.value); + public static @NotNull Generator<@Nullable JsonValue> last(@NotNull Generator<@Nullable JsonValue> stream) { + return new Generator<>() { + @Override + public boolean hasNext() { + return stream.hasNext(); + } + + @Override + public JsonValue next() throws NoSuchElementException { + var out = stream.next(); + while (stream.hasNext()) { + out = stream.next(); } - )); + return out; + } + }; } - public static @NotNull Stream<@Nullable JsonValue> nth(@NotNull Stream<@Nullable JsonValue> stream, @Nullable JsonValue limit) { + public static @NotNull Generator<@Nullable JsonValue> nth(@NotNull Generator<@Nullable JsonValue> stream, @Nullable JsonValue limit) { if (compare(limit, JsonNumber.ZERO) < 0) throw new JsonQueryException("nth doesn't support negative indices"); - class State { - @Nullable JsonValue limit; - public State(@Nullable JsonValue limit) { this.limit = limit; } - } - return stream.gather(Gatherer.ofSequential( - () -> new State(sub(add(limit, JsonNumber.ONE), JsonNumber.ONE)), - (state, value, downstream) -> { - if (compare(state.limit, JsonNumber.ZERO) <= 0) { - downstream.push(value); - return false; - } else { - state.limit = sub(state.limit, JsonNumber.ONE); - return true; - } - } - )); + return Generator.of(() -> { + for (var i = sub(add(limit, JsonNumber.ONE), JsonNumber.ONE); compare(i, JsonNumber.ZERO) > 0; i = sub(i, JsonNumber.ONE)) { + stream.next(); + } + return stream.next(); + }); + } + + public static @NotNull Generator<@Nullable JsonValue> select(@Nullable JsonValue value, @NotNull JQFilter filter) { + return filter.apply(value).mapMulti((result, consumer) -> { + if (isTruthy(result)) consumer.accept(value); + }); } // @@ -430,16 +429,24 @@ public class JsonMath { return has(value, index); } - public static @NotNull Stream<@Nullable JsonValue> values(@Nullable JsonValue value) { + public static @Nullable JsonValue first(@Nullable JsonValue value) { + return index(value, JsonNumber.ZERO); + } + + public static @Nullable JsonValue last(@Nullable JsonValue value) { + return index(value, new JsonNumber(-1)); + } + + public static @NotNull Generator<@Nullable JsonValue> values(@Nullable JsonValue value) { return values(value, false); } - public static @NotNull Stream<@Nullable JsonValue> values(@Nullable JsonValue value, boolean optional) { + public static @NotNull Generator<@Nullable JsonValue> values(@Nullable JsonValue value, boolean optional) { return switch (value) { - case JsonArray array -> array.stream(); - case JsonObject object -> object.values().stream(); + case JsonArray array -> Generator.from(array); + case JsonObject object -> Generator.from(object.values()); case null, default -> { - if (optional) yield Stream.empty(); + if (optional) yield Generator.empty(); throw new JsonQueryException(STR."Cannot iterate over \{Util.type(value)} (\{Util.value(value)})."); } }; @@ -451,7 +458,7 @@ public class JsonMath { public static @NotNull JsonValue mapValues(@Nullable JsonValue value, @NotNull JQFilter expression) { return switch (value) { - case JsonArray array -> new JsonArray(array.stream().map(expression).flatMap(s -> s.limit(1)).toList()); + case JsonArray array -> new JsonArray(Generator.from(array).map(expression).flatMap(s -> s.limit(1)).toList()); case JsonObject object -> { var out = new LinkedHashMap(); object.forEach((key, v) -> expression.apply(v).limit(1).forEach(newValue -> out.put(key, newValue))); @@ -469,7 +476,7 @@ public class JsonMath { return JsonBoolean.valueOf(values(value).flatMap(expression).anyMatch(JsonMath::isTruthy)); } - public static @NotNull JsonBoolean any(@NotNull Stream<@Nullable JsonValue> values, @NotNull JQFilter expression) { + public static @NotNull JsonBoolean any(@NotNull Generator<@Nullable JsonValue> values, @NotNull JQFilter expression) { return JsonBoolean.valueOf(values.flatMap(expression).anyMatch(JsonMath::isTruthy)); } @@ -481,7 +488,7 @@ public class JsonMath { return JsonBoolean.valueOf(values(value).flatMap(expression).allMatch(JsonMath::isTruthy)); } - public static @NotNull JsonBoolean all(@NotNull Stream<@Nullable JsonValue> values, @NotNull JQFilter expression) { + public static @NotNull JsonBoolean all(@NotNull Generator<@Nullable JsonValue> values, @NotNull JQFilter expression) { return JsonBoolean.valueOf(values.flatMap(expression).allMatch(JsonMath::isTruthy)); } @@ -497,18 +504,18 @@ public class JsonMath { }; } - private static @NotNull Stream<@Nullable JsonValue> flatten0(@Nullable JsonValue value) { + private static @NotNull Generator<@Nullable JsonValue> flatten0(@Nullable JsonValue value) { return flatten0(value, -1); } - private static @NotNull Stream<@Nullable JsonValue> flatten0(@Nullable JsonValue value, int depth) { + private static @NotNull Generator<@Nullable JsonValue> flatten0(@Nullable JsonValue value, int depth) { return switch (value) { - case JsonArray array when depth == 0 -> Stream.of(array); + case JsonArray array when depth == 0 -> Generator.of(array); case JsonArray array -> { var d = depth < 0 ? depth : depth - 1; - yield array.stream().flatMap(v -> flatten0(v, d)); + yield Generator.from(array).flatMap(v -> flatten0(v, d)); } - case null, default -> Stream.of(value); + case null, default -> Generator.of(value); }; } @@ -703,8 +710,8 @@ public class JsonMath { return contains(container, content); } - public static @NotNull Stream<@NotNull JsonArray> combinations(@Nullable JsonValue value) { - if (length0(value) == 0) return Stream.of(JsonArray.EMPTY); + public static @NotNull Generator<@NotNull JsonArray> combinations(@Nullable JsonValue value) { + if (length0(value) == 0) return Generator.of(JsonArray.EMPTY); if (!(value instanceof JsonArray array)) throw indexError(value, JsonNumber.ZERO); return values(array.getFirst()) @@ -716,7 +723,7 @@ public class JsonMath { })); } - public static @NotNull Stream<@NotNull JsonArray> combinations(@Nullable JsonValue value, @Nullable JsonValue n) { + public static @NotNull Generator<@NotNull JsonArray> combinations(@Nullable JsonValue value, @Nullable JsonValue n) { if (!(n instanceof JsonNumber(var number))) throw new JsonQueryException("Range bounds must be numeric."); return combinations(new JsonArray(Collections.nCopies((int) Math.ceil(number), value))); } @@ -747,6 +754,40 @@ public class JsonMath { return new JsonArray(out.stream().map(JsonArray::new).toList()); } + private static final JsonString KEY = new JsonString("key"); + private static final JsonString VALUE = new JsonString("value"); + + public static @NotNull JsonArray toEntries(@Nullable JsonValue value) { + return new JsonArray((switch (value) { + case JsonArray array -> IntStream.range(0, array.size()) + .mapToObj(i -> JsonObject.of( + "key", new JsonNumber(i), + "value", array.get(i) + )); + case JsonObject object -> object.entrySet().stream() + .map(entry -> JsonObject.of( + "key", new JsonString(entry.getKey()), + "value", entry.getValue() + )); + case null, default -> throw error(value, "has no keys"); + }).toList()); + } + + public static @NotNull JsonObject fromEntries(@Nullable JsonValue value) { + var out = new LinkedHashMap(); + values(value).forEach(entry -> { + var key = index(entry, KEY); + var val = index(entry, VALUE); + if (!(key instanceof JsonString(var k))) throw new JsonQueryException(STR."Cannot use \{Util.type(key)} (\{Util.value(key)}) as object key."); + out.put(k, val); + }); + return new JsonObject(out); + } + + public static @NotNull JsonObject withEntries(@Nullable JsonValue value, @NotNull JQFilter expression) { + return fromEntries(map(toEntries(value), expression)); + } + private record SortEntry(@Nullable JsonValue value, @NotNull Iterable<@Nullable JsonValue> key) { private static final Comparator> LEXICOGRAPHIC_COMPARATOR = (a, b) -> compareLexicographically(a, b, JsonMath.COMPARATOR); private static final Comparator COMPARATOR = Comparator.comparing(SortEntry::key, LEXICOGRAPHIC_COMPARATOR); @@ -754,60 +795,116 @@ public class JsonMath { // // - public static boolean isArray(@Nullable JsonValue value) { + public static @NotNull JsonBoolean isArray(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isArray0(value)); + } + + public static @NotNull JsonBoolean isObject(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isObject0(value)); + } + + public static @NotNull JsonBoolean isIterable(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isIterable0(value)); + } + + public static @NotNull JsonBoolean isBoolean(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isBoolean0(value)); + } + + public static @NotNull JsonBoolean isNumber(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isNumber0(value)); + } + + public static @NotNull JsonBoolean isNormal(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isNormal0(value)); + } + + public static @NotNull JsonBoolean isFinite(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isFinite0(value)); + } + + public static @NotNull JsonBoolean isInfinite(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isInfinite0(value)); + } + + public static @NotNull JsonBoolean isNan(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isNan0(value)); + } + + public static @NotNull JsonBoolean isString(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isString0(value)); + } + + public static @NotNull JsonBoolean isNull(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isNull0(value)); + } + + public static @NotNull JsonBoolean isValue(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isValue0(value)); + } + + public static @NotNull JsonBoolean isScalar(@Nullable JsonValue value) { + return JsonBoolean.valueOf(isScalar0(value)); + } + + public static @NotNull JsonBoolean isEmpty(@NotNull Generator stream) { + return JsonBoolean.valueOf(isEmpty0(stream)); + } + + public static boolean isArray0(@Nullable JsonValue value) { return value instanceof JsonArray; } - public static boolean isObject(@Nullable JsonValue value) { + public static boolean isObject0(@Nullable JsonValue value) { return value instanceof JsonObject; } - public static boolean isIterable(@Nullable JsonValue value) { - return isArray(value) || isObject(value); + public static boolean isIterable0(@Nullable JsonValue value) { + return isArray0(value) || isObject0(value); } - public static boolean isBoolean(@Nullable JsonValue value) { + public static boolean isBoolean0(@Nullable JsonValue value) { return value instanceof JsonBoolean; } - public static boolean isNumber(@Nullable JsonValue value) { + public static boolean isNumber0(@Nullable JsonValue value) { return value instanceof JsonNumber; } - public static boolean isNormal(@Nullable JsonValue value) { + public static boolean isNormal0(@Nullable JsonValue value) { return value instanceof JsonNumber(var number) && Math.abs(number) >= Double.MIN_NORMAL; } - public static boolean isFinite(@Nullable JsonValue value) { + public static boolean isFinite0(@Nullable JsonValue value) { return value instanceof JsonNumber(var number) && Double.isFinite(number); } - public static boolean isInfinite(@Nullable JsonValue value) { + public static boolean isInfinite0(@Nullable JsonValue value) { return value instanceof JsonNumber(var number) && Double.isInfinite(number); } - public static boolean isNan(@Nullable JsonValue value) { + public static boolean isNan0(@Nullable JsonValue value) { return value instanceof JsonNumber(var number) && Double.isNaN(number); } - public static boolean isString(@Nullable JsonValue value) { + public static boolean isString0(@Nullable JsonValue value) { return value instanceof JsonString; } - public static boolean isNull(@Nullable JsonValue value) { + public static boolean isNull0(@Nullable JsonValue value) { return value == null; } - public static boolean isValue(@Nullable JsonValue value) { + public static boolean isValue0(@Nullable JsonValue value) { return value != null; } - public static boolean isScalar(@Nullable JsonValue value) { - return !isIterable(value); + public static boolean isScalar0(@Nullable JsonValue value) { + return !isIterable0(value); } - public static boolean isEmpty(@NotNull Stream stream) { - return stream.map(_ -> Boolean.TRUE).findFirst().isEmpty(); + public static boolean isEmpty0(@NotNull Generator stream) { + return !stream.hasNext(); } // @@ -861,7 +958,7 @@ public class JsonMath { case JsonString string -> string; case JsonNumber _, JsonBoolean _ -> tostring(value); case null, default -> value; - }).iterator(); + }); if (!it.hasNext()) return JsonString.EMPTY; @@ -943,14 +1040,14 @@ public class JsonMath { // // - public static @NotNull Stream<@NotNull JsonValue> match( + public static @NotNull Generator<@NotNull JsonValue> match( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args ) { var input = regexInput(context); return match(context, args, false).flatMap(Function.identity()).map(result -> match0(input, result)); } - private static @NotNull Stream> match( + private static @NotNull Generator> match( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args, boolean global ) { var input = regexInput(context); @@ -969,7 +1066,7 @@ public class JsonMath { }; } - private static @NotNull Stream<@NotNull MatchResult> match( + private static @NotNull Generator<@NotNull MatchResult> match( @NotNull String input, @Nullable JsonValue pattern, @Nullable JsonValue flags, boolean global ) { if (!(pattern instanceof JsonString(var patternString))) throw error(pattern, "is not a string"); @@ -1005,17 +1102,24 @@ public class JsonMath { return out; } - private static @NotNull Stream<@NotNull MatchResult> matchesAsStream(@NotNull Matcher matcher) { - var it = new Iterator() { - @Override - public boolean hasNext() { return matcher.find(); } + private static @NotNull Generator<@NotNull MatchResult> matchesAsStream(@NotNull Matcher matcher) { + return new Generator<>() { + private boolean valid = false; @Override - public MatchResult next() { return matcher.toMatchResult(); } + public boolean hasNext() { + if (valid) return true; + return valid = matcher.find(); + } + + @Override + public MatchResult next() { + if (!valid) valid = matcher.find(); + if (!valid) throw new NoSuchElementException(); + valid = false; + return matcher.toMatchResult(); + } }; - - var split = Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED); - return StreamSupport.stream(split, false); } private static @NotNull JsonValue match0(@NotNull String input, @NotNull MatchResult result) { @@ -1047,13 +1151,13 @@ public class JsonMath { throw error(context.root(), "cannot be matched, as it is not a string"); } - public static @NotNull Stream<@NotNull JsonValue> test( + public static @NotNull Generator<@NotNull JsonValue> test( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args ) { - return Util.lazy(() -> JsonBoolean.valueOf(!isEmpty(match(context, args, false)))); + return Generator.of(() -> JsonBoolean.valueOf(!isEmpty0(match(context, args, false)))); } - public static @NotNull Stream<@NotNull JsonValue> capture( + public static @NotNull Generator<@NotNull JsonValue> capture( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args ) { return match(context, args, false).flatMap(Function.identity()).map(JsonMath::capture0); @@ -1065,51 +1169,61 @@ public class JsonMath { return new JsonObject(out); } - public static @NotNull Stream<@NotNull JsonValue> scan( + public static @NotNull Generator<@NotNull JsonValue> scan( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args ) { return match(context, args, true).flatMap(Function.identity()).map(MatchResult::group).map(JsonString::new); } - public static @NotNull Stream<@NotNull JsonValue> split( + public static @NotNull Generator<@NotNull JsonValue> split( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args ) { - return Util.lazy(() -> new JsonArray(splits(context, args).toList())); + return Generator.of(() -> new JsonArray(splits(context, args).toList())); } - public static @NotNull Stream<@NotNull JsonValue> splits( + public static @NotNull Generator<@NotNull JsonValue> splits( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args ) { if (!(context.root() instanceof JsonString(var inputString))) throw error(context.root(), "cannot be matched, as it is not a string"); - return match(context, args, true).flatMap(s -> s.gather(splits0(inputString))); + return match(context, args, true).flatMap(it -> new Generator<>() { + private boolean finished; + private int offset; + + @Override + public boolean hasNext() { + return !finished; + } + + @Override + public JsonValue next() throws NoSuchElementException { + if (it.hasNext()) { + var match = it.next(); + var out = new JsonString(inputString.substring(offset, match.start())); + offset = match.end(); + return out; + } else if (!finished) { + finished = true; + return new JsonString(inputString.substring(offset)); + } else { + throw new NoSuchElementException(); + } + } + }); } - private static @NotNull Gatherer splits0(@NotNull String input) { - class State { int offset; } - return Gatherer.ofSequential( - State::new, - Gatherer.Integrator.ofGreedy((state, value, downstream) -> { - downstream.push(new JsonString(input.substring(state.offset, value.start()))); - state.offset = value.end(); - return true; - }), - (state, downstream) -> downstream.push(new JsonString(input.substring(state.offset))) - ); - } - - public static @NotNull Stream<@NotNull JsonValue> sub( + public static @NotNull Generator<@NotNull JsonValue> sub( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args ) { return sub0(context, args, false); } - public static @NotNull Stream<@NotNull JsonValue> gsub( + public static @NotNull Generator<@NotNull JsonValue> gsub( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args ) { return sub0(context, args, true); } - private static @NotNull Stream<@NotNull JsonValue> sub0( + private static @NotNull Generator<@NotNull JsonValue> sub0( @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args, boolean global ) { var input = regexInput(context); @@ -1120,12 +1234,11 @@ public class JsonMath { }; var subst = args.get(1); - return match(context, matchArgs, global).flatMap(s -> { + return match(context, matchArgs, global).flatMap(it -> { var offset = 0; var fragments = new ArrayList(); var values = new ArrayList(); - var it = s.iterator(); while (it.hasNext()) { var result = it.next(); var capture = capture0(result); @@ -1184,6 +1297,33 @@ public class JsonMath { } // + // + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") + .withZone(ZoneId.of("UTC")); + + public static @NotNull JsonNumber fromdateiso8601(@Nullable JsonValue value) { + return JsonNumber.valueOf(switch (value) { + case JsonString(var string) -> DATE_TIME_FORMATTER.parse(string, Instant::from).getEpochSecond(); + case null, default -> throw new JsonQueryException("fromdateiso8601/1 requires string inputs"); + }); + } + + public static @NotNull JsonNumber fromdate(@Nullable JsonValue value) { + return fromdateiso8601(value); + } + + public static @NotNull JsonString todateiso8601(@Nullable JsonValue value) { + return new JsonString(switch (value) { + case JsonNumber(var number) -> DATE_TIME_FORMATTER.format(Instant.ofEpochSecond((long) number)); + case null, default -> throw new JsonQueryException("todateiso8601/1 requires number inputs"); + }); + } + + public static @NotNull JsonString todate(@Nullable JsonValue value) { + return todateiso8601(value); + } + // + // public static @NotNull JsonNumber length(@Nullable JsonValue value) { return new JsonNumber(length0(value)); @@ -1199,6 +1339,21 @@ public class JsonMath { case null -> 0; }; } + + public static @NotNull Generator<@Nullable JsonValue> repeat(@NotNull Supplier<@NotNull Generator<@Nullable JsonValue>> delegate) { + return new Generator<>() { + private @NotNull Generator current = Generator.empty(); + + @Override + public boolean hasNext() { return true; } + + @Override + public JsonValue next() throws NoSuchElementException { + while (!current.hasNext()) current = delegate.get(); + return current.next(); + } + }; + } // // diff --git a/query/src/main/java/eu/jonahbauer/json/query/JsonQuery.java b/query/src/main/java/eu/jonahbauer/json/query/JsonQuery.java index 1aa38c8..297b96c 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/JsonQuery.java +++ b/query/src/main/java/eu/jonahbauer/json/query/JsonQuery.java @@ -1,8 +1,9 @@ package eu.jonahbauer.json.query; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import eu.jonahbauer.json.query.parser.JQParser; -import eu.jonahbauer.json.query.parser.ast.JQExpression; +import eu.jonahbauer.json.query.parser.ast.JQProgram; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; @@ -10,25 +11,23 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.UncheckedIOException; -import java.util.stream.Stream; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class JsonQuery { - private final @NotNull JQExpression expression; + private final @NotNull JQProgram programm; public static @NotNull JsonQuery parse(@NotNull String query) { try { var parser = new JQParser(query); var programm = parser.parseTopLevel(); if (programm.expression() == null) throw new IllegalArgumentException(); - return new JsonQuery(programm.expression()); + return new JsonQuery(programm); } catch (IOException ex) { throw new UncheckedIOException(ex); } } - public @NotNull Stream<@NotNull JsonValue> run(@Nullable JsonValue value) { - var context = new JQExpression.Context(value); - return expression.evaluate(context); + public @NotNull Generator<@NotNull JsonValue> run(@Nullable JsonValue value) { + return programm.run(value); } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/impl/ConcatGenerator.java b/query/src/main/java/eu/jonahbauer/json/query/impl/ConcatGenerator.java index d66f108..d008518 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/impl/ConcatGenerator.java +++ b/query/src/main/java/eu/jonahbauer/json/query/impl/ConcatGenerator.java @@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull; import java.util.LinkedList; import java.util.List; +import java.util.NoSuchElementException; import java.util.Queue; final class ConcatGenerator implements Generator { @@ -14,15 +15,28 @@ final class ConcatGenerator implements Generator { } @Override - public T next() throws EndOfStreamException { + public boolean hasNext() { while (!generators.isEmpty()) { - try { - var current = generators.peek(); - return current.next(); - } catch (EndOfStreamException ex) { + var current = generators.peek(); + if (!current.hasNext()) { generators.remove(); + } else { + return true; } } - throw new EndOfStreamException(); + return false; + } + + @Override + public T next() { + while (!generators.isEmpty()) { + var current = generators.peek(); + if (!current.hasNext()) { + generators.remove(); + } else { + return current.next(); + } + } + throw new NoSuchElementException(); } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/impl/EmptyGenerator.java b/query/src/main/java/eu/jonahbauer/json/query/impl/EmptyGenerator.java index ad543cf..b841f3b 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/impl/EmptyGenerator.java +++ b/query/src/main/java/eu/jonahbauer/json/query/impl/EmptyGenerator.java @@ -1,10 +1,17 @@ package eu.jonahbauer.json.query.impl; +import java.util.NoSuchElementException; + enum EmptyGenerator implements Generator { INSTANCE; @Override - public Object next() throws EndOfStreamException { - throw new EndOfStreamException(); + public boolean hasNext() { + return false; + } + + @Override + public Object next() { + throw new NoSuchElementException(); } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/impl/FlatMappingGenerator.java b/query/src/main/java/eu/jonahbauer/json/query/impl/FlatMappingGenerator.java index 7807410..cd50279 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/impl/FlatMappingGenerator.java +++ b/query/src/main/java/eu/jonahbauer/json/query/impl/FlatMappingGenerator.java @@ -15,13 +15,18 @@ final class FlatMappingGenerator implements Generator { } @Override - public S next() throws EndOfStreamException { - while (true) { - try { - return current.next(); - } catch (EndOfStreamException _) { - current = function.apply(source.next()); - } + public boolean hasNext() { + while (!current.hasNext() && source.hasNext()) { + current = function.apply(source.next()); } + return current.hasNext(); + } + + @Override + public S next() { + while (!current.hasNext() && source.hasNext()) { + current = function.apply(source.next()); + } + return current.next(); } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/impl/Generator.java b/query/src/main/java/eu/jonahbauer/json/query/impl/Generator.java index 06763e1..ca9c519 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/impl/Generator.java +++ b/query/src/main/java/eu/jonahbauer/json/query/impl/Generator.java @@ -2,11 +2,56 @@ package eu.jonahbauer.json.query.impl; import org.jetbrains.annotations.NotNull; -import java.util.List; -import java.util.function.Function; +import java.util.*; +import java.util.function.*; +import java.util.stream.Stream; public interface Generator { - T next() throws EndOfStreamException; + boolean hasNext(); + T next() throws NoSuchElementException; + + default void forEach(@NotNull Consumer action) { + while (hasNext()) { + action.accept(next()); + } + } + + default T reduce(T seed, @NotNull BinaryOperator operator) { + while (hasNext()) { + seed = operator.apply(seed, next()); + } + return seed; + } + + default boolean anyMatch(@NotNull Predicate predicate) { + while (hasNext()) { + if (predicate.test(next())) return true; + } + return false; + } + + default boolean allMatch(@NotNull Predicate predicate) { + while (hasNext()) { + if (!predicate.test(next())) return false; + } + return true; + } + + default @NotNull List toList() { + var out = new ArrayList(); + while (hasNext()) { + out.add(next()); + } + return Collections.unmodifiableList(out); + } + + default @NotNull Generator filter(@NotNull Predicate predicate) { + return new FilterGenerator<>(this, predicate); + } + + default @NotNull Generator limit(int count) { + return new LimitGenerator<>(this, count); + } default @NotNull Generator map(@NotNull Function function) { return new MappingGenerator<>(this, function); @@ -16,6 +61,10 @@ public interface Generator { return new FlatMappingGenerator<>(this, function); } + default @NotNull Generator mapMulti(@NotNull BiConsumer> function) { + return new MapMultiGenerator<>(this, function); + } + @SuppressWarnings("unchecked") static @NotNull Generator empty() { return (Generator) EmptyGenerator.INSTANCE; @@ -29,5 +78,19 @@ public interface Generator { return new SingletonGenerator<>(value); } - class EndOfStreamException extends RuntimeException {} + static @NotNull Generator of(@NotNull Supplier value) { + return new SupplierGenerator<>(value); + } + + static @NotNull Generator from(@NotNull Iterable value) { + return new IteratorGenerator<>(value.iterator()); + } + + static @NotNull Generator from(@NotNull Iterator value) { + return new IteratorGenerator<>(value); + } + + static @NotNull Generator from(@NotNull Stream value) { + return new IteratorGenerator<>(value.iterator()); + } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/impl/MappingGenerator.java b/query/src/main/java/eu/jonahbauer/json/query/impl/MappingGenerator.java index 3759ba5..cb72c09 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/impl/MappingGenerator.java +++ b/query/src/main/java/eu/jonahbauer/json/query/impl/MappingGenerator.java @@ -2,11 +2,25 @@ package eu.jonahbauer.json.query.impl; import org.jetbrains.annotations.NotNull; +import java.util.Objects; import java.util.function.Function; -record MappingGenerator(@NotNull Generator delegate, @NotNull Function function) implements Generator { +final class MappingGenerator implements Generator { + private final @NotNull Generator delegate; + private final @NotNull Function function; + + public MappingGenerator(@NotNull Generator delegate, @NotNull Function function) { + this.delegate = Objects.requireNonNull(delegate); + this.function = Objects.requireNonNull(function); + } + @Override - public S next() throws EndOfStreamException { + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public S next() { return function.apply(delegate.next()); } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/impl/SingletonGenerator.java b/query/src/main/java/eu/jonahbauer/json/query/impl/SingletonGenerator.java index 8f8a140..0004c1f 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/impl/SingletonGenerator.java +++ b/query/src/main/java/eu/jonahbauer/json/query/impl/SingletonGenerator.java @@ -1,5 +1,7 @@ package eu.jonahbauer.json.query.impl; +import java.util.NoSuchElementException; + final class SingletonGenerator implements Generator { private boolean done; private T value; @@ -9,13 +11,17 @@ final class SingletonGenerator implements Generator { } @Override - public T next() throws EndOfStreamException { - if (!done) { - var out = value; - done = true; - value = null; - return value; - } - throw new EndOfStreamException(); + public boolean hasNext() { + return !done; + } + + @Override + public T next() { + if (done) throw new NoSuchElementException(); + + var out = value; + done = true; + value = null; + return out; } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/JQParser.java b/query/src/main/java/eu/jonahbauer/json/query/parser/JQParser.java index 2eea9a3..c0dfbd2 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/JQParser.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/JQParser.java @@ -107,15 +107,11 @@ public class JQParser { return new JQFunction(name, params, body); } - private @NotNull JQExpression parseExpression() throws IOException { + public @NotNull JQExpression parseExpression() throws IOException { if (peek(JQTokenKind.DEF)) { - - } else if (peek(JQTokenKind.REDUCE)) { - - } else if (peek(JQTokenKind.FOREACH)) { - - } else if (peek(JQTokenKind.IF)) { - + var function = parseFuncDef(); + var expr = parsePipeExpression(); + return new JQFunctionDefinition(function, expr); } else if (peek(JQTokenKind.LABEL)) { } else { @@ -254,7 +250,7 @@ public class JQParser { } private @NotNull JQExpression parseErrorSuppression() throws IOException { - var expression = parseTermOrAs(); + var expression = parseControlFlow(); if (tryConsume(JQTokenKind.QUESTION_MARK)) { return new JQTryExpression(expression); } else { @@ -262,35 +258,82 @@ public class JQParser { } } - private @NotNull JQExpression parseTermOrAs() throws IOException { - var term = parseTerm(); - if (tryConsume(JQTokenKind.AS)) { - var patterns = new ArrayList(); - patterns.add(parsePattern()); - while (tryConsume(JQTokenKind.ALTERNATION)) { - patterns.add(parsePattern()); + private @NotNull JQExpression parseControlFlow() throws IOException { + if (tryConsume(JQTokenKind.REDUCE)) { + var expr = parseTerm(); + consume(JQTokenKind.AS); + var patterns = parsePatterns(); + consume(JQTokenKind.LPAREN); + var init = parseExpression(); + consume(JQTokenKind.SEMICOLON); + var update = parseExpression(); + consume(JQTokenKind.RPAREN); + return new JQReduceExpression(expr, patterns, init, update); + } else if (tryConsume(JQTokenKind.FOREACH)) { + var expr = parseTerm(); + consume(JQTokenKind.AS); + var patterns = parsePatterns(); + consume(JQTokenKind.LPAREN); + var init = parseExpression(); + consume(JQTokenKind.SEMICOLON); + var update = parseExpression(); + var extract = tryConsume(JQTokenKind.SEMICOLON) ? parseExpression() : new JQRootExpression(); + consume(JQTokenKind.RPAREN); + return new JQForEachExpression(expr, patterns, init, update, extract); + } else if (tryConsume(JQTokenKind.IF)) {; + var conds = new ArrayList(); + var thens = new ArrayList(); + + do { + conds.add(parseExpression()); + consume(JQTokenKind.THEN); + thens.add(parseExpression()); + } while (tryConsume(JQTokenKind.ELSE_IF)); + + var otherwise = tryConsume(JQTokenKind.ELSE) ? parseExpression() : null; + consume(JQTokenKind.END); + + var out = new JQIfExpression(conds.removeLast(), thens.removeLast(), otherwise); + while (!conds.isEmpty()) { + out = new JQIfExpression(conds.removeLast(), thens.removeLast(), out); } - consume(JQTokenKind.PIPE); - var expr = parseExpression(); - return new JQAsExpression(term, patterns, expr); + + return out; + } else { + var term = parseTerm(); + if (tryConsume(JQTokenKind.AS)) { + var patterns = parsePatterns(); + consume(JQTokenKind.PIPE); + var expr = parseExpression(); + return new JQAsExpression(term, patterns, expr); + } + return term; } - return term; } - private @NotNull JQAsExpression.Pattern parsePattern() throws IOException { + private @NotNull JQPatterns parsePatterns() throws IOException { + var patterns = new ArrayList(); + patterns.add(parsePattern()); + while (tryConsume(JQTokenKind.ALTERNATION)) { + patterns.add(parsePattern()); + } + return new JQPatterns(patterns); + } + + private @NotNull JQPatterns.Pattern parsePattern() throws IOException { if (tryConsume(JQTokenKind.LBRACKET)) { - var patterns = new ArrayList(); + var patterns = new ArrayList(); do { patterns.add(parsePattern()); } while (tryConsume(JQTokenKind.COMMA)); consume(JQTokenKind.RBRACKET); - return new JQAsExpression.Pattern.ArrayPattern(patterns); + return new JQPatterns.Pattern.ArrayPattern(patterns); } else if (tryConsume(JQTokenKind.LBRACE)) { - var patterns = new LinkedHashMap(); + var patterns = new LinkedHashMap(); do { if (tryConsume(JQTokenKind.DOLLAR)) { var ident = consume(JQTokenKind.IDENT).text(); - var pattern = new JQAsExpression.Pattern.ValuePattern("$" + ident); + var pattern = new JQPatterns.Pattern.ValuePattern("$" + ident); patterns.put(new JQConstant(JsonString.valueOf(ident)), pattern); } else if (peek(JQTokenKind.IDENT)) { var ident = consume(JQTokenKind.IDENT).text(); @@ -311,11 +354,11 @@ public class JQParser { } } while (tryConsume(JQTokenKind.COMMA)); consume(JQTokenKind.RBRACE); - return new JQAsExpression.Pattern.ObjectPattern(patterns); + return new JQPatterns.Pattern.ObjectPattern(patterns); } else { consume(JQTokenKind.DOLLAR); var ident = consume(JQTokenKind.IDENT).text(); - return new JQAsExpression.Pattern.ValuePattern("$" + ident); + return new JQPatterns.Pattern.ValuePattern("$" + ident); } } @@ -400,7 +443,7 @@ public class JQParser { } private @NotNull JQExpression parseVariableExpression() throws IOException { - var dollar = consume(JQTokenKind.DOLLAR); + consume(JQTokenKind.DOLLAR); var ident = consume(JQTokenKind.IDENT).text(); return new JQVariableExpression("$" + ident); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAlternativeExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAlternativeExpression.java index 35708ff..354d752 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAlternativeExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAlternativeExpression.java @@ -2,17 +2,12 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonMath; -import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.NoSuchElementException; import java.util.Objects; -import java.util.function.BiConsumer; -import java.util.function.BinaryOperator; -import java.util.function.Supplier; -import java.util.stream.Gatherer; -import java.util.stream.Stream; @SuppressWarnings("preview") public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression { @@ -22,8 +17,48 @@ public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQEx } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { - return first.evaluate(context).gather(new AlternativeGatherer(second, context)); + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { + var first = this.first.evaluate(context); + var second = this.second.evaluate(context); + return new Generator<>() { + private boolean empty = true; + + private JsonValue value; + private boolean hasValue; + + private boolean advance() { + if (hasValue) return true; + while (first.hasNext()) { + var value = first.next(); + if (JsonMath.isTruthy(value)) { + this.empty = false; + this.value = value; + this.hasValue = true; + return true; + } + } + if (empty && second.hasNext()) { + this.value = second.next(); + this.hasValue = true; + return true; + } + return false; + } + + @Override + public boolean hasNext() { + return advance(); + } + + @Override + public @Nullable JsonValue next() throws NoSuchElementException { + if (!advance()) throw new NoSuchElementException(); + var out = value; + hasValue = false; + value = null; + return out; + } + }; } @Override @@ -35,46 +70,4 @@ public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQEx public @NotNull String toString() { return first + " // " + second; } - - @NoArgsConstructor - @AllArgsConstructor - private static class State { - private boolean empty = true; - } - - private record AlternativeGatherer( - @NotNull JQExpression expression, - @NotNull Context context - ) implements Gatherer { - - @Override - public @NotNull Supplier initializer() { - return State::new; - } - - @Override - public @NotNull Integrator integrator() { - return Integrator.ofGreedy((state, element, downstream) -> { - if (JsonMath.isTruthy(element)) { - state.empty = false; - return downstream.push(element); - } - return true; - }); - } - - @Override - public @NotNull BinaryOperator combiner() { - return (state1, state2) -> new State(state1.empty && state2.empty); - } - - @Override - public @NotNull BiConsumer> finisher() { - return (state, downstream) -> { - if (!state.empty) return; - var it = expression.evaluate(context).iterator(); - while (it.hasNext() && downstream.push(it.next())); - }; - } - } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQArrayConstructionExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQArrayConstructionExpression.java index 661d14e..54bc85b 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQArrayConstructionExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQArrayConstructionExpression.java @@ -2,12 +2,11 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonArray; import eu.jonahbauer.json.JsonValue; -import eu.jonahbauer.json.query.util.Util; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Objects; -import java.util.stream.Stream; public record JQArrayConstructionExpression(@NotNull JQExpression expression) implements JQExpression { public JQArrayConstructionExpression { @@ -15,8 +14,8 @@ public record JQArrayConstructionExpression(@NotNull JQExpression expression) im } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { - return Util.lazy(() -> new JsonArray(expression.evaluate(context).toList())); + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { + return Generator.of(() -> new JsonArray(expression.evaluate(context).toList())); } @Override diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAsExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAsExpression.java index 8cef3cb..bd0f6c3 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAsExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAsExpression.java @@ -1,57 +1,26 @@ package eu.jonahbauer.json.query.parser.ast; -import eu.jonahbauer.json.*; -import eu.jonahbauer.json.query.JsonMath; -import eu.jonahbauer.json.query.JsonQueryException; -import eu.jonahbauer.json.query.util.Util; +import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.Objects; public record JQAsExpression( - @NotNull JQExpression variable, @NotNull List<@NotNull Pattern> patterns, @NotNull JQExpression expression + @NotNull JQExpression variable, @NotNull JQPatterns patterns, @NotNull JQExpression expression ) implements JQExpression { public JQAsExpression { Objects.requireNonNull(variable); Objects.requireNonNull(expression); - - patterns = List.copyOf(patterns); + Objects.requireNonNull(patterns); } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { - var variables = new HashMap(); - patterns.stream().map(Pattern::variables).flatMap(Set::stream).forEach(key -> variables.put(key, null)); - + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return variable.evaluate(context) - .flatMap(value -> { - Stream> result = null; - - // find first pattern that does not throw - var it = patterns.iterator(); - while (it.hasNext()) { - try { - result = it.next().bind(context, value); - } catch (JsonQueryException ex) { - if (!it.hasNext()) throw ex; - } - } - - // execute expression for all possible pattern matches - assert result != null; - return result - .map(vars -> { - var out = new HashMap<>(variables); - out.putAll(vars); - return context.withVariables(out); - }) - .flatMap(expression::evaluate); - }); + .flatMap(value -> patterns.bind(context, value).map(context::withVariables).flatMap(expression::evaluate)); } @Override @@ -61,113 +30,7 @@ public record JQAsExpression( @Override public @NotNull String toString() { - return variable - + " as " + patterns.stream().map(Objects::toString).collect(Collectors.joining(" ?// ")) - + " | " + expression; + return variable + " as " + patterns + " | " + expression; } - public sealed interface Pattern { - @NotNull Set<@NotNull String> variables(); - @NotNull Stream> bind(@NotNull Context context, @Nullable JsonValue value); - - record ValuePattern(@NotNull String name) implements Pattern { - public ValuePattern { - Objects.requireNonNull(name); - if (!name.startsWith("$")) throw new IllegalArgumentException(); - } - - @Override - public @NotNull Set<@NotNull String> variables() { - return Set.of(name); - } - - @Override - public @NotNull Stream> bind(@NotNull Context context, @Nullable JsonValue value) { - var map = new HashMap(); - map.put(name, value); - return Stream.of(Collections.unmodifiableMap(map)); - } - - @Override - public @NotNull String toString() { - return name; - } - } - - record ArrayPattern(@NotNull List<@NotNull Pattern> patterns) implements Pattern { - public ArrayPattern { - patterns = List.copyOf(patterns); - if (patterns.isEmpty()) throw new IllegalArgumentException(); - } - - @Override - public @NotNull Set<@NotNull String> variables() { - var out = new HashSet(); - patterns.forEach(p -> out.addAll(p.variables())); - return Collections.unmodifiableSet(out); - } - - @Override - public @NotNull Stream> bind(@NotNull Context context, @Nullable JsonValue value) { - var streams = new ArrayList>>>(); - - for (int i = 0; i < patterns.size(); i++) { - var k = new JsonNumber(i); - var v = JsonMath.index(value, k); - - var pattern = patterns.get(i); - streams.add(() -> pattern.bind(context, v)); - } - - return Util.crossReversed(streams).map(list -> { - var map = new HashMap(); - list.forEach(map::putAll); - return map; - }); - } - - @Override - public @NotNull String toString() { - return patterns.stream().map(Objects::toString).collect(Collectors.joining(", ", "[", "]")); - } - } - - record ObjectPattern(@NotNull SequencedMap<@NotNull JQExpression, @NotNull Pattern> patterns) implements Pattern { - - public ObjectPattern { - patterns = Collections.unmodifiableSequencedMap(new LinkedHashMap<>(patterns)); - if (patterns.isEmpty()) throw new IllegalArgumentException(); - } - - @Override - public @NotNull Set<@NotNull String> variables() { - var out = new HashSet(); - patterns.values().forEach(p -> out.addAll(p.variables())); - return Collections.unmodifiableSet(out); - } - - @Override - public @NotNull Stream> bind(@NotNull Context context, @Nullable JsonValue value) { - var streams = new ArrayList>>>(); - - this.patterns.reversed().forEach((keyExpression, pattern) -> streams.add(() -> { - var keyStream = keyExpression.evaluate(context); - return keyStream.flatMap(key -> pattern.bind(context, JsonMath.index(value, key))); - })); - - return Util.cross(streams).map(list -> { - var map = new HashMap(); - list.reversed().forEach(map::putAll); - return map; - }); - } - - @Override - public @NotNull String toString() { - var out = new StringJoiner(", ", "{", "}"); - patterns.forEach((key, pattern) -> out.add(key + ": " + pattern)); - return out.toString(); - } - } - } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignment.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignment.java index df5e544..fcc9c22 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignment.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignment.java @@ -2,6 +2,7 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonMath; +import eu.jonahbauer.json.query.impl.Generator; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,7 +43,7 @@ public record JQAssignment(@NotNull JQExpression target, @NotNull JQExpression v } @Override - public @NotNull Stream evaluate(@NotNull Context context) { + public @NotNull Generator evaluate(@NotNull Context context) { throw new UnsupportedOperationException("not yet implemented"); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignmentCoerce.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignmentCoerce.java index 5583b58..58ef049 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignmentCoerce.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignmentCoerce.java @@ -1,6 +1,7 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -14,7 +15,7 @@ public record JQAssignmentCoerce(@NotNull JQExpression target, @NotNull JQExpres } @Override - public @NotNull Stream evaluate(@NotNull Context context) { + public @NotNull Generator evaluate(@NotNull Context context) { throw new UnsupportedOperationException("not yet implemented"); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignmentPipe.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignmentPipe.java index f0865d5..dfc90c6 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignmentPipe.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQAssignmentPipe.java @@ -1,10 +1,10 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import java.util.Objects; -import java.util.stream.Stream; public record JQAssignmentPipe(@NotNull JQExpression target, @NotNull JQExpression value) implements JQExpression { @@ -14,7 +14,7 @@ public record JQAssignmentPipe(@NotNull JQExpression target, @NotNull JQExpressi } @Override - public @NotNull Stream evaluate(@NotNull Context context) { + public @NotNull Generator evaluate(@NotNull Context context) { throw new UnsupportedOperationException("not yet implemented"); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBinaryExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBinaryExpression.java index 96351ce..dc92a72 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBinaryExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBinaryExpression.java @@ -2,6 +2,7 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonMath; +import eu.jonahbauer.json.query.impl.Generator; import eu.jonahbauer.json.query.util.Util; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; @@ -10,7 +11,6 @@ import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Objects; import java.util.function.BinaryOperator; -import java.util.stream.Stream; public record JQBinaryExpression(@NotNull JQExpression first, @NotNull JQExpression second, @NotNull Operator operator) implements JQExpression { public static @NotNull JQBinaryExpression add(@NotNull JQExpression first, @NotNull JQExpression second) { @@ -64,7 +64,7 @@ public record JQBinaryExpression(@NotNull JQExpression first, @NotNull JQExpress } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return Util.crossReversed(List.of(first, second), context) .map(values -> operator.apply(values.getFirst(), values.getLast())); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBooleanAndExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBooleanAndExpression.java index 68496d4..c36dfd7 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBooleanAndExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBooleanAndExpression.java @@ -3,10 +3,10 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonBoolean; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonMath; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import java.util.Objects; -import java.util.stream.Stream; public record JQBooleanAndExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression { @@ -16,10 +16,10 @@ public record JQBooleanAndExpression(@NotNull JQExpression first, @NotNull JQExp } @Override - public @NotNull Stream evaluate(@NotNull Context context) { + public @NotNull Generator evaluate(@NotNull Context context) { return first.evaluate(context) .flatMap(value -> JsonMath.isFalsy(value) - ? Stream.of(JsonBoolean.FALSE) + ? Generator.of(JsonBoolean.FALSE) : second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf) ); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBooleanOrExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBooleanOrExpression.java index cd753d9..944c8a2 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBooleanOrExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBooleanOrExpression.java @@ -3,6 +3,7 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonBoolean; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonMath; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -16,10 +17,10 @@ public record JQBooleanOrExpression(@NotNull JQExpression first, @NotNull JQExpr } @Override - public @NotNull Stream evaluate(@NotNull Context context) { + public @NotNull Generator evaluate(@NotNull Context context) { return first.evaluate(context) .flatMap(value -> JsonMath.isTruthy(value) - ? Stream.of(JsonBoolean.TRUE) + ? Generator.of(JsonBoolean.TRUE) : second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf) ); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBuiltIn.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBuiltIn.java index 9511680..65f9408 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBuiltIn.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQBuiltIn.java @@ -3,30 +3,33 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.*; import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonQueryException; +import eu.jonahbauer.json.query.impl.Generator; +import eu.jonahbauer.json.query.parser.JQParser; +import eu.jonahbauer.json.query.parser.ast.JQExpression.Context; import eu.jonahbauer.json.query.util.Util; +import lombok.SneakyThrows; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.util.*; -import java.util.function.Function; +import java.util.function.*; import java.util.stream.Collectors; -import java.util.stream.Gatherer; -import java.util.stream.Stream; public enum JQBuiltIn implements JQInvocable { - ABS(0, (context, _) -> context.stream().map(JsonMath::abs)), + ABS(0, Implementation.map$V(JsonMath::abs)), ADD(0, (context, _) -> context.stream().map(value -> JsonMath.values(value).reduce(null, JsonMath::add))), - NOT(0, (context, _) -> context.stream().map(JsonMath::not)), + NOT(0, Implementation.map$V(JsonMath::not)), // error handling ERROR$0(0, (context, _) -> context.stream().flatMap(JsonMath::error)), ERROR$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(JsonMath::error)), - HALT(0, (_, _) -> Stream.of((JsonValue) null).flatMap(_ -> JsonMath.halt())), + HALT(0, (_, _) -> Generator.of((JsonValue) null).flatMap(_ -> JsonMath.halt())), HALT_ERROR$0(0, (context, _) -> context.stream().flatMap(JsonMath::halt)), HALT_ERROR$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(value -> JsonMath.halt(context.root(), value))), // stream operations - EMPTY(0, (_, _) -> Stream.empty()), + EMPTY(0, (_, _) -> Generator.empty()), RANGE$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(JsonMath::range)), RANGE$2(2, (context, args) -> Util.cross(args, context).flatMap(bounds -> JsonMath.range(bounds.get(0), bounds.get(1)))), RANGE$3(3, (context, args) -> Util.cross(args, context).flatMap(bounds -> JsonMath.range(bounds.get(0), bounds.get(1), bounds.get(2)))), @@ -34,113 +37,84 @@ public enum JQBuiltIn implements JQInvocable { FIRST$1(1, (context, args) -> JsonMath.first(args.getFirst().evaluate(context))), LAST$1(1, (context, args) -> JsonMath.last(args.getFirst().evaluate(context))), NTH$2(2, (context, args) -> args.getFirst().evaluate(context).flatMap(n -> JsonMath.nth(args.getLast().evaluate(context), n))), + SELECT(1, Implementation.mapF$F(JsonMath::select)), // iterable operations - MAP(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.map(value, filter)); - }), - MAP_VALUES(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.mapValues(value, filter)); - }), - KEYS(0, (context, _) -> context.stream().map(JsonMath::keys)), - KEYS_UNSORTED(0, (context, _) -> context.stream().map(JsonMath::keysUnsorted)), - HAS(1, (context, args) -> args.getFirst().evaluate(context).map(index -> JsonMath.has(context.root(), index))), - IN(1, (context, args) -> args.getFirst().evaluate(context).map(value -> JsonMath.in(context.root(), value))), - FIRST$0(0, (context, _) -> context.stream().map(value -> JsonMath.index(value, JsonNumber.ZERO))), - LAST$0(0, (context, _) -> context.stream().map(value -> JsonMath.index(value, new JsonNumber(-1)))), - NTH$1(1, (context, args) -> args.getFirst().evaluate(context).map(index -> JsonMath.index(context.root(), index))), - ANY$0(0, (context, _) -> context.stream().map(JsonMath::any)), - ANY$1(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.any(value, filter)); - }), - ANY$2(2, (context, args) -> { - var filter = args.getFirst().bind(context); - return Util.lazy(() -> JsonMath.any(args.getFirst().evaluate(context), filter)); - }), - ALL$0(0, (context, _) -> context.stream().map(JsonMath::all)), - ALL$1(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.all(value, filter)); - }), - ALL$2(2, (context, args) -> { - var filter = args.getFirst().bind(context); - return Util.lazy(() -> JsonMath.all(args.getFirst().evaluate(context), filter)); - }), - FLATTEN$0(0, (context, _) -> context.stream().map(JsonMath::flatten)), - FLATTEN$1(1, (context, args) -> context.stream().flatMap(value -> args.getFirst().evaluate(context).map(depth -> JsonMath.flatten(value, depth)))), - SORT(0, (context, _) -> context.stream().map(JsonMath::sort)), - SORT_BY(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.sort(value, filter)); - }), - MIN(0, (context, _) -> context.stream().map(JsonMath::min)), - MIN_BY(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.min(value, filter)); - }), - MAX(0, (context, _) -> context.stream().map(JsonMath::max)), - MAX_BY(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.max(value, filter)); - }), - UNIQUE(0, (context, _) -> context.stream().map(JsonMath::unique)), - UNIQUE_BY(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.unique(value, filter)); - }), - GROUP_BY(1, (context, args) -> { - var filter = args.getFirst().bind(context); - return context.stream().map(value -> JsonMath.group(value, filter)); - }), - REVERSE(0, (context, _) -> context.stream().map(JsonMath::reverse)), - CONTAINS(1, (context, args) -> args.getFirst().evaluate(context).map(content -> JsonMath.contains(context.root(), content))), - INDICES(1, (context, args) -> args.getFirst().evaluate(context).map(content -> JsonMath.indices(context.root(), content))), - INDEX(1, (context, args) -> args.getFirst().evaluate(context).map(content -> JsonMath.firstindex(context.root(), content))), - RINDEX(1, (context, args) -> args.getFirst().evaluate(context).map(content -> JsonMath.lastindex(context.root(), content))), - INSIDE(1, (context, args) -> args.getFirst().evaluate(context).map(content -> JsonMath.inside(context.root(), content))), - COMBINATIONS$0(0, (context, _) -> context.stream().flatMap(JsonMath::combinations)), - COMBINATIONS$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(n -> JsonMath.combinations(context.root(), n))), - BSEARCH(1, (context, args) -> args.getFirst().evaluate(context).map(value -> JsonMath.bsearch(context.root(), value))), - TRANSPOSE(0, (context, _) -> context.stream().map(JsonMath::transpose)), + MAP(1, Implementation.mapF$V(JsonMath::map)), + MAP_VALUES(1, Implementation.mapF$V(JsonMath::mapValues)), + KEYS(0, Implementation.map$V(JsonMath::keys)), + KEYS_UNSORTED(0, Implementation.map$V(JsonMath::keysUnsorted)), + HAS(1, Implementation.mapV$V(JsonMath::has)), + IN(1, Implementation.mapV$V(JsonMath::in)), + FIRST$0(0, Implementation.map$V(JsonMath::first)), + LAST$0(0, Implementation.map$V(JsonMath::last)), + NTH$1(1, Implementation.mapV$V(JsonMath::index)), + ANY$0(0, Implementation.map$V(JsonMath::any)), + ANY$1(1, Implementation.mapF$V(JsonMath::any)), + ANY$2(2, Implementation.mapFF$V((root, gen, filter) -> JsonMath.any(gen.apply(root), filter))), + ALL$0(0, Implementation.map$V(JsonMath::all)), + ALL$1(1, Implementation.mapF$V(JsonMath::all)), + ALL$2(2, Implementation.mapFF$V((root, gen, filter) -> JsonMath.all(gen.apply(root), filter))), + FLATTEN$0(0, Implementation.map$V(JsonMath::flatten)), + FLATTEN$1(1, Implementation.mapV$V(JsonMath::flatten)), + SORT(0, Implementation.map$V(JsonMath::sort)), + SORT_BY(1, Implementation.mapF$V(JsonMath::sort)), + MIN(0, Implementation.map$V(JsonMath::min)), + MIN_BY(1, Implementation.mapF$V(JsonMath::min)), + MAX(0, Implementation.map$V(JsonMath::max)), + MAX_BY(1, Implementation.mapF$V(JsonMath::max)), + UNIQUE(0, Implementation.map$V(JsonMath::unique)), + UNIQUE_BY(1, Implementation.mapF$V(JsonMath::unique)), + GROUP_BY(1, Implementation.mapF$V(JsonMath::group)), + REVERSE(0, Implementation.map$V(JsonMath::reverse)), + CONTAINS(1, Implementation.mapV$V(JsonMath::contains)), + INDICES(1, Implementation.mapV$V(JsonMath::indices)), + INDEX(1, Implementation.mapV$V(JsonMath::firstindex)), + RINDEX(1, Implementation.mapV$V(JsonMath::lastindex)), + INSIDE(1, Implementation.mapV$V(JsonMath::inside)), + COMBINATIONS$0(0, Implementation.map$F(JsonMath::combinations)), + COMBINATIONS$1(1, Implementation.mapV$F(JsonMath::combinations)), + BSEARCH(1, Implementation.mapV$V(JsonMath::bsearch)), + TRANSPOSE(0, Implementation.map$V(JsonMath::transpose)), + TO_ENTRIES(0, Implementation.map$V(JsonMath::toEntries)), + FROM_ENTRIES(0, Implementation.map$V(JsonMath::fromEntries)), + WITH_ENTRIES(1, Implementation.mapF$V(JsonMath::withEntries)), // filters - ARRAYS(0, (context, _) -> context.stream().filter(JsonMath::isArray)), - OBJECTS(0, (context, _) -> context.stream().filter(JsonMath::isObject)), - ITERABLES(0, (context, _) -> context.stream().filter(JsonMath::isIterable)), - BOOLEANS(0, (context, _) -> context.stream().filter(JsonMath::isBoolean)), - NUMBERS(0, (context, _) -> context.stream().filter(JsonMath::isNumber)), - NORMALS(0, (context, _) -> context.stream().filter(JsonMath::isNormal)), - FINITES(0, (context, _) -> context.stream().filter(JsonMath::isFinite)), - STRINGS(0, (context, _) -> context.stream().filter(JsonMath::isString)), - NULLS(0, (context, _) -> context.stream().filter(JsonMath::isNull)), - VALUES(0, (context, _) -> context.stream().filter(JsonMath::isValue)), - SCALARS(0, (context, _) -> context.stream().filter(JsonMath::isScalar)), + ARRAYS(0, Implementation.filter(JsonMath::isArray0)), + OBJECTS(0, Implementation.filter(JsonMath::isObject0)), + ITERABLES(0, Implementation.filter(JsonMath::isIterable0)), + BOOLEANS(0, Implementation.filter(JsonMath::isBoolean0)), + NUMBERS(0, Implementation.filter(JsonMath::isNumber0)), + NORMALS(0, Implementation.filter(JsonMath::isNormal0)), + FINITES(0, Implementation.filter(JsonMath::isFinite0)), + STRINGS(0, Implementation.filter(JsonMath::isString0)), + NULLS(0, Implementation.filter(JsonMath::isNull0)), + VALUES(0, Implementation.filter(JsonMath::isValue0)), + SCALARS(0, Implementation.filter(JsonMath::isScalar0)), // checks - ISINFINITE(0, (context, _) -> context.stream().map(JsonMath::isInfinite).map(JsonBoolean::valueOf)), - ISNAN(0, (context, _) -> context.stream().map(JsonMath::isNan).map(JsonBoolean::valueOf)), - ISFINITE(0, (context, _) -> context.stream().map(JsonMath::isFinite).map(JsonBoolean::valueOf)), - ISNORMAL(0, (context, _) -> context.stream().map(JsonMath::isNormal).map(JsonBoolean::valueOf)), - ISEMPTY(1, (context, args) -> Util.lazy(() -> JsonBoolean.valueOf(JsonMath.isEmpty(args.getFirst().evaluate(context))))), + ISINFINITE(0, Implementation.map$V(JsonMath::isInfinite)), + ISNAN(0, Implementation.map$V(JsonMath::isNan)), + ISFINITE(0, Implementation.map$V(JsonMath::isFinite)), + ISNORMAL(0, Implementation.map$V(JsonMath::isNormal)), + ISEMPTY(1, (context, args) -> Generator.of(() -> JsonMath.isEmpty(args.getFirst().evaluate(context)))), // string operations - TRIM(0, (context, _) -> context.stream().map(JsonMath::trim)), - LTRIM(0, (context, _) -> context.stream().map(JsonMath::ltrim)), - RTRIM(0, (context, _) -> context.stream().map(JsonMath::rtrim)), - LTRIMSTR(1, (context, args) -> args.getFirst().evaluate(context).map(prefix -> JsonMath.ltrimstr(context.root(), prefix))), - RTRIMSTR(1, (context, args) -> args.getFirst().evaluate(context).map(suffix -> JsonMath.rtrimstr(context.root(), suffix))), - SPLIT$1(1, (context, args) -> args.getFirst().evaluate(context).map(separator -> JsonMath.split(context.root(), separator))), - JOIN(1, (context, args) -> args.getFirst().evaluate(context).map(separator -> JsonMath.join(context.root(), separator))), - IMPLODE(0, (context, _) -> context.stream().map(JsonMath::implode)), - EXPLODE(0, (context, _) -> context.stream().map(JsonMath::explode)), - ASCII_UPCASE(0, (context, _) -> context.stream().map(JsonMath::asciiUpcase)), - ASCII_DOWNCASE(0, (context, _) -> context.stream().map(JsonMath::asciiDowncase)), - UTF8BYTELENGTH(0, (context, _) -> context.stream().map(JsonMath::utf8ByteLength)), - STARTSWITH(1, (context, args) -> args.getFirst().evaluate(context).map(prefix -> JsonMath.startswith(context.root(), prefix))), - ENDSWITH(1, (context, args) -> args.getFirst().evaluate(context).map(prefix -> JsonMath.endswith(context.root(), prefix))), + TRIM(0, Implementation.map$V(JsonMath::trim)), + LTRIM(0, Implementation.map$V(JsonMath::ltrim)), + RTRIM(0, Implementation.map$V(JsonMath::rtrim)), + LTRIMSTR(1, Implementation.mapV$V(JsonMath::ltrimstr)), + RTRIMSTR(1, Implementation.mapV$V(JsonMath::rtrimstr)), + SPLIT$1(1, Implementation.mapV$V(JsonMath::split)), + JOIN(1, Implementation.mapV$V(JsonMath::join)), + IMPLODE(0, Implementation.map$V(JsonMath::implode)), + EXPLODE(0, Implementation.map$V(JsonMath::explode)), + ASCII_UPCASE(0, Implementation.map$V(JsonMath::asciiUpcase)), + ASCII_DOWNCASE(0, Implementation.map$V(JsonMath::asciiDowncase)), + UTF8BYTELENGTH(0, Implementation.map$V(JsonMath::utf8ByteLength)), + STARTSWITH(1, Implementation.mapV$V(JsonMath::startswith)), + ENDSWITH(1, Implementation.mapV$V(JsonMath::endswith)), // regex TEST$1(1, JsonMath::test), @@ -160,76 +134,83 @@ public enum JQBuiltIn implements JQInvocable { GSUB$3(3, JsonMath::gsub), // conversions - TYPE(0, (context, _) -> context.stream().map(JsonMath::type)), - TOJSON(0, (context, _) -> context.stream().map(JsonMath::tojson)), - TOSTRING(0, (context, _) -> context.stream().map(JsonMath::tostring)), - TONUMBER(0, (context, _) -> context.stream().map(JsonMath::tonumber)), - FROMJSON(0, (context, _) -> context.stream().map(JsonMath::fromjson)), + TYPE(0, Implementation.map$V(JsonMath::type)), + TOJSON(0, Implementation.map$V(JsonMath::tojson)), + TOSTRING(0, Implementation.map$V(JsonMath::tostring)), + TONUMBER(0, Implementation.map$V(JsonMath::tonumber)), + FROMJSON(0, Implementation.map$V(JsonMath::fromjson)), + + // time + FROMDATEISO8601(0, Implementation.map$V(JsonMath::fromdateiso8601)), + FROMDATE(0, Implementation.map$V(JsonMath::fromdate)), + TODATEISO8601(0, Implementation.map$V(JsonMath::todateiso8601)), + TODATE(0, Implementation.map$V(JsonMath::todate)), + NOW(0, Implementation.map$V(_ -> JsonNumber.valueOf(System.currentTimeMillis()))), // misc - LENGTH(0, (context, _) -> context.stream().map(JsonMath::length)), - REPEAT(1, (context, args) -> Stream.generate(() -> args.getFirst().evaluate(context)).flatMap(Function.identity())), + LENGTH(0, Implementation.map$V(JsonMath::length)), + REPEAT(1, (context, args) -> JsonMath.repeat(() -> args.getFirst().evaluate(context))), // math library - ACOS(0, (context, _) -> context.stream().map(JsonMath::acos)), - ACOSH(0, (context, _) -> context.stream().map(JsonMath::acosh)), - ASIN(0, (context, _) -> context.stream().map(JsonMath::asin)), - ASINH(0, (context, _) -> context.stream().map(JsonMath::asinh)), - ATAN(0, (context, _) -> context.stream().map(JsonMath::atan)), - ATANH(0, (context, _) -> context.stream().map(JsonMath::atanh)), - CBRT(0, (context, _) -> context.stream().map(JsonMath::cbrt)), - CEIL(0, (context, _) -> context.stream().map(JsonMath::ceil)), - COS(0, (context, _) -> context.stream().map(JsonMath::cos)), - COSH(0, (context, _) -> context.stream().map(JsonMath::cosh)), - ERF(0, (context, _) -> context.stream().map(JsonMath::erf)), - ERFC(0, (context, _) -> context.stream().map(JsonMath::erfc)), - EXP(0, (context, _) -> context.stream().map(JsonMath::exp)), - EXP10(0, (context, _) -> context.stream().map(JsonMath::exp10)), - EXP2(0, (context, _) -> context.stream().map(JsonMath::exp2)), - EXPM1(0, (context, _) -> context.stream().map(JsonMath::expm1)), - FABS(0, (context, _) -> context.stream().map(JsonMath::fabs)), - FLOOR(0, (context, _) -> context.stream().map(JsonMath::floor)), - GAMMA(0, (context, _) -> context.stream().map(JsonMath::gamma)), - J0(0, (context, _) -> context.stream().map(JsonMath::j0)), - J1(0, (context, _) -> context.stream().map(JsonMath::j1)), - LGAMMA(0, (context, _) -> context.stream().map(JsonMath::lgamma)), - LOG(0, (context, _) -> context.stream().map(JsonMath::log)), - LOG10(0, (context, _) -> context.stream().map(JsonMath::log10)), - LOG1P(0, (context, _) -> context.stream().map(JsonMath::log1p)), - LOG2(0, (context, _) -> context.stream().map(JsonMath::log2)), - LOGB(0, (context, _) -> context.stream().map(JsonMath::logb)), - NEARBYINT(0, (context, _) -> context.stream().map(JsonMath::nearbyint)), - RINT(0, (context, _) -> context.stream().map(JsonMath::rint)), - ROUND(0, (context, _) -> context.stream().map(JsonMath::round)), - SIGNIFICAND(0, (context, _) -> context.stream().map(JsonMath::significand)), - SIN(0, (context, _) -> context.stream().map(JsonMath::sin)), - SINH(0, (context, _) -> context.stream().map(JsonMath::sinh)), - SQRT(0, (context, _) -> context.stream().map(JsonMath::sqrt)), - TAN(0, (context, _) -> context.stream().map(JsonMath::tan)), - TANH(0, (context, _) -> context.stream().map(JsonMath::tanh)), - TGAMMA(0, (context, _) -> context.stream().map(JsonMath::tgamma)), - TRUNC(0, (context, _) -> context.stream().map(JsonMath::trunc)), - Y0(0, (context, _) -> context.stream().map(JsonMath::y0)), - Y1(0, (context, _) -> context.stream().map(JsonMath::y1)), - ATAN2(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.atan2(values.get(0), values.get(1)))), - COPYSIGN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.copysign(values.get(0), values.get(1)))), - DREM(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.drem(values.get(0), values.get(1)))), - FDIM(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fdim(values.get(0), values.get(1)))), - FMAX(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fmax(values.get(0), values.get(1)))), - FMIN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fmin(values.get(0), values.get(1)))), - FMOD(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fmod(values.get(0), values.get(1)))), - FREXP(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.frexp(values.get(0), values.get(1)))), - HYPOT(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.hypot(values.get(0), values.get(1)))), - JN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.jn(values.get(0), values.get(1)))), - LDEXP(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.ldexp(values.get(0), values.get(1)))), - MODF(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.modf(values.get(0), values.get(1)))), - NEXTAFTER(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.nextafter(values.get(0), values.get(1)))), - NEXTTOWARD(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.nexttoward(values.get(0), values.get(1)))), - POW(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.pow(values.get(0), values.get(1)))), - REMAINDER(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.remainder(values.get(0), values.get(1)))), - SCALB(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.scalb(values.get(0), values.get(1)))), - SCALBLN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.scalbln(values.get(0), values.get(1)))), - YN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.yn(values.get(0), values.get(1)))), + ACOS(0, Implementation.map$V(JsonMath::acos)), + ACOSH(0, Implementation.map$V(JsonMath::acosh)), + ASIN(0, Implementation.map$V(JsonMath::asin)), + ASINH(0, Implementation.map$V(JsonMath::asinh)), + ATAN(0, Implementation.map$V(JsonMath::atan)), + ATANH(0, Implementation.map$V(JsonMath::atanh)), + CBRT(0, Implementation.map$V(JsonMath::cbrt)), + CEIL(0, Implementation.map$V(JsonMath::ceil)), + COS(0, Implementation.map$V(JsonMath::cos)), + COSH(0, Implementation.map$V(JsonMath::cosh)), + ERF(0, Implementation.map$V(JsonMath::erf)), + ERFC(0, Implementation.map$V(JsonMath::erfc)), + EXP(0, Implementation.map$V(JsonMath::exp)), + EXP10(0, Implementation.map$V(JsonMath::exp10)), + EXP2(0, Implementation.map$V(JsonMath::exp2)), + EXPM1(0, Implementation.map$V(JsonMath::expm1)), + FABS(0, Implementation.map$V(JsonMath::fabs)), + FLOOR(0, Implementation.map$V(JsonMath::floor)), + GAMMA(0, Implementation.map$V(JsonMath::gamma)), + J0(0, Implementation.map$V(JsonMath::j0)), + J1(0, Implementation.map$V(JsonMath::j1)), + LGAMMA(0, Implementation.map$V(JsonMath::lgamma)), + LOG(0, Implementation.map$V(JsonMath::log)), + LOG10(0, Implementation.map$V(JsonMath::log10)), + LOG1P(0, Implementation.map$V(JsonMath::log1p)), + LOG2(0, Implementation.map$V(JsonMath::log2)), + LOGB(0, Implementation.map$V(JsonMath::logb)), + NEARBYINT(0, Implementation.map$V(JsonMath::nearbyint)), + RINT(0, Implementation.map$V(JsonMath::rint)), + ROUND(0, Implementation.map$V(JsonMath::round)), + SIGNIFICAND(0, Implementation.map$V(JsonMath::significand)), + SIN(0, Implementation.map$V(JsonMath::sin)), + SINH(0, Implementation.map$V(JsonMath::sinh)), + SQRT(0, Implementation.map$V(JsonMath::sqrt)), + TAN(0, Implementation.map$V(JsonMath::tan)), + TANH(0, Implementation.map$V(JsonMath::tanh)), + TGAMMA(0, Implementation.map$V(JsonMath::tgamma)), + TRUNC(0, Implementation.map$V(JsonMath::trunc)), + Y0(0, Implementation.map$V(JsonMath::y0)), + Y1(0, Implementation.map$V(JsonMath::y1)), + ATAN2(2, Implementation.mapVV$V((_, a, b) -> JsonMath.atan2(a, b))), + COPYSIGN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.copysign(a, b))), + DREM(2, Implementation.mapVV$V((_, a, b) -> JsonMath.drem(a, b))), + FDIM(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fdim(a, b))), + FMAX(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmax(a, b))), + FMIN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmin(a, b))), + FMOD(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmod(a, b))), + FREXP(2, Implementation.mapVV$V((_, a, b) -> JsonMath.frexp(a, b))), + HYPOT(2, Implementation.mapVV$V((_, a, b) -> JsonMath.hypot(a, b))), + JN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.jn(a, b))), + LDEXP(2, Implementation.mapVV$V((_, a, b) -> JsonMath.ldexp(a, b))), + MODF(2, Implementation.mapVV$V((_, a, b) -> JsonMath.modf(a, b))), + NEXTAFTER(2, Implementation.mapVV$V((_, a, b) -> JsonMath.nextafter(a, b))), + NEXTTOWARD(2, Implementation.mapVV$V((_, a, b) -> JsonMath.nexttoward(a, b))), + POW(2, Implementation.mapVV$V((_, a, b) -> JsonMath.pow(a, b))), + REMAINDER(2, Implementation.mapVV$V((_, a, b) -> JsonMath.remainder(a, b))), + SCALB(2, Implementation.mapVV$V((_, a, b) -> JsonMath.scalb(a, b))), + SCALBLN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.scalbln(a, b))), + YN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.yn(a, b))), FMA(3, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fma(values.get(0), values.get(1), values.get(2)))) ; @@ -239,38 +220,159 @@ public enum JQBuiltIn implements JQInvocable { Function.identity() )); - private final @NotNull String identifier; - private final int arity; - private final @NotNull Implementation implementation; + private final @NotNull JQInvocable implementation; + JQBuiltIn(@NotNull String body) { + this(List.of(), parse(body)); + } + + JQBuiltIn(@NotNull String arg0, @NotNull String body) { + this(List.of(arg0), parse(body)); + } + + JQBuiltIn(@NotNull String arg0, @NotNull String arg1, @NotNull String body) { + this(List.of(arg0, arg1), parse(body)); + } + + JQBuiltIn(@NotNull String arg0, @NotNull String arg1, @NotNull String arg2, @NotNull String body) { + this(List.of(arg0, arg1, arg2), parse(body)); + } + + JQBuiltIn(@NotNull List<@NotNull String> params, @NotNull JQExpression body) { + this.implementation = new JQFunction(getIdentifier(name()), params, body); + } + JQBuiltIn(int arity, @NotNull Implementation implementation) { - var identifier = this.name().toLowerCase(Locale.ROOT); + this.implementation = new InvocableDelegate(getIdentifier(name()), arity, implementation); + } + + @SneakyThrows(IOException.class) + private static @NotNull JQExpression parse(@NotNull String expression) { + return new JQParser(expression).parseExpression(); + } + + private static @NotNull String getIdentifier(@NotNull String name) { + var identifier = name.toLowerCase(Locale.ROOT); var idx = identifier.lastIndexOf("$"); if (idx != -1) identifier = identifier.substring(0, idx); - - this.identifier = identifier; - this.arity = arity; - this.implementation = implementation; + return identifier; } @Override - public @NotNull Stream<@Nullable JsonValue> invoke(JQExpression.@NotNull Context context, @NotNull List<@NotNull JQExpression> args) { - if (args.size() != arity) throw new JsonQueryException("invalid argument count"); + public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull Context context, @NotNull List<@NotNull JQExpression> args) { return implementation.invoke(context, args); } @Override public @NotNull String identifier() { - return identifier; + return implementation.identifier(); } @Override public int arity() { - return arity; + return implementation.arity(); + } + + private record InvocableDelegate( + @NotNull String identifier, int arity, + @NotNull Implementation delegate + ) implements JQInvocable { + + @Override + public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull Context context, @NotNull List<@NotNull JQExpression> args) { + if (args.size() != arity) throw new JsonQueryException("invalid argument count"); + return delegate.invoke(context, args); + } } @FunctionalInterface private interface Implementation { - @NotNull Stream<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args); + @NotNull Generator<@Nullable JsonValue> invoke(@NotNull Context context, @NotNull List<@NotNull JQExpression> args); + + // format: map$ + // where and use V for value, F for filter + + /** + * Produces a zero-arg implementation applying the function to the current value. + */ + static @NotNull Implementation map$F(@NotNull Function> operator) { + return (context, _) -> context.stream().flatMap(operator); + } + + /** + * Produces a zero-arg implementation applying the function to the current value. + */ + static @NotNull Implementation map$V(@NotNull Function operator) { + return (context, _) -> context.stream().map(operator); + } + + /** + * Produces a one-arg implementation applying the function to the current value and the first argument + * (passed by value). + */ + static @NotNull Implementation mapV$V(@NotNull BiFunction function) { + return (context, args) -> { + var arg = args.getFirst(); + return context.stream().flatMap(root -> arg.evaluate(context).map(value -> function.apply(root, value))); + }; + } + + /** + * Produces a one-arg implementation applying the function to the current value and the first argument + * (passed by value). + */ + static @NotNull Implementation mapV$F(@NotNull BiFunction> function) { + return (context, args) -> { + var arg = args.getFirst(); + return context.stream().flatMap(root -> arg.evaluate(context).flatMap(value -> function.apply(root, value))); + }; + } + + /** + * Produces a one-arg implementation applying the function to the current value and the first argument + * (passed as filter). + */ + static @NotNull Implementation mapF$F(@NotNull BiFunction> function) { + return (context, args) -> { + var arg = args.getFirst().bind(context); + return context.stream().flatMap(root -> function.apply(root, arg)); + }; + } + + /** + * Produces a one-arg implementation applying the function to the current value and the first argument + * (passed as filter). + */ + static @NotNull Implementation mapF$V(@NotNull BiFunction function) { + return (context, args) -> { + var arg = args.getFirst().bind(context); + return context.stream().map(root -> function.apply(root, arg)); + }; + } + + static @NotNull Implementation mapVV$V(@NotNull TriFunction function) { + return (context, args) -> { + var arg0 = args.get(0); + var arg1 = args.get(1); + return context.stream().flatMap(root -> arg0.evaluate(context).flatMap(value0 -> arg1.evaluate(context).map(value1 -> function.apply(root, value0, value1)))); + }; + } + + static @NotNull Implementation mapFF$V(@NotNull TriFunction function) { + return (context, args) -> { + var arg0 = args.get(0).bind(context); + var arg1 = args.get(1).bind(context); + return context.stream().map(root -> function.apply(root, arg0, arg1)); + }; + } + + static @NotNull Implementation filter(@NotNull Predicate<@Nullable JsonValue> predicate) { + return (context, _) -> context.stream().filter(predicate); + } + } + + @FunctionalInterface + private interface TriFunction { + R apply(S s, T t, U u); } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQCommaExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQCommaExpression.java index 7a1fed6..1a4ae9b 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQCommaExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQCommaExpression.java @@ -1,13 +1,11 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.function.Supplier; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; public record JQCommaExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression { public JQCommaExpression { @@ -16,34 +14,8 @@ public record JQCommaExpression(@NotNull JQExpression first, @NotNull JQExpressi } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { - var it = new Iterator() { - private Iterator delegate; - private Queue>> queue = new LinkedList<>(List.of( - () -> first.evaluate(context).iterator(), - () -> second.evaluate(context).iterator() - )); - - @Override - public boolean hasNext() { - if (delegate == null) delegate = queue.remove().get(); - while (!delegate.hasNext() && !queue.isEmpty()) { - delegate = queue.remove().get(); - } - return delegate.hasNext(); - } - - @Override - public JsonValue next() { - if (delegate == null) delegate = queue.remove().get(); - while (!delegate.hasNext() && !queue.isEmpty()) { - delegate = queue.remove().get(); - } - return delegate.next(); - } - }; - var spliterator = Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED); - return StreamSupport.stream(spliterator, false); + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { + return Generator.concat(first.evaluate(context), second.evaluate(context)); } @Override diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQConstant.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQConstant.java index edd2c94..efb7b16 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQConstant.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQConstant.java @@ -1,16 +1,15 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.stream.Stream; - public record JQConstant(@Nullable JsonValue value) implements JQExpression { @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { - return Stream.of(value); + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { + return Generator.of(value); } @Override diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQExpression.java index c41acfe..de5e553 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQExpression.java @@ -2,16 +2,16 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonQueryException; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; public interface JQExpression { - @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context); + @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context); boolean isConstant(); @@ -82,8 +82,8 @@ public interface JQExpression { return new Context(root, v, functions); } - public @NotNull Stream<@Nullable JsonValue> stream() { - return Stream.of(root()); + public @NotNull Generator<@Nullable JsonValue> stream() { + return Generator.of(root()); } } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFilter.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFilter.java index 54d2663..b102621 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFilter.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFilter.java @@ -1,12 +1,12 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.function.Function; -import java.util.stream.Stream; @FunctionalInterface -public interface JQFilter extends Function<@Nullable JsonValue, @NotNull Stream<@Nullable JsonValue>> { +public interface JQFilter extends Function<@Nullable JsonValue, @NotNull Generator<@Nullable JsonValue>> { } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFunction.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFunction.java index 1ed25cc..8b37438 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFunction.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFunction.java @@ -1,11 +1,12 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.JsonQueryException; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.stream.Stream; public record JQFunction(@NotNull String identifier, @NotNull List<@NotNull String> params, @NotNull JQExpression body) implements JQInvocable { public JQFunction { @@ -15,7 +16,9 @@ public record JQFunction(@NotNull String identifier, @NotNull List<@NotNull Stri } @Override - public @NotNull Stream<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> arguments) { + public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> arguments) { + if (arguments.size() != params.size()) throw new JsonQueryException("invalid argument count"); + var expression = body; var functions = new ArrayList(); @@ -24,7 +27,7 @@ public record JQFunction(@NotNull String identifier, @NotNull List<@NotNull Stri if (param.startsWith("$")) { expression = new JQAsExpression( new JQFunctionInvocation(param.substring(1), List.of()), - List.of(new JQAsExpression.Pattern.ValuePattern(param)), + new JQPatterns(List.of(new JQPatterns.Pattern.ValuePattern(param))), expression ); param = param.substring(1); diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFunctionInvocation.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFunctionInvocation.java index 474a308..85c0c28 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFunctionInvocation.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQFunctionInvocation.java @@ -1,13 +1,13 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Objects; import java.util.StringJoiner; -import java.util.stream.Stream; public record JQFunctionInvocation(@NotNull String name, @NotNull List<@NotNull JQExpression> args) implements JQExpression { public JQFunctionInvocation { @@ -16,7 +16,7 @@ public record JQFunctionInvocation(@NotNull String name, @NotNull List<@NotNull } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { var function = context.function(name, args.size()); return function.invoke(context, args); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQIndexExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQIndexExpression.java index 40b689a..d574f0e 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQIndexExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQIndexExpression.java @@ -5,14 +5,13 @@ import eu.jonahbauer.json.JsonString; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonQueryException; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Objects; -import java.util.stream.Stream; public record JQIndexExpression(@NotNull JQExpression expression, @NotNull JQExpression index, boolean optional) implements JQExpression { - public JQIndexExpression { Objects.requireNonNull(expression); Objects.requireNonNull(index); @@ -31,7 +30,7 @@ public record JQIndexExpression(@NotNull JQExpression expression, @NotNull JQExp } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return expression.evaluate(context).flatMap(value -> index.evaluate(context).mapMulti((index, downstream) -> { try { downstream.accept(JsonMath.index(value, index)); diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQInvocable.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQInvocable.java index aa923d2..c5efa87 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQInvocable.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQInvocable.java @@ -1,14 +1,15 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.stream.Stream; public interface JQInvocable { - @NotNull Stream<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args); + @NotNull + Generator<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args); int arity(); @NotNull String identifier(); diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQIterateExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQIterateExpression.java index de6a1df..4c95de8 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQIterateExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQIterateExpression.java @@ -2,11 +2,11 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonMath; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Objects; -import java.util.stream.Stream; public record JQIterateExpression(@NotNull JQExpression expression, boolean optional) implements JQExpression { public JQIterateExpression { @@ -14,7 +14,7 @@ public record JQIterateExpression(@NotNull JQExpression expression, boolean opti } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return expression.evaluate(context).flatMap(value -> JsonMath.values(value, optional)); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQLocExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQLocExpression.java index b8cc79c..5771217 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQLocExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQLocExpression.java @@ -4,15 +4,14 @@ import eu.jonahbauer.json.JsonNumber; import eu.jonahbauer.json.JsonObject; import eu.jonahbauer.json.JsonString; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.stream.Stream; - public record JQLocExpression(@NotNull String file, int line) implements JQExpression { @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { - return Stream.of(JsonObject.of( + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { + return Generator.of(JsonObject.of( "file", new JsonString(file), "line", new JsonNumber(line) )); diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQNegation.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQNegation.java index 03aede9..960f94e 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQNegation.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQNegation.java @@ -2,6 +2,7 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonMath; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,7 +15,7 @@ public record JQNegation(@NotNull JQExpression expression) implements JQExpressi } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return expression.evaluate(context).map(JsonMath::neg); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQParenthesizedExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQParenthesizedExpression.java index 63c8779..5b3b397 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQParenthesizedExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQParenthesizedExpression.java @@ -1,11 +1,11 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Objects; -import java.util.stream.Stream; public record JQParenthesizedExpression(@NotNull JQExpression expression) implements JQExpression { @@ -14,7 +14,7 @@ public record JQParenthesizedExpression(@NotNull JQExpression expression) implem } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return expression.evaluate(context); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQPipeExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQPipeExpression.java index a7ce7a6..db17df0 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQPipeExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQPipeExpression.java @@ -1,6 +1,7 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,7 +15,7 @@ public record JQPipeExpression(@NotNull JQExpression first, @NotNull JQExpressio } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return first.evaluate(context).flatMap(value -> second.evaluate(context.withRoot(value))); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQProgram.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQProgram.java index 8570ebf..e8b7ad5 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQProgram.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQProgram.java @@ -1,5 +1,7 @@ package eu.jonahbauer.json.query.parser.ast; +import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,4 +13,8 @@ public record JQProgram( @NotNull List<@NotNull JQFunction> functions, @Nullable JQExpression expression ) { + public @NotNull Generator<@Nullable JsonValue> run(@Nullable JsonValue value) { + if (expression == null) return Generator.empty(); + return expression.evaluate(new JQExpression.Context(value).withFunctions(functions)); + } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQRecursionExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQRecursionExpression.java index c473d49..864ee84 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQRecursionExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQRecursionExpression.java @@ -3,22 +3,21 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonArray; import eu.jonahbauer.json.JsonObject; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.stream.Stream; - public record JQRecursionExpression() implements JQExpression { @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return recurse(context.root()); } - private @NotNull Stream<@Nullable JsonValue> recurse(@Nullable JsonValue value) { + private @NotNull Generator<@Nullable JsonValue> recurse(@Nullable JsonValue value) { return switch (value) { - case JsonArray array -> Stream.concat(Stream.of(array), array.stream().flatMap(this::recurse)); - case JsonObject object -> Stream.concat(Stream.of(object), object.values().stream().flatMap(this::recurse)); - case null, default -> Stream.of(value); + case JsonArray array -> Generator.concat(Generator.of(array), Generator.from(array).flatMap(this::recurse)); + case JsonObject object -> Generator.concat(Generator.of(object), Generator.from(object.values()).flatMap(this::recurse)); + case null, default -> Generator.of(value); }; } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQRootExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQRootExpression.java index 47a38a5..b094793 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQRootExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQRootExpression.java @@ -1,14 +1,13 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.stream.Stream; - public record JQRootExpression() implements JQExpression { @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return context.stream(); } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQSliceExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQSliceExpression.java index b827b91..f968f44 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQSliceExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQSliceExpression.java @@ -5,6 +5,7 @@ import eu.jonahbauer.json.JsonNumber; import eu.jonahbauer.json.JsonString; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonQueryException; +import eu.jonahbauer.json.query.impl.Generator; import eu.jonahbauer.json.query.util.Util; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,29 +23,29 @@ public record JQSliceExpression(@NotNull JQExpression expression, @Nullable JQEx } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return expression.evaluate(context).flatMap(value -> switch (value) { case JsonArray array -> slice(context, "an array slice", array.size(), array::subList); case JsonString string -> slice(context, "a string slice", string.length(), string::subSequence); - case null -> Stream.of((JsonValue) null); + case null -> Generator.of((JsonValue) null); default -> { - if (optional) yield Stream.empty(); + if (optional) yield Generator.empty(); throw new JsonQueryException(STR."Cannot index \{Util.type(value)} with object."); } }); } - private @NotNull Stream<@Nullable JsonValue> slice(@NotNull Context context, @NotNull String type, int length, @NotNull BiFunction slice) { + private @NotNull Generator<@Nullable JsonValue> slice(@NotNull Context context, @NotNull String type, int length, @NotNull BiFunction slice) { return getIndices(start, context, type, length, 0) - .mapToObj(start -> getIndices(end, context, type, length, length) - .mapToObj(end -> start > end ? slice.apply(0, 0) : slice.apply(start, end)) + .map(start -> getIndices(end, context, type, length, length) + .map(end -> start > end ? slice.apply(0, 0) : slice.apply(start, end)) ) .flatMap(Function.identity()); } - private @NotNull IntStream getIndices(@Nullable JQExpression expression, @NotNull Context context, @NotNull String type, int length, int fallback) { - if (expression == null) return IntStream.of(fallback); - return expression.evaluate(context).mapToInt(value -> getIndex(value, type, length)); + private @NotNull Generator getIndices(@Nullable JQExpression expression, @NotNull Context context, @NotNull String type, int length, int fallback) { + if (expression == null) return Generator.of(fallback); + return expression.evaluate(context).map(value -> getIndex(value, type, length)); } private int getIndex(@Nullable JsonValue value, @NotNull String type, int length) { diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQStringInterpolation.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQStringInterpolation.java index ca489b5..9a83f4b 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQStringInterpolation.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQStringInterpolation.java @@ -2,6 +2,7 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonString; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import eu.jonahbauer.json.query.util.Util; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,7 +23,7 @@ public record JQStringInterpolation( } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { return Util.crossReversed(values, context) .map(values -> StringTemplate.of(fragments, values.stream().map(JQStringInterpolation::toString).toList())) .map(STR::process) diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQTryExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQTryExpression.java index 6c09ebe..26d7e6c 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQTryExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQTryExpression.java @@ -4,13 +4,12 @@ import eu.jonahbauer.json.JsonString; import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.query.JsonQueryException; import eu.jonahbauer.json.query.JsonQueryHaltException; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.Function; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; public record JQTryExpression(@NotNull JQExpression expression, @Nullable JQExpression fallback) implements JQExpression { public JQTryExpression { @@ -22,13 +21,11 @@ public record JQTryExpression(@NotNull JQExpression expression, @Nullable JQExpr } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { - var iterator = new QuietIterator( - expression.evaluate(context).iterator(), - fallback == null ? null : ex -> fallback.evaluate(context.withRoot(new JsonString(ex.getMessage()))).iterator() + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { + return new QuietIterator( + expression.evaluate(context), + fallback == null ? null : ex -> fallback.evaluate(context.withRoot(new JsonString(ex.getMessage()))) ); - var spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED); - return StreamSupport.stream(spliterator, false); } @Override @@ -41,58 +38,62 @@ public record JQTryExpression(@NotNull JQExpression expression, @Nullable JQExpr return "try " + expression + (fallback == null ? "" : " catch " + fallback); } - private static class QuietIterator implements Iterator<@Nullable JsonValue> { - private @Nullable Iterator<@Nullable JsonValue> delegate; - private @Nullable Function<@NotNull JsonQueryException, @NotNull Iterator<@Nullable JsonValue>> fallback; + private static class QuietIterator implements Generator<@Nullable JsonValue> { + private @Nullable Generator<@Nullable JsonValue> delegate; + private @Nullable Generator<@Nullable JsonValue> fallback; + private @Nullable Function<@NotNull JsonQueryException, @NotNull Generator<@Nullable JsonValue>> fallbackSupplier; - private @Nullable JsonValue next; - private boolean hasNext; - - private boolean valid = false; + private JsonValue value; + private boolean hasValue; private QuietIterator( - @NotNull Iterator<@Nullable JsonValue> delegate, - @Nullable Function<@NotNull JsonQueryException, @NotNull Iterator<@Nullable JsonValue>> fallback + @NotNull Generator<@Nullable JsonValue> delegate, + @Nullable Function<@NotNull JsonQueryException, @NotNull Generator<@Nullable JsonValue>> fallback ) { this.delegate = delegate; - this.fallback = fallback; + this.fallbackSupplier = fallback; } - private void ensureValid() { - if (valid) return; - - while (true) { - try { - if (delegate != null && delegate.hasNext()) { // still have values in current stream - next = delegate.next(); - hasNext = true; - } else { // end of stream - next = null; - hasNext = false; - } - break; - } catch (JsonQueryHaltException ex) { - throw ex; - } catch (JsonQueryException ex) { // switch to fallback - delegate = fallback != null ? fallback.apply(ex) : null; - fallback = null; + private boolean advance() { + if (hasValue) return true; + assert fallback == null && delegate != null; + try { + hasValue = delegate.hasNext(); + value = hasValue ? delegate.next() : null; + return hasValue; + } catch (JsonQueryHaltException ex) { + throw ex; + } catch (JsonQueryException ex) { + delegate = null; + if (fallbackSupplier != null) { + fallback = fallbackSupplier.apply(ex); + hasValue = false; + value = null; + return fallback.hasNext(); + } else { + hasValue = false; + value = null; + return false; } } - valid = true; } @Override public boolean hasNext() { - ensureValid(); - return hasNext; + if (fallback != null) return fallback.hasNext(); + if (delegate == null) return false; + return advance(); } @Override public @Nullable JsonValue next() { - ensureValid(); - if (!hasNext) throw new NoSuchElementException(); - valid = false; - return next; + if (fallback != null) return fallback.next(); + if (delegate == null) throw new NoSuchElementException(); + if (!advance()) throw new NoSuchElementException(); + var out = value; + hasValue = false; + value = null; + return out; } } } diff --git a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQVariableExpression.java b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQVariableExpression.java index c84b7a2..582bac2 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQVariableExpression.java +++ b/query/src/main/java/eu/jonahbauer/json/query/parser/ast/JQVariableExpression.java @@ -1,11 +1,11 @@ package eu.jonahbauer.json.query.parser.ast; import eu.jonahbauer.json.JsonValue; +import eu.jonahbauer.json.query.impl.Generator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Objects; -import java.util.stream.Stream; public record JQVariableExpression(@NotNull String name) implements JQExpression { public JQVariableExpression { @@ -14,8 +14,8 @@ public record JQVariableExpression(@NotNull String name) implements JQExpression } @Override - public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { - return Stream.of(context.variable(name)); + public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) { + return Generator.of(() -> context.variable(name)); } @Override diff --git a/query/src/main/java/eu/jonahbauer/json/query/util/Util.java b/query/src/main/java/eu/jonahbauer/json/query/util/Util.java index 3baf3eb..4948d1a 100644 --- a/query/src/main/java/eu/jonahbauer/json/query/util/Util.java +++ b/query/src/main/java/eu/jonahbauer/json/query/util/Util.java @@ -1,6 +1,7 @@ package eu.jonahbauer.json.query.util; import eu.jonahbauer.json.*; +import eu.jonahbauer.json.query.impl.Generator; import eu.jonahbauer.json.query.parser.ast.JQExpression; import lombok.experimental.UtilityClass; import org.jetbrains.annotations.NotNull; @@ -15,42 +16,34 @@ import java.util.stream.Stream; @UtilityClass public class Util { - public static @NotNull Stream lazy(@NotNull Supplier supplier) { - return Stream.of((Object) null).map(_ -> supplier.get()); - } - - public static @NotNull Stream lazyStream(@NotNull Supplier> supplier) { - return Stream.of((Object) null).flatMap(_ -> supplier.get()); - } - - public static @NotNull Stream<@NotNull List> cross(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) { + public static @NotNull Generator<@NotNull List> cross(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) { return cross(asSupplier(expressions, context)).map(Collections::unmodifiableList); } - public static @NotNull Stream<@NotNull List> cross(@NotNull List<@NotNull Supplier<@NotNull Stream>> expressions) { - if (expressions.isEmpty()) return Stream.of(new ArrayList().reversed()); + public static @NotNull Generator<@NotNull List> cross(@NotNull List<@NotNull Supplier<@NotNull Generator>> expressions) { + if (expressions.isEmpty()) return Generator.of(new ArrayList().reversed()); return expressions.getFirst().get() .flatMap(value -> cross(expressions.subList(1, expressions.size())) - .peek(list -> list.addFirst(value)) + .map(list -> { list.addFirst(value); return list; }) ); } - public static @NotNull Stream<@NotNull List> crossReversed(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) { + public static @NotNull Generator<@NotNull List> crossReversed(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) { return crossReversed(asSupplier(expressions, context)).map(Collections::unmodifiableList); } - public static @NotNull Stream<@NotNull List> crossReversed(@NotNull List<@NotNull Supplier<@NotNull Stream>> expressions) { - if (expressions.isEmpty()) return Stream.of(new ArrayList<>()); + public static @NotNull Generator<@NotNull List> crossReversed(@NotNull List<@NotNull Supplier<@NotNull Generator>> expressions) { + if (expressions.isEmpty()) return Generator.of(new ArrayList<>()); return expressions.getLast().get() .flatMap(value -> crossReversed(expressions.subList(0, expressions.size() - 1)) - .peek(list -> list.addLast(value)) + .map(list -> { list.addLast(value); return list; }) ); } - private static @NotNull List<@NotNull Supplier<@NotNull Stream>> asSupplier(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) { - var list = new ArrayList>>(expressions.size()); + private static @NotNull List<@NotNull Supplier<@NotNull Generator>> asSupplier(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) { + var list = new ArrayList>>(expressions.size()); expressions.forEach(expr -> list.add(() -> expr.evaluate(context))); return list; }