fixup query

This commit is contained in:
jbb01 2025-04-07 19:01:58 +02:00
parent dc5791815e
commit bdb505fce7
No known key found for this signature in database
GPG Key ID: 83C72CB6D5442CF1
40 changed files with 980 additions and 739 deletions

View File

@ -2,6 +2,7 @@ package eu.jonahbauer.json.query;
import eu.jonahbauer.json.*; import eu.jonahbauer.json.*;
import eu.jonahbauer.json.exceptions.JsonParserException; 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.parser.ast.*;
import eu.jonahbauer.json.query.util.Util; import eu.jonahbauer.json.query.util.Util;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
@ -9,11 +10,11 @@ import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.function.DoubleBinaryOperator; import java.util.function.*;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.MatchResult; import java.util.regex.MatchResult;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -236,20 +237,20 @@ public class JsonMath {
//</editor-fold> //</editor-fold>
//<editor-fold desc="error handling" defaultstate="collapsed"> //<editor-fold desc="error handling" defaultstate="collapsed">
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); 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); 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); 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) { throw new JsonQueryHaltException(value, switch (code) {
case JsonNumber(var number) -> (int) number; case JsonNumber(var number) -> (int) number;
case null, default -> throw new JsonQueryException(STR."\{Util.type(value)} (\{Util.value(value)}) halt_error/1: number required"); 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 {
//</editor-fold> //</editor-fold>
//<editor-fold desc="stream operations" defaultstate="collapsed"> //<editor-fold desc="stream operations" defaultstate="collapsed">
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); 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) { var min = switch (from) {
case JsonNumber(var number) -> number; case JsonNumber(var number) -> number;
case null, default -> throw new JsonQueryException("Range bounds must be numeric."); case null, default -> throw new JsonQueryException("Range bounds must be numeric.");
@ -271,81 +272,79 @@ public class JsonMath {
case JsonNumber(var number) -> number; case JsonNumber(var number) -> number;
case null, default -> throw new JsonQueryException("Range bounds must be numeric."); 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) { 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) { } 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 { } 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) { if (compare(limit, JsonNumber.ZERO) > 0) {
class State { return new Generator<>() {
@Nullable JsonValue limit; private @Nullable JsonValue i = limit;
public State(@Nullable JsonValue limit) { this.limit = limit; }
@Override
public boolean hasNext() {
return compare(i, JsonNumber.ZERO) > 0;
} }
return stream.gather(Gatherer.ofSequential( @Override
() -> new State(limit), public JsonValue next() {
(state, value, downstream) -> { if (compare(i, JsonNumber.ZERO) <= 0) throw new NoSuchElementException();
state.limit = JsonMath.sub(state.limit, JsonNumber.ONE); i = sub(i, JsonNumber.ONE);
downstream.push(value); return stream.next();
return compare(state.limit, JsonNumber.ZERO) > 0;
} }
)); };
} else if (Objects.equals(limit, JsonNumber.ZERO)) { } else if (Objects.equals(limit, JsonNumber.ZERO)) {
return Stream.empty(); return Generator.empty();
} else { } else {
return stream; 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); return stream.limit(1);
} }
public static @NotNull Stream<@Nullable JsonValue> last(@NotNull Stream<@Nullable JsonValue> stream) { public static @NotNull Generator<@Nullable JsonValue> last(@NotNull Generator<@Nullable JsonValue> stream) {
class State { return new Generator<>() {
boolean hasValue; @Override
@Nullable JsonValue value; public boolean hasNext() {
} return stream.hasNext();
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 Stream<@Nullable JsonValue> nth(@NotNull Stream<@Nullable JsonValue> stream, @Nullable JsonValue limit) { @Override
public JsonValue next() throws NoSuchElementException {
var out = stream.next();
while (stream.hasNext()) {
out = stream.next();
}
return out;
}
};
}
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"); if (compare(limit, JsonNumber.ZERO) < 0) throw new JsonQueryException("nth doesn't support negative indices");
class State { return Generator.of(() -> {
@Nullable JsonValue limit; for (var i = sub(add(limit, JsonNumber.ONE), JsonNumber.ONE); compare(i, JsonNumber.ZERO) > 0; i = sub(i, JsonNumber.ONE)) {
public State(@Nullable JsonValue limit) { this.limit = limit; } stream.next();
} }
return stream.gather(Gatherer.ofSequential( return stream.next();
() -> 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;
} }
}
)); 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);
});
} }
//</editor-fold> //</editor-fold>
@ -430,16 +429,24 @@ public class JsonMath {
return has(value, index); 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); 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) { return switch (value) {
case JsonArray array -> array.stream(); case JsonArray array -> Generator.from(array);
case JsonObject object -> object.values().stream(); case JsonObject object -> Generator.from(object.values());
case null, default -> { 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)})."); 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) { public static @NotNull JsonValue mapValues(@Nullable JsonValue value, @NotNull JQFilter expression) {
return switch (value) { 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 -> { case JsonObject object -> {
var out = new LinkedHashMap<String, JsonValue>(); var out = new LinkedHashMap<String, JsonValue>();
object.forEach((key, v) -> expression.apply(v).limit(1).forEach(newValue -> out.put(key, newValue))); 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)); 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)); 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)); 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)); 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); 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) { return switch (value) {
case JsonArray array when depth == 0 -> Stream.of(array); case JsonArray array when depth == 0 -> Generator.of(array);
case JsonArray array -> { case JsonArray array -> {
var d = depth < 0 ? depth : depth - 1; 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); return contains(container, content);
} }
public static @NotNull Stream<@NotNull JsonArray> combinations(@Nullable JsonValue value) { public static @NotNull Generator<@NotNull JsonArray> combinations(@Nullable JsonValue value) {
if (length0(value) == 0) return Stream.of(JsonArray.EMPTY); if (length0(value) == 0) return Generator.of(JsonArray.EMPTY);
if (!(value instanceof JsonArray array)) throw indexError(value, JsonNumber.ZERO); if (!(value instanceof JsonArray array)) throw indexError(value, JsonNumber.ZERO);
return values(array.getFirst()) 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."); 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))); 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()); 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<String, JsonValue>();
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 record SortEntry(@Nullable JsonValue value, @NotNull Iterable<@Nullable JsonValue> key) {
private static final Comparator<Iterable<JsonValue>> LEXICOGRAPHIC_COMPARATOR = (a, b) -> compareLexicographically(a, b, JsonMath.COMPARATOR); private static final Comparator<Iterable<JsonValue>> LEXICOGRAPHIC_COMPARATOR = (a, b) -> compareLexicographically(a, b, JsonMath.COMPARATOR);
private static final Comparator<SortEntry> COMPARATOR = Comparator.comparing(SortEntry::key, LEXICOGRAPHIC_COMPARATOR); private static final Comparator<SortEntry> COMPARATOR = Comparator.comparing(SortEntry::key, LEXICOGRAPHIC_COMPARATOR);
@ -754,60 +795,116 @@ public class JsonMath {
//</editor-fold> //</editor-fold>
//<editor-fold desc="filters" defaultstate="collapsed"> //<editor-fold desc="filters" defaultstate="collapsed">
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; return value instanceof JsonArray;
} }
public static boolean isObject(@Nullable JsonValue value) { public static boolean isObject0(@Nullable JsonValue value) {
return value instanceof JsonObject; return value instanceof JsonObject;
} }
public static boolean isIterable(@Nullable JsonValue value) { public static boolean isIterable0(@Nullable JsonValue value) {
return isArray(value) || isObject(value); return isArray0(value) || isObject0(value);
} }
public static boolean isBoolean(@Nullable JsonValue value) { public static boolean isBoolean0(@Nullable JsonValue value) {
return value instanceof JsonBoolean; return value instanceof JsonBoolean;
} }
public static boolean isNumber(@Nullable JsonValue value) { public static boolean isNumber0(@Nullable JsonValue value) {
return value instanceof JsonNumber; 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; 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); 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); 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); 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; return value instanceof JsonString;
} }
public static boolean isNull(@Nullable JsonValue value) { public static boolean isNull0(@Nullable JsonValue value) {
return value == null; return value == null;
} }
public static boolean isValue(@Nullable JsonValue value) { public static boolean isValue0(@Nullable JsonValue value) {
return value != null; return value != null;
} }
public static boolean isScalar(@Nullable JsonValue value) { public static boolean isScalar0(@Nullable JsonValue value) {
return !isIterable(value); return !isIterable0(value);
} }
public static boolean isEmpty(@NotNull Stream<?> stream) { public static boolean isEmpty0(@NotNull Generator<?> stream) {
return stream.map(_ -> Boolean.TRUE).findFirst().isEmpty(); return !stream.hasNext();
} }
//</editor-fold> //</editor-fold>
@ -861,7 +958,7 @@ public class JsonMath {
case JsonString string -> string; case JsonString string -> string;
case JsonNumber _, JsonBoolean _ -> tostring(value); case JsonNumber _, JsonBoolean _ -> tostring(value);
case null, default -> value; case null, default -> value;
}).iterator(); });
if (!it.hasNext()) return JsonString.EMPTY; if (!it.hasNext()) return JsonString.EMPTY;
@ -943,14 +1040,14 @@ public class JsonMath {
//</editor-fold> //</editor-fold>
//<editor-fold desc="regex" defaultstate="collapsed"> //<editor-fold desc="regex" defaultstate="collapsed">
public static @NotNull Stream<@NotNull JsonValue> match( public static @NotNull Generator<@NotNull JsonValue> match(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) { ) {
var input = regexInput(context); var input = regexInput(context);
return match(context, args, false).flatMap(Function.identity()).map(result -> match0(input, result)); return match(context, args, false).flatMap(Function.identity()).map(result -> match0(input, result));
} }
private static @NotNull Stream<Stream<@NotNull MatchResult>> match( private static @NotNull Generator<Generator<@NotNull MatchResult>> match(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args, boolean global @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args, boolean global
) { ) {
var input = regexInput(context); 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 @NotNull String input, @Nullable JsonValue pattern, @Nullable JsonValue flags, boolean global
) { ) {
if (!(pattern instanceof JsonString(var patternString))) throw error(pattern, "is not a string"); if (!(pattern instanceof JsonString(var patternString))) throw error(pattern, "is not a string");
@ -1005,17 +1102,24 @@ public class JsonMath {
return out; return out;
} }
private static @NotNull Stream<@NotNull MatchResult> matchesAsStream(@NotNull Matcher matcher) { private static @NotNull Generator<@NotNull MatchResult> matchesAsStream(@NotNull Matcher matcher) {
var it = new Iterator<MatchResult>() { return new Generator<>() {
@Override private boolean valid = false;
public boolean hasNext() { return matcher.find(); }
@Override @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) { 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"); 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 @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 @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) { ) {
return match(context, args, false).flatMap(Function.identity()).map(JsonMath::capture0); return match(context, args, false).flatMap(Function.identity()).map(JsonMath::capture0);
@ -1065,51 +1169,61 @@ public class JsonMath {
return new JsonObject(out); 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 @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) { ) {
return match(context, args, true).flatMap(Function.identity()).map(MatchResult::group).map(JsonString::new); 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 @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 @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"); 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;
} }
private static @NotNull Gatherer<MatchResult, ?, JsonValue> splits0(@NotNull String input) { @Override
class State { int offset; } public JsonValue next() throws NoSuchElementException {
return Gatherer.ofSequential( if (it.hasNext()) {
State::new, var match = it.next();
Gatherer.Integrator.ofGreedy((state, value, downstream) -> { var out = new JsonString(inputString.substring(offset, match.start()));
downstream.push(new JsonString(input.substring(state.offset, value.start()))); offset = match.end();
state.offset = value.end(); return out;
return true; } else if (!finished) {
}), finished = true;
(state, downstream) -> downstream.push(new JsonString(input.substring(state.offset))) return new JsonString(inputString.substring(offset));
); } else {
throw new NoSuchElementException();
}
}
});
} }
public static @NotNull Stream<@NotNull JsonValue> sub( public static @NotNull Generator<@NotNull JsonValue> sub(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) { ) {
return sub0(context, args, false); 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 @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) { ) {
return sub0(context, args, true); 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 @NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args, boolean global
) { ) {
var input = regexInput(context); var input = regexInput(context);
@ -1120,12 +1234,11 @@ public class JsonMath {
}; };
var subst = args.get(1); var subst = args.get(1);
return match(context, matchArgs, global).flatMap(s -> { return match(context, matchArgs, global).flatMap(it -> {
var offset = 0; var offset = 0;
var fragments = new ArrayList<String>(); var fragments = new ArrayList<String>();
var values = new ArrayList<JQExpression>(); var values = new ArrayList<JQExpression>();
var it = s.iterator();
while (it.hasNext()) { while (it.hasNext()) {
var result = it.next(); var result = it.next();
var capture = capture0(result); var capture = capture0(result);
@ -1184,6 +1297,33 @@ public class JsonMath {
} }
//</editor-fold> //</editor-fold>
//<editor-fold desc="time" defaultstate="collapsed">
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);
}
//</editor-fold>
//<editor-fold desc="misc" defaultstate="collapsed"> //<editor-fold desc="misc" defaultstate="collapsed">
public static @NotNull JsonNumber length(@Nullable JsonValue value) { public static @NotNull JsonNumber length(@Nullable JsonValue value) {
return new JsonNumber(length0(value)); return new JsonNumber(length0(value));
@ -1199,6 +1339,21 @@ public class JsonMath {
case null -> 0; case null -> 0;
}; };
} }
public static @NotNull Generator<@Nullable JsonValue> repeat(@NotNull Supplier<@NotNull Generator<@Nullable JsonValue>> delegate) {
return new Generator<>() {
private @NotNull Generator<JsonValue> current = Generator.empty();
@Override
public boolean hasNext() { return true; }
@Override
public JsonValue next() throws NoSuchElementException {
while (!current.hasNext()) current = delegate.get();
return current.next();
}
};
}
//</editor-fold> //</editor-fold>
//<editor-fold desc="math library" defaultstate="collapsed"> //<editor-fold desc="math library" defaultstate="collapsed">

View File

@ -1,8 +1,9 @@
package eu.jonahbauer.json.query; package eu.jonahbauer.json.query;
import eu.jonahbauer.json.JsonValue; 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.JQParser;
import eu.jonahbauer.json.query.parser.ast.JQExpression; import eu.jonahbauer.json.query.parser.ast.JQProgram;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -10,25 +11,23 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.stream.Stream;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonQuery { public class JsonQuery {
private final @NotNull JQExpression expression; private final @NotNull JQProgram programm;
public static @NotNull JsonQuery parse(@NotNull String query) { public static @NotNull JsonQuery parse(@NotNull String query) {
try { try {
var parser = new JQParser(query); var parser = new JQParser(query);
var programm = parser.parseTopLevel(); var programm = parser.parseTopLevel();
if (programm.expression() == null) throw new IllegalArgumentException(); if (programm.expression() == null) throw new IllegalArgumentException();
return new JsonQuery(programm.expression()); return new JsonQuery(programm);
} catch (IOException ex) { } catch (IOException ex) {
throw new UncheckedIOException(ex); throw new UncheckedIOException(ex);
} }
} }
public @NotNull Stream<@NotNull JsonValue> run(@Nullable JsonValue value) { public @NotNull Generator<@NotNull JsonValue> run(@Nullable JsonValue value) {
var context = new JQExpression.Context(value); return programm.run(value);
return expression.evaluate(context);
} }
} }

View File

@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue; import java.util.Queue;
final class ConcatGenerator<T> implements Generator<T> { final class ConcatGenerator<T> implements Generator<T> {
@ -14,15 +15,28 @@ final class ConcatGenerator<T> implements Generator<T> {
} }
@Override @Override
public T next() throws EndOfStreamException { public boolean hasNext() {
while (!generators.isEmpty()) { while (!generators.isEmpty()) {
try {
var current = generators.peek(); var current = generators.peek();
return current.next(); if (!current.hasNext()) {
} catch (EndOfStreamException ex) {
generators.remove(); 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();
} }
} }

View File

@ -1,10 +1,17 @@
package eu.jonahbauer.json.query.impl; package eu.jonahbauer.json.query.impl;
import java.util.NoSuchElementException;
enum EmptyGenerator implements Generator<Object> { enum EmptyGenerator implements Generator<Object> {
INSTANCE; INSTANCE;
@Override @Override
public Object next() throws EndOfStreamException { public boolean hasNext() {
throw new EndOfStreamException(); return false;
}
@Override
public Object next() {
throw new NoSuchElementException();
} }
} }

View File

@ -15,13 +15,18 @@ final class FlatMappingGenerator<T, S> implements Generator<S> {
} }
@Override @Override
public S next() throws EndOfStreamException { public boolean hasNext() {
while (true) { while (!current.hasNext() && source.hasNext()) {
try {
return current.next();
} catch (EndOfStreamException _) {
current = function.apply(source.next()); 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();
} }
} }

View File

@ -2,11 +2,56 @@ package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.*;
import java.util.function.Function; import java.util.function.*;
import java.util.stream.Stream;
public interface Generator<T> { public interface Generator<T> {
T next() throws EndOfStreamException; boolean hasNext();
T next() throws NoSuchElementException;
default void forEach(@NotNull Consumer<? super T> action) {
while (hasNext()) {
action.accept(next());
}
}
default T reduce(T seed, @NotNull BinaryOperator<T> operator) {
while (hasNext()) {
seed = operator.apply(seed, next());
}
return seed;
}
default boolean anyMatch(@NotNull Predicate<? super T> predicate) {
while (hasNext()) {
if (predicate.test(next())) return true;
}
return false;
}
default boolean allMatch(@NotNull Predicate<? super T> predicate) {
while (hasNext()) {
if (!predicate.test(next())) return false;
}
return true;
}
default @NotNull List<T> toList() {
var out = new ArrayList<T>();
while (hasNext()) {
out.add(next());
}
return Collections.unmodifiableList(out);
}
default @NotNull Generator<T> filter(@NotNull Predicate<? super T> predicate) {
return new FilterGenerator<>(this, predicate);
}
default @NotNull Generator<T> limit(int count) {
return new LimitGenerator<>(this, count);
}
default <S> @NotNull Generator<S> map(@NotNull Function<? super T, ? extends S> function) { default <S> @NotNull Generator<S> map(@NotNull Function<? super T, ? extends S> function) {
return new MappingGenerator<>(this, function); return new MappingGenerator<>(this, function);
@ -16,6 +61,10 @@ public interface Generator<T> {
return new FlatMappingGenerator<>(this, function); return new FlatMappingGenerator<>(this, function);
} }
default <S> @NotNull Generator<S> mapMulti(@NotNull BiConsumer<? super T, ? super Consumer<S>> function) {
return new MapMultiGenerator<>(this, function);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static <T> @NotNull Generator<T> empty() { static <T> @NotNull Generator<T> empty() {
return (Generator<T>) EmptyGenerator.INSTANCE; return (Generator<T>) EmptyGenerator.INSTANCE;
@ -29,5 +78,19 @@ public interface Generator<T> {
return new SingletonGenerator<>(value); return new SingletonGenerator<>(value);
} }
class EndOfStreamException extends RuntimeException {} static <T> @NotNull Generator<T> of(@NotNull Supplier<T> value) {
return new SupplierGenerator<>(value);
}
static <T> @NotNull Generator<T> from(@NotNull Iterable<T> value) {
return new IteratorGenerator<>(value.iterator());
}
static <T> @NotNull Generator<T> from(@NotNull Iterator<T> value) {
return new IteratorGenerator<>(value);
}
static <T> @NotNull Generator<T> from(@NotNull Stream<T> value) {
return new IteratorGenerator<>(value.iterator());
}
} }

View File

@ -2,11 +2,25 @@ package eu.jonahbauer.json.query.impl;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
record MappingGenerator<T, S>(@NotNull Generator<T> delegate, @NotNull Function<? super T, ? extends S> function) implements Generator<S> { final class MappingGenerator<T, S> implements Generator<S> {
private final @NotNull Generator<T> delegate;
private final @NotNull Function<? super T, ? extends S> function;
public MappingGenerator(@NotNull Generator<T> delegate, @NotNull Function<? super T, ? extends S> function) {
this.delegate = Objects.requireNonNull(delegate);
this.function = Objects.requireNonNull(function);
}
@Override @Override
public S next() throws EndOfStreamException { public boolean hasNext() {
return delegate.hasNext();
}
@Override
public S next() {
return function.apply(delegate.next()); return function.apply(delegate.next());
} }
} }

View File

@ -1,5 +1,7 @@
package eu.jonahbauer.json.query.impl; package eu.jonahbauer.json.query.impl;
import java.util.NoSuchElementException;
final class SingletonGenerator<T> implements Generator<T> { final class SingletonGenerator<T> implements Generator<T> {
private boolean done; private boolean done;
private T value; private T value;
@ -9,13 +11,17 @@ final class SingletonGenerator<T> implements Generator<T> {
} }
@Override @Override
public T next() throws EndOfStreamException { public boolean hasNext() {
if (!done) { return !done;
}
@Override
public T next() {
if (done) throw new NoSuchElementException();
var out = value; var out = value;
done = true; done = true;
value = null; value = null;
return value; return out;
}
throw new EndOfStreamException();
} }
} }

View File

@ -107,15 +107,11 @@ public class JQParser {
return new JQFunction(name, params, body); return new JQFunction(name, params, body);
} }
private @NotNull JQExpression parseExpression() throws IOException { public @NotNull JQExpression parseExpression() throws IOException {
if (peek(JQTokenKind.DEF)) { if (peek(JQTokenKind.DEF)) {
var function = parseFuncDef();
} else if (peek(JQTokenKind.REDUCE)) { var expr = parsePipeExpression();
return new JQFunctionDefinition(function, expr);
} else if (peek(JQTokenKind.FOREACH)) {
} else if (peek(JQTokenKind.IF)) {
} else if (peek(JQTokenKind.LABEL)) { } else if (peek(JQTokenKind.LABEL)) {
} else { } else {
@ -254,7 +250,7 @@ public class JQParser {
} }
private @NotNull JQExpression parseErrorSuppression() throws IOException { private @NotNull JQExpression parseErrorSuppression() throws IOException {
var expression = parseTermOrAs(); var expression = parseControlFlow();
if (tryConsume(JQTokenKind.QUESTION_MARK)) { if (tryConsume(JQTokenKind.QUESTION_MARK)) {
return new JQTryExpression(expression); return new JQTryExpression(expression);
} else { } else {
@ -262,35 +258,82 @@ public class JQParser {
} }
} }
private @NotNull JQExpression parseTermOrAs() throws IOException { 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<JQExpression>();
var thens = new ArrayList<JQExpression>();
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);
}
return out;
} else {
var term = parseTerm(); var term = parseTerm();
if (tryConsume(JQTokenKind.AS)) { if (tryConsume(JQTokenKind.AS)) {
var patterns = new ArrayList<JQAsExpression.Pattern>(); var patterns = parsePatterns();
patterns.add(parsePattern());
while (tryConsume(JQTokenKind.ALTERNATION)) {
patterns.add(parsePattern());
}
consume(JQTokenKind.PIPE); consume(JQTokenKind.PIPE);
var expr = parseExpression(); var expr = parseExpression();
return new JQAsExpression(term, patterns, expr); 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<JQPatterns.Pattern>();
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)) { if (tryConsume(JQTokenKind.LBRACKET)) {
var patterns = new ArrayList<JQAsExpression.Pattern>(); var patterns = new ArrayList<JQPatterns.Pattern>();
do { do {
patterns.add(parsePattern()); patterns.add(parsePattern());
} while (tryConsume(JQTokenKind.COMMA)); } while (tryConsume(JQTokenKind.COMMA));
consume(JQTokenKind.RBRACKET); consume(JQTokenKind.RBRACKET);
return new JQAsExpression.Pattern.ArrayPattern(patterns); return new JQPatterns.Pattern.ArrayPattern(patterns);
} else if (tryConsume(JQTokenKind.LBRACE)) { } else if (tryConsume(JQTokenKind.LBRACE)) {
var patterns = new LinkedHashMap<JQExpression, JQAsExpression.Pattern>(); var patterns = new LinkedHashMap<JQExpression, JQPatterns.Pattern>();
do { do {
if (tryConsume(JQTokenKind.DOLLAR)) { if (tryConsume(JQTokenKind.DOLLAR)) {
var ident = consume(JQTokenKind.IDENT).text(); 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); patterns.put(new JQConstant(JsonString.valueOf(ident)), pattern);
} else if (peek(JQTokenKind.IDENT)) { } else if (peek(JQTokenKind.IDENT)) {
var ident = consume(JQTokenKind.IDENT).text(); var ident = consume(JQTokenKind.IDENT).text();
@ -311,11 +354,11 @@ public class JQParser {
} }
} while (tryConsume(JQTokenKind.COMMA)); } while (tryConsume(JQTokenKind.COMMA));
consume(JQTokenKind.RBRACE); consume(JQTokenKind.RBRACE);
return new JQAsExpression.Pattern.ObjectPattern(patterns); return new JQPatterns.Pattern.ObjectPattern(patterns);
} else { } else {
consume(JQTokenKind.DOLLAR); consume(JQTokenKind.DOLLAR);
var ident = consume(JQTokenKind.IDENT).text(); 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 { private @NotNull JQExpression parseVariableExpression() throws IOException {
var dollar = consume(JQTokenKind.DOLLAR); consume(JQTokenKind.DOLLAR);
var ident = consume(JQTokenKind.IDENT).text(); var ident = consume(JQTokenKind.IDENT).text();
return new JQVariableExpression("$" + ident); return new JQVariableExpression("$" + ident);
} }

View File

@ -2,17 +2,12 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import lombok.AllArgsConstructor; import eu.jonahbauer.json.query.impl.Generator;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.NoSuchElementException;
import java.util.Objects; 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") @SuppressWarnings("preview")
public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression { public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
@ -22,8 +17,48 @@ public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQEx
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return first.evaluate(context).gather(new AlternativeGatherer(second, 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 @Override
@ -35,46 +70,4 @@ public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQEx
public @NotNull String toString() { public @NotNull String toString() {
return first + " // " + second; return first + " // " + second;
} }
@NoArgsConstructor
@AllArgsConstructor
private static class State {
private boolean empty = true;
}
private record AlternativeGatherer(
@NotNull JQExpression expression,
@NotNull Context context
) implements Gatherer<JsonValue, State, JsonValue> {
@Override
public @NotNull Supplier<State> initializer() {
return State::new;
}
@Override
public @NotNull Integrator<State, JsonValue, JsonValue> integrator() {
return Integrator.ofGreedy((state, element, downstream) -> {
if (JsonMath.isTruthy(element)) {
state.empty = false;
return downstream.push(element);
}
return true;
});
}
@Override
public @NotNull BinaryOperator<State> combiner() {
return (state1, state2) -> new State(state1.empty && state2.empty);
}
@Override
public @NotNull BiConsumer<State, Downstream<? super JsonValue>> finisher() {
return (state, downstream) -> {
if (!state.empty) return;
var it = expression.evaluate(context).iterator();
while (it.hasNext() && downstream.push(it.next()));
};
}
}
} }

View File

@ -2,12 +2,11 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonArray; import eu.jonahbauer.json.JsonArray;
import eu.jonahbauer.json.JsonValue; 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
public record JQArrayConstructionExpression(@NotNull JQExpression expression) implements JQExpression { public record JQArrayConstructionExpression(@NotNull JQExpression expression) implements JQExpression {
public JQArrayConstructionExpression { public JQArrayConstructionExpression {
@ -15,8 +14,8 @@ public record JQArrayConstructionExpression(@NotNull JQExpression expression) im
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Util.lazy(() -> new JsonArray(expression.evaluate(context).toList())); return Generator.of(() -> new JsonArray(expression.evaluate(context).toList()));
} }
@Override @Override

View File

@ -1,57 +1,26 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.*; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.util.Util;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public record JQAsExpression( 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 { ) implements JQExpression {
public JQAsExpression { public JQAsExpression {
Objects.requireNonNull(variable); Objects.requireNonNull(variable);
Objects.requireNonNull(expression); Objects.requireNonNull(expression);
Objects.requireNonNull(patterns);
patterns = List.copyOf(patterns);
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
var variables = new HashMap<String, JsonValue>();
patterns.stream().map(Pattern::variables).flatMap(Set::stream).forEach(key -> variables.put(key, null));
return variable.evaluate(context) return variable.evaluate(context)
.flatMap(value -> { .flatMap(value -> patterns.bind(context, value).map(context::withVariables).flatMap(expression::evaluate));
Stream<Map<String, JsonValue>> 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);
});
} }
@Override @Override
@ -61,113 +30,7 @@ public record JQAsExpression(
@Override @Override
public @NotNull String toString() { public @NotNull String toString() {
return variable return variable + " as " + patterns + " | " + expression;
+ " as " + patterns.stream().map(Objects::toString).collect(Collectors.joining(" ?// "))
+ " | " + expression;
} }
public sealed interface Pattern {
@NotNull Set<@NotNull String> variables();
@NotNull Stream<Map<@NotNull String, @Nullable JsonValue>> 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<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull Context context, @Nullable JsonValue value) {
var map = new HashMap<String, JsonValue>();
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<String>();
patterns.forEach(p -> out.addAll(p.variables()));
return Collections.unmodifiableSet(out);
}
@Override
public @NotNull Stream<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull Context context, @Nullable JsonValue value) {
var streams = new ArrayList<Supplier<Stream<Map<String, JsonValue>>>>();
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<String, JsonValue>();
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<String>();
patterns.values().forEach(p -> out.addAll(p.variables()));
return Collections.unmodifiableSet(out);
}
@Override
public @NotNull Stream<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull Context context, @Nullable JsonValue value) {
var streams = new ArrayList<Supplier<Stream<Map<String, JsonValue>>>>();
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<String, JsonValue>();
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();
}
}
}
} }

View File

@ -2,6 +2,7 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -42,7 +43,7 @@ public record JQAssignment(@NotNull JQExpression target, @NotNull JQExpression v
} }
@Override @Override
public @NotNull Stream<JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
throw new UnsupportedOperationException("not yet implemented"); throw new UnsupportedOperationException("not yet implemented");
} }

View File

@ -1,6 +1,7 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
@ -14,7 +15,7 @@ public record JQAssignmentCoerce(@NotNull JQExpression target, @NotNull JQExpres
} }
@Override @Override
public @NotNull Stream<JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
throw new UnsupportedOperationException("not yet implemented"); throw new UnsupportedOperationException("not yet implemented");
} }

View File

@ -1,10 +1,10 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
public record JQAssignmentPipe(@NotNull JQExpression target, @NotNull JQExpression value) implements JQExpression { public record JQAssignmentPipe(@NotNull JQExpression target, @NotNull JQExpression value) implements JQExpression {
@ -14,7 +14,7 @@ public record JQAssignmentPipe(@NotNull JQExpression target, @NotNull JQExpressi
} }
@Override @Override
public @NotNull Stream<JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
throw new UnsupportedOperationException("not yet implemented"); throw new UnsupportedOperationException("not yet implemented");
} }

View File

@ -2,6 +2,7 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.util.Util; import eu.jonahbauer.json.query.util.Util;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -10,7 +11,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.BinaryOperator; 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 record JQBinaryExpression(@NotNull JQExpression first, @NotNull JQExpression second, @NotNull Operator operator) implements JQExpression {
public static @NotNull JQBinaryExpression add(@NotNull JQExpression first, @NotNull JQExpression second) { public static @NotNull JQBinaryExpression add(@NotNull JQExpression first, @NotNull JQExpression second) {
@ -64,7 +64,7 @@ public record JQBinaryExpression(@NotNull JQExpression first, @NotNull JQExpress
} }
@Override @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) return Util.crossReversed(List.of(first, second), context)
.map(values -> operator.apply(values.getFirst(), values.getLast())); .map(values -> operator.apply(values.getFirst(), values.getLast()));
} }

View File

@ -3,10 +3,10 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonBoolean; import eu.jonahbauer.json.JsonBoolean;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
public record JQBooleanAndExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression { public record JQBooleanAndExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
@ -16,10 +16,10 @@ public record JQBooleanAndExpression(@NotNull JQExpression first, @NotNull JQExp
} }
@Override @Override
public @NotNull Stream<JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
return first.evaluate(context) return first.evaluate(context)
.flatMap(value -> JsonMath.isFalsy(value) .flatMap(value -> JsonMath.isFalsy(value)
? Stream.of(JsonBoolean.FALSE) ? Generator.of(JsonBoolean.FALSE)
: second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf) : second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf)
); );
} }

View File

@ -3,6 +3,7 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonBoolean; import eu.jonahbauer.json.JsonBoolean;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
@ -16,10 +17,10 @@ public record JQBooleanOrExpression(@NotNull JQExpression first, @NotNull JQExpr
} }
@Override @Override
public @NotNull Stream<JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
return first.evaluate(context) return first.evaluate(context)
.flatMap(value -> JsonMath.isTruthy(value) .flatMap(value -> JsonMath.isTruthy(value)
? Stream.of(JsonBoolean.TRUE) ? Generator.of(JsonBoolean.TRUE)
: second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf) : second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf)
); );
} }

View File

@ -3,30 +3,33 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.*; import eu.jonahbauer.json.*;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.JsonQueryException; 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 eu.jonahbauer.json.query.util.Util;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Gatherer;
import java.util.stream.Stream;
public enum JQBuiltIn implements JQInvocable { 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))), 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 handling
ERROR$0(0, (context, _) -> context.stream().flatMap(JsonMath::error)), ERROR$0(0, (context, _) -> context.stream().flatMap(JsonMath::error)),
ERROR$1(1, (context, args) -> args.getFirst().evaluate(context).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$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))), HALT_ERROR$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(value -> JsonMath.halt(context.root(), value))),
// stream operations // stream operations
EMPTY(0, (_, _) -> Stream.empty()), EMPTY(0, (_, _) -> Generator.empty()),
RANGE$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(JsonMath::range)), 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$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)))), 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))), FIRST$1(1, (context, args) -> JsonMath.first(args.getFirst().evaluate(context))),
LAST$1(1, (context, args) -> JsonMath.last(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))), 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 // iterable operations
MAP(1, (context, args) -> { MAP(1, Implementation.mapF$V(JsonMath::map)),
var filter = args.getFirst().bind(context); MAP_VALUES(1, Implementation.mapF$V(JsonMath::mapValues)),
return context.stream().map(value -> JsonMath.map(value, filter)); KEYS(0, Implementation.map$V(JsonMath::keys)),
}), KEYS_UNSORTED(0, Implementation.map$V(JsonMath::keysUnsorted)),
MAP_VALUES(1, (context, args) -> { HAS(1, Implementation.mapV$V(JsonMath::has)),
var filter = args.getFirst().bind(context); IN(1, Implementation.mapV$V(JsonMath::in)),
return context.stream().map(value -> JsonMath.mapValues(value, filter)); FIRST$0(0, Implementation.map$V(JsonMath::first)),
}), LAST$0(0, Implementation.map$V(JsonMath::last)),
KEYS(0, (context, _) -> context.stream().map(JsonMath::keys)), NTH$1(1, Implementation.mapV$V(JsonMath::index)),
KEYS_UNSORTED(0, (context, _) -> context.stream().map(JsonMath::keysUnsorted)), ANY$0(0, Implementation.map$V(JsonMath::any)),
HAS(1, (context, args) -> args.getFirst().evaluate(context).map(index -> JsonMath.has(context.root(), index))), ANY$1(1, Implementation.mapF$V(JsonMath::any)),
IN(1, (context, args) -> args.getFirst().evaluate(context).map(value -> JsonMath.in(context.root(), value))), ANY$2(2, Implementation.mapFF$V((root, gen, filter) -> JsonMath.any(gen.apply(root), filter))),
FIRST$0(0, (context, _) -> context.stream().map(value -> JsonMath.index(value, JsonNumber.ZERO))), ALL$0(0, Implementation.map$V(JsonMath::all)),
LAST$0(0, (context, _) -> context.stream().map(value -> JsonMath.index(value, new JsonNumber(-1)))), ALL$1(1, Implementation.mapF$V(JsonMath::all)),
NTH$1(1, (context, args) -> args.getFirst().evaluate(context).map(index -> JsonMath.index(context.root(), index))), ALL$2(2, Implementation.mapFF$V((root, gen, filter) -> JsonMath.all(gen.apply(root), filter))),
ANY$0(0, (context, _) -> context.stream().map(JsonMath::any)), FLATTEN$0(0, Implementation.map$V(JsonMath::flatten)),
ANY$1(1, (context, args) -> { FLATTEN$1(1, Implementation.mapV$V(JsonMath::flatten)),
var filter = args.getFirst().bind(context); SORT(0, Implementation.map$V(JsonMath::sort)),
return context.stream().map(value -> JsonMath.any(value, filter)); SORT_BY(1, Implementation.mapF$V(JsonMath::sort)),
}), MIN(0, Implementation.map$V(JsonMath::min)),
ANY$2(2, (context, args) -> { MIN_BY(1, Implementation.mapF$V(JsonMath::min)),
var filter = args.getFirst().bind(context); MAX(0, Implementation.map$V(JsonMath::max)),
return Util.lazy(() -> JsonMath.any(args.getFirst().evaluate(context), filter)); MAX_BY(1, Implementation.mapF$V(JsonMath::max)),
}), UNIQUE(0, Implementation.map$V(JsonMath::unique)),
ALL$0(0, (context, _) -> context.stream().map(JsonMath::all)), UNIQUE_BY(1, Implementation.mapF$V(JsonMath::unique)),
ALL$1(1, (context, args) -> { GROUP_BY(1, Implementation.mapF$V(JsonMath::group)),
var filter = args.getFirst().bind(context); REVERSE(0, Implementation.map$V(JsonMath::reverse)),
return context.stream().map(value -> JsonMath.all(value, filter)); CONTAINS(1, Implementation.mapV$V(JsonMath::contains)),
}), INDICES(1, Implementation.mapV$V(JsonMath::indices)),
ALL$2(2, (context, args) -> { INDEX(1, Implementation.mapV$V(JsonMath::firstindex)),
var filter = args.getFirst().bind(context); RINDEX(1, Implementation.mapV$V(JsonMath::lastindex)),
return Util.lazy(() -> JsonMath.all(args.getFirst().evaluate(context), filter)); INSIDE(1, Implementation.mapV$V(JsonMath::inside)),
}), COMBINATIONS$0(0, Implementation.map$F(JsonMath::combinations)),
FLATTEN$0(0, (context, _) -> context.stream().map(JsonMath::flatten)), COMBINATIONS$1(1, Implementation.mapV$F(JsonMath::combinations)),
FLATTEN$1(1, (context, args) -> context.stream().flatMap(value -> args.getFirst().evaluate(context).map(depth -> JsonMath.flatten(value, depth)))), BSEARCH(1, Implementation.mapV$V(JsonMath::bsearch)),
SORT(0, (context, _) -> context.stream().map(JsonMath::sort)), TRANSPOSE(0, Implementation.map$V(JsonMath::transpose)),
SORT_BY(1, (context, args) -> { TO_ENTRIES(0, Implementation.map$V(JsonMath::toEntries)),
var filter = args.getFirst().bind(context); FROM_ENTRIES(0, Implementation.map$V(JsonMath::fromEntries)),
return context.stream().map(value -> JsonMath.sort(value, filter)); WITH_ENTRIES(1, Implementation.mapF$V(JsonMath::withEntries)),
}),
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)),
// filters // filters
ARRAYS(0, (context, _) -> context.stream().filter(JsonMath::isArray)), ARRAYS(0, Implementation.filter(JsonMath::isArray0)),
OBJECTS(0, (context, _) -> context.stream().filter(JsonMath::isObject)), OBJECTS(0, Implementation.filter(JsonMath::isObject0)),
ITERABLES(0, (context, _) -> context.stream().filter(JsonMath::isIterable)), ITERABLES(0, Implementation.filter(JsonMath::isIterable0)),
BOOLEANS(0, (context, _) -> context.stream().filter(JsonMath::isBoolean)), BOOLEANS(0, Implementation.filter(JsonMath::isBoolean0)),
NUMBERS(0, (context, _) -> context.stream().filter(JsonMath::isNumber)), NUMBERS(0, Implementation.filter(JsonMath::isNumber0)),
NORMALS(0, (context, _) -> context.stream().filter(JsonMath::isNormal)), NORMALS(0, Implementation.filter(JsonMath::isNormal0)),
FINITES(0, (context, _) -> context.stream().filter(JsonMath::isFinite)), FINITES(0, Implementation.filter(JsonMath::isFinite0)),
STRINGS(0, (context, _) -> context.stream().filter(JsonMath::isString)), STRINGS(0, Implementation.filter(JsonMath::isString0)),
NULLS(0, (context, _) -> context.stream().filter(JsonMath::isNull)), NULLS(0, Implementation.filter(JsonMath::isNull0)),
VALUES(0, (context, _) -> context.stream().filter(JsonMath::isValue)), VALUES(0, Implementation.filter(JsonMath::isValue0)),
SCALARS(0, (context, _) -> context.stream().filter(JsonMath::isScalar)), SCALARS(0, Implementation.filter(JsonMath::isScalar0)),
// checks // checks
ISINFINITE(0, (context, _) -> context.stream().map(JsonMath::isInfinite).map(JsonBoolean::valueOf)), ISINFINITE(0, Implementation.map$V(JsonMath::isInfinite)),
ISNAN(0, (context, _) -> context.stream().map(JsonMath::isNan).map(JsonBoolean::valueOf)), ISNAN(0, Implementation.map$V(JsonMath::isNan)),
ISFINITE(0, (context, _) -> context.stream().map(JsonMath::isFinite).map(JsonBoolean::valueOf)), ISFINITE(0, Implementation.map$V(JsonMath::isFinite)),
ISNORMAL(0, (context, _) -> context.stream().map(JsonMath::isNormal).map(JsonBoolean::valueOf)), ISNORMAL(0, Implementation.map$V(JsonMath::isNormal)),
ISEMPTY(1, (context, args) -> Util.lazy(() -> JsonBoolean.valueOf(JsonMath.isEmpty(args.getFirst().evaluate(context))))), ISEMPTY(1, (context, args) -> Generator.of(() -> JsonMath.isEmpty(args.getFirst().evaluate(context)))),
// string operations // string operations
TRIM(0, (context, _) -> context.stream().map(JsonMath::trim)), TRIM(0, Implementation.map$V(JsonMath::trim)),
LTRIM(0, (context, _) -> context.stream().map(JsonMath::ltrim)), LTRIM(0, Implementation.map$V(JsonMath::ltrim)),
RTRIM(0, (context, _) -> context.stream().map(JsonMath::rtrim)), RTRIM(0, Implementation.map$V(JsonMath::rtrim)),
LTRIMSTR(1, (context, args) -> args.getFirst().evaluate(context).map(prefix -> JsonMath.ltrimstr(context.root(), prefix))), LTRIMSTR(1, Implementation.mapV$V(JsonMath::ltrimstr)),
RTRIMSTR(1, (context, args) -> args.getFirst().evaluate(context).map(suffix -> JsonMath.rtrimstr(context.root(), suffix))), RTRIMSTR(1, Implementation.mapV$V(JsonMath::rtrimstr)),
SPLIT$1(1, (context, args) -> args.getFirst().evaluate(context).map(separator -> JsonMath.split(context.root(), separator))), SPLIT$1(1, Implementation.mapV$V(JsonMath::split)),
JOIN(1, (context, args) -> args.getFirst().evaluate(context).map(separator -> JsonMath.join(context.root(), separator))), JOIN(1, Implementation.mapV$V(JsonMath::join)),
IMPLODE(0, (context, _) -> context.stream().map(JsonMath::implode)), IMPLODE(0, Implementation.map$V(JsonMath::implode)),
EXPLODE(0, (context, _) -> context.stream().map(JsonMath::explode)), EXPLODE(0, Implementation.map$V(JsonMath::explode)),
ASCII_UPCASE(0, (context, _) -> context.stream().map(JsonMath::asciiUpcase)), ASCII_UPCASE(0, Implementation.map$V(JsonMath::asciiUpcase)),
ASCII_DOWNCASE(0, (context, _) -> context.stream().map(JsonMath::asciiDowncase)), ASCII_DOWNCASE(0, Implementation.map$V(JsonMath::asciiDowncase)),
UTF8BYTELENGTH(0, (context, _) -> context.stream().map(JsonMath::utf8ByteLength)), UTF8BYTELENGTH(0, Implementation.map$V(JsonMath::utf8ByteLength)),
STARTSWITH(1, (context, args) -> args.getFirst().evaluate(context).map(prefix -> JsonMath.startswith(context.root(), prefix))), STARTSWITH(1, Implementation.mapV$V(JsonMath::startswith)),
ENDSWITH(1, (context, args) -> args.getFirst().evaluate(context).map(prefix -> JsonMath.endswith(context.root(), prefix))), ENDSWITH(1, Implementation.mapV$V(JsonMath::endswith)),
// regex // regex
TEST$1(1, JsonMath::test), TEST$1(1, JsonMath::test),
@ -160,76 +134,83 @@ public enum JQBuiltIn implements JQInvocable {
GSUB$3(3, JsonMath::gsub), GSUB$3(3, JsonMath::gsub),
// conversions // conversions
TYPE(0, (context, _) -> context.stream().map(JsonMath::type)), TYPE(0, Implementation.map$V(JsonMath::type)),
TOJSON(0, (context, _) -> context.stream().map(JsonMath::tojson)), TOJSON(0, Implementation.map$V(JsonMath::tojson)),
TOSTRING(0, (context, _) -> context.stream().map(JsonMath::tostring)), TOSTRING(0, Implementation.map$V(JsonMath::tostring)),
TONUMBER(0, (context, _) -> context.stream().map(JsonMath::tonumber)), TONUMBER(0, Implementation.map$V(JsonMath::tonumber)),
FROMJSON(0, (context, _) -> context.stream().map(JsonMath::fromjson)), 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 // misc
LENGTH(0, (context, _) -> context.stream().map(JsonMath::length)), LENGTH(0, Implementation.map$V(JsonMath::length)),
REPEAT(1, (context, args) -> Stream.generate(() -> args.getFirst().evaluate(context)).flatMap(Function.identity())), REPEAT(1, (context, args) -> JsonMath.repeat(() -> args.getFirst().evaluate(context))),
// math library // math library
ACOS(0, (context, _) -> context.stream().map(JsonMath::acos)), ACOS(0, Implementation.map$V(JsonMath::acos)),
ACOSH(0, (context, _) -> context.stream().map(JsonMath::acosh)), ACOSH(0, Implementation.map$V(JsonMath::acosh)),
ASIN(0, (context, _) -> context.stream().map(JsonMath::asin)), ASIN(0, Implementation.map$V(JsonMath::asin)),
ASINH(0, (context, _) -> context.stream().map(JsonMath::asinh)), ASINH(0, Implementation.map$V(JsonMath::asinh)),
ATAN(0, (context, _) -> context.stream().map(JsonMath::atan)), ATAN(0, Implementation.map$V(JsonMath::atan)),
ATANH(0, (context, _) -> context.stream().map(JsonMath::atanh)), ATANH(0, Implementation.map$V(JsonMath::atanh)),
CBRT(0, (context, _) -> context.stream().map(JsonMath::cbrt)), CBRT(0, Implementation.map$V(JsonMath::cbrt)),
CEIL(0, (context, _) -> context.stream().map(JsonMath::ceil)), CEIL(0, Implementation.map$V(JsonMath::ceil)),
COS(0, (context, _) -> context.stream().map(JsonMath::cos)), COS(0, Implementation.map$V(JsonMath::cos)),
COSH(0, (context, _) -> context.stream().map(JsonMath::cosh)), COSH(0, Implementation.map$V(JsonMath::cosh)),
ERF(0, (context, _) -> context.stream().map(JsonMath::erf)), ERF(0, Implementation.map$V(JsonMath::erf)),
ERFC(0, (context, _) -> context.stream().map(JsonMath::erfc)), ERFC(0, Implementation.map$V(JsonMath::erfc)),
EXP(0, (context, _) -> context.stream().map(JsonMath::exp)), EXP(0, Implementation.map$V(JsonMath::exp)),
EXP10(0, (context, _) -> context.stream().map(JsonMath::exp10)), EXP10(0, Implementation.map$V(JsonMath::exp10)),
EXP2(0, (context, _) -> context.stream().map(JsonMath::exp2)), EXP2(0, Implementation.map$V(JsonMath::exp2)),
EXPM1(0, (context, _) -> context.stream().map(JsonMath::expm1)), EXPM1(0, Implementation.map$V(JsonMath::expm1)),
FABS(0, (context, _) -> context.stream().map(JsonMath::fabs)), FABS(0, Implementation.map$V(JsonMath::fabs)),
FLOOR(0, (context, _) -> context.stream().map(JsonMath::floor)), FLOOR(0, Implementation.map$V(JsonMath::floor)),
GAMMA(0, (context, _) -> context.stream().map(JsonMath::gamma)), GAMMA(0, Implementation.map$V(JsonMath::gamma)),
J0(0, (context, _) -> context.stream().map(JsonMath::j0)), J0(0, Implementation.map$V(JsonMath::j0)),
J1(0, (context, _) -> context.stream().map(JsonMath::j1)), J1(0, Implementation.map$V(JsonMath::j1)),
LGAMMA(0, (context, _) -> context.stream().map(JsonMath::lgamma)), LGAMMA(0, Implementation.map$V(JsonMath::lgamma)),
LOG(0, (context, _) -> context.stream().map(JsonMath::log)), LOG(0, Implementation.map$V(JsonMath::log)),
LOG10(0, (context, _) -> context.stream().map(JsonMath::log10)), LOG10(0, Implementation.map$V(JsonMath::log10)),
LOG1P(0, (context, _) -> context.stream().map(JsonMath::log1p)), LOG1P(0, Implementation.map$V(JsonMath::log1p)),
LOG2(0, (context, _) -> context.stream().map(JsonMath::log2)), LOG2(0, Implementation.map$V(JsonMath::log2)),
LOGB(0, (context, _) -> context.stream().map(JsonMath::logb)), LOGB(0, Implementation.map$V(JsonMath::logb)),
NEARBYINT(0, (context, _) -> context.stream().map(JsonMath::nearbyint)), NEARBYINT(0, Implementation.map$V(JsonMath::nearbyint)),
RINT(0, (context, _) -> context.stream().map(JsonMath::rint)), RINT(0, Implementation.map$V(JsonMath::rint)),
ROUND(0, (context, _) -> context.stream().map(JsonMath::round)), ROUND(0, Implementation.map$V(JsonMath::round)),
SIGNIFICAND(0, (context, _) -> context.stream().map(JsonMath::significand)), SIGNIFICAND(0, Implementation.map$V(JsonMath::significand)),
SIN(0, (context, _) -> context.stream().map(JsonMath::sin)), SIN(0, Implementation.map$V(JsonMath::sin)),
SINH(0, (context, _) -> context.stream().map(JsonMath::sinh)), SINH(0, Implementation.map$V(JsonMath::sinh)),
SQRT(0, (context, _) -> context.stream().map(JsonMath::sqrt)), SQRT(0, Implementation.map$V(JsonMath::sqrt)),
TAN(0, (context, _) -> context.stream().map(JsonMath::tan)), TAN(0, Implementation.map$V(JsonMath::tan)),
TANH(0, (context, _) -> context.stream().map(JsonMath::tanh)), TANH(0, Implementation.map$V(JsonMath::tanh)),
TGAMMA(0, (context, _) -> context.stream().map(JsonMath::tgamma)), TGAMMA(0, Implementation.map$V(JsonMath::tgamma)),
TRUNC(0, (context, _) -> context.stream().map(JsonMath::trunc)), TRUNC(0, Implementation.map$V(JsonMath::trunc)),
Y0(0, (context, _) -> context.stream().map(JsonMath::y0)), Y0(0, Implementation.map$V(JsonMath::y0)),
Y1(0, (context, _) -> context.stream().map(JsonMath::y1)), Y1(0, Implementation.map$V(JsonMath::y1)),
ATAN2(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.atan2(values.get(0), values.get(1)))), ATAN2(2, Implementation.mapVV$V((_, a, b) -> JsonMath.atan2(a, b))),
COPYSIGN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.copysign(values.get(0), values.get(1)))), COPYSIGN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.copysign(a, b))),
DREM(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.drem(values.get(0), values.get(1)))), DREM(2, Implementation.mapVV$V((_, a, b) -> JsonMath.drem(a, b))),
FDIM(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fdim(values.get(0), values.get(1)))), FDIM(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fdim(a, b))),
FMAX(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fmax(values.get(0), values.get(1)))), FMAX(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmax(a, b))),
FMIN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fmin(values.get(0), values.get(1)))), FMIN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmin(a, b))),
FMOD(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fmod(values.get(0), values.get(1)))), FMOD(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmod(a, b))),
FREXP(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.frexp(values.get(0), values.get(1)))), FREXP(2, Implementation.mapVV$V((_, a, b) -> JsonMath.frexp(a, b))),
HYPOT(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.hypot(values.get(0), values.get(1)))), HYPOT(2, Implementation.mapVV$V((_, a, b) -> JsonMath.hypot(a, b))),
JN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.jn(values.get(0), values.get(1)))), JN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.jn(a, b))),
LDEXP(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.ldexp(values.get(0), values.get(1)))), LDEXP(2, Implementation.mapVV$V((_, a, b) -> JsonMath.ldexp(a, b))),
MODF(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.modf(values.get(0), values.get(1)))), MODF(2, Implementation.mapVV$V((_, a, b) -> JsonMath.modf(a, b))),
NEXTAFTER(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.nextafter(values.get(0), values.get(1)))), NEXTAFTER(2, Implementation.mapVV$V((_, a, b) -> JsonMath.nextafter(a, b))),
NEXTTOWARD(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.nexttoward(values.get(0), values.get(1)))), NEXTTOWARD(2, Implementation.mapVV$V((_, a, b) -> JsonMath.nexttoward(a, b))),
POW(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.pow(values.get(0), values.get(1)))), POW(2, Implementation.mapVV$V((_, a, b) -> JsonMath.pow(a, b))),
REMAINDER(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.remainder(values.get(0), values.get(1)))), REMAINDER(2, Implementation.mapVV$V((_, a, b) -> JsonMath.remainder(a, b))),
SCALB(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.scalb(values.get(0), values.get(1)))), SCALB(2, Implementation.mapVV$V((_, a, b) -> JsonMath.scalb(a, b))),
SCALBLN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.scalbln(values.get(0), values.get(1)))), SCALBLN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.scalbln(a, b))),
YN(2, (context, args) -> Util.cross(args, context).map(values -> JsonMath.yn(values.get(0), values.get(1)))), 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)))) 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() Function.identity()
)); ));
private final @NotNull String identifier; private final @NotNull JQInvocable implementation;
private final int arity;
private final @NotNull Implementation 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) { 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("$"); var idx = identifier.lastIndexOf("$");
if (idx != -1) identifier = identifier.substring(0, idx); if (idx != -1) identifier = identifier.substring(0, idx);
return identifier;
this.identifier = identifier;
this.arity = arity;
this.implementation = implementation;
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> invoke(JQExpression.@NotNull Context context, @NotNull List<@NotNull JQExpression> args) { 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 implementation.invoke(context, args); return implementation.invoke(context, args);
} }
@Override @Override
public @NotNull String identifier() { public @NotNull String identifier() {
return identifier; return implementation.identifier();
} }
@Override @Override
public int arity() { 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 @FunctionalInterface
private interface Implementation { 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<args>$<return>
// where <args> and <return> 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<? super @Nullable JsonValue, ? extends @NotNull Generator<? extends @Nullable JsonValue>> 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<? super @Nullable JsonValue, ? extends @Nullable JsonValue> 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<? super @Nullable JsonValue, ? super @Nullable JsonValue, ? extends @Nullable JsonValue> 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<? super @Nullable JsonValue, ? super @Nullable JsonValue, ? extends @NotNull Generator<? extends @Nullable JsonValue>> 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<? super @Nullable JsonValue, ? super @NotNull JQFilter, ? extends @NotNull Generator<? extends @Nullable JsonValue>> 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<? super @Nullable JsonValue, ? super @NotNull JQFilter, ? extends @Nullable JsonValue> 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<? super @Nullable JsonValue, ? super @Nullable JsonValue, ? super @Nullable JsonValue, ? extends @Nullable JsonValue> 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<? super @Nullable JsonValue, ? super @NotNull JQFilter, ? super @NotNull JQFilter, ? extends @Nullable JsonValue> 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<S, T, U, R> {
R apply(S s, T t, U u);
} }
} }

View File

@ -1,13 +1,11 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; 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 record JQCommaExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
public JQCommaExpression { public JQCommaExpression {
@ -16,34 +14,8 @@ public record JQCommaExpression(@NotNull JQExpression first, @NotNull JQExpressi
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
var it = new Iterator<JsonValue>() { return Generator.concat(first.evaluate(context), second.evaluate(context));
private Iterator<JsonValue> delegate;
private Queue<Supplier<Iterator<JsonValue>>> 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);
} }
@Override @Override

View File

@ -1,16 +1,15 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
public record JQConstant(@Nullable JsonValue value) implements JQExpression { public record JQConstant(@Nullable JsonValue value) implements JQExpression {
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Stream.of(value); return Generator.of(value);
} }
@Override @Override

View File

@ -2,16 +2,16 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonQueryException; import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
public interface JQExpression { public interface JQExpression {
@NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context); @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context);
boolean isConstant(); boolean isConstant();
@ -82,8 +82,8 @@ public interface JQExpression {
return new Context(root, v, functions); return new Context(root, v, functions);
} }
public @NotNull Stream<@Nullable JsonValue> stream() { public @NotNull Generator<@Nullable JsonValue> stream() {
return Stream.of(root()); return Generator.of(root());
} }
} }
} }

View File

@ -1,12 +1,12 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream;
@FunctionalInterface @FunctionalInterface
public interface JQFilter extends Function<@Nullable JsonValue, @NotNull Stream<@Nullable JsonValue>> { public interface JQFilter extends Function<@Nullable JsonValue, @NotNull Generator<@Nullable JsonValue>> {
} }

View File

@ -1,11 +1,12 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; 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 record JQFunction(@NotNull String identifier, @NotNull List<@NotNull String> params, @NotNull JQExpression body) implements JQInvocable {
public JQFunction { public JQFunction {
@ -15,7 +16,9 @@ public record JQFunction(@NotNull String identifier, @NotNull List<@NotNull Stri
} }
@Override @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 expression = body;
var functions = new ArrayList<JQFunction>(); var functions = new ArrayList<JQFunction>();
@ -24,7 +27,7 @@ public record JQFunction(@NotNull String identifier, @NotNull List<@NotNull Stri
if (param.startsWith("$")) { if (param.startsWith("$")) {
expression = new JQAsExpression( expression = new JQAsExpression(
new JQFunctionInvocation(param.substring(1), List.of()), new JQFunctionInvocation(param.substring(1), List.of()),
List.of(new JQAsExpression.Pattern.ValuePattern(param)), new JQPatterns(List.of(new JQPatterns.Pattern.ValuePattern(param))),
expression expression
); );
param = param.substring(1); param = param.substring(1);

View File

@ -1,13 +1,13 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.StringJoiner; import java.util.StringJoiner;
import java.util.stream.Stream;
public record JQFunctionInvocation(@NotNull String name, @NotNull List<@NotNull JQExpression> args) implements JQExpression { public record JQFunctionInvocation(@NotNull String name, @NotNull List<@NotNull JQExpression> args) implements JQExpression {
public JQFunctionInvocation { public JQFunctionInvocation {
@ -16,7 +16,7 @@ public record JQFunctionInvocation(@NotNull String name, @NotNull List<@NotNull
} }
@Override @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()); var function = context.function(name, args.size());
return function.invoke(context, args); return function.invoke(context, args);
} }

View File

@ -5,14 +5,13 @@ import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.JsonQueryException; import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
public record JQIndexExpression(@NotNull JQExpression expression, @NotNull JQExpression index, boolean optional) implements JQExpression { public record JQIndexExpression(@NotNull JQExpression expression, @NotNull JQExpression index, boolean optional) implements JQExpression {
public JQIndexExpression { public JQIndexExpression {
Objects.requireNonNull(expression); Objects.requireNonNull(expression);
Objects.requireNonNull(index); Objects.requireNonNull(index);
@ -31,7 +30,7 @@ public record JQIndexExpression(@NotNull JQExpression expression, @NotNull JQExp
} }
@Override @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) -> { return expression.evaluate(context).flatMap(value -> index.evaluate(context).mapMulti((index, downstream) -> {
try { try {
downstream.accept(JsonMath.index(value, index)); downstream.accept(JsonMath.index(value, index));

View File

@ -1,14 +1,15 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
public interface JQInvocable { 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(); int arity();
@NotNull String identifier(); @NotNull String identifier();

View File

@ -2,11 +2,11 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
public record JQIterateExpression(@NotNull JQExpression expression, boolean optional) implements JQExpression { public record JQIterateExpression(@NotNull JQExpression expression, boolean optional) implements JQExpression {
public JQIterateExpression { public JQIterateExpression {
@ -14,7 +14,7 @@ public record JQIterateExpression(@NotNull JQExpression expression, boolean opti
} }
@Override @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)); return expression.evaluate(context).flatMap(value -> JsonMath.values(value, optional));
} }

View File

@ -4,15 +4,14 @@ import eu.jonahbauer.json.JsonNumber;
import eu.jonahbauer.json.JsonObject; import eu.jonahbauer.json.JsonObject;
import eu.jonahbauer.json.JsonString; import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
public record JQLocExpression(@NotNull String file, int line) implements JQExpression { public record JQLocExpression(@NotNull String file, int line) implements JQExpression {
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Stream.of(JsonObject.of( return Generator.of(JsonObject.of(
"file", new JsonString(file), "file", new JsonString(file),
"line", new JsonNumber(line) "line", new JsonNumber(line)
)); ));

View File

@ -2,6 +2,7 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonMath; import eu.jonahbauer.json.query.JsonMath;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -14,7 +15,7 @@ public record JQNegation(@NotNull JQExpression expression) implements JQExpressi
} }
@Override @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); return expression.evaluate(context).map(JsonMath::neg);
} }

View File

@ -1,11 +1,11 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
public record JQParenthesizedExpression(@NotNull JQExpression expression) implements JQExpression { public record JQParenthesizedExpression(@NotNull JQExpression expression) implements JQExpression {
@ -14,7 +14,7 @@ public record JQParenthesizedExpression(@NotNull JQExpression expression) implem
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return expression.evaluate(context); return expression.evaluate(context);
} }

View File

@ -1,6 +1,7 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -14,7 +15,7 @@ public record JQPipeExpression(@NotNull JQExpression first, @NotNull JQExpressio
} }
@Override @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))); return first.evaluate(context).flatMap(value -> second.evaluate(context.withRoot(value)));
} }

View File

@ -1,5 +1,7 @@
package eu.jonahbauer.json.query.parser.ast; 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -11,4 +13,8 @@ public record JQProgram(
@NotNull List<@NotNull JQFunction> functions, @NotNull List<@NotNull JQFunction> functions,
@Nullable JQExpression expression @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));
}
} }

View File

@ -3,22 +3,21 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonArray; import eu.jonahbauer.json.JsonArray;
import eu.jonahbauer.json.JsonObject; import eu.jonahbauer.json.JsonObject;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
public record JQRecursionExpression() implements JQExpression { public record JQRecursionExpression() implements JQExpression {
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return recurse(context.root()); 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) { return switch (value) {
case JsonArray array -> Stream.concat(Stream.of(array), array.stream().flatMap(this::recurse)); case JsonArray array -> Generator.concat(Generator.of(array), Generator.from(array).flatMap(this::recurse));
case JsonObject object -> Stream.concat(Stream.of(object), object.values().stream().flatMap(this::recurse)); case JsonObject object -> Generator.concat(Generator.of(object), Generator.from(object.values()).flatMap(this::recurse));
case null, default -> Stream.of(value); case null, default -> Generator.of(value);
}; };
} }

View File

@ -1,14 +1,13 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
public record JQRootExpression() implements JQExpression { public record JQRootExpression() implements JQExpression {
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return context.stream(); return context.stream();
} }

View File

@ -5,6 +5,7 @@ import eu.jonahbauer.json.JsonNumber;
import eu.jonahbauer.json.JsonString; import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonQueryException; import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.util.Util; import eu.jonahbauer.json.query.util.Util;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -22,29 +23,29 @@ public record JQSliceExpression(@NotNull JQExpression expression, @Nullable JQEx
} }
@Override @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) { return expression.evaluate(context).flatMap(value -> switch (value) {
case JsonArray array -> slice(context, "an array slice", array.size(), array::subList); 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 JsonString string -> slice(context, "a string slice", string.length(), string::subSequence);
case null -> Stream.of((JsonValue) null); case null -> Generator.of((JsonValue) null);
default -> { default -> {
if (optional) yield Stream.empty(); if (optional) yield Generator.empty();
throw new JsonQueryException(STR."Cannot index \{Util.type(value)} with object."); 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<Integer, Integer, JsonValue> slice) { private @NotNull Generator<@Nullable JsonValue> slice(@NotNull Context context, @NotNull String type, int length, @NotNull BiFunction<Integer, Integer, JsonValue> slice) {
return getIndices(start, context, type, length, 0) return getIndices(start, context, type, length, 0)
.mapToObj(start -> getIndices(end, context, type, length, length) .map(start -> getIndices(end, context, type, length, length)
.mapToObj(end -> start > end ? slice.apply(0, 0) : slice.apply(start, end)) .map(end -> start > end ? slice.apply(0, 0) : slice.apply(start, end))
) )
.flatMap(Function.identity()); .flatMap(Function.identity());
} }
private @NotNull IntStream getIndices(@Nullable JQExpression expression, @NotNull Context context, @NotNull String type, int length, int fallback) { private @NotNull Generator<Integer> getIndices(@Nullable JQExpression expression, @NotNull Context context, @NotNull String type, int length, int fallback) {
if (expression == null) return IntStream.of(fallback); if (expression == null) return Generator.of(fallback);
return expression.evaluate(context).mapToInt(value -> getIndex(value, type, length)); return expression.evaluate(context).map(value -> getIndex(value, type, length));
} }
private int getIndex(@Nullable JsonValue value, @NotNull String type, int length) { private int getIndex(@Nullable JsonValue value, @NotNull String type, int length) {

View File

@ -2,6 +2,7 @@ package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonString; import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.util.Util; import eu.jonahbauer.json.query.util.Util;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -22,7 +23,7 @@ public record JQStringInterpolation(
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Util.crossReversed(values, context) return Util.crossReversed(values, context)
.map(values -> StringTemplate.of(fragments, values.stream().map(JQStringInterpolation::toString).toList())) .map(values -> StringTemplate.of(fragments, values.stream().map(JQStringInterpolation::toString).toList()))
.map(STR::process) .map(STR::process)

View File

@ -4,13 +4,12 @@ import eu.jonahbauer.json.JsonString;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.JsonQueryException; import eu.jonahbauer.json.query.JsonQueryException;
import eu.jonahbauer.json.query.JsonQueryHaltException; import eu.jonahbauer.json.query.JsonQueryHaltException;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.function.Function; 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 record JQTryExpression(@NotNull JQExpression expression, @Nullable JQExpression fallback) implements JQExpression {
public JQTryExpression { public JQTryExpression {
@ -22,13 +21,11 @@ public record JQTryExpression(@NotNull JQExpression expression, @Nullable JQExpr
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
var iterator = new QuietIterator( return new QuietIterator(
expression.evaluate(context).iterator(), expression.evaluate(context),
fallback == null ? null : ex -> fallback.evaluate(context.withRoot(new JsonString(ex.getMessage()))).iterator() 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 @Override
@ -41,58 +38,62 @@ public record JQTryExpression(@NotNull JQExpression expression, @Nullable JQExpr
return "try " + expression + (fallback == null ? "" : " catch " + fallback); return "try " + expression + (fallback == null ? "" : " catch " + fallback);
} }
private static class QuietIterator implements Iterator<@Nullable JsonValue> { private static class QuietIterator implements Generator<@Nullable JsonValue> {
private @Nullable Iterator<@Nullable JsonValue> delegate; private @Nullable Generator<@Nullable JsonValue> delegate;
private @Nullable Function<@NotNull JsonQueryException, @NotNull Iterator<@Nullable JsonValue>> fallback; private @Nullable Generator<@Nullable JsonValue> fallback;
private @Nullable Function<@NotNull JsonQueryException, @NotNull Generator<@Nullable JsonValue>> fallbackSupplier;
private @Nullable JsonValue next; private JsonValue value;
private boolean hasNext; private boolean hasValue;
private boolean valid = false;
private QuietIterator( private QuietIterator(
@NotNull Iterator<@Nullable JsonValue> delegate, @NotNull Generator<@Nullable JsonValue> delegate,
@Nullable Function<@NotNull JsonQueryException, @NotNull Iterator<@Nullable JsonValue>> fallback @Nullable Function<@NotNull JsonQueryException, @NotNull Generator<@Nullable JsonValue>> fallback
) { ) {
this.delegate = delegate; this.delegate = delegate;
this.fallback = fallback; this.fallbackSupplier = fallback;
} }
private void ensureValid() { private boolean advance() {
if (valid) return; if (hasValue) return true;
assert fallback == null && delegate != null;
while (true) {
try { try {
if (delegate != null && delegate.hasNext()) { // still have values in current stream hasValue = delegate.hasNext();
next = delegate.next(); value = hasValue ? delegate.next() : null;
hasNext = true; return hasValue;
} else { // end of stream
next = null;
hasNext = false;
}
break;
} catch (JsonQueryHaltException ex) { } catch (JsonQueryHaltException ex) {
throw ex; throw ex;
} catch (JsonQueryException ex) { // switch to fallback } catch (JsonQueryException ex) {
delegate = fallback != null ? fallback.apply(ex) : null; delegate = null;
fallback = 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 @Override
public boolean hasNext() { public boolean hasNext() {
ensureValid(); if (fallback != null) return fallback.hasNext();
return hasNext; if (delegate == null) return false;
return advance();
} }
@Override @Override
public @Nullable JsonValue next() { public @Nullable JsonValue next() {
ensureValid(); if (fallback != null) return fallback.next();
if (!hasNext) throw new NoSuchElementException(); if (delegate == null) throw new NoSuchElementException();
valid = false; if (!advance()) throw new NoSuchElementException();
return next; var out = value;
hasValue = false;
value = null;
return out;
} }
} }
} }

View File

@ -1,11 +1,11 @@
package eu.jonahbauer.json.query.parser.ast; package eu.jonahbauer.json.query.parser.ast;
import eu.jonahbauer.json.JsonValue; import eu.jonahbauer.json.JsonValue;
import eu.jonahbauer.json.query.impl.Generator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
public record JQVariableExpression(@NotNull String name) implements JQExpression { public record JQVariableExpression(@NotNull String name) implements JQExpression {
public JQVariableExpression { public JQVariableExpression {
@ -14,8 +14,8 @@ public record JQVariableExpression(@NotNull String name) implements JQExpression
} }
@Override @Override
public @NotNull Stream<@Nullable JsonValue> evaluate(@NotNull Context context) { public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
return Stream.of(context.variable(name)); return Generator.of(() -> context.variable(name));
} }
@Override @Override

View File

@ -1,6 +1,7 @@
package eu.jonahbauer.json.query.util; package eu.jonahbauer.json.query.util;
import eu.jonahbauer.json.*; import eu.jonahbauer.json.*;
import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.parser.ast.JQExpression; import eu.jonahbauer.json.query.parser.ast.JQExpression;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -15,42 +16,34 @@ import java.util.stream.Stream;
@UtilityClass @UtilityClass
public class Util { public class Util {
public static <T> @NotNull Stream<T> lazy(@NotNull Supplier<T> supplier) { public static @NotNull Generator<@NotNull List<JsonValue>> cross(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) {
return Stream.of((Object) null).map(_ -> supplier.get());
}
public static <T> @NotNull Stream<T> lazyStream(@NotNull Supplier<Stream<T>> supplier) {
return Stream.of((Object) null).flatMap(_ -> supplier.get());
}
public static @NotNull Stream<@NotNull List<JsonValue>> cross(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) {
return cross(asSupplier(expressions, context)).map(Collections::unmodifiableList); return cross(asSupplier(expressions, context)).map(Collections::unmodifiableList);
} }
public static <T> @NotNull Stream<@NotNull List<T>> cross(@NotNull List<@NotNull Supplier<@NotNull Stream<T>>> expressions) { public static <T> @NotNull Generator<@NotNull List<T>> cross(@NotNull List<@NotNull Supplier<@NotNull Generator<T>>> expressions) {
if (expressions.isEmpty()) return Stream.of(new ArrayList<T>().reversed()); if (expressions.isEmpty()) return Generator.of(new ArrayList<T>().reversed());
return expressions.getFirst().get() return expressions.getFirst().get()
.flatMap(value -> cross(expressions.subList(1, expressions.size())) .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<JsonValue>> crossReversed(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) { public static @NotNull Generator<@NotNull List<JsonValue>> crossReversed(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) {
return crossReversed(asSupplier(expressions, context)).map(Collections::unmodifiableList); return crossReversed(asSupplier(expressions, context)).map(Collections::unmodifiableList);
} }
public static <T> @NotNull Stream<@NotNull List<T>> crossReversed(@NotNull List<@NotNull Supplier<@NotNull Stream<T>>> expressions) { public static <T> @NotNull Generator<@NotNull List<T>> crossReversed(@NotNull List<@NotNull Supplier<@NotNull Generator<T>>> expressions) {
if (expressions.isEmpty()) return Stream.of(new ArrayList<>()); if (expressions.isEmpty()) return Generator.of(new ArrayList<>());
return expressions.getLast().get() return expressions.getLast().get()
.flatMap(value -> crossReversed(expressions.subList(0, expressions.size() - 1)) .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<JsonValue>>> asSupplier(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) { private static @NotNull List<@NotNull Supplier<@NotNull Generator<JsonValue>>> asSupplier(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) {
var list = new ArrayList<Supplier<Stream<JsonValue>>>(expressions.size()); var list = new ArrayList<Supplier<Generator<JsonValue>>>(expressions.size());
expressions.forEach(expr -> list.add(() -> expr.evaluate(context))); expressions.forEach(expr -> list.add(() -> expr.evaluate(context)));
return list; return list;
} }