diff --git a/query/src/main/java/eu/jonahbauer/json/query/JsonMath.java b/query/src/main/java/eu/jonahbauer/json/query/JsonMath.java
index f6b6259..58e5b90 100644
--- a/query/src/main/java/eu/jonahbauer/json/query/JsonMath.java
+++ b/query/src/main/java/eu/jonahbauer/json/query/JsonMath.java
@@ -2,6 +2,7 @@ package eu.jonahbauer.json.query;
import eu.jonahbauer.json.*;
import eu.jonahbauer.json.exceptions.JsonParserException;
+import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.parser.ast.*;
import eu.jonahbauer.json.query.util.Util;
import lombok.experimental.UtilityClass;
@@ -9,11 +10,11 @@ import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.*;
-import java.util.function.DoubleBinaryOperator;
-import java.util.function.DoubleUnaryOperator;
-import java.util.function.Function;
-import java.util.function.Predicate;
+import java.util.function.*;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -236,20 +237,20 @@ public class JsonMath {
//
//
- public static @NotNull Stream<@Nullable JsonValue> error(@Nullable JsonValue value) {
+ public static @NotNull Generator<@Nullable JsonValue> error(@Nullable JsonValue value) {
if (value != null) throw new JsonQueryUserException(value);
- return Stream.empty();
+ return Generator.empty();
}
- public static @NotNull Stream<@Nullable JsonValue> halt() {
+ public static @NotNull Generator<@Nullable JsonValue> halt() {
throw new JsonQueryHaltException(null, 0);
}
- public static @NotNull Stream<@Nullable JsonValue> halt(@Nullable JsonValue value) {
+ public static @NotNull Generator<@Nullable JsonValue> halt(@Nullable JsonValue value) {
throw new JsonQueryHaltException(value, 5);
}
- public static @NotNull Stream<@Nullable JsonValue> halt(@Nullable JsonValue value, @Nullable JsonValue code) {
+ public static @NotNull Generator<@Nullable JsonValue> halt(@Nullable JsonValue value, @Nullable JsonValue code) {
throw new JsonQueryHaltException(value, switch (code) {
case JsonNumber(var number) -> (int) number;
case null, default -> throw new JsonQueryException(STR."\{Util.type(value)} (\{Util.value(value)}) halt_error/1: number required");
@@ -258,11 +259,11 @@ public class JsonMath {
//
//
- public static @NotNull Stream<@NotNull JsonValue> range(@Nullable JsonValue upto) {
+ public static @NotNull Generator<@NotNull JsonValue> range(@Nullable JsonValue upto) {
return range(new JsonNumber(0), upto);
}
- public static @NotNull Stream<@NotNull JsonValue> range(@Nullable JsonValue from, @Nullable JsonValue upto) {
+ public static @NotNull Generator<@NotNull JsonValue> range(@Nullable JsonValue from, @Nullable JsonValue upto) {
var min = switch (from) {
case JsonNumber(var number) -> number;
case null, default -> throw new JsonQueryException("Range bounds must be numeric.");
@@ -271,81 +272,79 @@ public class JsonMath {
case JsonNumber(var number) -> number;
case null, default -> throw new JsonQueryException("Range bounds must be numeric.");
};
- return IntStream.range((int) Math.ceil(min), (int) Math.ceil(max)).mapToObj(JsonNumber::new);
+ return Generator.from(IntStream.range((int) Math.ceil(min), (int) Math.ceil(max)).boxed()).map(JsonNumber::new);
}
- public static @NotNull Stream<@Nullable JsonValue> range(@Nullable JsonValue from, @Nullable JsonValue upto, @Nullable JsonValue by) {
+ public static @NotNull Generator<@Nullable JsonValue> range(@Nullable JsonValue from, @Nullable JsonValue upto, @Nullable JsonValue by) {
if (compare(by, JsonNumber.ZERO) > 0) {
- return Stream.iterate(from, i -> compare(i, upto) < 0, i -> add(i, by));
+ return Generator.from(Stream.iterate(from, i -> compare(i, upto) < 0, i -> add(i, by)));
} else if (compare(by, JsonNumber.ZERO) < 0) {
- return Stream.iterate(from, i -> compare(i, upto) > 0, i -> add(i, by));
+ return Generator.from(Stream.iterate(from, i -> compare(i, upto) > 0, i -> add(i, by)));
} else {
- return Stream.empty();
+ return Generator.empty();
}
}
- public static @NotNull Stream<@Nullable JsonValue> limit(@NotNull Stream<@Nullable JsonValue> stream, @Nullable JsonValue limit) {
+ public static @NotNull Generator<@Nullable JsonValue> limit(@NotNull Generator<@Nullable JsonValue> stream, @Nullable JsonValue limit) {
if (compare(limit, JsonNumber.ZERO) > 0) {
- class State {
- @Nullable JsonValue limit;
- public State(@Nullable JsonValue limit) { this.limit = limit; }
- }
+ return new Generator<>() {
+ private @Nullable JsonValue i = limit;
- return stream.gather(Gatherer.ofSequential(
- () -> new State(limit),
- (state, value, downstream) -> {
- state.limit = JsonMath.sub(state.limit, JsonNumber.ONE);
- downstream.push(value);
- return compare(state.limit, JsonNumber.ZERO) > 0;
- }
- ));
+ @Override
+ public boolean hasNext() {
+ return compare(i, JsonNumber.ZERO) > 0;
+ }
+
+ @Override
+ public JsonValue next() {
+ if (compare(i, JsonNumber.ZERO) <= 0) throw new NoSuchElementException();
+ i = sub(i, JsonNumber.ONE);
+ return stream.next();
+ }
+ };
} else if (Objects.equals(limit, JsonNumber.ZERO)) {
- return Stream.empty();
+ return Generator.empty();
} else {
return stream;
}
}
- public static @NotNull Stream<@Nullable JsonValue> first(@NotNull Stream<@Nullable JsonValue> stream) {
+ public static @NotNull Generator<@Nullable JsonValue> first(@NotNull Generator<@Nullable JsonValue> stream) {
return stream.limit(1);
}
- public static @NotNull Stream<@Nullable JsonValue> last(@NotNull Stream<@Nullable JsonValue> stream) {
- class State {
- boolean hasValue;
- @Nullable JsonValue value;
- }
- return stream.gather(Gatherer.ofSequential(
- State::new,
- Gatherer.Integrator.ofGreedy((state, value, _) -> {
- state.hasValue = true;
- state.value = value;
- return true;
- }),
- (state, downstream) -> {
- if (state.hasValue) downstream.push(state.value);
+ public static @NotNull Generator<@Nullable JsonValue> last(@NotNull Generator<@Nullable JsonValue> stream) {
+ return new Generator<>() {
+ @Override
+ public boolean hasNext() {
+ return stream.hasNext();
+ }
+
+ @Override
+ public JsonValue next() throws NoSuchElementException {
+ var out = stream.next();
+ while (stream.hasNext()) {
+ out = stream.next();
}
- ));
+ return out;
+ }
+ };
}
- public static @NotNull Stream<@Nullable JsonValue> nth(@NotNull Stream<@Nullable JsonValue> stream, @Nullable JsonValue limit) {
+ public static @NotNull Generator<@Nullable JsonValue> nth(@NotNull Generator<@Nullable JsonValue> stream, @Nullable JsonValue limit) {
if (compare(limit, JsonNumber.ZERO) < 0) throw new JsonQueryException("nth doesn't support negative indices");
- class State {
- @Nullable JsonValue limit;
- public State(@Nullable JsonValue limit) { this.limit = limit; }
- }
- return stream.gather(Gatherer.ofSequential(
- () -> new State(sub(add(limit, JsonNumber.ONE), JsonNumber.ONE)),
- (state, value, downstream) -> {
- if (compare(state.limit, JsonNumber.ZERO) <= 0) {
- downstream.push(value);
- return false;
- } else {
- state.limit = sub(state.limit, JsonNumber.ONE);
- return true;
- }
- }
- ));
+ return Generator.of(() -> {
+ for (var i = sub(add(limit, JsonNumber.ONE), JsonNumber.ONE); compare(i, JsonNumber.ZERO) > 0; i = sub(i, JsonNumber.ONE)) {
+ stream.next();
+ }
+ return stream.next();
+ });
+ }
+
+ public static @NotNull Generator<@Nullable JsonValue> select(@Nullable JsonValue value, @NotNull JQFilter filter) {
+ return filter.apply(value).mapMulti((result, consumer) -> {
+ if (isTruthy(result)) consumer.accept(value);
+ });
}
//
@@ -430,16 +429,24 @@ public class JsonMath {
return has(value, index);
}
- public static @NotNull Stream<@Nullable JsonValue> values(@Nullable JsonValue value) {
+ public static @Nullable JsonValue first(@Nullable JsonValue value) {
+ return index(value, JsonNumber.ZERO);
+ }
+
+ public static @Nullable JsonValue last(@Nullable JsonValue value) {
+ return index(value, new JsonNumber(-1));
+ }
+
+ public static @NotNull Generator<@Nullable JsonValue> values(@Nullable JsonValue value) {
return values(value, false);
}
- public static @NotNull Stream<@Nullable JsonValue> values(@Nullable JsonValue value, boolean optional) {
+ public static @NotNull Generator<@Nullable JsonValue> values(@Nullable JsonValue value, boolean optional) {
return switch (value) {
- case JsonArray array -> array.stream();
- case JsonObject object -> object.values().stream();
+ case JsonArray array -> Generator.from(array);
+ case JsonObject object -> Generator.from(object.values());
case null, default -> {
- if (optional) yield Stream.empty();
+ if (optional) yield Generator.empty();
throw new JsonQueryException(STR."Cannot iterate over \{Util.type(value)} (\{Util.value(value)}).");
}
};
@@ -451,7 +458,7 @@ public class JsonMath {
public static @NotNull JsonValue mapValues(@Nullable JsonValue value, @NotNull JQFilter expression) {
return switch (value) {
- case JsonArray array -> new JsonArray(array.stream().map(expression).flatMap(s -> s.limit(1)).toList());
+ case JsonArray array -> new JsonArray(Generator.from(array).map(expression).flatMap(s -> s.limit(1)).toList());
case JsonObject object -> {
var out = new LinkedHashMap();
object.forEach((key, v) -> expression.apply(v).limit(1).forEach(newValue -> out.put(key, newValue)));
@@ -469,7 +476,7 @@ public class JsonMath {
return JsonBoolean.valueOf(values(value).flatMap(expression).anyMatch(JsonMath::isTruthy));
}
- public static @NotNull JsonBoolean any(@NotNull Stream<@Nullable JsonValue> values, @NotNull JQFilter expression) {
+ public static @NotNull JsonBoolean any(@NotNull Generator<@Nullable JsonValue> values, @NotNull JQFilter expression) {
return JsonBoolean.valueOf(values.flatMap(expression).anyMatch(JsonMath::isTruthy));
}
@@ -481,7 +488,7 @@ public class JsonMath {
return JsonBoolean.valueOf(values(value).flatMap(expression).allMatch(JsonMath::isTruthy));
}
- public static @NotNull JsonBoolean all(@NotNull Stream<@Nullable JsonValue> values, @NotNull JQFilter expression) {
+ public static @NotNull JsonBoolean all(@NotNull Generator<@Nullable JsonValue> values, @NotNull JQFilter expression) {
return JsonBoolean.valueOf(values.flatMap(expression).allMatch(JsonMath::isTruthy));
}
@@ -497,18 +504,18 @@ public class JsonMath {
};
}
- private static @NotNull Stream<@Nullable JsonValue> flatten0(@Nullable JsonValue value) {
+ private static @NotNull Generator<@Nullable JsonValue> flatten0(@Nullable JsonValue value) {
return flatten0(value, -1);
}
- private static @NotNull Stream<@Nullable JsonValue> flatten0(@Nullable JsonValue value, int depth) {
+ private static @NotNull Generator<@Nullable JsonValue> flatten0(@Nullable JsonValue value, int depth) {
return switch (value) {
- case JsonArray array when depth == 0 -> Stream.of(array);
+ case JsonArray array when depth == 0 -> Generator.of(array);
case JsonArray array -> {
var d = depth < 0 ? depth : depth - 1;
- yield array.stream().flatMap(v -> flatten0(v, d));
+ yield Generator.from(array).flatMap(v -> flatten0(v, d));
}
- case null, default -> Stream.of(value);
+ case null, default -> Generator.of(value);
};
}
@@ -703,8 +710,8 @@ public class JsonMath {
return contains(container, content);
}
- public static @NotNull Stream<@NotNull JsonArray> combinations(@Nullable JsonValue value) {
- if (length0(value) == 0) return Stream.of(JsonArray.EMPTY);
+ public static @NotNull Generator<@NotNull JsonArray> combinations(@Nullable JsonValue value) {
+ if (length0(value) == 0) return Generator.of(JsonArray.EMPTY);
if (!(value instanceof JsonArray array)) throw indexError(value, JsonNumber.ZERO);
return values(array.getFirst())
@@ -716,7 +723,7 @@ public class JsonMath {
}));
}
- public static @NotNull Stream<@NotNull JsonArray> combinations(@Nullable JsonValue value, @Nullable JsonValue n) {
+ public static @NotNull Generator<@NotNull JsonArray> combinations(@Nullable JsonValue value, @Nullable JsonValue n) {
if (!(n instanceof JsonNumber(var number))) throw new JsonQueryException("Range bounds must be numeric.");
return combinations(new JsonArray(Collections.nCopies((int) Math.ceil(number), value)));
}
@@ -747,6 +754,40 @@ public class JsonMath {
return new JsonArray(out.stream().map(JsonArray::new).toList());
}
+ private static final JsonString KEY = new JsonString("key");
+ private static final JsonString VALUE = new JsonString("value");
+
+ public static @NotNull JsonArray toEntries(@Nullable JsonValue value) {
+ return new JsonArray((switch (value) {
+ case JsonArray array -> IntStream.range(0, array.size())
+ .mapToObj(i -> JsonObject.of(
+ "key", new JsonNumber(i),
+ "value", array.get(i)
+ ));
+ case JsonObject object -> object.entrySet().stream()
+ .map(entry -> JsonObject.of(
+ "key", new JsonString(entry.getKey()),
+ "value", entry.getValue()
+ ));
+ case null, default -> throw error(value, "has no keys");
+ }).toList());
+ }
+
+ public static @NotNull JsonObject fromEntries(@Nullable JsonValue value) {
+ var out = new LinkedHashMap();
+ values(value).forEach(entry -> {
+ var key = index(entry, KEY);
+ var val = index(entry, VALUE);
+ if (!(key instanceof JsonString(var k))) throw new JsonQueryException(STR."Cannot use \{Util.type(key)} (\{Util.value(key)}) as object key.");
+ out.put(k, val);
+ });
+ return new JsonObject(out);
+ }
+
+ public static @NotNull JsonObject withEntries(@Nullable JsonValue value, @NotNull JQFilter expression) {
+ return fromEntries(map(toEntries(value), expression));
+ }
+
private record SortEntry(@Nullable JsonValue value, @NotNull Iterable<@Nullable JsonValue> key) {
private static final Comparator> LEXICOGRAPHIC_COMPARATOR = (a, b) -> compareLexicographically(a, b, JsonMath.COMPARATOR);
private static final Comparator COMPARATOR = Comparator.comparing(SortEntry::key, LEXICOGRAPHIC_COMPARATOR);
@@ -754,60 +795,116 @@ public class JsonMath {
//
//
- public static boolean isArray(@Nullable JsonValue value) {
+ public static @NotNull JsonBoolean isArray(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isArray0(value));
+ }
+
+ public static @NotNull JsonBoolean isObject(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isObject0(value));
+ }
+
+ public static @NotNull JsonBoolean isIterable(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isIterable0(value));
+ }
+
+ public static @NotNull JsonBoolean isBoolean(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isBoolean0(value));
+ }
+
+ public static @NotNull JsonBoolean isNumber(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isNumber0(value));
+ }
+
+ public static @NotNull JsonBoolean isNormal(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isNormal0(value));
+ }
+
+ public static @NotNull JsonBoolean isFinite(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isFinite0(value));
+ }
+
+ public static @NotNull JsonBoolean isInfinite(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isInfinite0(value));
+ }
+
+ public static @NotNull JsonBoolean isNan(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isNan0(value));
+ }
+
+ public static @NotNull JsonBoolean isString(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isString0(value));
+ }
+
+ public static @NotNull JsonBoolean isNull(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isNull0(value));
+ }
+
+ public static @NotNull JsonBoolean isValue(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isValue0(value));
+ }
+
+ public static @NotNull JsonBoolean isScalar(@Nullable JsonValue value) {
+ return JsonBoolean.valueOf(isScalar0(value));
+ }
+
+ public static @NotNull JsonBoolean isEmpty(@NotNull Generator> stream) {
+ return JsonBoolean.valueOf(isEmpty0(stream));
+ }
+
+ public static boolean isArray0(@Nullable JsonValue value) {
return value instanceof JsonArray;
}
- public static boolean isObject(@Nullable JsonValue value) {
+ public static boolean isObject0(@Nullable JsonValue value) {
return value instanceof JsonObject;
}
- public static boolean isIterable(@Nullable JsonValue value) {
- return isArray(value) || isObject(value);
+ public static boolean isIterable0(@Nullable JsonValue value) {
+ return isArray0(value) || isObject0(value);
}
- public static boolean isBoolean(@Nullable JsonValue value) {
+ public static boolean isBoolean0(@Nullable JsonValue value) {
return value instanceof JsonBoolean;
}
- public static boolean isNumber(@Nullable JsonValue value) {
+ public static boolean isNumber0(@Nullable JsonValue value) {
return value instanceof JsonNumber;
}
- public static boolean isNormal(@Nullable JsonValue value) {
+ public static boolean isNormal0(@Nullable JsonValue value) {
return value instanceof JsonNumber(var number) && Math.abs(number) >= Double.MIN_NORMAL;
}
- public static boolean isFinite(@Nullable JsonValue value) {
+ public static boolean isFinite0(@Nullable JsonValue value) {
return value instanceof JsonNumber(var number) && Double.isFinite(number);
}
- public static boolean isInfinite(@Nullable JsonValue value) {
+ public static boolean isInfinite0(@Nullable JsonValue value) {
return value instanceof JsonNumber(var number) && Double.isInfinite(number);
}
- public static boolean isNan(@Nullable JsonValue value) {
+ public static boolean isNan0(@Nullable JsonValue value) {
return value instanceof JsonNumber(var number) && Double.isNaN(number);
}
- public static boolean isString(@Nullable JsonValue value) {
+ public static boolean isString0(@Nullable JsonValue value) {
return value instanceof JsonString;
}
- public static boolean isNull(@Nullable JsonValue value) {
+ public static boolean isNull0(@Nullable JsonValue value) {
return value == null;
}
- public static boolean isValue(@Nullable JsonValue value) {
+ public static boolean isValue0(@Nullable JsonValue value) {
return value != null;
}
- public static boolean isScalar(@Nullable JsonValue value) {
- return !isIterable(value);
+ public static boolean isScalar0(@Nullable JsonValue value) {
+ return !isIterable0(value);
}
- public static boolean isEmpty(@NotNull Stream> stream) {
- return stream.map(_ -> Boolean.TRUE).findFirst().isEmpty();
+ public static boolean isEmpty0(@NotNull Generator> stream) {
+ return !stream.hasNext();
}
//
@@ -861,7 +958,7 @@ public class JsonMath {
case JsonString string -> string;
case JsonNumber _, JsonBoolean _ -> tostring(value);
case null, default -> value;
- }).iterator();
+ });
if (!it.hasNext()) return JsonString.EMPTY;
@@ -943,14 +1040,14 @@ public class JsonMath {
//
//
- public static @NotNull Stream<@NotNull JsonValue> match(
+ public static @NotNull Generator<@NotNull JsonValue> match(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) {
var input = regexInput(context);
return match(context, args, false).flatMap(Function.identity()).map(result -> match0(input, result));
}
- private static @NotNull Stream> match(
+ private static @NotNull Generator> match(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args, boolean global
) {
var input = regexInput(context);
@@ -969,7 +1066,7 @@ public class JsonMath {
};
}
- private static @NotNull Stream<@NotNull MatchResult> match(
+ private static @NotNull Generator<@NotNull MatchResult> match(
@NotNull String input, @Nullable JsonValue pattern, @Nullable JsonValue flags, boolean global
) {
if (!(pattern instanceof JsonString(var patternString))) throw error(pattern, "is not a string");
@@ -1005,17 +1102,24 @@ public class JsonMath {
return out;
}
- private static @NotNull Stream<@NotNull MatchResult> matchesAsStream(@NotNull Matcher matcher) {
- var it = new Iterator() {
- @Override
- public boolean hasNext() { return matcher.find(); }
+ private static @NotNull Generator<@NotNull MatchResult> matchesAsStream(@NotNull Matcher matcher) {
+ return new Generator<>() {
+ private boolean valid = false;
@Override
- public MatchResult next() { return matcher.toMatchResult(); }
+ public boolean hasNext() {
+ if (valid) return true;
+ return valid = matcher.find();
+ }
+
+ @Override
+ public MatchResult next() {
+ if (!valid) valid = matcher.find();
+ if (!valid) throw new NoSuchElementException();
+ valid = false;
+ return matcher.toMatchResult();
+ }
};
-
- var split = Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED);
- return StreamSupport.stream(split, false);
}
private static @NotNull JsonValue match0(@NotNull String input, @NotNull MatchResult result) {
@@ -1047,13 +1151,13 @@ public class JsonMath {
throw error(context.root(), "cannot be matched, as it is not a string");
}
- public static @NotNull Stream<@NotNull JsonValue> test(
+ public static @NotNull Generator<@NotNull JsonValue> test(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) {
- return Util.lazy(() -> JsonBoolean.valueOf(!isEmpty(match(context, args, false))));
+ return Generator.of(() -> JsonBoolean.valueOf(!isEmpty0(match(context, args, false))));
}
- public static @NotNull Stream<@NotNull JsonValue> capture(
+ public static @NotNull Generator<@NotNull JsonValue> capture(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) {
return match(context, args, false).flatMap(Function.identity()).map(JsonMath::capture0);
@@ -1065,51 +1169,61 @@ public class JsonMath {
return new JsonObject(out);
}
- public static @NotNull Stream<@NotNull JsonValue> scan(
+ public static @NotNull Generator<@NotNull JsonValue> scan(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) {
return match(context, args, true).flatMap(Function.identity()).map(MatchResult::group).map(JsonString::new);
}
- public static @NotNull Stream<@NotNull JsonValue> split(
+ public static @NotNull Generator<@NotNull JsonValue> split(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) {
- return Util.lazy(() -> new JsonArray(splits(context, args).toList()));
+ return Generator.of(() -> new JsonArray(splits(context, args).toList()));
}
- public static @NotNull Stream<@NotNull JsonValue> splits(
+ public static @NotNull Generator<@NotNull JsonValue> splits(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) {
if (!(context.root() instanceof JsonString(var inputString))) throw error(context.root(), "cannot be matched, as it is not a string");
- return match(context, args, true).flatMap(s -> s.gather(splits0(inputString)));
+ return match(context, args, true).flatMap(it -> new Generator<>() {
+ private boolean finished;
+ private int offset;
+
+ @Override
+ public boolean hasNext() {
+ return !finished;
+ }
+
+ @Override
+ public JsonValue next() throws NoSuchElementException {
+ if (it.hasNext()) {
+ var match = it.next();
+ var out = new JsonString(inputString.substring(offset, match.start()));
+ offset = match.end();
+ return out;
+ } else if (!finished) {
+ finished = true;
+ return new JsonString(inputString.substring(offset));
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+ });
}
- private static @NotNull Gatherer splits0(@NotNull String input) {
- class State { int offset; }
- return Gatherer.ofSequential(
- State::new,
- Gatherer.Integrator.ofGreedy((state, value, downstream) -> {
- downstream.push(new JsonString(input.substring(state.offset, value.start())));
- state.offset = value.end();
- return true;
- }),
- (state, downstream) -> downstream.push(new JsonString(input.substring(state.offset)))
- );
- }
-
- public static @NotNull Stream<@NotNull JsonValue> sub(
+ public static @NotNull Generator<@NotNull JsonValue> sub(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) {
return sub0(context, args, false);
}
- public static @NotNull Stream<@NotNull JsonValue> gsub(
+ public static @NotNull Generator<@NotNull JsonValue> gsub(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args
) {
return sub0(context, args, true);
}
- private static @NotNull Stream<@NotNull JsonValue> sub0(
+ private static @NotNull Generator<@NotNull JsonValue> sub0(
@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args, boolean global
) {
var input = regexInput(context);
@@ -1120,12 +1234,11 @@ public class JsonMath {
};
var subst = args.get(1);
- return match(context, matchArgs, global).flatMap(s -> {
+ return match(context, matchArgs, global).flatMap(it -> {
var offset = 0;
var fragments = new ArrayList();
var values = new ArrayList();
- var it = s.iterator();
while (it.hasNext()) {
var result = it.next();
var capture = capture0(result);
@@ -1184,6 +1297,33 @@ public class JsonMath {
}
//
+ //
+ private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
+ .withZone(ZoneId.of("UTC"));
+
+ public static @NotNull JsonNumber fromdateiso8601(@Nullable JsonValue value) {
+ return JsonNumber.valueOf(switch (value) {
+ case JsonString(var string) -> DATE_TIME_FORMATTER.parse(string, Instant::from).getEpochSecond();
+ case null, default -> throw new JsonQueryException("fromdateiso8601/1 requires string inputs");
+ });
+ }
+
+ public static @NotNull JsonNumber fromdate(@Nullable JsonValue value) {
+ return fromdateiso8601(value);
+ }
+
+ public static @NotNull JsonString todateiso8601(@Nullable JsonValue value) {
+ return new JsonString(switch (value) {
+ case JsonNumber(var number) -> DATE_TIME_FORMATTER.format(Instant.ofEpochSecond((long) number));
+ case null, default -> throw new JsonQueryException("todateiso8601/1 requires number inputs");
+ });
+ }
+
+ public static @NotNull JsonString todate(@Nullable JsonValue value) {
+ return todateiso8601(value);
+ }
+ //
+
//
public static @NotNull JsonNumber length(@Nullable JsonValue value) {
return new JsonNumber(length0(value));
@@ -1199,6 +1339,21 @@ public class JsonMath {
case null -> 0;
};
}
+
+ public static @NotNull Generator<@Nullable JsonValue> repeat(@NotNull Supplier<@NotNull Generator<@Nullable JsonValue>> delegate) {
+ return new Generator<>() {
+ private @NotNull Generator current = Generator.empty();
+
+ @Override
+ public boolean hasNext() { return true; }
+
+ @Override
+ public JsonValue next() throws NoSuchElementException {
+ while (!current.hasNext()) current = delegate.get();
+ return current.next();
+ }
+ };
+ }
//
//
diff --git a/query/src/main/java/eu/jonahbauer/json/query/JsonQuery.java b/query/src/main/java/eu/jonahbauer/json/query/JsonQuery.java
index 1aa38c8..297b96c 100644
--- a/query/src/main/java/eu/jonahbauer/json/query/JsonQuery.java
+++ b/query/src/main/java/eu/jonahbauer/json/query/JsonQuery.java
@@ -1,8 +1,9 @@
package eu.jonahbauer.json.query;
import eu.jonahbauer.json.JsonValue;
+import eu.jonahbauer.json.query.impl.Generator;
import eu.jonahbauer.json.query.parser.JQParser;
-import eu.jonahbauer.json.query.parser.ast.JQExpression;
+import eu.jonahbauer.json.query.parser.ast.JQProgram;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
@@ -10,25 +11,23 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.UncheckedIOException;
-import java.util.stream.Stream;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonQuery {
- private final @NotNull JQExpression expression;
+ private final @NotNull JQProgram programm;
public static @NotNull JsonQuery parse(@NotNull String query) {
try {
var parser = new JQParser(query);
var programm = parser.parseTopLevel();
if (programm.expression() == null) throw new IllegalArgumentException();
- return new JsonQuery(programm.expression());
+ return new JsonQuery(programm);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
- public @NotNull Stream<@NotNull JsonValue> run(@Nullable JsonValue value) {
- var context = new JQExpression.Context(value);
- return expression.evaluate(context);
+ public @NotNull Generator<@NotNull JsonValue> run(@Nullable JsonValue value) {
+ return programm.run(value);
}
}
diff --git a/query/src/main/java/eu/jonahbauer/json/query/impl/ConcatGenerator.java b/query/src/main/java/eu/jonahbauer/json/query/impl/ConcatGenerator.java
index d66f108..d008518 100644
--- a/query/src/main/java/eu/jonahbauer/json/query/impl/ConcatGenerator.java
+++ b/query/src/main/java/eu/jonahbauer/json/query/impl/ConcatGenerator.java
@@ -4,6 +4,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Queue;
final class ConcatGenerator implements Generator {
@@ -14,15 +15,28 @@ final class ConcatGenerator implements Generator {
}
@Override
- public T next() throws EndOfStreamException {
+ public boolean hasNext() {
while (!generators.isEmpty()) {
- try {
- var current = generators.peek();
- return current.next();
- } catch (EndOfStreamException ex) {
+ var current = generators.peek();
+ if (!current.hasNext()) {
generators.remove();
+ } else {
+ return true;
}
}
- throw new EndOfStreamException();
+ return false;
+ }
+
+ @Override
+ public T next() {
+ while (!generators.isEmpty()) {
+ var current = generators.peek();
+ if (!current.hasNext()) {
+ generators.remove();
+ } else {
+ return current.next();
+ }
+ }
+ throw new NoSuchElementException();
}
}
diff --git a/query/src/main/java/eu/jonahbauer/json/query/impl/EmptyGenerator.java b/query/src/main/java/eu/jonahbauer/json/query/impl/EmptyGenerator.java
index ad543cf..b841f3b 100644
--- a/query/src/main/java/eu/jonahbauer/json/query/impl/EmptyGenerator.java
+++ b/query/src/main/java/eu/jonahbauer/json/query/impl/EmptyGenerator.java
@@ -1,10 +1,17 @@
package eu.jonahbauer.json.query.impl;
+import java.util.NoSuchElementException;
+
enum EmptyGenerator implements Generator