Compare commits
No commits in common. "87ae709f6d0912e5eb8cbf1b84edab29d9efe4aa" and "4d9a3ef4abd27d67a1fc8f2b9b636e6f8e449ae3" have entirely different histories.
87ae709f6d
...
4d9a3ef4ab
@ -10,8 +10,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
* numbers and therefore all numbers are stored as {@code double}.
|
* numbers and therefore all numbers are stored as {@code double}.
|
||||||
*/
|
*/
|
||||||
public record JsonNumber(double value) implements JsonValue, JsonToken {
|
public record JsonNumber(double value) implements JsonValue, JsonToken {
|
||||||
public static final @NotNull JsonNumber ZERO = new JsonNumber(0);
|
public JsonNumber {
|
||||||
public static final @NotNull JsonNumber ONE = new JsonNumber(1);
|
if (!Double.isFinite(value)) throw new IllegalArgumentException("value must be finite");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given int to a JSON number.
|
* Converts the given int to a JSON number.
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
plugins {
|
|
||||||
`java-library`
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "eu.jonahbauer.chat"
|
|
||||||
version = "1.0-SNAPSHOT"
|
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain {
|
|
||||||
languageVersion = JavaLanguageVersion.of(22)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api(project(":core"))
|
|
||||||
api(libs.annotations)
|
|
||||||
|
|
||||||
compileOnly(libs.lombok)
|
|
||||||
annotationProcessor(libs.lombok)
|
|
||||||
|
|
||||||
testImplementation(libs.junit.jupiter)
|
|
||||||
testRuntimeOnly(libs.junit.platform.launcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
withType<JavaCompile> {
|
|
||||||
options.encoding = "UTF-8"
|
|
||||||
options.compilerArgs.add("--enable-preview")
|
|
||||||
}
|
|
||||||
|
|
||||||
withType<Test> {
|
|
||||||
useJUnitPlatform()
|
|
||||||
jvmArgs("--enable-preview")
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,33 +0,0 @@
|
|||||||
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.JQProgram;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class JsonQuery {
|
|
||||||
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);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new UncheckedIOException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Generator<@NotNull JsonValue> run(@Nullable JsonValue value) {
|
|
||||||
return programm.run(value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query;
|
|
||||||
|
|
||||||
public class JsonQueryException extends RuntimeException {
|
|
||||||
public JsonQueryException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonQueryException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonString;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class JsonQueryHaltException extends JsonQueryException {
|
|
||||||
private final @Nullable JsonValue value;
|
|
||||||
private final int statusCode;
|
|
||||||
|
|
||||||
public JsonQueryHaltException(@Nullable JsonValue value, int statusCode) {
|
|
||||||
super(getMessage(value));
|
|
||||||
this.value = value;
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull String getMessage(@Nullable JsonValue value) {
|
|
||||||
return switch (value) {
|
|
||||||
case JsonString(var string) -> string;
|
|
||||||
case null -> "";
|
|
||||||
default -> JsonValue.toJsonString(value);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class JsonQueryParserException extends JsonQueryException {
|
|
||||||
private final int line;
|
|
||||||
private final int column;
|
|
||||||
|
|
||||||
public JsonQueryParserException(int line, int column, @NotNull String message) {
|
|
||||||
super(message);
|
|
||||||
if (line < 0 || column < 0) throw new IllegalArgumentException();
|
|
||||||
|
|
||||||
this.line = line;
|
|
||||||
this.column = column;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonQueryParserException(int line, int column, @NotNull String message, @NotNull Throwable cause) {
|
|
||||||
this(line, column, message);
|
|
||||||
initCause(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String getMessage() {
|
|
||||||
return STR."\{super.getMessage()} at line \{line}, column \{column}";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public class JsonQueryTokenizerException extends JsonQueryParserException {
|
|
||||||
public JsonQueryTokenizerException(int line, int column, @NotNull String message) {
|
|
||||||
super(line, column, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonQueryTokenizerException(int line, int column, @NotNull String message, @NotNull Throwable cause) {
|
|
||||||
super(line, column, message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonString;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class JsonQueryUserException extends JsonQueryException {
|
|
||||||
private final @NotNull JsonValue value;
|
|
||||||
|
|
||||||
public JsonQueryUserException(@NotNull JsonValue value) {
|
|
||||||
super(getMessage(value));
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull String getMessage(@NotNull JsonValue value) {
|
|
||||||
return switch (value) {
|
|
||||||
case JsonString(var string) -> string;
|
|
||||||
case JsonValue v -> "(not a string): " + v.toJsonString();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,155 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.SequencedMap;
|
|
||||||
|
|
||||||
import static eu.jonahbauer.json.JsonTemplateProcessor.JSON;
|
|
||||||
|
|
||||||
public class Main {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("Object Identifier-Index: .foo, .foo.bar");
|
|
||||||
|
|
||||||
JsonQuery.parse("{\"foo\": 1, \"bar\": 2} | reduce . as {(\"foo\", \"bar\"): $item} (0; . + $item, 2 * . * $item)")
|
|
||||||
.run(null)
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[] as {$a, $b, c: {$d, $e}} ?// {$a, $b, c: {$d, $e}} | {$a, $b, $d, $e}")
|
|
||||||
.run(JsonValue.parse("[{\"a\": 1, \"b\": 2, \"c\": {\"d\": 3, \"e\": 4}}, {\"a\": 1, \"b\": 2, \"c\": [{\"d\": 3, \"e\": 4}]}]"))
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".foo")
|
|
||||||
.run(JSON."{\"foo\": 42, \"bar\": \"less interesting data\"}")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".foo")
|
|
||||||
.run(JSON."{\"notfoo\": true, \"alsonotfoo\": false}")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[\"foo\"]")
|
|
||||||
.run(JSON."{\"foo\": 42}")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".foo?")
|
|
||||||
.run(JSON."{\"foo\": 42, \"bar\": \"less interesting data\"}")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("Optional Object Identifier-Index: .foo?");
|
|
||||||
|
|
||||||
JsonQuery.parse(".foo?")
|
|
||||||
.run(JSON."{\"notfoo\": true, \"alsonotfoo\": false}")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[\"foo\"]?")
|
|
||||||
.run(JSON."{\"foo\": 42}")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse("[.foo?]")
|
|
||||||
.run(JSON."[1, 2]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("Array Index: .[<number>]");
|
|
||||||
|
|
||||||
JsonQuery.parse(".[0]")
|
|
||||||
.run(JSON."[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[2]")
|
|
||||||
.run(JSON."[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[-2]")
|
|
||||||
.run(JSON."[1, 2, 3]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("Array/String Slice: .[<number>:<number>]");
|
|
||||||
|
|
||||||
JsonQuery.parse(".[2:4]")
|
|
||||||
.run(JSON."[\"a\",\"b\",\"c\",\"d\",\"e\"]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[2:4]")
|
|
||||||
.run(JSON."\"abcdefghi\"")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[:3]")
|
|
||||||
.run(JSON."[\"a\",\"b\",\"c\",\"d\",\"e\"]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[-2:]")
|
|
||||||
.run(JSON."[\"a\",\"b\",\"c\",\"d\",\"e\"]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
System.out.println();
|
|
||||||
System.out.println("Array/Object Value Iterator: .[]");
|
|
||||||
|
|
||||||
JsonQuery.parse(".[]")
|
|
||||||
.run(JSON."[{\"name\":\"JSON\", \"good\":true}, {\"name\":\"XML\", \"good\":false}]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[]")
|
|
||||||
.run(JSON."[]")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".foo[]")
|
|
||||||
.run(JSON."{\"foo\":[1,2,3]}")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
JsonQuery.parse(".[]")
|
|
||||||
.run(JSON."{\"a\": 1, \"b\": 1}")
|
|
||||||
.forEach(System.out::println);
|
|
||||||
|
|
||||||
// var root = JsonValue.valueOf(List.of(
|
|
||||||
// List.of(
|
|
||||||
// List.of(10, 20),
|
|
||||||
// List.of(30, 40)
|
|
||||||
// ),
|
|
||||||
// List.of(
|
|
||||||
// List.of(1, 2),
|
|
||||||
// List.of(3, 4)
|
|
||||||
// )
|
|
||||||
// ));
|
|
||||||
//
|
|
||||||
// var key = new JQCommaExpression(new JQConstant(new JsonNumber(0)), new JQConstant(new JsonNumber(1)));
|
|
||||||
//
|
|
||||||
// var pattern = new ArrayPattern(List.of(
|
|
||||||
// new JQAsExpression.Pattern.ObjectPattern(of(
|
|
||||||
// key, new JQAsExpression.Pattern.ObjectPattern(of(
|
|
||||||
// key,
|
|
||||||
// new JQAsExpression.Pattern.ValuePattern("$x")
|
|
||||||
// ))
|
|
||||||
// )),
|
|
||||||
// new JQAsExpression.Pattern.ObjectPattern(of(
|
|
||||||
// key, new JQAsExpression.Pattern.ObjectPattern(of(
|
|
||||||
// key,
|
|
||||||
// new JQAsExpression.Pattern.ValuePattern("$y")
|
|
||||||
// ))
|
|
||||||
// ))
|
|
||||||
// ));
|
|
||||||
//
|
|
||||||
// var expression = new JQBinaryExpression(
|
|
||||||
// new JQVariableExpression("$x"),
|
|
||||||
// new JQVariableExpression("$y"),
|
|
||||||
// JQBinaryExpression.Operator.ADD
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// var as = new JQAsExpression(new JQConstant(root), List.of(pattern), expression);
|
|
||||||
// as.evaluate(new JQExpression.Context(null)).forEach(System.out::println);
|
|
||||||
//
|
|
||||||
// var parser = new JQParser("[{(0, 1): {(0,1): $x}}, {(0, 1): {(0, 1): $y}}]");
|
|
||||||
// var pattern2 = parser.parsePattern();
|
|
||||||
// System.out.println(pattern);
|
|
||||||
// System.out.println(pattern2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <K, V> SequencedMap<K, V> of(K key1, V value1) {
|
|
||||||
var map = new LinkedHashMap<K, V>();
|
|
||||||
map.put(key1, value1);
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.impl;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
final class ConcatGenerator<T> implements Generator<T> {
|
|
||||||
private final @NotNull Queue<@NotNull Generator<? extends T>> generators;
|
|
||||||
|
|
||||||
public ConcatGenerator(@NotNull List<@NotNull Generator<? extends T>> generators) {
|
|
||||||
this.generators = new LinkedList<>(generators);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
while (!generators.isEmpty()) {
|
|
||||||
var current = generators.peek();
|
|
||||||
if (!current.hasNext()) {
|
|
||||||
generators.remove();
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.impl;
|
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
enum EmptyGenerator implements Generator<Object> {
|
|
||||||
INSTANCE;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object next() {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.impl;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
final class FlatMappingGenerator<T, S> implements Generator<S> {
|
|
||||||
private final @NotNull Generator<T> source;
|
|
||||||
private final @NotNull Function<? super T, ? extends Generator<? extends S>> function;
|
|
||||||
private @NotNull Generator<? extends S> current = Generator.empty();
|
|
||||||
|
|
||||||
public FlatMappingGenerator(@NotNull Generator<T> source, @NotNull Function<? super T, ? extends Generator<? extends S>> function) {
|
|
||||||
this.source = source;
|
|
||||||
this.function = function;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
while (!current.hasNext() && source.hasNext()) {
|
|
||||||
current = function.apply(source.next());
|
|
||||||
}
|
|
||||||
return current.hasNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public S next() {
|
|
||||||
while (!current.hasNext() && source.hasNext()) {
|
|
||||||
current = function.apply(source.next());
|
|
||||||
}
|
|
||||||
return current.next();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.impl;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.*;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public interface Generator<T> {
|
|
||||||
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) {
|
|
||||||
return new MappingGenerator<>(this, function);
|
|
||||||
}
|
|
||||||
|
|
||||||
default <S> @NotNull Generator<S> flatMap(@NotNull Function<? super T, ? extends Generator<? extends S>> 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")
|
|
||||||
static <T> @NotNull Generator<T> empty() {
|
|
||||||
return (Generator<T>) EmptyGenerator.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T> @NotNull Generator<T> concat(@NotNull Generator<? extends T> first, @NotNull Generator<? extends T> second) {
|
|
||||||
return new ConcatGenerator<>(List.of(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T> @NotNull Generator<T> of(T value) {
|
|
||||||
return new SingletonGenerator<>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.impl;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
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
|
|
||||||
public boolean hasNext() {
|
|
||||||
return delegate.hasNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public S next() {
|
|
||||||
return function.apply(delegate.next());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.impl;
|
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
final class SingletonGenerator<T> implements Generator<T> {
|
|
||||||
private boolean done;
|
|
||||||
private T value;
|
|
||||||
|
|
||||||
public SingletonGenerator(T value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return !done;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T next() {
|
|
||||||
if (done) throw new NoSuchElementException();
|
|
||||||
|
|
||||||
var out = value;
|
|
||||||
done = true;
|
|
||||||
value = null;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,668 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.*;
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryParserException;
|
|
||||||
import eu.jonahbauer.json.query.parser.ast.*;
|
|
||||||
import eu.jonahbauer.json.query.parser.tokenizer.JQToken;
|
|
||||||
import eu.jonahbauer.json.query.parser.tokenizer.JQTokenKind;
|
|
||||||
import eu.jonahbauer.json.query.parser.tokenizer.JQTokenizer;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.BinaryOperator;
|
|
||||||
|
|
||||||
public class JQParser {
|
|
||||||
private final @NotNull JQTokenizer tokenizer;
|
|
||||||
private final @NotNull Queue<JQToken> pushback = new LinkedList<>();
|
|
||||||
|
|
||||||
public JQParser(@NotNull String string) {
|
|
||||||
this.tokenizer = new JQTokenizer(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQParser(@NotNull Reader reader) {
|
|
||||||
this.tokenizer = new JQTokenizer(reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull JQProgram parseTopLevel() throws IOException {
|
|
||||||
var module = parseModule();
|
|
||||||
var imports = parseImports();
|
|
||||||
var functions = parseFuncDefs();
|
|
||||||
var expr = peek() == null ? null : parseExpression();
|
|
||||||
|
|
||||||
return new JQProgram(module, imports, functions, expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable JQModule parseModule() throws IOException {
|
|
||||||
if (!tryConsume(JQTokenKind.MODULE)) return null;
|
|
||||||
var metadata = parseExpression();
|
|
||||||
consume(JQTokenKind.SEMICOLON);
|
|
||||||
return new JQModule(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull List<@NotNull JQImport> parseImports() throws IOException {
|
|
||||||
var out = new ArrayList<JQImport>();
|
|
||||||
|
|
||||||
while (peek(JQTokenKind.IMPORT) || peek(JQTokenKind.INCLUDE)) {
|
|
||||||
out.add(parseImport());
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQImport parseImport() throws IOException {
|
|
||||||
var include = tryConsume(JQTokenKind.INCLUDE);
|
|
||||||
if (!include) consume(JQTokenKind.IMPORT);
|
|
||||||
|
|
||||||
var path = parseString();
|
|
||||||
|
|
||||||
String as;
|
|
||||||
if (include) {
|
|
||||||
as = null;
|
|
||||||
} else {
|
|
||||||
consume(JQTokenKind.AS);
|
|
||||||
var dollar = tryConsume(JQTokenKind.DOLLAR);
|
|
||||||
var ident = consume(JQTokenKind.IDENT);
|
|
||||||
as = (dollar ? "$" : "") + ident.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tryConsume(JQTokenKind.SEMICOLON)) {
|
|
||||||
return new JQImport(path, as, null);
|
|
||||||
} else {
|
|
||||||
var metadata = parseExpression();
|
|
||||||
consume(JQTokenKind.SEMICOLON);
|
|
||||||
return new JQImport(path, as, metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull List<@NotNull JQFunction> parseFuncDefs() throws IOException {
|
|
||||||
var out = new ArrayList<JQFunction>();
|
|
||||||
while (peek(JQTokenKind.DEF)) {
|
|
||||||
out.add(parseFuncDef());
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQFunction parseFuncDef() throws IOException {
|
|
||||||
consume(JQTokenKind.DEF);
|
|
||||||
var name = consume(JQTokenKind.IDENT).text();
|
|
||||||
|
|
||||||
var params = new ArrayList<String>();
|
|
||||||
if (tryConsume(JQTokenKind.LPAREN)) {
|
|
||||||
do {
|
|
||||||
var dollar = tryConsume(JQTokenKind.DOLLAR);
|
|
||||||
var param = consume(JQTokenKind.IDENT).text();
|
|
||||||
params.add((dollar ? "$" : "") + param);
|
|
||||||
} while (tryConsume(JQTokenKind.SEMICOLON));
|
|
||||||
|
|
||||||
consume(JQTokenKind.RPAREN);
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(JQTokenKind.COLON);
|
|
||||||
var body = parseExpression();
|
|
||||||
consume(JQTokenKind.SEMICOLON);
|
|
||||||
|
|
||||||
return new JQFunction(name, params, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull JQExpression parseExpression() throws IOException {
|
|
||||||
if (peek(JQTokenKind.DEF)) {
|
|
||||||
var function = parseFuncDef();
|
|
||||||
var expr = parsePipeExpression();
|
|
||||||
return new JQFunctionDefinition(function, expr);
|
|
||||||
} else if (peek(JQTokenKind.LABEL)) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return parsePipeExpression();
|
|
||||||
}
|
|
||||||
throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseNonAssocBinaryExpression(
|
|
||||||
@NotNull Map<@NotNull JQTokenKind, @NotNull BinaryOperator<@NotNull JQExpression>> operations,
|
|
||||||
@NotNull Downstream next
|
|
||||||
) throws IOException {
|
|
||||||
var first = next.get();
|
|
||||||
|
|
||||||
var operator = peek();
|
|
||||||
var operation = operator == null ? null : operations.get(operator.kind());
|
|
||||||
if (operation != null) {
|
|
||||||
next();
|
|
||||||
var second = next.get();
|
|
||||||
first = operation.apply(first, second);
|
|
||||||
}
|
|
||||||
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseLeftAssocBinaryExpression(
|
|
||||||
@NotNull Map<@NotNull JQTokenKind, @NotNull BinaryOperator<@NotNull JQExpression>> operations,
|
|
||||||
@NotNull Downstream next
|
|
||||||
) throws IOException {
|
|
||||||
var first = next.get();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
var operator = peek();
|
|
||||||
var operation = operator == null ? null : operations.get(operator.kind());
|
|
||||||
if (operation == null) break;
|
|
||||||
|
|
||||||
next();
|
|
||||||
var second = next.get();
|
|
||||||
first = operation.apply(first, second);
|
|
||||||
}
|
|
||||||
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface Downstream {
|
|
||||||
@NotNull
|
|
||||||
JQExpression get() throws IOException;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parsePipeExpression() throws IOException {
|
|
||||||
return parseLeftAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.PIPE, JQPipeExpression::new
|
|
||||||
), this::parseCommaExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseCommaExpression() throws IOException {
|
|
||||||
return parseLeftAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.COMMA, JQCommaExpression::new
|
|
||||||
), this::parseAlternativeExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseAlternativeExpression() throws IOException {
|
|
||||||
return parseLeftAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.DEFINEDOR, JQAlternativeExpression::new
|
|
||||||
), this::parseAssignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseAssignment() throws IOException {
|
|
||||||
return parseNonAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.ASSIGN, JQAssignment::new,
|
|
||||||
JQTokenKind.SETPLUS, JQAssignment::add,
|
|
||||||
JQTokenKind.SETMINUS, JQAssignment::sub,
|
|
||||||
JQTokenKind.SETMULT, JQAssignment::mul,
|
|
||||||
JQTokenKind.SETDIV, JQAssignment::div,
|
|
||||||
JQTokenKind.SETMOD, JQAssignment::mod,
|
|
||||||
JQTokenKind.SETPIPE, JQAssignmentPipe::new,
|
|
||||||
JQTokenKind.SETDEFINEDOR, JQAssignmentCoerce::new
|
|
||||||
), this::parseBooleanOrExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseBooleanOrExpression() throws IOException {
|
|
||||||
return parseLeftAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.OR, JQBooleanOrExpression::new
|
|
||||||
), this::parseBooleanAndExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseBooleanAndExpression() throws IOException {
|
|
||||||
return parseLeftAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.AND, JQBooleanAndExpression::new
|
|
||||||
), this::parseComparisonExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseComparisonExpression() throws IOException {
|
|
||||||
return parseNonAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.EQ, JQBinaryExpression::eq,
|
|
||||||
JQTokenKind.NEQ, JQBinaryExpression::neq,
|
|
||||||
JQTokenKind.LESS, JQBinaryExpression::lt,
|
|
||||||
JQTokenKind.GREATER, JQBinaryExpression::gt,
|
|
||||||
JQTokenKind.LESSEQ, JQBinaryExpression::leq,
|
|
||||||
JQTokenKind.GREATEREQ, JQBinaryExpression::geq
|
|
||||||
), this::parseAdditiveExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseAdditiveExpression() throws IOException {
|
|
||||||
return parseLeftAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.PLUS, JQBinaryExpression::add,
|
|
||||||
JQTokenKind.MINUS, JQBinaryExpression::sub
|
|
||||||
), this::parseMultiplicativeExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseMultiplicativeExpression() throws IOException {
|
|
||||||
return parseLeftAssocBinaryExpression(Map.of(
|
|
||||||
JQTokenKind.MULT, JQBinaryExpression::mul,
|
|
||||||
JQTokenKind.DIV, JQBinaryExpression::div,
|
|
||||||
JQTokenKind.MOD, JQBinaryExpression::mod
|
|
||||||
), this::parseTryCatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseTryCatch() throws IOException {
|
|
||||||
if (tryConsume(JQTokenKind.TRY)) {
|
|
||||||
var expr = parseNegation();
|
|
||||||
var fallback = tryConsume(JQTokenKind.CATCH) ? parseNegation() : null;
|
|
||||||
return new JQTryExpression(expr, fallback);
|
|
||||||
} else {
|
|
||||||
return parseNegation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseNegation() throws IOException {
|
|
||||||
if (tryConsume(JQTokenKind.MINUS)) {
|
|
||||||
var expr = parseErrorSuppression();
|
|
||||||
return new JQNegation(expr);
|
|
||||||
} else {
|
|
||||||
return parseErrorSuppression();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseErrorSuppression() throws IOException {
|
|
||||||
var expression = parseControlFlow();
|
|
||||||
if (tryConsume(JQTokenKind.QUESTION_MARK)) {
|
|
||||||
return new JQTryExpression(expression);
|
|
||||||
} else {
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
if (tryConsume(JQTokenKind.AS)) {
|
|
||||||
var patterns = parsePatterns();
|
|
||||||
consume(JQTokenKind.PIPE);
|
|
||||||
var expr = parseExpression();
|
|
||||||
return new JQAsExpression(term, patterns, expr);
|
|
||||||
}
|
|
||||||
return term;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
var patterns = new ArrayList<JQPatterns.Pattern>();
|
|
||||||
do {
|
|
||||||
patterns.add(parsePattern());
|
|
||||||
} while (tryConsume(JQTokenKind.COMMA));
|
|
||||||
consume(JQTokenKind.RBRACKET);
|
|
||||||
return new JQPatterns.Pattern.ArrayPattern(patterns);
|
|
||||||
} else if (tryConsume(JQTokenKind.LBRACE)) {
|
|
||||||
var patterns = new LinkedHashMap<JQExpression, JQPatterns.Pattern>();
|
|
||||||
do {
|
|
||||||
if (tryConsume(JQTokenKind.DOLLAR)) {
|
|
||||||
var ident = consume(JQTokenKind.IDENT).text();
|
|
||||||
var pattern = new JQPatterns.Pattern.ValuePattern("$" + ident);
|
|
||||||
patterns.put(new JQConstant(JsonString.valueOf(ident)), pattern);
|
|
||||||
} else if (peek(JQTokenKind.IDENT)) {
|
|
||||||
var ident = consume(JQTokenKind.IDENT).text();
|
|
||||||
consume(JQTokenKind.COLON);
|
|
||||||
var pattern = parsePattern();
|
|
||||||
patterns.put(new JQConstant(JsonString.valueOf(ident)), pattern);
|
|
||||||
} else if (tryConsume(JQTokenKind.LPAREN)) {
|
|
||||||
var expr = parseExpression();
|
|
||||||
consume(JQTokenKind.RPAREN);
|
|
||||||
consume(JQTokenKind.COLON);
|
|
||||||
var pattern = parsePattern();
|
|
||||||
patterns.put(new JQParenthesizedExpression(expr), pattern);
|
|
||||||
} else {
|
|
||||||
var key = parseString();
|
|
||||||
consume(JQTokenKind.COLON);
|
|
||||||
var pattern = parsePattern();
|
|
||||||
patterns.put(key, pattern);
|
|
||||||
}
|
|
||||||
} while (tryConsume(JQTokenKind.COMMA));
|
|
||||||
consume(JQTokenKind.RBRACE);
|
|
||||||
return new JQPatterns.Pattern.ObjectPattern(patterns);
|
|
||||||
} else {
|
|
||||||
consume(JQTokenKind.DOLLAR);
|
|
||||||
var ident = consume(JQTokenKind.IDENT).text();
|
|
||||||
return new JQPatterns.Pattern.ValuePattern("$" + ident);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseTerm() throws IOException {
|
|
||||||
var next = peek();
|
|
||||||
var term = switch (next == null ? null : next.kind()) {
|
|
||||||
case NUMBER -> new JQConstant(new JsonNumber(Objects.requireNonNull(next().nval())));
|
|
||||||
case LPAREN -> parseParenthesizedExpression();
|
|
||||||
case LBRACKET -> parseArrayConstructionExpression();
|
|
||||||
case LBRACE -> parseObjectConstructionExpression();
|
|
||||||
case QQSTRING_START -> parseString();
|
|
||||||
case FORMAT -> throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
case BREAK -> {
|
|
||||||
consume(JQTokenKind.IDENT);
|
|
||||||
consume(JQTokenKind.DOLLAR);
|
|
||||||
var label = consume(JQTokenKind.IDENT).text();
|
|
||||||
throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
}
|
|
||||||
case IDENT -> switch (Objects.requireNonNull(peek()).text()) {
|
|
||||||
case "null" -> {
|
|
||||||
consume(JQTokenKind.IDENT);
|
|
||||||
yield new JQConstant(null);
|
|
||||||
}
|
|
||||||
case "true" -> {
|
|
||||||
consume(JQTokenKind.IDENT);
|
|
||||||
yield new JQConstant(JsonBoolean.TRUE);
|
|
||||||
}
|
|
||||||
case "false" -> {
|
|
||||||
consume(JQTokenKind.IDENT);
|
|
||||||
yield new JQConstant(JsonBoolean.FALSE);
|
|
||||||
}
|
|
||||||
default -> parseFunctionInvocation();
|
|
||||||
};
|
|
||||||
case LOC -> {
|
|
||||||
var loc = consume(JQTokenKind.LOC);
|
|
||||||
yield new JQLocExpression("<top-level>", loc.line());
|
|
||||||
}
|
|
||||||
case DOLLAR -> parseVariableExpression();
|
|
||||||
case FIELD -> {
|
|
||||||
var name = consume(JQTokenKind.FIELD).text().substring(1);
|
|
||||||
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
|
|
||||||
yield new JQIndexExpression(new JQRootExpression(), name, optional);
|
|
||||||
}
|
|
||||||
case DOT -> {
|
|
||||||
consume(JQTokenKind.DOT);
|
|
||||||
if (peek(JQTokenKind.QQSTRING_START)) {
|
|
||||||
var name = parseString();
|
|
||||||
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
|
|
||||||
yield new JQIndexExpression(new JQRootExpression(), name, optional);
|
|
||||||
} else {
|
|
||||||
yield new JQRootExpression();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case REC -> {
|
|
||||||
consume(JQTokenKind.REC);
|
|
||||||
yield new JQRecursionExpression();
|
|
||||||
}
|
|
||||||
case null -> throw new JsonQueryParserException(-1, -1, "unexpected end of file");
|
|
||||||
default -> {
|
|
||||||
var token = Objects.requireNonNull(peek());
|
|
||||||
throw new JsonQueryParserException(token.line(), token.column(), "unexpected token " + token.kind());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
while (peek(JQTokenKind.LBRACKET) || peek(JQTokenKind.DOT) || peek(JQTokenKind.FIELD)) {
|
|
||||||
term = parseIndexingExpression(term);
|
|
||||||
}
|
|
||||||
|
|
||||||
return term;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseFunctionInvocation() throws IOException {
|
|
||||||
var name = consume(JQTokenKind.IDENT).text();
|
|
||||||
var args = new ArrayList<JQExpression>();
|
|
||||||
if (tryConsume(JQTokenKind.LPAREN)) {
|
|
||||||
do {
|
|
||||||
args.add(parseExpression());
|
|
||||||
} while (tryConsume(JQTokenKind.SEMICOLON));
|
|
||||||
consume(JQTokenKind.RPAREN);
|
|
||||||
}
|
|
||||||
return new JQFunctionInvocation(name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQVariableExpression parseVariableExpression() throws IOException {
|
|
||||||
consume(JQTokenKind.DOLLAR);
|
|
||||||
var ident = consume(JQTokenKind.IDENT).text();
|
|
||||||
return new JQVariableExpression("$" + ident);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseParenthesizedExpression() throws IOException {
|
|
||||||
consume(JQTokenKind.LPAREN);
|
|
||||||
var expression = parseExpression();
|
|
||||||
consume(JQTokenKind.RPAREN);
|
|
||||||
return new JQParenthesizedExpression(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseArrayConstructionExpression() throws IOException {
|
|
||||||
consume(JQTokenKind.LBRACKET);
|
|
||||||
if (tryConsume(JQTokenKind.RBRACKET)) {
|
|
||||||
return new JQConstant(JsonArray.EMPTY);
|
|
||||||
} else {
|
|
||||||
var expression = parseExpression();
|
|
||||||
consume(JQTokenKind.RBRACKET);
|
|
||||||
return new JQArrayConstructionExpression(expression);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseObjectConstructionExpression() throws IOException {
|
|
||||||
consume(JQTokenKind.LBRACE);
|
|
||||||
if (tryConsume(JQTokenKind.RBRACE)) {
|
|
||||||
return new JQConstant(JsonObject.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries = new ArrayList<JQObjectConstructionExpression.Entry>();
|
|
||||||
while (!tryConsume(JQTokenKind.RBRACE)) {
|
|
||||||
if (!entries.isEmpty()) {
|
|
||||||
consume(JQTokenKind.COMMA);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peek(JQTokenKind.LOC)) {
|
|
||||||
var loc = consume(JQTokenKind.LOC);
|
|
||||||
entries.add(new JQObjectConstructionExpression.Entry(
|
|
||||||
new JQConstant(JsonValue.valueOf("__loc__")),
|
|
||||||
new JQLocExpression("<top-level>", loc.line())
|
|
||||||
));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var next = peek();
|
|
||||||
JQExpression key;
|
|
||||||
JQExpression value = null;
|
|
||||||
|
|
||||||
switch (next == null ? null : next.kind()) {
|
|
||||||
case IDENT -> {
|
|
||||||
key = new JQConstant(JsonString.valueOf(consume(JQTokenKind.IDENT).text()));
|
|
||||||
value = new JQIndexExpression(new JQRootExpression(), key, false);
|
|
||||||
}
|
|
||||||
case QQSTRING_START -> {
|
|
||||||
key = parseString();
|
|
||||||
value = new JQIndexExpression(new JQRootExpression(), key, false);
|
|
||||||
}
|
|
||||||
case DOLLAR -> {
|
|
||||||
var var = parseVariableExpression();
|
|
||||||
key = new JQConstant(JsonString.valueOf(var.name().substring(1)));
|
|
||||||
value = var;
|
|
||||||
}
|
|
||||||
case LPAREN -> {
|
|
||||||
key = parseParenthesizedExpression();
|
|
||||||
}
|
|
||||||
case AS, IMPORT, INCLUDE, MODULE, DEF, IF, THEN, ELSE, ELSE_IF, AND, OR, END, REDUCE, FOREACH, TRY,
|
|
||||||
CATCH, LABEL, BREAK -> {
|
|
||||||
key = new JQConstant(JsonString.valueOf(consume(next.kind()).text()));
|
|
||||||
value = key;
|
|
||||||
}
|
|
||||||
case null -> throw new JsonQueryParserException(-1, -1, "unexpected end of file");
|
|
||||||
default -> {
|
|
||||||
var token = Objects.requireNonNull(next);
|
|
||||||
throw new JsonQueryParserException(token.line(),
|
|
||||||
token.column(),
|
|
||||||
"unexpected token " + token.kind()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == null) {
|
|
||||||
consume(JQTokenKind.COLON);
|
|
||||||
} else if (!tryConsume(JQTokenKind.COLON)) {
|
|
||||||
entries.add(new JQObjectConstructionExpression.Entry(key, value));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = parseLeftAssocBinaryExpression(Map.of(JQTokenKind.PIPE, JQPipeExpression::new), this::parseNegation);
|
|
||||||
entries.add(new JQObjectConstructionExpression.Entry(key, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JQObjectConstructionExpression(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQExpression parseIndexingExpression(@NotNull JQExpression root) throws IOException {
|
|
||||||
if (tryConsume(JQTokenKind.LBRACKET)) {
|
|
||||||
if (tryConsume(JQTokenKind.RBRACKET)) {
|
|
||||||
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
|
|
||||||
return new JQIterateExpression(root, optional);
|
|
||||||
} else if (tryConsume(JQTokenKind.COLON)) {
|
|
||||||
var end = parseExpression();
|
|
||||||
consume(JQTokenKind.RBRACKET);
|
|
||||||
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
|
|
||||||
return new JQSliceExpression(root, null, end, optional);
|
|
||||||
} else {
|
|
||||||
var expr = parseExpression();
|
|
||||||
if (tryConsume(JQTokenKind.COLON)) {
|
|
||||||
var end = peek(JQTokenKind.RBRACKET) ? null : parseExpression();
|
|
||||||
consume(JQTokenKind.RBRACKET);
|
|
||||||
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
|
|
||||||
return new JQSliceExpression(root, expr, end, optional);
|
|
||||||
} else {
|
|
||||||
consume(JQTokenKind.RBRACKET);
|
|
||||||
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
|
|
||||||
return new JQIndexExpression(root, expr, optional);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (peek(JQTokenKind.FIELD)) {
|
|
||||||
var name = consume(JQTokenKind.FIELD).text().substring(1);
|
|
||||||
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
|
|
||||||
return new JQIndexExpression(root, name, optional);
|
|
||||||
} else {
|
|
||||||
consume(JQTokenKind.DOT);
|
|
||||||
var name = parseString();
|
|
||||||
var optional = tryConsume(JQTokenKind.QUESTION_MARK);
|
|
||||||
return new JQIndexExpression(root, name, optional);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQStringInterpolation parseString() throws IOException {
|
|
||||||
var format = peek(JQTokenKind.FORMAT) ? next().text() : null;
|
|
||||||
|
|
||||||
var current = new StringBuilder();
|
|
||||||
var fragments = new ArrayList<String>();
|
|
||||||
var values = new ArrayList<JQExpression>();
|
|
||||||
|
|
||||||
consume(JQTokenKind.QQSTRING_START);
|
|
||||||
|
|
||||||
current.setLength(0);
|
|
||||||
while (peek(JQTokenKind.QQSTRING_TEXT)) {
|
|
||||||
current.append(next().sval());
|
|
||||||
}
|
|
||||||
fragments.add(current.toString());
|
|
||||||
|
|
||||||
while (tryConsume(JQTokenKind.QQSTRING_INTERP_START)) {
|
|
||||||
values.add(parseExpression());
|
|
||||||
consume(JQTokenKind.QQSTRING_INTERP_END);
|
|
||||||
|
|
||||||
current.setLength(0);
|
|
||||||
while (peek(JQTokenKind.QQSTRING_TEXT)) {
|
|
||||||
current.append(next().sval());
|
|
||||||
}
|
|
||||||
fragments.add(current.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(JQTokenKind.QQSTRING_END);
|
|
||||||
return new JQStringInterpolation(format, fragments, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void consume(@NotNull String ident) throws IOException {
|
|
||||||
var token = next();
|
|
||||||
if (token.kind() != JQTokenKind.IDENT || !Objects.equals(ident, token.text())) {
|
|
||||||
throw new JsonQueryParserException(0, 0, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQToken consume(@NotNull JQTokenKind kind) throws IOException {
|
|
||||||
var token = next();
|
|
||||||
if (token.kind() != kind) throw new JsonQueryParserException(token.line(), token.column(), "unexpected " + token.kind() + " expected " + kind);
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryConsume(@NotNull String ident) throws IOException {
|
|
||||||
var out = peek(ident);
|
|
||||||
if (out) next();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryConsume(@NotNull JQTokenKind kind) throws IOException {
|
|
||||||
var out = peek(kind);
|
|
||||||
if (out) next();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean peek(@NotNull String ident) throws IOException {
|
|
||||||
var token = peek();
|
|
||||||
return token != null && token.kind() == JQTokenKind.IDENT && Objects.equals(ident, token.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean peek(@NotNull JQTokenKind kind) throws IOException {
|
|
||||||
var token = peek();
|
|
||||||
return token != null && token.kind() == kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable JQToken peek() throws IOException {
|
|
||||||
if (pushback.isEmpty()) {
|
|
||||||
var next = tokenizer.next();
|
|
||||||
pushback(next);
|
|
||||||
return next;
|
|
||||||
} else {
|
|
||||||
return pushback.peek();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull JQToken next() throws IOException {
|
|
||||||
if (pushback.isEmpty()) {
|
|
||||||
var next = tokenizer.next();
|
|
||||||
if (next == null) throw new JsonQueryParserException(0, 0, "unexpected $end");
|
|
||||||
return next;
|
|
||||||
} else {
|
|
||||||
return pushback.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pushback(@Nullable JQToken token) {
|
|
||||||
pushback.add(token);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
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.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@SuppressWarnings("preview")
|
|
||||||
public record JQAlternativeExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
|
|
||||||
public JQAlternativeExpression {
|
|
||||||
Objects.requireNonNull(first);
|
|
||||||
Objects.requireNonNull(second);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
var first = this.first.evaluate(context);
|
|
||||||
var second = this.second.evaluate(context);
|
|
||||||
return new Generator<>() {
|
|
||||||
private boolean empty = true;
|
|
||||||
|
|
||||||
private JsonValue value;
|
|
||||||
private boolean hasValue;
|
|
||||||
|
|
||||||
private boolean advance() {
|
|
||||||
if (hasValue) return true;
|
|
||||||
while (first.hasNext()) {
|
|
||||||
var value = first.next();
|
|
||||||
if (JsonMath.isTruthy(value)) {
|
|
||||||
this.empty = false;
|
|
||||||
this.value = value;
|
|
||||||
this.hasValue = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (empty && second.hasNext()) {
|
|
||||||
this.value = second.next();
|
|
||||||
this.hasValue = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonValue next() throws NoSuchElementException {
|
|
||||||
if (!advance()) throw new NoSuchElementException();
|
|
||||||
var out = value;
|
|
||||||
hasValue = false;
|
|
||||||
value = null;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return first.isConstant() && second.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return first + " // " + second;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonArray;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQArrayConstructionExpression(@NotNull JQExpression expression) implements JQExpression {
|
|
||||||
public JQArrayConstructionExpression {
|
|
||||||
Objects.requireNonNull(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return Generator.of(() -> new JsonArray(expression.evaluate(context).toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "[" + expression + "]";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQAsExpression(
|
|
||||||
@NotNull JQExpression variable, @NotNull JQPatterns patterns, @NotNull JQExpression expression
|
|
||||||
) implements JQExpression {
|
|
||||||
|
|
||||||
public JQAsExpression {
|
|
||||||
Objects.requireNonNull(variable);
|
|
||||||
Objects.requireNonNull(expression);
|
|
||||||
Objects.requireNonNull(patterns);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return variable.evaluate(context).flatMap(value -> patterns.bind(context, value, expression::evaluate));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return variable.isConstant() && expression.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return variable + " as " + patterns + " | " + expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonMath;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.BinaryOperator;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public record JQAssignment(@NotNull JQExpression target, @NotNull JQExpression value, @NotNull Operator operator) implements JQExpression {
|
|
||||||
public static @NotNull JQAssignment add(@NotNull JQExpression target, @NotNull JQExpression value) {
|
|
||||||
return new JQAssignment(target, value, Operator.ADD);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQAssignment sub(@NotNull JQExpression target, @NotNull JQExpression value) {
|
|
||||||
return new JQAssignment(target, value, Operator.SUB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQAssignment mul(@NotNull JQExpression target, @NotNull JQExpression value) {
|
|
||||||
return new JQAssignment(target, value, Operator.MUL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQAssignment div(@NotNull JQExpression target, @NotNull JQExpression value) {
|
|
||||||
return new JQAssignment(target, value, Operator.DIV);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQAssignment mod(@NotNull JQExpression target, @NotNull JQExpression value) {
|
|
||||||
return new JQAssignment(target, value, Operator.MOD);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQAssignment {
|
|
||||||
Objects.requireNonNull(target);
|
|
||||||
Objects.requireNonNull(value);
|
|
||||||
Objects.requireNonNull(operator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQAssignment(@NotNull JQExpression target, @NotNull JQExpression value) {
|
|
||||||
this(target, value, Operator.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return STR."\{target} \{operator.symbol} \{value}";
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public enum Operator {
|
|
||||||
ID((_, value) -> value, "="),
|
|
||||||
ADD(JsonMath::add, "+="),
|
|
||||||
SUB(JsonMath::sub, "-="),
|
|
||||||
MUL(JsonMath::mul, "*="),
|
|
||||||
DIV(JsonMath::div, "/="),
|
|
||||||
MOD(JsonMath::mod, "%="),
|
|
||||||
;
|
|
||||||
|
|
||||||
private final @NotNull BinaryOperator<@Nullable JsonValue> operator;
|
|
||||||
private final @NotNull String symbol;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public record JQAssignmentCoerce(@NotNull JQExpression target, @NotNull JQExpression value) implements JQExpression {
|
|
||||||
|
|
||||||
public JQAssignmentCoerce {
|
|
||||||
Objects.requireNonNull(target);
|
|
||||||
Objects.requireNonNull(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return target + " //= " + value;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQAssignmentPipe(@NotNull JQExpression target, @NotNull JQExpression value) implements JQExpression {
|
|
||||||
|
|
||||||
public JQAssignmentPipe {
|
|
||||||
Objects.requireNonNull(target);
|
|
||||||
Objects.requireNonNull(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
throw new UnsupportedOperationException("not yet implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return target + " |= " + value;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonMath;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import eu.jonahbauer.json.query.util.Util;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.BinaryOperator;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.ADD);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression sub(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.SUB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression mul(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.MUL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression div(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.DIV);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression mod(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.MOD);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression eq(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.EQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression neq(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.NEQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression lt(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.LT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression gt(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.GT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression leq(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.LEQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull JQBinaryExpression geq(@NotNull JQExpression first, @NotNull JQExpression second) {
|
|
||||||
return new JQBinaryExpression(first, second, Operator.GEQ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQBinaryExpression {
|
|
||||||
Objects.requireNonNull(first);
|
|
||||||
Objects.requireNonNull(second);
|
|
||||||
Objects.requireNonNull(operator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return Util.crossReversed(List.of(first, second), context)
|
|
||||||
.map(values -> operator.apply(values.getFirst(), values.getLast()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return first.isConstant() && second.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return STR."\{first} \{operator.symbol} \{second}";
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public enum Operator implements BinaryOperator<@Nullable JsonValue> {
|
|
||||||
ADD(JsonMath::add, "+"),
|
|
||||||
SUB(JsonMath::sub, "-"),
|
|
||||||
MUL(JsonMath::mul, "*"),
|
|
||||||
DIV(JsonMath::div, "/"),
|
|
||||||
MOD(JsonMath::mod, "%"),
|
|
||||||
EQ(JsonMath::eq, "=="),
|
|
||||||
NEQ(JsonMath::neq, "!="),
|
|
||||||
LT(JsonMath::lt, "<"),
|
|
||||||
GT(JsonMath::gt, ">"),
|
|
||||||
LEQ(JsonMath::leq, "<="),
|
|
||||||
GEQ(JsonMath::geq, ">="),
|
|
||||||
;
|
|
||||||
|
|
||||||
private final @NotNull BinaryOperator<@Nullable JsonValue> operator;
|
|
||||||
private final @NotNull String symbol;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonValue apply(@Nullable JsonValue value, @Nullable JsonValue value2) {
|
|
||||||
return operator.apply(value, value2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonBoolean;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonMath;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQBooleanAndExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
|
|
||||||
|
|
||||||
public JQBooleanAndExpression {
|
|
||||||
Objects.requireNonNull(first);
|
|
||||||
Objects.requireNonNull(second);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return first.evaluate(context)
|
|
||||||
.flatMap(value -> JsonMath.isFalsy(value)
|
|
||||||
? Generator.of(JsonBoolean.FALSE)
|
|
||||||
: second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return first.isConstant() && second.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return first + " and " + second;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonBoolean;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonMath;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public record JQBooleanOrExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
|
|
||||||
|
|
||||||
public JQBooleanOrExpression {
|
|
||||||
Objects.requireNonNull(first);
|
|
||||||
Objects.requireNonNull(second);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return first.evaluate(context)
|
|
||||||
.flatMap(value -> JsonMath.isTruthy(value)
|
|
||||||
? Generator.of(JsonBoolean.TRUE)
|
|
||||||
: second.evaluate(context).map(JsonMath::isTruthy).map(JsonBoolean::valueOf)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return first.isConstant() && second.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return first + " or " + second;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,402 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.*;
|
|
||||||
import eu.jonahbauer.json.query.JsonMath;
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryException;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import eu.jonahbauer.json.query.parser.JQParser;
|
|
||||||
import eu.jonahbauer.json.query.parser.ast.JQExpression.Context;
|
|
||||||
import eu.jonahbauer.json.query.util.Util;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public enum JQBuiltIn implements JQInvocable {
|
|
||||||
ABS(0, Implementation.map$V(JsonMath::abs)),
|
|
||||||
ADD(0, (context, _) -> context.stream().map(value -> JsonMath.values(value).reduce(null, JsonMath::add))),
|
|
||||||
NOT(0, Implementation.map$V(JsonMath::not)),
|
|
||||||
|
|
||||||
// error handling
|
|
||||||
ERROR$0(0, (context, _) -> context.stream().flatMap(JsonMath::error)),
|
|
||||||
ERROR$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(JsonMath::error)),
|
|
||||||
HALT(0, (_, _) -> Generator.of((JsonValue) null).flatMap(_ -> JsonMath.halt())),
|
|
||||||
HALT_ERROR$0(0, (context, _) -> context.stream().flatMap(JsonMath::halt)),
|
|
||||||
HALT_ERROR$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(value -> JsonMath.halt(context.root(), value))),
|
|
||||||
|
|
||||||
// stream operations
|
|
||||||
EMPTY(0, (_, _) -> Generator.empty()),
|
|
||||||
RANGE$1(1, (context, args) -> args.getFirst().evaluate(context).flatMap(JsonMath::range)),
|
|
||||||
RANGE$2(2, (context, args) -> Util.cross(args, context).flatMap(bounds -> JsonMath.range(bounds.get(0), bounds.get(1)))),
|
|
||||||
RANGE$3(3, (context, args) -> Util.cross(args, context).flatMap(bounds -> JsonMath.range(bounds.get(0), bounds.get(1), bounds.get(2)))),
|
|
||||||
LIMIT(2, (context, args) -> args.getFirst().evaluate(context).flatMap(limit -> JsonMath.limit(args.getLast().evaluate(context), limit))),
|
|
||||||
FIRST$1(1, (context, args) -> JsonMath.first(args.getFirst().evaluate(context))),
|
|
||||||
LAST$1(1, (context, args) -> JsonMath.last(args.getFirst().evaluate(context))),
|
|
||||||
NTH$2(2, (context, args) -> args.getFirst().evaluate(context).flatMap(n -> JsonMath.nth(args.getLast().evaluate(context), n))),
|
|
||||||
SELECT(1, Implementation.mapF$F(JsonMath::select)),
|
|
||||||
|
|
||||||
// loops
|
|
||||||
RECURSE$0(0, Implementation.map$F(JsonMath::recurse)),
|
|
||||||
RECURSE$1(1, Implementation.mapF$F(JsonMath::recurse)),
|
|
||||||
RECURSE$2(2, Implementation.mapFF$F(JsonMath::recurse)),
|
|
||||||
WALK$1(1, Implementation.mapF$F(JsonMath::walk)),
|
|
||||||
WHILE(2, Implementation.mapFF$F(JsonMath::while_)),
|
|
||||||
UNTIL(2, Implementation.mapFF$F(JsonMath::until)),
|
|
||||||
|
|
||||||
// iterable operations
|
|
||||||
MAP(1, Implementation.mapF$V(JsonMath::map)),
|
|
||||||
MAP_VALUES(1, Implementation.mapF$V(JsonMath::mapValues)),
|
|
||||||
KEYS(0, Implementation.map$V(JsonMath::keys)),
|
|
||||||
KEYS_UNSORTED(0, Implementation.map$V(JsonMath::keysUnsorted)),
|
|
||||||
HAS(1, Implementation.mapV$V(JsonMath::has)),
|
|
||||||
IN(1, Implementation.mapV$V(JsonMath::in)),
|
|
||||||
FIRST$0(0, Implementation.map$V(JsonMath::first)),
|
|
||||||
LAST$0(0, Implementation.map$V(JsonMath::last)),
|
|
||||||
NTH$1(1, Implementation.mapV$V(JsonMath::index)),
|
|
||||||
ANY$0(0, Implementation.map$V(JsonMath::any)),
|
|
||||||
ANY$1(1, Implementation.mapF$V(JsonMath::any)),
|
|
||||||
ANY$2(2, Implementation.mapFF$V((root, gen, filter) -> JsonMath.any(gen.apply(root), filter))),
|
|
||||||
ALL$0(0, Implementation.map$V(JsonMath::all)),
|
|
||||||
ALL$1(1, Implementation.mapF$V(JsonMath::all)),
|
|
||||||
ALL$2(2, Implementation.mapFF$V((root, gen, filter) -> JsonMath.all(gen.apply(root), filter))),
|
|
||||||
FLATTEN$0(0, Implementation.map$V(JsonMath::flatten)),
|
|
||||||
FLATTEN$1(1, Implementation.mapV$V(JsonMath::flatten)),
|
|
||||||
SORT(0, Implementation.map$V(JsonMath::sort)),
|
|
||||||
SORT_BY(1, Implementation.mapF$V(JsonMath::sort)),
|
|
||||||
MIN(0, Implementation.map$V(JsonMath::min)),
|
|
||||||
MIN_BY(1, Implementation.mapF$V(JsonMath::min)),
|
|
||||||
MAX(0, Implementation.map$V(JsonMath::max)),
|
|
||||||
MAX_BY(1, Implementation.mapF$V(JsonMath::max)),
|
|
||||||
UNIQUE(0, Implementation.map$V(JsonMath::unique)),
|
|
||||||
UNIQUE_BY(1, Implementation.mapF$V(JsonMath::unique)),
|
|
||||||
GROUP_BY(1, Implementation.mapF$V(JsonMath::group)),
|
|
||||||
REVERSE(0, Implementation.map$V(JsonMath::reverse)),
|
|
||||||
CONTAINS(1, Implementation.mapV$V(JsonMath::contains)),
|
|
||||||
INDICES(1, Implementation.mapV$V(JsonMath::indices)),
|
|
||||||
INDEX(1, Implementation.mapV$V(JsonMath::firstindex)),
|
|
||||||
RINDEX(1, Implementation.mapV$V(JsonMath::lastindex)),
|
|
||||||
INSIDE(1, Implementation.mapV$V(JsonMath::inside)),
|
|
||||||
COMBINATIONS$0(0, Implementation.map$F(JsonMath::combinations)),
|
|
||||||
COMBINATIONS$1(1, Implementation.mapV$F(JsonMath::combinations)),
|
|
||||||
BSEARCH(1, Implementation.mapV$V(JsonMath::bsearch)),
|
|
||||||
TRANSPOSE(0, Implementation.map$V(JsonMath::transpose)),
|
|
||||||
TO_ENTRIES(0, Implementation.map$V(JsonMath::toEntries)),
|
|
||||||
FROM_ENTRIES(0, Implementation.map$V(JsonMath::fromEntries)),
|
|
||||||
WITH_ENTRIES(1, Implementation.mapF$V(JsonMath::withEntries)),
|
|
||||||
|
|
||||||
// filters
|
|
||||||
ARRAYS(0, Implementation.filter(JsonMath::isArray0)),
|
|
||||||
OBJECTS(0, Implementation.filter(JsonMath::isObject0)),
|
|
||||||
ITERABLES(0, Implementation.filter(JsonMath::isIterable0)),
|
|
||||||
BOOLEANS(0, Implementation.filter(JsonMath::isBoolean0)),
|
|
||||||
NUMBERS(0, Implementation.filter(JsonMath::isNumber0)),
|
|
||||||
NORMALS(0, Implementation.filter(JsonMath::isNormal0)),
|
|
||||||
FINITES(0, Implementation.filter(JsonMath::isFinite0)),
|
|
||||||
STRINGS(0, Implementation.filter(JsonMath::isString0)),
|
|
||||||
NULLS(0, Implementation.filter(JsonMath::isNull0)),
|
|
||||||
VALUES(0, Implementation.filter(JsonMath::isValue0)),
|
|
||||||
SCALARS(0, Implementation.filter(JsonMath::isScalar0)),
|
|
||||||
|
|
||||||
// checks
|
|
||||||
ISINFINITE(0, Implementation.map$V(JsonMath::isInfinite)),
|
|
||||||
ISNAN(0, Implementation.map$V(JsonMath::isNan)),
|
|
||||||
ISFINITE(0, Implementation.map$V(JsonMath::isFinite)),
|
|
||||||
ISNORMAL(0, Implementation.map$V(JsonMath::isNormal)),
|
|
||||||
ISEMPTY(1, (context, args) -> Generator.of(() -> JsonMath.isEmpty(args.getFirst().evaluate(context)))),
|
|
||||||
|
|
||||||
// string operations
|
|
||||||
TRIM(0, Implementation.map$V(JsonMath::trim)),
|
|
||||||
LTRIM(0, Implementation.map$V(JsonMath::ltrim)),
|
|
||||||
RTRIM(0, Implementation.map$V(JsonMath::rtrim)),
|
|
||||||
LTRIMSTR(1, Implementation.mapV$V(JsonMath::ltrimstr)),
|
|
||||||
RTRIMSTR(1, Implementation.mapV$V(JsonMath::rtrimstr)),
|
|
||||||
SPLIT$1(1, Implementation.mapV$V(JsonMath::split)),
|
|
||||||
JOIN(1, Implementation.mapV$V(JsonMath::join)),
|
|
||||||
IMPLODE(0, Implementation.map$V(JsonMath::implode)),
|
|
||||||
EXPLODE(0, Implementation.map$V(JsonMath::explode)),
|
|
||||||
ASCII_UPCASE(0, Implementation.map$V(JsonMath::asciiUpcase)),
|
|
||||||
ASCII_DOWNCASE(0, Implementation.map$V(JsonMath::asciiDowncase)),
|
|
||||||
UTF8BYTELENGTH(0, Implementation.map$V(JsonMath::utf8ByteLength)),
|
|
||||||
STARTSWITH(1, Implementation.mapV$V(JsonMath::startswith)),
|
|
||||||
ENDSWITH(1, Implementation.mapV$V(JsonMath::endswith)),
|
|
||||||
|
|
||||||
// regex
|
|
||||||
TEST$1(1, JsonMath::test),
|
|
||||||
TEST$2(2, JsonMath::test),
|
|
||||||
MATCH$1(1, JsonMath::match),
|
|
||||||
MATCH$2(2, JsonMath::match),
|
|
||||||
CAPTURE$1(1, JsonMath::capture),
|
|
||||||
CAPTURE$2(2, JsonMath::capture),
|
|
||||||
SCAN$1(1, JsonMath::scan),
|
|
||||||
SCAN$2(2, JsonMath::scan),
|
|
||||||
SPLIT$2(2, JsonMath::split),
|
|
||||||
SPLITS$1(1, JsonMath::splits),
|
|
||||||
SPLITS$2(2, JsonMath::splits),
|
|
||||||
SUB$2(2, JsonMath::sub),
|
|
||||||
SUB$3(3, JsonMath::sub),
|
|
||||||
GSUB$2(2, JsonMath::gsub),
|
|
||||||
GSUB$3(3, JsonMath::gsub),
|
|
||||||
|
|
||||||
// conversions
|
|
||||||
TYPE(0, Implementation.map$V(JsonMath::type)),
|
|
||||||
TOJSON(0, Implementation.map$V(JsonMath::tojson)),
|
|
||||||
TOSTRING(0, Implementation.map$V(JsonMath::tostring)),
|
|
||||||
TONUMBER(0, Implementation.map$V(JsonMath::tonumber)),
|
|
||||||
FROMJSON(0, Implementation.map$V(JsonMath::fromjson)),
|
|
||||||
|
|
||||||
// time
|
|
||||||
FROMDATEISO8601(0, Implementation.map$V(JsonMath::fromdateiso8601)),
|
|
||||||
FROMDATE(0, Implementation.map$V(JsonMath::fromdate)),
|
|
||||||
TODATEISO8601(0, Implementation.map$V(JsonMath::todateiso8601)),
|
|
||||||
TODATE(0, Implementation.map$V(JsonMath::todate)),
|
|
||||||
NOW(0, Implementation.map$V(_ -> JsonNumber.valueOf(System.currentTimeMillis()))),
|
|
||||||
|
|
||||||
// paths
|
|
||||||
GETPATH(1, Implementation.mapV$V(JsonMath::getpath)),
|
|
||||||
PATHS$0(0, Implementation.map$F(JsonMath::paths)),
|
|
||||||
PATHS$1(1, Implementation.mapF$F(JsonMath::paths)),
|
|
||||||
|
|
||||||
// misc
|
|
||||||
ENV(0, Implementation.map$V(_ -> JsonMath.env())),
|
|
||||||
LENGTH(0, Implementation.map$V(JsonMath::length)),
|
|
||||||
REPEAT(1, (context, args) -> JsonMath.repeat(() -> args.getFirst().evaluate(context))),
|
|
||||||
|
|
||||||
// math library
|
|
||||||
INFINITE(0, Implementation.map$V(_ -> JsonNumber.valueOf(Double.POSITIVE_INFINITY))),
|
|
||||||
NAN(0, Implementation.map$V(_ -> JsonNumber.valueOf(Double.NaN))),
|
|
||||||
ACOS(0, Implementation.map$V(JsonMath::acos)),
|
|
||||||
ACOSH(0, Implementation.map$V(JsonMath::acosh)),
|
|
||||||
ASIN(0, Implementation.map$V(JsonMath::asin)),
|
|
||||||
ASINH(0, Implementation.map$V(JsonMath::asinh)),
|
|
||||||
ATAN(0, Implementation.map$V(JsonMath::atan)),
|
|
||||||
ATANH(0, Implementation.map$V(JsonMath::atanh)),
|
|
||||||
CBRT(0, Implementation.map$V(JsonMath::cbrt)),
|
|
||||||
CEIL(0, Implementation.map$V(JsonMath::ceil)),
|
|
||||||
COS(0, Implementation.map$V(JsonMath::cos)),
|
|
||||||
COSH(0, Implementation.map$V(JsonMath::cosh)),
|
|
||||||
ERF(0, Implementation.map$V(JsonMath::erf)),
|
|
||||||
ERFC(0, Implementation.map$V(JsonMath::erfc)),
|
|
||||||
EXP(0, Implementation.map$V(JsonMath::exp)),
|
|
||||||
EXP10(0, Implementation.map$V(JsonMath::exp10)),
|
|
||||||
EXP2(0, Implementation.map$V(JsonMath::exp2)),
|
|
||||||
EXPM1(0, Implementation.map$V(JsonMath::expm1)),
|
|
||||||
FABS(0, Implementation.map$V(JsonMath::fabs)),
|
|
||||||
FLOOR(0, Implementation.map$V(JsonMath::floor)),
|
|
||||||
GAMMA(0, Implementation.map$V(JsonMath::gamma)),
|
|
||||||
J0(0, Implementation.map$V(JsonMath::j0)),
|
|
||||||
J1(0, Implementation.map$V(JsonMath::j1)),
|
|
||||||
LGAMMA(0, Implementation.map$V(JsonMath::lgamma)),
|
|
||||||
LOG(0, Implementation.map$V(JsonMath::log)),
|
|
||||||
LOG10(0, Implementation.map$V(JsonMath::log10)),
|
|
||||||
LOG1P(0, Implementation.map$V(JsonMath::log1p)),
|
|
||||||
LOG2(0, Implementation.map$V(JsonMath::log2)),
|
|
||||||
LOGB(0, Implementation.map$V(JsonMath::logb)),
|
|
||||||
NEARBYINT(0, Implementation.map$V(JsonMath::nearbyint)),
|
|
||||||
RINT(0, Implementation.map$V(JsonMath::rint)),
|
|
||||||
ROUND(0, Implementation.map$V(JsonMath::round)),
|
|
||||||
SIGNIFICAND(0, Implementation.map$V(JsonMath::significand)),
|
|
||||||
SIN(0, Implementation.map$V(JsonMath::sin)),
|
|
||||||
SINH(0, Implementation.map$V(JsonMath::sinh)),
|
|
||||||
SQRT(0, Implementation.map$V(JsonMath::sqrt)),
|
|
||||||
TAN(0, Implementation.map$V(JsonMath::tan)),
|
|
||||||
TANH(0, Implementation.map$V(JsonMath::tanh)),
|
|
||||||
TGAMMA(0, Implementation.map$V(JsonMath::tgamma)),
|
|
||||||
TRUNC(0, Implementation.map$V(JsonMath::trunc)),
|
|
||||||
Y0(0, Implementation.map$V(JsonMath::y0)),
|
|
||||||
Y1(0, Implementation.map$V(JsonMath::y1)),
|
|
||||||
ATAN2(2, Implementation.mapVV$V((_, a, b) -> JsonMath.atan2(a, b))),
|
|
||||||
COPYSIGN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.copysign(a, b))),
|
|
||||||
DREM(2, Implementation.mapVV$V((_, a, b) -> JsonMath.drem(a, b))),
|
|
||||||
FDIM(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fdim(a, b))),
|
|
||||||
FMAX(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmax(a, b))),
|
|
||||||
FMIN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmin(a, b))),
|
|
||||||
FMOD(2, Implementation.mapVV$V((_, a, b) -> JsonMath.fmod(a, b))),
|
|
||||||
FREXP(2, Implementation.mapVV$V((_, a, b) -> JsonMath.frexp(a, b))),
|
|
||||||
HYPOT(2, Implementation.mapVV$V((_, a, b) -> JsonMath.hypot(a, b))),
|
|
||||||
JN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.jn(a, b))),
|
|
||||||
LDEXP(2, Implementation.mapVV$V((_, a, b) -> JsonMath.ldexp(a, b))),
|
|
||||||
MODF(2, Implementation.mapVV$V((_, a, b) -> JsonMath.modf(a, b))),
|
|
||||||
NEXTAFTER(2, Implementation.mapVV$V((_, a, b) -> JsonMath.nextafter(a, b))),
|
|
||||||
NEXTTOWARD(2, Implementation.mapVV$V((_, a, b) -> JsonMath.nexttoward(a, b))),
|
|
||||||
POW(2, Implementation.mapVV$V((_, a, b) -> JsonMath.pow(a, b))),
|
|
||||||
REMAINDER(2, Implementation.mapVV$V((_, a, b) -> JsonMath.remainder(a, b))),
|
|
||||||
SCALB(2, Implementation.mapVV$V((_, a, b) -> JsonMath.scalb(a, b))),
|
|
||||||
SCALBLN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.scalbln(a, b))),
|
|
||||||
YN(2, Implementation.mapVV$V((_, a, b) -> JsonMath.yn(a, b))),
|
|
||||||
FMA(3, (context, args) -> Util.cross(args, context).map(values -> JsonMath.fma(values.get(0), values.get(1), values.get(2))))
|
|
||||||
;
|
|
||||||
|
|
||||||
public static final @NotNull Map<@NotNull String, @NotNull JQInvocable> ALL_BUILTINS = Arrays.stream(JQBuiltIn.values())
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
JQInvocable::reference,
|
|
||||||
Function.identity()
|
|
||||||
));
|
|
||||||
|
|
||||||
private final @NotNull JQInvocable implementation;
|
|
||||||
|
|
||||||
JQBuiltIn(@NotNull String body) {
|
|
||||||
this(List.of(), parse(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
JQBuiltIn(@NotNull String arg0, @NotNull String body) {
|
|
||||||
this(List.of(arg0), parse(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
JQBuiltIn(@NotNull String arg0, @NotNull String arg1, @NotNull String body) {
|
|
||||||
this(List.of(arg0, arg1), parse(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
JQBuiltIn(@NotNull String arg0, @NotNull String arg1, @NotNull String arg2, @NotNull String body) {
|
|
||||||
this(List.of(arg0, arg1, arg2), parse(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
JQBuiltIn(@NotNull List<@NotNull String> params, @NotNull JQExpression body) {
|
|
||||||
this.implementation = new JQFunction(getIdentifier(name()), params, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
JQBuiltIn(int arity, @NotNull Implementation implementation) {
|
|
||||||
this.implementation = new InvocableDelegate(getIdentifier(name()), arity, implementation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows(IOException.class)
|
|
||||||
private static @NotNull JQExpression parse(@NotNull String expression) {
|
|
||||||
return new JQParser(expression).parseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull String getIdentifier(@NotNull String name) {
|
|
||||||
var identifier = name.toLowerCase(Locale.ROOT);
|
|
||||||
var idx = identifier.lastIndexOf("$");
|
|
||||||
if (idx != -1) identifier = identifier.substring(0, idx);
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull Context context, @NotNull List<@NotNull JQExpression> args) {
|
|
||||||
return implementation.invoke(context, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String identifier() {
|
|
||||||
return implementation.identifier();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int arity() {
|
|
||||||
return implementation.arity();
|
|
||||||
}
|
|
||||||
|
|
||||||
private record InvocableDelegate(
|
|
||||||
@NotNull String identifier, int arity,
|
|
||||||
@NotNull Implementation delegate
|
|
||||||
) implements JQInvocable {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull Context context, @NotNull List<@NotNull JQExpression> args) {
|
|
||||||
if (args.size() != arity) throw new JsonQueryException("invalid argument count");
|
|
||||||
return delegate.invoke(context, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
private interface Implementation {
|
|
||||||
@NotNull 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 mapFF$F(@NotNull TriFunction<? super @Nullable JsonValue, ? super @NotNull JQFilter, ? super @NotNull JQFilter, ? extends @NotNull Generator<? extends @Nullable JsonValue>> function) {
|
|
||||||
return (context, args) -> {
|
|
||||||
var arg0 = args.get(0).bind(context);
|
|
||||||
var arg1 = args.get(1).bind(context);
|
|
||||||
return context.stream().flatMap(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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
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.*;
|
|
||||||
|
|
||||||
public record JQCommaExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
|
|
||||||
public JQCommaExpression {
|
|
||||||
Objects.requireNonNull(first);
|
|
||||||
Objects.requireNonNull(second);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return Generator.concat(first.evaluate(context), second.evaluate(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return first.isConstant() && second.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return first + ", " + second;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
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 JQConstant(@Nullable JsonValue value) implements JQExpression {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return Generator.of(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return JsonValue.toJsonString(value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonMath;
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryException;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public interface JQExpression {
|
|
||||||
|
|
||||||
@NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context);
|
|
||||||
|
|
||||||
boolean isConstant();
|
|
||||||
|
|
||||||
@NotNull String toString();
|
|
||||||
|
|
||||||
default @NotNull JQFilter bind(@NotNull Context context) {
|
|
||||||
return value -> evaluate(context.withRoot(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
record Context(
|
|
||||||
@Nullable JsonValue root,
|
|
||||||
@NotNull Map<@NotNull String, @Nullable JsonValue> variables,
|
|
||||||
@NotNull Map<@NotNull String, @NotNull JQInvocable> functions
|
|
||||||
) {
|
|
||||||
|
|
||||||
public Context(@Nullable JsonValue root) {
|
|
||||||
this(root, Map.of("$ENV", JsonMath.env()), JQBuiltIn.ALL_BUILTINS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Context {
|
|
||||||
var map = new LinkedHashMap<String, @Nullable JsonValue>();
|
|
||||||
variables.forEach((key, value) -> {
|
|
||||||
Objects.requireNonNull(key);
|
|
||||||
if (!key.startsWith("$")) throw new IllegalArgumentException();
|
|
||||||
map.put(key, value);
|
|
||||||
});
|
|
||||||
variables = Collections.unmodifiableMap(map);
|
|
||||||
functions = Map.copyOf(functions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull JQInvocable function(@NotNull String name, int arity) {
|
|
||||||
var out = functions.get(name + "/" + arity);
|
|
||||||
if (out == null) throw new JsonQueryException(name + "/" + arity + " is not defined.");
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable JsonValue variable(@NotNull String name) {
|
|
||||||
if (!variables.containsKey(name)) throw new JsonQueryException(name + " is not defined.");
|
|
||||||
return variables.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Context withRoot(@Nullable JsonValue root) {
|
|
||||||
return new Context(root, variables, functions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Context withFunction(@NotNull JQInvocable function) {
|
|
||||||
var f = new HashMap<>(functions);
|
|
||||||
f.put(function.reference(), function);
|
|
||||||
return new Context(root, variables, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Context withFunctions(@NotNull List<? extends @NotNull JQInvocable> functions) {
|
|
||||||
var f = new HashMap<>(this.functions);
|
|
||||||
functions.forEach(func -> f.put(func.reference(), func));
|
|
||||||
return new Context(root, variables, f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Context withVariable(@NotNull String name, @Nullable JsonValue variable) {
|
|
||||||
var v = new HashMap<>(variables);
|
|
||||||
v.put(name, variable);
|
|
||||||
return new Context(root, v, functions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Context withVariables(@NotNull Map<@NotNull String, @Nullable JsonValue> variables) {
|
|
||||||
var v = new HashMap<>(this.variables);
|
|
||||||
v.putAll(variables);
|
|
||||||
return new Context(root, v, functions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> stream() {
|
|
||||||
return Generator.of(root());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface JQFilter extends Function<@Nullable JsonValue, @NotNull Generator<@Nullable JsonValue>> {
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
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(initial -> {
|
|
||||||
var state = new Object() {
|
|
||||||
private JsonValue state = initial;
|
|
||||||
};
|
|
||||||
return expression.evaluate(context).flatMap(value -> patterns.bind(context, value, ctx -> {
|
|
||||||
return update.evaluate(ctx.withRoot(state.state)).map(v -> {
|
|
||||||
state.state = v;
|
|
||||||
return ctx.withRoot(v);
|
|
||||||
}).flatMap(extract::evaluate);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant() && init.isConstant() && update.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "foreach " + expression + " as " + patterns + " (" + init + "; " + update + "; " + extract + ")";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryException;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public record JQFunction(@NotNull String identifier, @NotNull List<@NotNull String> params, @NotNull JQExpression body) implements JQInvocable {
|
|
||||||
public JQFunction {
|
|
||||||
Objects.requireNonNull(identifier);
|
|
||||||
Objects.requireNonNull(body);
|
|
||||||
params = List.copyOf(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> arguments) {
|
|
||||||
if (arguments.size() != params.size()) throw new JsonQueryException("invalid argument count");
|
|
||||||
|
|
||||||
var expression = body;
|
|
||||||
|
|
||||||
var functions = new ArrayList<JQFunction>();
|
|
||||||
for (int i = params.size() - 1; i >= 0; i--) {
|
|
||||||
String param = params.get(i);
|
|
||||||
if (param.startsWith("$")) {
|
|
||||||
expression = new JQAsExpression(
|
|
||||||
new JQFunctionInvocation(param.substring(1), List.of()),
|
|
||||||
new JQPatterns(List.of(new JQPatterns.Pattern.ValuePattern(param))),
|
|
||||||
expression
|
|
||||||
);
|
|
||||||
param = param.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
functions.add(new JQFunction(param, List.of(), arguments.get(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression.evaluate(context.withFunctions(functions));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int arity() {
|
|
||||||
return params().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "def " + identifier + (params.isEmpty() ? "" : String.join("; ", params)) + ": " + body + ";";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.StringJoiner;
|
|
||||||
|
|
||||||
public record JQFunctionInvocation(@NotNull String name, @NotNull List<@NotNull JQExpression> args) implements JQExpression {
|
|
||||||
public JQFunctionInvocation {
|
|
||||||
Objects.requireNonNull(name);
|
|
||||||
args = List.copyOf(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
var function = context.function(name, args.size());
|
|
||||||
return function.invoke(context, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
var out = new StringJoiner("; ", name + "(", ")");
|
|
||||||
out.setEmptyValue(name);
|
|
||||||
for (var arg : args) out.add(arg.toString());
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public record JQImport(
|
|
||||||
@NotNull JQStringInterpolation path,
|
|
||||||
@Nullable String as,
|
|
||||||
@Nullable JQExpression metadata
|
|
||||||
) {
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonNumber;
|
|
||||||
import eu.jonahbauer.json.JsonString;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonMath;
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryException;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQIndexExpression(@NotNull JQExpression expression, @NotNull JQExpression index, boolean optional) implements JQExpression {
|
|
||||||
public JQIndexExpression {
|
|
||||||
Objects.requireNonNull(expression);
|
|
||||||
Objects.requireNonNull(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQIndexExpression(@NotNull JQExpression expression, @Nullable JsonValue index, boolean optional) {
|
|
||||||
this(expression, new JQConstant(index), optional);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQIndexExpression(@NotNull JQExpression expression, double index, boolean optional) {
|
|
||||||
this(expression, new JsonNumber(index), optional);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQIndexExpression(@NotNull JQExpression expression, @NotNull String index, boolean optional) {
|
|
||||||
this(expression, new JsonString(index), optional);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return expression.evaluate(context).flatMap(value -> index.evaluate(context).mapMulti((index, downstream) -> {
|
|
||||||
try {
|
|
||||||
downstream.accept(JsonMath.index(value, index));
|
|
||||||
} catch (JsonQueryException ex) {
|
|
||||||
if (!optional) throw ex;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant() && index.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return expression + "[" + index + "]" + (optional ? "?" : "");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface JQInvocable {
|
|
||||||
@NotNull
|
|
||||||
Generator<@Nullable JsonValue> invoke(@NotNull JQExpression.Context context, @NotNull List<@NotNull JQExpression> args);
|
|
||||||
|
|
||||||
int arity();
|
|
||||||
@NotNull String identifier();
|
|
||||||
default @NotNull String reference() {
|
|
||||||
return identifier() + "/" + arity();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
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 JQIterateExpression(@NotNull JQExpression expression, boolean optional) implements JQExpression {
|
|
||||||
public JQIterateExpression {
|
|
||||||
Objects.requireNonNull(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return expression.evaluate(context).flatMap(value -> JsonMath.values(value, optional));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return expression + "[]" + (optional ? "?" : "");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonNumber;
|
|
||||||
import eu.jonahbauer.json.JsonObject;
|
|
||||||
import eu.jonahbauer.json.JsonString;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public record JQLocExpression(@NotNull String file, int line) implements JQExpression {
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return Generator.of(JsonObject.of(
|
|
||||||
"file", new JsonString(file),
|
|
||||||
"line", new JsonNumber(line)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "$__loc__";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQModule(@NotNull JQExpression metadata) {
|
|
||||||
public JQModule {
|
|
||||||
Objects.requireNonNull(metadata);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonMath;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public record JQNegation(@NotNull JQExpression expression) implements JQExpression {
|
|
||||||
public JQNegation {
|
|
||||||
Objects.requireNonNull(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return expression.evaluate(context).map(JsonMath::neg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "-" + expression;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonObject;
|
|
||||||
import eu.jonahbauer.json.JsonString;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryException;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import eu.jonahbauer.json.query.util.Util;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.StringJoiner;
|
|
||||||
|
|
||||||
public record JQObjectConstructionExpression(@NotNull List<@NotNull Entry> entries) implements JQExpression {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
var generator = Generator.of(new LinkedHashMap<@NotNull String, @Nullable JsonValue>());
|
|
||||||
for (var entry : entries) {
|
|
||||||
generator = generator.flatMap(map -> entry.key().evaluate(context)
|
|
||||||
.flatMap(key -> {
|
|
||||||
if (!(key instanceof JsonString(var string))) {
|
|
||||||
throw new JsonQueryException("Cannot use " + Util.type(key) + "(" + Util.value(key) + ") as object key.");
|
|
||||||
}
|
|
||||||
return entry.value().evaluate(context)
|
|
||||||
.map(value -> {
|
|
||||||
var out = new LinkedHashMap<>(map);
|
|
||||||
out.put(string, value);
|
|
||||||
return out;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return generator.map(JsonObject::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
var out = new StringJoiner(", ", "{", "}");
|
|
||||||
entries.forEach(entry -> out.add(entry.key() + ": " + entry.value()));
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public record Entry(@NotNull JQExpression key, @NotNull JQExpression value) { }
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQParenthesizedExpression(@NotNull JQExpression expression) implements JQExpression {
|
|
||||||
|
|
||||||
public JQParenthesizedExpression {
|
|
||||||
Objects.requireNonNull(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return expression.evaluate(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "(" + expression + ")";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,206 +0,0 @@
|
|||||||
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.parser.ast.JQExpression.Context;
|
|
||||||
import eu.jonahbauer.json.query.util.Util;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Function;
|
|
||||||
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<@Nullable JsonValue> bind(@NotNull Context context, @Nullable JsonValue value, Function<@NotNull Context, @NotNull Generator<@Nullable JsonValue>> downstream) {
|
|
||||||
return new PatternGeneratorImpl(patterns, context, value, downstream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class PatternGeneratorImpl implements Generator<@Nullable JsonValue> {
|
|
||||||
// one generator per pattern
|
|
||||||
private final @NotNull Generator<@NotNull Generator<@NotNull Map<@NotNull String, @Nullable JsonValue>>> patterns;
|
|
||||||
private final @NotNull Context context;
|
|
||||||
private final @NotNull Function<@NotNull Context, @NotNull Generator<@Nullable JsonValue>> downstream;
|
|
||||||
|
|
||||||
// the generator for the current pattern
|
|
||||||
private @Nullable Generator<@Nullable JsonValue> current;
|
|
||||||
|
|
||||||
public PatternGeneratorImpl(
|
|
||||||
@NotNull List<@NotNull Pattern> patterns, @NotNull Context context, @Nullable JsonValue value,
|
|
||||||
@NotNull Function<@NotNull Context, @NotNull Generator<@Nullable JsonValue>> downstream
|
|
||||||
) {
|
|
||||||
this.context = context;
|
|
||||||
this.downstream = downstream;
|
|
||||||
var variables = new HashMap<String, JsonValue>();
|
|
||||||
patterns.stream().map(Pattern::variables).flatMap(Collection::stream).forEach(key -> variables.put(key, null));
|
|
||||||
this.patterns = Generator.from(patterns).map(pattern -> pattern.bind(context, value).map(vars -> {
|
|
||||||
var out = new HashMap<>(variables);
|
|
||||||
out.putAll(vars);
|
|
||||||
return out;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> T advance(@NotNull Function<Generator<@Nullable JsonValue>, T> function) {
|
|
||||||
var ex = new JsonQueryException("no match");
|
|
||||||
while (true) {
|
|
||||||
if (current == null) {
|
|
||||||
// find the next matching pattern
|
|
||||||
while (patterns.hasNext()) {
|
|
||||||
try {
|
|
||||||
var match = patterns.next();
|
|
||||||
current = match.map(context::withVariables).flatMap(downstream);
|
|
||||||
break;
|
|
||||||
} catch (JsonQueryException e) {
|
|
||||||
ex = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no matching pattern; propagate exception from last match attempt
|
|
||||||
if (current == null) {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return function.apply(current);
|
|
||||||
} catch (JsonQueryException e) {
|
|
||||||
// exception during execution; try the next pattern
|
|
||||||
current = null;
|
|
||||||
ex = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return advance(Generator::hasNext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonValue next() throws NoSuchElementException {
|
|
||||||
return advance(Generator::next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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 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 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 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 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public record JQPipeExpression(@NotNull JQExpression first, @NotNull JQExpression second) implements JQExpression {
|
|
||||||
public JQPipeExpression {
|
|
||||||
Objects.requireNonNull(first);
|
|
||||||
Objects.requireNonNull(second);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return first.evaluate(context).flatMap(value -> second.evaluate(context.withRoot(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return first.isConstant() && second.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return first + " | " + second;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record JQProgram(
|
|
||||||
@Nullable JQModule module,
|
|
||||||
@NotNull List<@NotNull JQImport> imports,
|
|
||||||
@NotNull List<@NotNull JQFunction> functions,
|
|
||||||
@Nullable JQExpression expression
|
|
||||||
) {
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> run(@Nullable JsonValue value) {
|
|
||||||
if (expression == null) return Generator.empty();
|
|
||||||
return expression.evaluate(new JQExpression.Context(value).withFunctions(functions));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonArray;
|
|
||||||
import eu.jonahbauer.json.JsonObject;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public record JQRecursionExpression() implements JQExpression {
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return recurse(context.root());
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull Generator<@Nullable JsonValue> recurse(@Nullable JsonValue value) {
|
|
||||||
return switch (value) {
|
|
||||||
case JsonArray array -> Generator.concat(Generator.of(array), Generator.from(array).flatMap(this::recurse));
|
|
||||||
case JsonObject object -> Generator.concat(Generator.of(object), Generator.from(object.values()).flatMap(this::recurse));
|
|
||||||
case null, default -> Generator.of(value);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
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).flatMap(initial -> {
|
|
||||||
var state = new Object() {
|
|
||||||
private JsonValue state = initial;
|
|
||||||
};
|
|
||||||
return Generator.concat(
|
|
||||||
expression.evaluate(context).flatMap(value -> patterns.bind(context, value, ctx -> {
|
|
||||||
var gen = update.evaluate(ctx.withRoot(state.state));
|
|
||||||
while (gen.hasNext()) {
|
|
||||||
state.state = gen.next();
|
|
||||||
}
|
|
||||||
return Generator.empty();
|
|
||||||
})),
|
|
||||||
Generator.of(() -> state.state)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant() && init.isConstant() && update.isConstant();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "reduce " + expression + " as " + patterns + " (" + init + "; " + update + ")";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
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 JQRootExpression() implements JQExpression {
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return context.stream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return ".";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
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 JQSliceExpression(@NotNull JQExpression expression, @Nullable JQExpression start, @Nullable JQExpression end, boolean optional) implements JQExpression {
|
|
||||||
public JQSliceExpression {
|
|
||||||
Objects.requireNonNull(expression);
|
|
||||||
if (start == null && end == null) throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return expression.evaluate(context)
|
|
||||||
.flatMap(value -> (start == null ? Generator.of((JsonValue) null) : start.evaluate(context))
|
|
||||||
.flatMap(start -> (end == null ? Generator.of((JsonValue) null) : end.evaluate(context))
|
|
||||||
.map(end -> JsonMath.slice(value, start, end))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant() && (start == null || start.isConstant()) && (end == null || end.isConstant());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return expression + "[" + (start == null ? "" : start) + ":" + (end == null ? "" : end) + "]";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonString;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import eu.jonahbauer.json.query.util.Util;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@SuppressWarnings("preview")
|
|
||||||
public record JQStringInterpolation(
|
|
||||||
@Nullable String format,
|
|
||||||
@NotNull List<@NotNull String> fragments,
|
|
||||||
@NotNull List<@NotNull JQExpression> values
|
|
||||||
) implements JQExpression {
|
|
||||||
public JQStringInterpolation {
|
|
||||||
fragments = List.copyOf(fragments);
|
|
||||||
values = List.copyOf(values);
|
|
||||||
if (fragments.size() != values.size() + 1) throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return Util.crossReversed(values, context)
|
|
||||||
.map(values -> StringTemplate.of(fragments, values.stream().map(JQStringInterpolation::toString).toList()))
|
|
||||||
.map(STR::process)
|
|
||||||
.map(JsonString::valueOf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull String toString(@Nullable JsonValue value) {
|
|
||||||
return value instanceof JsonString(var string) ? string : JsonValue.toJsonString(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return format == null && values.stream().allMatch(JQExpression::isConstant);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
var out = new StringBuilder();
|
|
||||||
if (format != null) out.append("@").append(format);
|
|
||||||
out.append("\"");
|
|
||||||
|
|
||||||
var it1 = fragments.iterator();
|
|
||||||
for (JQExpression value : values) {
|
|
||||||
var fragment = JsonString.quote(it1.next());
|
|
||||||
out.append(fragment, 1, fragment.length() - 1);
|
|
||||||
out.append("\\(");
|
|
||||||
out.append(value);
|
|
||||||
out.append(")");
|
|
||||||
}
|
|
||||||
var last = JsonString.quote(it1.next());
|
|
||||||
out.append(last, 1, last.length());
|
|
||||||
|
|
||||||
return out.toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonString;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryException;
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryHaltException;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public record JQTryExpression(@NotNull JQExpression expression, @Nullable JQExpression fallback) implements JQExpression {
|
|
||||||
public JQTryExpression {
|
|
||||||
Objects.requireNonNull(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQTryExpression(@NotNull JQExpression expression) {
|
|
||||||
this(expression, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return new QuietIterator(
|
|
||||||
expression.evaluate(context),
|
|
||||||
fallback == null ? null : ex -> fallback.evaluate(context.withRoot(new JsonString(ex.getMessage())))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return expression.isConstant() && (fallback == null || fallback.isConstant());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return "try " + expression + (fallback == null ? "" : " catch " + fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class QuietIterator implements Generator<@Nullable JsonValue> {
|
|
||||||
private @Nullable Generator<@Nullable JsonValue> delegate;
|
|
||||||
private @Nullable Generator<@Nullable JsonValue> fallback;
|
|
||||||
private @Nullable Function<@NotNull JsonQueryException, @NotNull Generator<@Nullable JsonValue>> fallbackSupplier;
|
|
||||||
|
|
||||||
private JsonValue value;
|
|
||||||
private boolean hasValue;
|
|
||||||
|
|
||||||
private QuietIterator(
|
|
||||||
@NotNull Generator<@Nullable JsonValue> delegate,
|
|
||||||
@Nullable Function<@NotNull JsonQueryException, @NotNull Generator<@Nullable JsonValue>> fallback
|
|
||||||
) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.fallbackSupplier = fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean advance() {
|
|
||||||
if (hasValue) return true;
|
|
||||||
assert fallback == null && delegate != null;
|
|
||||||
try {
|
|
||||||
hasValue = delegate.hasNext();
|
|
||||||
value = hasValue ? delegate.next() : null;
|
|
||||||
return hasValue;
|
|
||||||
} catch (JsonQueryHaltException ex) {
|
|
||||||
throw ex;
|
|
||||||
} catch (JsonQueryException ex) {
|
|
||||||
delegate = null;
|
|
||||||
if (fallbackSupplier != null) {
|
|
||||||
fallback = fallbackSupplier.apply(ex);
|
|
||||||
hasValue = false;
|
|
||||||
value = null;
|
|
||||||
return fallback.hasNext();
|
|
||||||
} else {
|
|
||||||
hasValue = false;
|
|
||||||
value = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
if (fallback != null) return fallback.hasNext();
|
|
||||||
if (delegate == null) return false;
|
|
||||||
return advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable JsonValue next() {
|
|
||||||
if (fallback != null) return fallback.next();
|
|
||||||
if (delegate == null) throw new NoSuchElementException();
|
|
||||||
if (!advance()) throw new NoSuchElementException();
|
|
||||||
var out = value;
|
|
||||||
hasValue = false;
|
|
||||||
value = null;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.ast;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQVariableExpression(@NotNull String name) implements JQExpression {
|
|
||||||
public JQVariableExpression {
|
|
||||||
Objects.requireNonNull(name);
|
|
||||||
if (!name.startsWith("$")) throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Generator<@Nullable JsonValue> evaluate(@NotNull Context context) {
|
|
||||||
return Generator.of(() -> context.variable(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isConstant() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.tokenizer;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public record JQToken(
|
|
||||||
@NotNull JQTokenKind kind, @NotNull String text,
|
|
||||||
@Nullable Double nval, @Nullable String sval,
|
|
||||||
int line, int column
|
|
||||||
) {
|
|
||||||
public JQToken(@NotNull JQTokenKind kind, @NotNull String text, int line, int column) {
|
|
||||||
this(kind, text, null, null, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQToken(@NotNull JQTokenKind kind, @NotNull String text, @NotNull Double nval, int line, int column) {
|
|
||||||
this(kind, text, nval, null, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQToken(@NotNull JQTokenKind kind, @NotNull String text, @NotNull String sval, int line, int column) {
|
|
||||||
this(kind, text, null, sval, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
if (kind.isDynamic()) {
|
|
||||||
return kind + "(" + text + ")";
|
|
||||||
} else {
|
|
||||||
return kind.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.tokenizer;
|
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public enum JQTokenKind {
|
|
||||||
NEQ,
|
|
||||||
EQ,
|
|
||||||
DEFINEDOR,
|
|
||||||
SETPIPE,
|
|
||||||
SETPLUS,
|
|
||||||
SETMINUS,
|
|
||||||
SETMULT,
|
|
||||||
SETDIV,
|
|
||||||
SETMOD,
|
|
||||||
SETDEFINEDOR,
|
|
||||||
LESSEQ,
|
|
||||||
GREATEREQ,
|
|
||||||
REC,
|
|
||||||
ALTERNATION,
|
|
||||||
|
|
||||||
// keywords
|
|
||||||
AS("as"),
|
|
||||||
IMPORT("import"),
|
|
||||||
INCLUDE("include"),
|
|
||||||
MODULE("module"),
|
|
||||||
DEF("def"),
|
|
||||||
IF("if"),
|
|
||||||
THEN("then"),
|
|
||||||
ELSE("else"),
|
|
||||||
ELSE_IF("elif"),
|
|
||||||
AND("and"),
|
|
||||||
OR("or"),
|
|
||||||
END("end"),
|
|
||||||
REDUCE("reduce"),
|
|
||||||
FOREACH("foreach"),
|
|
||||||
TRY("try"),
|
|
||||||
CATCH("catch"),
|
|
||||||
LABEL("label"),
|
|
||||||
BREAK("break"),
|
|
||||||
LOC("$__loc__"),
|
|
||||||
|
|
||||||
// without name
|
|
||||||
DOT("."),
|
|
||||||
QUESTION_MARK("?"),
|
|
||||||
ASSIGN("="),
|
|
||||||
SEMICOLON(";"),
|
|
||||||
COMMA(","),
|
|
||||||
COLON(":"),
|
|
||||||
PIPE("|"),
|
|
||||||
PLUS("+"),
|
|
||||||
MINUS("-"),
|
|
||||||
MULT("*"),
|
|
||||||
DIV("/"),
|
|
||||||
MOD("%"),
|
|
||||||
DOLLAR("$"),
|
|
||||||
LESS("<"),
|
|
||||||
GREATER(">"),
|
|
||||||
|
|
||||||
LBRACKET("["),
|
|
||||||
LBRACE("{"),
|
|
||||||
LPAREN("("),
|
|
||||||
|
|
||||||
RBRACKET("]"),
|
|
||||||
RBRACE("}"),
|
|
||||||
RPAREN(")"),
|
|
||||||
|
|
||||||
QQSTRING_START,
|
|
||||||
QQSTRING_TEXT(true),
|
|
||||||
QQSTRING_END,
|
|
||||||
QQSTRING_INTERP_START,
|
|
||||||
QQSTRING_INTERP_END,
|
|
||||||
|
|
||||||
FIELD(true),
|
|
||||||
FORMAT(true),
|
|
||||||
NUMBER(true),
|
|
||||||
IDENT(true),
|
|
||||||
;
|
|
||||||
|
|
||||||
private final @NotNull String name;
|
|
||||||
@Getter(AccessLevel.PACKAGE)
|
|
||||||
private final boolean dynamic;
|
|
||||||
|
|
||||||
JQTokenKind(@NotNull String name) {
|
|
||||||
this.name = "\"" + name + "\"";
|
|
||||||
this.dynamic = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
JQTokenKind(boolean dynamic) {
|
|
||||||
this.name = name();
|
|
||||||
this.dynamic = dynamic;
|
|
||||||
}
|
|
||||||
|
|
||||||
JQTokenKind() {
|
|
||||||
this.name = name();
|
|
||||||
this.dynamic = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,419 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.parser.tokenizer;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.query.JsonQueryTokenizerException;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.IntPredicate;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
public final class JQTokenizer implements Iterable<JQToken> {
|
|
||||||
private final TrackingReader reader;
|
|
||||||
|
|
||||||
private final Queue<State> stack = Collections.asLifoQueue(new ArrayDeque<>());
|
|
||||||
|
|
||||||
public JQTokenizer(@NotNull String string) {
|
|
||||||
this.reader = new TrackingReader(new StringReader(string));
|
|
||||||
}
|
|
||||||
|
|
||||||
public JQTokenizer(@NotNull Reader reader) {
|
|
||||||
this.reader = new TrackingReader(new BufferedReader(reader));
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable JQToken next() throws IOException {
|
|
||||||
int chr;
|
|
||||||
int line;
|
|
||||||
int column;
|
|
||||||
|
|
||||||
if (stack.peek() == State.IN_QQSTRING) {
|
|
||||||
line = reader.getLineNumber();
|
|
||||||
column = reader.getColumnNumber();
|
|
||||||
chr = reader.read();
|
|
||||||
if (chr == -1) throw new JsonQueryTokenizerException(line, column, "unexpected $end");
|
|
||||||
|
|
||||||
var text = new StringBuilder().append((char) chr);
|
|
||||||
if (chr == '"') {
|
|
||||||
stack.remove(State.IN_QQSTRING);
|
|
||||||
return new JQToken(JQTokenKind.QQSTRING_END, text.toString(), line, column);
|
|
||||||
} else if (chr == '\\') {
|
|
||||||
int chr2 = reader.read();
|
|
||||||
if (chr2 != -1) text.append((char) chr2);
|
|
||||||
|
|
||||||
return switch (chr2) {
|
|
||||||
case '(' -> {
|
|
||||||
stack.add(State.IN_QQINTERP);
|
|
||||||
yield new JQToken(JQTokenKind.QQSTRING_INTERP_START, text.toString(), line, column);
|
|
||||||
}
|
|
||||||
case 'u' -> {
|
|
||||||
if (tryRead(text, this::isHexDigit, 4) != 4) {
|
|
||||||
throw new JsonQueryTokenizerException(line, column, "invalid \\uXXXX escape");
|
|
||||||
}
|
|
||||||
var code = Integer.parseInt(text.substring(2), 16);
|
|
||||||
yield new JQToken(JQTokenKind.QQSTRING_TEXT, text.toString(), String.valueOf((char) code), line, column);
|
|
||||||
}
|
|
||||||
case -1 -> throw new JsonQueryTokenizerException(line, column, "invalid character");
|
|
||||||
default -> new JQToken(JQTokenKind.QQSTRING_TEXT, text.toString(), switch (chr2) {
|
|
||||||
case '"' -> "\"";
|
|
||||||
case '\\' -> "\\";
|
|
||||||
case '/' -> "/";
|
|
||||||
case 'b' -> "\b";
|
|
||||||
case 'f' -> "\f";
|
|
||||||
case 'n' -> "\n";
|
|
||||||
case 'r' -> "\r";
|
|
||||||
case 't' -> "\t";
|
|
||||||
default -> throw new JsonQueryTokenizerException(line, column, "invalid escape");
|
|
||||||
}, line, column);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
tryRead(text, c -> c != '\\' && c != '"');
|
|
||||||
return new JQToken(JQTokenKind.QQSTRING_TEXT, text.toString(), text.toString(), line, column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
line = reader.getLineNumber();
|
|
||||||
column = reader.getColumnNumber();
|
|
||||||
chr = reader.read();
|
|
||||||
|
|
||||||
// rules producing no token
|
|
||||||
if (isWhitespace(chr)) continue;
|
|
||||||
if (chr == '#') {
|
|
||||||
readEndOfLineComment();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOF
|
|
||||||
if (chr == -1) return null;
|
|
||||||
|
|
||||||
var text = new StringBuilder().append((char) chr);
|
|
||||||
assert chr >= 0;
|
|
||||||
var result = switch ((Character) (char) chr) {
|
|
||||||
case Character c when c == '!' && tryRead(text, "=") -> JQTokenKind.NEQ;
|
|
||||||
case Character c when c == '=' && tryRead(text, "=") -> JQTokenKind.EQ;
|
|
||||||
case Character c when c == '/' && tryRead(text, "/") -> JQTokenKind.DEFINEDOR;
|
|
||||||
case Character c when c == '|' && tryRead(text, "=") -> JQTokenKind.SETPIPE;
|
|
||||||
case Character c when c == '+' && tryRead(text, "=") -> JQTokenKind.SETPLUS;
|
|
||||||
case Character c when c == '-' && tryRead(text, "=") -> JQTokenKind.SETMINUS;
|
|
||||||
case Character c when c == '*' && tryRead(text, "=") -> JQTokenKind.SETMULT;
|
|
||||||
case Character c when c == '/' && tryRead(text, "=") -> JQTokenKind.SETDIV;
|
|
||||||
case Character c when c == '%' && tryRead(text, "=") -> JQTokenKind.SETMOD;
|
|
||||||
case Character c when c == '/' && tryRead(text, "/=") -> JQTokenKind.SETDEFINEDOR;
|
|
||||||
case Character c when c == '<' && tryRead(text, "=") -> JQTokenKind.LESSEQ;
|
|
||||||
case Character c when c == '>' && tryRead(text, "=") -> JQTokenKind.GREATEREQ;
|
|
||||||
case Character c when c == '.' && tryRead(text, ".") -> JQTokenKind.REC;
|
|
||||||
case Character c when c == '?' && tryRead(text, "//") -> JQTokenKind.ALTERNATION;
|
|
||||||
case Character c when c == '@' && tryRead(text, this::isIdentifierPart) -> JQTokenKind.FORMAT;
|
|
||||||
case Character c when c == '$' && tryRead(text, "__loc__") -> JQTokenKind.LOC;
|
|
||||||
case '?' -> JQTokenKind.QUESTION_MARK;
|
|
||||||
case '=' -> JQTokenKind.ASSIGN;
|
|
||||||
case ';' -> JQTokenKind.SEMICOLON;
|
|
||||||
case ',' -> JQTokenKind.COMMA;
|
|
||||||
case ':' -> JQTokenKind.COLON;
|
|
||||||
case '|' -> JQTokenKind.PIPE;
|
|
||||||
case '+' -> JQTokenKind.PLUS;
|
|
||||||
case '-' -> JQTokenKind.MINUS;
|
|
||||||
case '*' -> JQTokenKind.MULT;
|
|
||||||
case '/' -> JQTokenKind.DIV;
|
|
||||||
case '%' -> JQTokenKind.MOD;
|
|
||||||
case '$' -> JQTokenKind.DOLLAR;
|
|
||||||
case '<' -> JQTokenKind.LESS;
|
|
||||||
case '>' -> JQTokenKind.GREATER;
|
|
||||||
|
|
||||||
case '[' -> {
|
|
||||||
stack.add(State.IN_BRACKET);
|
|
||||||
yield JQTokenKind.LBRACKET;
|
|
||||||
}
|
|
||||||
case '(' -> {
|
|
||||||
stack.add(State.IN_PAREN);
|
|
||||||
yield JQTokenKind.LPAREN;
|
|
||||||
}
|
|
||||||
case '{' -> {
|
|
||||||
stack.add(State.IN_BRACE);
|
|
||||||
yield JQTokenKind.LBRACE;
|
|
||||||
}
|
|
||||||
case Character c when c == ']' && stack.peek() == State.IN_BRACKET -> {
|
|
||||||
stack.remove();
|
|
||||||
yield JQTokenKind.RBRACKET;
|
|
||||||
}
|
|
||||||
case Character c when c == ')' && stack.peek() == State.IN_PAREN -> {
|
|
||||||
stack.remove();
|
|
||||||
yield JQTokenKind.RPAREN;
|
|
||||||
}
|
|
||||||
case Character c when c == ')' && stack.peek() == State.IN_QQINTERP -> {
|
|
||||||
stack.remove();
|
|
||||||
yield JQTokenKind.QQSTRING_INTERP_END;
|
|
||||||
}
|
|
||||||
case Character c when c == '}' && stack.peek() == State.IN_BRACE -> {
|
|
||||||
stack.remove();
|
|
||||||
yield JQTokenKind.RBRACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
case '"' -> {
|
|
||||||
stack.add(State.IN_QQSTRING);
|
|
||||||
yield JQTokenKind.QQSTRING_START;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Character c when c == '.' && tryRead(text, this::isDigit) -> {
|
|
||||||
readExponential(text);
|
|
||||||
yield new JQToken(JQTokenKind.NUMBER, text.toString(), Double.parseDouble(text.toString()), line, column);
|
|
||||||
}
|
|
||||||
case Character c when isDigit(c) -> {
|
|
||||||
readDigits(text);
|
|
||||||
if (tryRead(text, ".")) readDigits(text);
|
|
||||||
readExponential(text);
|
|
||||||
yield new JQToken(JQTokenKind.NUMBER, text.toString(), Double.parseDouble(text.toString()), line, column);
|
|
||||||
}
|
|
||||||
case Character c when c == '.' && tryRead(text, this::isIdentifierStart) -> {
|
|
||||||
tryRead(text, this::isIdentifierPart);
|
|
||||||
yield JQTokenKind.FIELD;
|
|
||||||
}
|
|
||||||
case '.' -> JQTokenKind.DOT;
|
|
||||||
case Character c when isIdentifierStart(c) -> {
|
|
||||||
tryRead(text, this::isIdentifierPart);
|
|
||||||
while (true) {
|
|
||||||
reader.mark(3);
|
|
||||||
if (reader.read() != ':') { reader.reset(); break; }
|
|
||||||
if (reader.read() != ':') { reader.reset(); break; }
|
|
||||||
chr = reader.read();
|
|
||||||
if (!isIdentifierStart(chr)) { reader.reset(); break; }
|
|
||||||
text.append("::").append((char) chr);
|
|
||||||
tryRead(text, this::isIdentifierPart);
|
|
||||||
}
|
|
||||||
yield switch (text.toString()) {
|
|
||||||
case "as" -> JQTokenKind.AS;
|
|
||||||
case "import" -> JQTokenKind.IMPORT;
|
|
||||||
case "include" -> JQTokenKind.INCLUDE;
|
|
||||||
case "module" -> JQTokenKind.MODULE;
|
|
||||||
case "def" -> JQTokenKind.DEF;
|
|
||||||
case "if" -> JQTokenKind.IF;
|
|
||||||
case "then" -> JQTokenKind.THEN;
|
|
||||||
case "else" -> JQTokenKind.ELSE;
|
|
||||||
case "elif" -> JQTokenKind.ELSE_IF;
|
|
||||||
case "and" -> JQTokenKind.AND;
|
|
||||||
case "or" -> JQTokenKind.OR;
|
|
||||||
case "end" -> JQTokenKind.END;
|
|
||||||
case "reduce" -> JQTokenKind.REDUCE;
|
|
||||||
case "foreach" -> JQTokenKind.FOREACH;
|
|
||||||
case "try" -> JQTokenKind.TRY;
|
|
||||||
case "catch" -> JQTokenKind.CATCH;
|
|
||||||
case "label" -> JQTokenKind.LABEL;
|
|
||||||
case "break" -> JQTokenKind.BREAK;
|
|
||||||
default -> JQTokenKind.IDENT;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default -> throw new JsonQueryTokenizerException(line, column, "invalid character");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (result instanceof JQToken token) {
|
|
||||||
return token;
|
|
||||||
} else {
|
|
||||||
return new JQToken((JQTokenKind) result, text.toString(), line, column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readEndOfLineComment() throws IOException {
|
|
||||||
int chr;
|
|
||||||
do {
|
|
||||||
chr = reader.read();
|
|
||||||
} while (chr != -1 && chr != '\r' && chr != '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readDigits(@NotNull StringBuilder text) throws IOException {
|
|
||||||
tryRead(text, this::isDigit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readExponential(@NotNull StringBuilder text) throws IOException {
|
|
||||||
reader.mark(3);
|
|
||||||
|
|
||||||
// [eE]
|
|
||||||
int e = reader.read();
|
|
||||||
if (e != 'e' && e != 'E') {
|
|
||||||
reader.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// [+-]?
|
|
||||||
int sign = reader.read();
|
|
||||||
int digit;
|
|
||||||
if (sign == '+' || sign == '-') {
|
|
||||||
digit = reader.read();
|
|
||||||
} else {
|
|
||||||
digit = sign;
|
|
||||||
sign = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDigit(digit)) {
|
|
||||||
reader.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
text.append((char) e);
|
|
||||||
if (sign != -1) text.append((char) sign);
|
|
||||||
text.append((char) digit);
|
|
||||||
readDigits(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryRead(@NotNull StringBuilder text, @NotNull String expected) throws IOException {
|
|
||||||
int length = expected.length();
|
|
||||||
reader.mark(length);
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
if (reader.read() != expected.charAt(i)) {
|
|
||||||
reader.reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
text.append(expected);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean tryRead(@NotNull StringBuilder text, @NotNull IntPredicate predicate) throws IOException {
|
|
||||||
return tryRead(text, predicate, Integer.MAX_VALUE) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int tryRead(@NotNull StringBuilder text, @NotNull IntPredicate predicate, int limit) throws IOException {
|
|
||||||
int i = 0;
|
|
||||||
for (; i < limit; i++) {
|
|
||||||
reader.mark(1);
|
|
||||||
int chr = reader.read();
|
|
||||||
if (chr == -1 || !predicate.test(chr)) {
|
|
||||||
reader.reset();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
text.append((char) chr);
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isWhitespace(int chr) {
|
|
||||||
return chr == ' ' || chr == '\t' || chr == '\r' || chr == '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDigit(int chr) {
|
|
||||||
return '0' <= chr && chr <= '9';
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isHexDigit(int chr) {
|
|
||||||
return '0' <= chr && chr <= '9' || 'a' <= chr && chr <= 'f' || 'A' <= chr && chr <= 'F';
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isIdentifierStart(int chr) {
|
|
||||||
return 'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' || chr == '_';
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isIdentifierPart(int chr) {
|
|
||||||
return isIdentifierStart(chr) || isDigit(chr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Iterator<JQToken> iterator() {
|
|
||||||
return new JQTokenizerIterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Stream<@NotNull JQToken> stream() {
|
|
||||||
return StreamSupport.stream(this.spliterator(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class JQTokenizerIterator implements Iterator<JQToken> {
|
|
||||||
private JQToken next;
|
|
||||||
private boolean valid = false;
|
|
||||||
|
|
||||||
private void ensureValid() {
|
|
||||||
try {
|
|
||||||
if (!valid) {
|
|
||||||
next = JQTokenizer.this.next();
|
|
||||||
valid = true;
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new UncheckedIOException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
ensureValid();
|
|
||||||
return next != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull JQToken next() {
|
|
||||||
ensureValid();
|
|
||||||
if (next == null) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
} else {
|
|
||||||
valid = false;
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum State {
|
|
||||||
IN_PAREN,
|
|
||||||
IN_BRACKET,
|
|
||||||
IN_BRACE,
|
|
||||||
IN_QQSTRING,
|
|
||||||
IN_QQINTERP,
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
private static class TrackingReader {
|
|
||||||
private final Reader delegate;
|
|
||||||
|
|
||||||
private boolean skipLF;
|
|
||||||
@Getter
|
|
||||||
private int lineNumber = 1;
|
|
||||||
@Getter
|
|
||||||
private int columnNumber = 1;
|
|
||||||
|
|
||||||
private boolean markedSkipLF;
|
|
||||||
private int markedLineNumber;
|
|
||||||
private int markedColumnNumber;
|
|
||||||
|
|
||||||
public int read() throws IOException {
|
|
||||||
int c = delegate.read();
|
|
||||||
|
|
||||||
// handle line feed
|
|
||||||
if (skipLF) {
|
|
||||||
skipLF = false;
|
|
||||||
} else if (c == '\n') {
|
|
||||||
lineNumber++;
|
|
||||||
columnNumber = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle carriage return
|
|
||||||
if (c == '\r') {
|
|
||||||
lineNumber++;
|
|
||||||
columnNumber = 1;
|
|
||||||
skipLF = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c != '\n' && c != '\r') {
|
|
||||||
columnNumber++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void mark(int readAheadLimit) throws IOException {
|
|
||||||
delegate.mark(readAheadLimit);
|
|
||||||
markedSkipLF = skipLF;
|
|
||||||
markedLineNumber = lineNumber;
|
|
||||||
markedColumnNumber = columnNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() throws IOException {
|
|
||||||
delegate.reset();
|
|
||||||
skipLF = markedSkipLF;
|
|
||||||
lineNumber = markedLineNumber;
|
|
||||||
columnNumber = markedColumnNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query.util;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.*;
|
|
||||||
import eu.jonahbauer.json.query.impl.Generator;
|
|
||||||
import eu.jonahbauer.json.query.parser.ast.JQExpression;
|
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@UtilityClass
|
|
||||||
public class Util {
|
|
||||||
|
|
||||||
public static @NotNull Generator<@NotNull List<JsonValue>> cross(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) {
|
|
||||||
return cross(asSupplier(expressions, context)).map(Collections::unmodifiableList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> @NotNull Generator<@NotNull List<T>> cross(@NotNull List<@NotNull Supplier<@NotNull Generator<T>>> expressions) {
|
|
||||||
if (expressions.isEmpty()) return Generator.of(new ArrayList<T>().reversed());
|
|
||||||
|
|
||||||
return expressions.getFirst().get()
|
|
||||||
.flatMap(value -> cross(expressions.subList(1, expressions.size()))
|
|
||||||
.map(list -> { list.addFirst(value); return list; })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> @NotNull Generator<@NotNull List<T>> crossReversed(@NotNull List<@NotNull Supplier<@NotNull Generator<T>>> expressions) {
|
|
||||||
if (expressions.isEmpty()) return Generator.of(new ArrayList<>());
|
|
||||||
|
|
||||||
return expressions.getLast().get()
|
|
||||||
.flatMap(value -> crossReversed(expressions.subList(0, expressions.size() - 1))
|
|
||||||
.map(list -> { list.addLast(value); return list; })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull List<@NotNull Supplier<@NotNull Generator<JsonValue>>> asSupplier(@NotNull List<@NotNull JQExpression> expressions, @NotNull JQExpression.Context context) {
|
|
||||||
var list = new ArrayList<Supplier<Generator<JsonValue>>>(expressions.size());
|
|
||||||
expressions.forEach(expr -> list.add(() -> expr.evaluate(context)));
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull String type(@Nullable JsonValue value) {
|
|
||||||
return switch (value) {
|
|
||||||
case JsonArray _ -> "array";
|
|
||||||
case JsonObject _ -> "object";
|
|
||||||
case JsonNumber _ -> "number";
|
|
||||||
case JsonString _ -> "string";
|
|
||||||
case JsonBoolean _ -> "boolean";
|
|
||||||
case null -> "null";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull String value(@Nullable JsonValue value) {
|
|
||||||
return JsonValue.toJsonString(value);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,186 +0,0 @@
|
|||||||
package eu.jonahbauer.json.query;
|
|
||||||
|
|
||||||
import eu.jonahbauer.json.JsonArray;
|
|
||||||
import eu.jonahbauer.json.JsonBoolean;
|
|
||||||
import eu.jonahbauer.json.JsonNumber;
|
|
||||||
import eu.jonahbauer.json.JsonValue;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
class JsonMathTest {
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("addArguments")
|
|
||||||
void add(@Nullable JsonValue first, @Nullable JsonValue second, @Nullable JsonValue expected) {
|
|
||||||
assertEquals(expected, JsonMath.add(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> addArguments() {
|
|
||||||
return arguments(
|
|
||||||
Arguments.of("foo", "bar", "foobar"),
|
|
||||||
Arguments.of(7, 1, 8),
|
|
||||||
Arguments.of(List.of(1, 2), List.of(3, 4), List.of(1, 2, 3, 4)),
|
|
||||||
Arguments.of(1, null, 1),
|
|
||||||
Arguments.of(Map.of("a", 1), Map.of("b", 1), Map.of("a", 1, "b", 1)),
|
|
||||||
Arguments.of(Map.of("a", 1), Map.of("a", 42), Map.of("a", 42))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("subArguments")
|
|
||||||
void sub(@Nullable JsonValue first, @Nullable JsonValue second, @Nullable JsonValue expected) {
|
|
||||||
assertEquals(expected, JsonMath.sub(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> subArguments() {
|
|
||||||
return arguments(
|
|
||||||
Arguments.of(4, 1, 3),
|
|
||||||
Arguments.of(List.of("xml", "yaml", "json"), List.of("xml", "yaml"), List.of("json"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("mulArguments")
|
|
||||||
void mul(@Nullable JsonValue first, @Nullable JsonValue second, @Nullable JsonValue expected) {
|
|
||||||
assertEquals(expected, JsonMath.mul(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> mulArguments() {
|
|
||||||
return arguments(
|
|
||||||
Arguments.of("foo", 0, ""),
|
|
||||||
Arguments.of(0, "foo", ""),
|
|
||||||
Arguments.of(1.5, "foo", "foofoo"),
|
|
||||||
Arguments.of("foo", 1.5, "foofoo"),
|
|
||||||
Arguments.of(-1, "foo", null),
|
|
||||||
Arguments.of("foo", -1, null),
|
|
||||||
Arguments.of("foo", 1E300, null),
|
|
||||||
Arguments.of(1E300, "foo", null),
|
|
||||||
Arguments.of(2, 3, 6),
|
|
||||||
Arguments.of(
|
|
||||||
Map.of("k", Map.of("a", 1, "b", 2)),
|
|
||||||
Map.of("k", Map.of("a", 0, "c", 3)),
|
|
||||||
Map.of("k", Map.of("a", 0, "b", 2, "c", 3))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("divArguments")
|
|
||||||
void div(@Nullable JsonValue first, @Nullable JsonValue second, @Nullable JsonValue expected) {
|
|
||||||
assertEquals(expected, JsonMath.div(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> divArguments() {
|
|
||||||
return arguments(
|
|
||||||
Arguments.of(10, 5, 2),
|
|
||||||
Arguments.of("a, b,c,d, e", ", ", List.of("a", "b,c,d", "e"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("eqArguments")
|
|
||||||
void eq(@Nullable JsonValue first, @Nullable JsonValue second, @Nullable JsonValue expected) {
|
|
||||||
assertEquals(expected, JsonMath.eq(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> eqArguments() {
|
|
||||||
return arguments(
|
|
||||||
Arguments.of(null, false, false),
|
|
||||||
Arguments.of(1, 1.0, true),
|
|
||||||
Arguments.of(1, "1", false),
|
|
||||||
Arguments.of(1, "banana", false)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("sortArguments")
|
|
||||||
void sort(@NotNull JsonArray value, @NotNull JsonArray expected) {
|
|
||||||
assertEquals(expected, JsonMath.sort(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> sortArguments() {
|
|
||||||
return arguments(
|
|
||||||
Arguments.of(Arrays.asList(8, 3, null, 6, true, false), Arrays.asList(null, false, true, 3, 6, 8)),
|
|
||||||
Arguments.of(List.of(List.of("a", "b", "c"), List.of("a", "b")), List.of(List.of("a", "b"), List.of("a", "b", "c"))),
|
|
||||||
Arguments.of(List.of("foo", "bar", "baz"), List.of("bar", "baz", "foo")),
|
|
||||||
Arguments.of(List.of(Map.of("a", 1), Map.of("b", 1)), List.of(Map.of("a", 1), Map.of("b", 1))),
|
|
||||||
Arguments.of(List.of(Map.of("b", 1), Map.of("a", 1)), List.of(Map.of("a", 1), Map.of("b", 1))),
|
|
||||||
Arguments.of(List.of(Map.of("a", 1), Map.of("a", 2)), List.of(Map.of("a", 1), Map.of("a", 2))),
|
|
||||||
Arguments.of(List.of(Map.of("a", 2), Map.of("a", 1)), List.of(Map.of("a", 1), Map.of("a", 2))),
|
|
||||||
Arguments.of(List.of(Map.of("a", 2), Map.of("a", 1, "c", 1)), List.of(Map.of("a", 2), Map.of("a", 1, "c", 1))),
|
|
||||||
Arguments.of(List.of(Map.of("a", 1, "c", 1), Map.of("a", 2)), List.of(Map.of("a", 2), Map.of("a", 1, "c", 1)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void lt() {
|
|
||||||
assertEquals(JsonBoolean.FALSE, JsonMath.lt(JsonNumber.valueOf(1), JsonNumber.valueOf(1)));
|
|
||||||
assertEquals(JsonBoolean.TRUE, JsonMath.lt(JsonNumber.valueOf(1), JsonNumber.valueOf(2)));
|
|
||||||
assertEquals(JsonBoolean.FALSE, JsonMath.lt(JsonNumber.valueOf(2), JsonNumber.valueOf(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void gt() {
|
|
||||||
assertEquals(JsonBoolean.FALSE, JsonMath.gt(JsonNumber.valueOf(1), JsonNumber.valueOf(1)));
|
|
||||||
assertEquals(JsonBoolean.FALSE, JsonMath.gt(JsonNumber.valueOf(1), JsonNumber.valueOf(2)));
|
|
||||||
assertEquals(JsonBoolean.TRUE, JsonMath.gt(JsonNumber.valueOf(2), JsonNumber.valueOf(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void leq() {
|
|
||||||
assertEquals(JsonBoolean.TRUE, JsonMath.leq(JsonNumber.valueOf(1), JsonNumber.valueOf(1)));
|
|
||||||
assertEquals(JsonBoolean.TRUE, JsonMath.leq(JsonNumber.valueOf(1), JsonNumber.valueOf(2)));
|
|
||||||
assertEquals(JsonBoolean.FALSE, JsonMath.leq(JsonNumber.valueOf(2), JsonNumber.valueOf(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void geq() {
|
|
||||||
assertEquals(JsonBoolean.TRUE, JsonMath.geq(JsonNumber.valueOf(1), JsonNumber.valueOf(1)));
|
|
||||||
assertEquals(JsonBoolean.FALSE, JsonMath.geq(JsonNumber.valueOf(1), JsonNumber.valueOf(2)));
|
|
||||||
assertEquals(JsonBoolean.TRUE, JsonMath.geq(JsonNumber.valueOf(2), JsonNumber.valueOf(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("andArguments")
|
|
||||||
void and(@Nullable JsonValue first, @Nullable JsonValue second, @Nullable JsonValue expected) {
|
|
||||||
assertEquals(expected, JsonMath.and(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> andArguments() {
|
|
||||||
return arguments(
|
|
||||||
Arguments.of(42, "a string", true),
|
|
||||||
Arguments.of(true, true, true),
|
|
||||||
Arguments.of(true, false, false)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("orArguments")
|
|
||||||
void or(@Nullable JsonValue first, @Nullable JsonValue second, @Nullable JsonValue expected) {
|
|
||||||
assertEquals(expected, JsonMath.or(first, second));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> orArguments() {
|
|
||||||
return arguments(
|
|
||||||
Arguments.of(false, "a string", true),
|
|
||||||
Arguments.of(false, null, false),
|
|
||||||
Arguments.of(1, false, true)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Stream<Arguments> arguments(@NotNull Arguments @NotNull ... arguments) {
|
|
||||||
return Stream.of(arguments).map(args -> Arguments.of(Arrays.stream(args.get()).map(JsonValue::valueOf).toArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -9,4 +9,3 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
rootProject.name = "json"
|
rootProject.name = "json"
|
||||||
include("core")
|
include("core")
|
||||||
include("query")
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user