fixup query
This commit is contained in:
parent
bdb505fce7
commit
ef88d89ce1
@ -0,0 +1,49 @@
|
||||
package eu.jonahbauer.json.query.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
final class FilterGenerator<T> implements Generator<T> {
|
||||
private final @NotNull Generator<T> generator;
|
||||
private final @NotNull Predicate<? super T> filter;
|
||||
|
||||
private @Nullable T value;
|
||||
private boolean hasValue;
|
||||
|
||||
public FilterGenerator(@NotNull Generator<T> generator, @NotNull Predicate<? super T> filter) {
|
||||
this.generator = generator;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public boolean advance() {
|
||||
if (hasValue) return true;
|
||||
while (generator.hasNext()) {
|
||||
var value = generator.next();
|
||||
if (filter.test(value)) {
|
||||
this.value = value;
|
||||
this.hasValue = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this.value = null;
|
||||
this.hasValue = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (!advance()) throw new NoSuchElementException();
|
||||
var out = value;
|
||||
hasValue = false;
|
||||
value = null;
|
||||
return out;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package eu.jonahbauer.json.query.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public record IteratorGenerator<T>(@NotNull Iterator<T> iterator) implements Generator<T> {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return iterator.next();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package eu.jonahbauer.json.query.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
||||
final class LimitGenerator<T> implements Generator<T> {
|
||||
private final @NotNull Generator<T> delegate;
|
||||
private int count;
|
||||
|
||||
public LimitGenerator(@NotNull Generator<T> delegate, int count) {
|
||||
if (count < 0) throw new IllegalArgumentException();
|
||||
this.delegate = Objects.requireNonNull(delegate);
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return count != 0 && delegate.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (count == 0 || !delegate.hasNext()) throw new NoSuchElementException();
|
||||
count--;
|
||||
return delegate.next();
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package eu.jonahbauer.json.query.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
final class MapMultiGenerator<T, S> implements Generator<S> {
|
||||
private final @NotNull Generator<T> generator;
|
||||
private final @NotNull BiConsumer<? super T, ? super Consumer<S>> mapping;
|
||||
private final @NotNull Queue<S> queue = new LinkedList<>();
|
||||
|
||||
public MapMultiGenerator(@NotNull Generator<T> generator, @NotNull BiConsumer<? super T, ? super Consumer<S>> mapping) {
|
||||
this.generator = Objects.requireNonNull(generator);
|
||||
this.mapping = Objects.requireNonNull(mapping);
|
||||
}
|
||||
|
||||
private boolean advance() {
|
||||
while (queue.isEmpty() && generator.hasNext()) {
|
||||
mapping.accept(generator.next(), (Consumer<S>) queue::add);
|
||||
}
|
||||
return !queue.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public S next() {
|
||||
if (!advance()) throw new NoSuchElementException();
|
||||
return queue.remove();
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package eu.jonahbauer.json.query.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
final class SupplierGenerator<T> implements Generator<T> {
|
||||
private @Nullable Supplier<? extends T> value;
|
||||
|
||||
public SupplierGenerator(@NotNull Supplier<? extends T> supplier) {
|
||||
this.value = Objects.requireNonNull(supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return value != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (value != null) {
|
||||
var out = value.get();
|
||||
value = null;
|
||||
return out;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package eu.jonahbauer.json.query.parser.ast;
|
||||
|
||||
import eu.jonahbauer.json.JsonValue;
|
||||
import eu.jonahbauer.json.query.impl.Generator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public record JQForEachExpression(
|
||||
@NotNull JQExpression expression,
|
||||
@NotNull JQPatterns patterns,
|
||||
@NotNull JQExpression init,
|
||||
@NotNull JQExpression update,
|
||||
@NotNull JQExpression extract
|
||||
) implements JQExpression {
|
||||
|
||||
@Override
|
||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
||||
return init.evaluate(context)
|
||||
.flatMap(s -> new Generator<>() {
|
||||
private final Generator<JsonValue> source = expression.evaluate(context);
|
||||
private @NotNull Generator<JsonValue> current = Generator.empty();
|
||||
private @Nullable JsonValue state = s;
|
||||
|
||||
private @NotNull Generator<JsonValue> advance() {
|
||||
var item = source.next();
|
||||
|
||||
return patterns.bind(context, item)
|
||||
.map(context::withVariables)
|
||||
.map(ctx -> ctx.withRoot(state))
|
||||
.flatMap(ctx -> update.evaluate(ctx).map(s -> {
|
||||
state = s;
|
||||
return ctx.withRoot(s);
|
||||
}).flatMap(extract::evaluate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while (!current.hasNext() && source.hasNext()) current = advance();
|
||||
return current.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonValue next() throws NoSuchElementException {
|
||||
while (!current.hasNext() && source.hasNext()) current = advance();
|
||||
return current.next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstant() {
|
||||
return expression.isConstant() && init.isConstant() && update.isConstant();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return "foreach " + expression + " as " + patterns + " (" + init + "; " + update + "; " + extract + ")";
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package eu.jonahbauer.json.query.parser.ast;
|
||||
|
||||
import eu.jonahbauer.json.JsonValue;
|
||||
import eu.jonahbauer.json.query.impl.Generator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record JQFunctionDefinition(@NotNull JQFunction function, @NotNull JQExpression expression) implements JQExpression {
|
||||
@Override
|
||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
||||
return expression.evaluate(context.withFunction(function));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstant() {
|
||||
return expression.isConstant();
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package eu.jonahbauer.json.query.parser.ast;
|
||||
|
||||
import eu.jonahbauer.json.JsonValue;
|
||||
import eu.jonahbauer.json.query.JsonMath;
|
||||
import eu.jonahbauer.json.query.impl.Generator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record JQIfExpression(@NotNull JQExpression condition, @NotNull JQExpression then, @Nullable JQExpression otherwise) implements JQExpression {
|
||||
public JQIfExpression {
|
||||
Objects.requireNonNull(condition);
|
||||
Objects.requireNonNull(then);
|
||||
}
|
||||
|
||||
public JQIfExpression(@NotNull JQExpression condition, @NotNull JQExpression then) {
|
||||
this(condition, then, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
||||
return condition.evaluate(context).flatMap(result -> JsonMath.isTruthy(result)
|
||||
? then.evaluate(context)
|
||||
: (otherwise != null ? otherwise.evaluate(context) : Generator.empty())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstant() {
|
||||
return condition.isConstant() && then.isConstant() && (otherwise == null || otherwise.isConstant());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
if (otherwise instanceof JQIfExpression) {
|
||||
return "if " + condition + " then " + then + " el" + otherwise;
|
||||
} else if (otherwise != null) {
|
||||
return "if " + condition + " then " + then + " else " + otherwise + " end";
|
||||
} else {
|
||||
return "if " + condition + " then " + then + " end";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
package eu.jonahbauer.json.query.parser.ast;
|
||||
|
||||
import eu.jonahbauer.json.JsonNumber;
|
||||
import eu.jonahbauer.json.JsonValue;
|
||||
import eu.jonahbauer.json.query.JsonMath;
|
||||
import eu.jonahbauer.json.query.JsonQueryException;
|
||||
import eu.jonahbauer.json.query.impl.Generator;
|
||||
import eu.jonahbauer.json.query.util.Util;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public record JQPatterns(@NotNull List<@NotNull Pattern> patterns) {
|
||||
|
||||
public JQPatterns {
|
||||
patterns = List.copyOf(patterns);
|
||||
if (patterns.isEmpty()) throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public @NotNull Generator<@NotNull Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull JQExpression.Context context, @Nullable JsonValue v) {
|
||||
var variables = new HashMap<String, JsonValue>();
|
||||
Generator.from(patterns).map(Pattern::variables).flatMap(Generator::from).forEach(key -> variables.put(key, null));
|
||||
|
||||
var result = new Generator<@NotNull Map<@NotNull String, @Nullable JsonValue>>() {
|
||||
private final Generator<@NotNull Generator<@NotNull Map<@NotNull String, @Nullable JsonValue>>> source
|
||||
= Generator.from(patterns).map(pattern -> pattern.bind(context, v));
|
||||
private @NotNull Generator<@NotNull Map<@NotNull String, @Nullable JsonValue>> current = Generator.of(() -> { throw new JsonQueryException("empty"); });
|
||||
|
||||
private boolean hasValue;
|
||||
private @Nullable Map<@NotNull String, @Nullable JsonValue> value;
|
||||
|
||||
private boolean advance() {
|
||||
if (hasValue) return true;
|
||||
while (true) {
|
||||
try {
|
||||
hasValue = current.hasNext();
|
||||
value = hasValue ? current.next() : null;
|
||||
return hasValue;
|
||||
} catch (JsonQueryException _) {
|
||||
// switch to next pattern on error
|
||||
if (source.hasNext()) {
|
||||
current = source.next();
|
||||
} else {
|
||||
value = null;
|
||||
hasValue = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Map<@NotNull String, @Nullable JsonValue> next() {
|
||||
if (!advance()) throw new NoSuchElementException();
|
||||
assert value != null;
|
||||
|
||||
var out = value;
|
||||
value = null;
|
||||
hasValue = false;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
return result.map(vars -> {
|
||||
var out = new HashMap<>(variables);
|
||||
out.putAll(vars);
|
||||
return out;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return patterns.stream().map(Objects::toString).collect(Collectors.joining(" ?// "));
|
||||
}
|
||||
|
||||
public sealed interface Pattern {
|
||||
@NotNull
|
||||
Set<@NotNull String> variables();
|
||||
@NotNull
|
||||
Generator<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull JQExpression.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 Generator<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull JQExpression.Context context, @Nullable JsonValue value) {
|
||||
var map = new HashMap<String, JsonValue>();
|
||||
map.put(name, value);
|
||||
return Generator.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 Generator<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull JQExpression.Context context, @Nullable JsonValue value) {
|
||||
var streams = new ArrayList<Supplier<Generator<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 Generator<Map<@NotNull String, @Nullable JsonValue>> bind(@NotNull JQExpression.Context context, @Nullable JsonValue value) {
|
||||
var streams = new ArrayList<Supplier<Generator<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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package eu.jonahbauer.json.query.parser.ast;
|
||||
|
||||
import eu.jonahbauer.json.JsonValue;
|
||||
import eu.jonahbauer.json.query.impl.Generator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record JQReduceExpression(
|
||||
@NotNull JQExpression expression,
|
||||
@NotNull JQPatterns patterns,
|
||||
@NotNull JQExpression init,
|
||||
@NotNull JQExpression update
|
||||
) implements JQExpression {
|
||||
|
||||
@Override
|
||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
||||
return init.evaluate(context)
|
||||
.map(state -> {
|
||||
var it = expression.evaluate(context);
|
||||
while (it.hasNext()) {
|
||||
var item = it.next();
|
||||
|
||||
var bindings = patterns.bind(context, item);
|
||||
var binding = bindings.next();
|
||||
while (bindings.hasNext()) {
|
||||
binding = bindings.next();
|
||||
}
|
||||
|
||||
JsonValue nextState = null;
|
||||
var up = update.evaluate(context.withVariables(binding).withRoot(state));
|
||||
while (up.hasNext()) {
|
||||
nextState = up.next();
|
||||
}
|
||||
state = nextState;
|
||||
}
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstant() {
|
||||
return expression.isConstant() && init.isConstant() && update.isConstant();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return "reduce " + expression + " as " + patterns + " (" + init + "; " + update + ")";
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user