Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a5d1492a5 | |||
| 92a20c82f8 | |||
| 375382f04e |
@@ -1,27 +1,2 @@
|
||||
# json
|
||||
|
||||
A simple JSON library with support for `StringTemplate`s which were available in JDK 21 and 22 as a preview
|
||||
feature and removed again in JDK 23. The library allows placeholders to be used as keys, values and inside strings.
|
||||
|
||||
### Example
|
||||
```java
|
||||
var parameter = "numbers";
|
||||
var value = List.of(1, 2, 3, 4);
|
||||
var name = "World";
|
||||
|
||||
var json = JSON_OBJECT."""
|
||||
{
|
||||
\{parameter}: \{value},
|
||||
"string": "Hello \{name}!"
|
||||
}
|
||||
""";
|
||||
|
||||
assertEquals(3, json.getArray("numbers").getNumber(2));
|
||||
assertEquals(JsonString.valueOf("Hello World!"), json.get("string"));
|
||||
|
||||
System.out.println(json.toPrettyJsonString());
|
||||
// {
|
||||
// "numbers": [1, 2, 3, 4],
|
||||
// "string": "Hello World!"
|
||||
// }
|
||||
```
|
||||
@@ -8,18 +8,20 @@ description = "json"
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(22)
|
||||
version = 21
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(libs.annotations)
|
||||
|
||||
compileOnly(libs.lombok)
|
||||
annotationProcessor(libs.lombok)
|
||||
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testImplementation(libs.bundles.junit)
|
||||
}
|
||||
|
||||
tasks {
|
||||
@@ -1,409 +0,0 @@
|
||||
package eu.jonahbauer.json;
|
||||
|
||||
import eu.jonahbauer.json.exceptions.JsonConversionException;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Java representation of a JSON array.
|
||||
* @param elements the array's elements
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public record JsonArray(
|
||||
@NotNull List<? extends @Nullable JsonValue> elements
|
||||
) implements JsonValue, List<@Nullable JsonValue> {
|
||||
public static final @NotNull JsonArray EMPTY = new JsonArray(List.of());
|
||||
|
||||
public JsonArray {
|
||||
Objects.requireNonNull(elements, "elements");
|
||||
elements = Util.defensiveCopy(elements);
|
||||
}
|
||||
|
||||
public static @NotNull JsonArray of(@Nullable JsonValue @NotNull... elements) {
|
||||
return new JsonArray(Arrays.stream(elements).toList());
|
||||
}
|
||||
|
||||
//<editor-fold desc="valueOf(...)" defaultstate="collapsed">
|
||||
/**
|
||||
* Creates a JSON array from the given list, using {@link JsonValue#valueOf(Object)} to convert the objects to
|
||||
* {@link JsonValue}s.
|
||||
* @param list a list of objects
|
||||
* @return a new JSON array
|
||||
* @throws JsonConversionException if the given list or its entries cannot be converted to json
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(@Nullable List<?> list) {
|
||||
if (list == null) return null;
|
||||
if (list instanceof JsonArray json) return json;
|
||||
return new JsonArray(list.stream().map(JsonValue::valueOf).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonValue#valueOf(Object)} to convert the objects to
|
||||
* {@link JsonValue}s.
|
||||
* @param array an array of objects
|
||||
* @return a new JSON array
|
||||
* @throws JsonConversionException if the given array or its entries cannot be converted to json
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(@Nullable Object @Nullable... array) {
|
||||
return array == null ? null : new JsonArray(Arrays.stream(array).map(JsonValue::valueOf).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonBoolean#valueOf(boolean)} to convert the
|
||||
* booleans to {@link JsonBoolean}s.
|
||||
* @param array an array of booleans
|
||||
* @return a new JSON array
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(boolean @Nullable... array) {
|
||||
if (array == null) return null;
|
||||
var list = new ArrayList<JsonValue>();
|
||||
for (var b : array) {
|
||||
list.add(JsonBoolean.valueOf(b));
|
||||
}
|
||||
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(int)} to convert the
|
||||
* bytes to {@link JsonNumber}s.
|
||||
* @param array an array of bytes
|
||||
* @return a new JSON array
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(byte @Nullable... array) {
|
||||
if (array == null) return null;
|
||||
var list = new ArrayList<JsonValue>();
|
||||
for (var b : array) {
|
||||
list.add(JsonNumber.valueOf(b));
|
||||
}
|
||||
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(int)} to convert the
|
||||
* shorts to {@link JsonNumber}s.
|
||||
* @param array an array of shorts
|
||||
* @return a new JSON array
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(short @Nullable... array) {
|
||||
if (array == null) return null;
|
||||
var list = new ArrayList<JsonValue>();
|
||||
for (var s : array) {
|
||||
list.add(JsonNumber.valueOf(s));
|
||||
}
|
||||
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonString#valueOf(char)} to convert the
|
||||
* chars to {@link JsonString}s.
|
||||
* @param array an array of chars
|
||||
* @return a new JSON array
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(char @Nullable... array) {
|
||||
if (array == null) return null;
|
||||
var list = new ArrayList<JsonValue>();
|
||||
for (var c : array) {
|
||||
list.add(JsonString.valueOf(c));
|
||||
}
|
||||
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(int)} to convert the
|
||||
* ints to {@link JsonNumber}s.
|
||||
* @param array an array of ints
|
||||
* @return a new JSON array
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(int @Nullable... array) {
|
||||
return array == null ? null : new JsonArray(Arrays.stream(array).mapToObj(JsonNumber::valueOf).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(long)} to convert the
|
||||
* longs to {@link JsonNumber}s.
|
||||
* @param array an array of longs
|
||||
* @return a new JSON array
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(long @Nullable... array) {
|
||||
return array == null ? null : new JsonArray(Arrays.stream(array).mapToObj(JsonNumber::valueOf).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(double)} to convert the
|
||||
* floats to {@link JsonNumber}s.
|
||||
* @param array an array of floats
|
||||
* @return a new JSON array
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(float @Nullable... array) {
|
||||
if (array == null) return null;
|
||||
var list = new ArrayList<JsonValue>();
|
||||
for (var f : array) {
|
||||
list.add(JsonNumber.valueOf(f));
|
||||
}
|
||||
return new JsonArray(Util.trusted(Collections.unmodifiableList(list)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new JSON array from the given array, using {@link JsonNumber#valueOf(double)} to convert the
|
||||
* doubles to {@link JsonNumber}s.
|
||||
* @param array an array of doubles
|
||||
* @return a new JSON array
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonArray valueOf(double @Nullable... array) {
|
||||
return array == null ? null : new JsonArray(Arrays.stream(array).mapToObj(JsonNumber::valueOf).toList());
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="List" defaultstate="collapsed">
|
||||
@Override
|
||||
public int size() {
|
||||
return elements.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return elements.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return elements.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked") // elements is immutable
|
||||
public @NotNull Iterator<@Nullable JsonValue> iterator() {
|
||||
return (Iterator<JsonValue>) elements.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object @NotNull[] toArray() {
|
||||
return elements.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T @NotNull[] toArray(T @NotNull[] a) {
|
||||
return elements.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(@Nullable JsonValue value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("SlowListContainsAll")
|
||||
public boolean containsAll(@NotNull Collection<?> c) {
|
||||
return elements.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NotNull Collection<? extends @Nullable JsonValue> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, @NotNull Collection<? extends @Nullable JsonValue> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonValue get(int index) {
|
||||
return elements.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonValue set(int index, @Nullable JsonValue element) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, @Nullable JsonValue element) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonValue remove(int index) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return elements.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return elements.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked") // elements is immutable
|
||||
public @NotNull ListIterator<@Nullable JsonValue> listIterator() {
|
||||
return (ListIterator<JsonValue>) elements.listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked") // elements is immutable
|
||||
public @NotNull ListIterator<@Nullable JsonValue> listIterator(int index) {
|
||||
return (ListIterator<JsonValue>) elements.listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JsonArray subList(int fromIndex, int toIndex) {
|
||||
return new JsonArray(elements.subList(fromIndex, toIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JsonArray reversed() {
|
||||
return new JsonArray(Util.trusted(elements.reversed()));
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="get...(int index)" defaultstate="collapsed">
|
||||
/**
|
||||
* {@return the element at the specified index in this array}
|
||||
* @param index index of the element to return
|
||||
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonString}
|
||||
* @throws NullPointerException if the element at the specified index is {@code null}
|
||||
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
|
||||
*/
|
||||
public @NotNull JsonString getString(int index) {
|
||||
return get(index, JsonString.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the element at the specified index in this array}
|
||||
* @param index index of the element to return
|
||||
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonNumber}
|
||||
* @throws NullPointerException if the element at the specified index is {@code null}
|
||||
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
|
||||
*/
|
||||
public @NotNull JsonNumber getNumber(int index) {
|
||||
return get(index, JsonNumber.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the element at the specified index in this array}
|
||||
* @param index index of the element to return
|
||||
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonBoolean}
|
||||
* @throws NullPointerException if the element at the specified index is {@code null}
|
||||
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
|
||||
*/
|
||||
public @NotNull JsonBoolean getBoolean(int index) {
|
||||
return get(index, JsonBoolean.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the element at the specified index in this array}
|
||||
* @param index index of the element to return
|
||||
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonArray}
|
||||
* @throws NullPointerException if the element at the specified index is {@code null}
|
||||
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
|
||||
*/
|
||||
public @NotNull JsonArray getArray(int index) {
|
||||
return get(index, JsonArray.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the element at the specified index in this array}
|
||||
* @param index index of the element to return
|
||||
* @throws ClassCastException if the element at the specified index is not an instance of {@link JsonObject}
|
||||
* @throws NullPointerException if the element at the specified index is {@code null}
|
||||
* @throws IndexOutOfBoundsException if the index is less than zero or greater than or equals to the size of the array
|
||||
*/
|
||||
public @NotNull JsonObject getObject(int index) {
|
||||
return get(index, JsonObject.class);
|
||||
}
|
||||
|
||||
private <T extends JsonValue> @NotNull T get(int index, @NotNull Class<T> type) {
|
||||
var value = elements().get(index);
|
||||
if (value == null) {
|
||||
throw new NullPointerException("Value at index" + index + " is null.");
|
||||
} else {
|
||||
return type.cast(value);
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
var joiner = new StringJoiner(",", "[", "]");
|
||||
for (JsonValue element : elements) {
|
||||
joiner.add(JsonValue.toJsonString(element));
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toPrettyJsonString(int depth) {
|
||||
if (size() < 2) return toJsonString();
|
||||
|
||||
var entries = new ArrayList<String>(elements.size());
|
||||
var length = 0L;
|
||||
var composite = false;
|
||||
for (var element : elements) {
|
||||
var string = JsonValue.toPrettyJsonString(element, depth + Util.PRETTY_PRINT_INDENT.length());
|
||||
entries.add(string);
|
||||
|
||||
composite |= element instanceof JsonObject || element instanceof JsonArray;
|
||||
length += string.length() + 2; // +2 to account for ", " and "[]"
|
||||
}
|
||||
|
||||
if (!composite && depth + length <= Util.PRETTY_PRINT_ARRAY_MULTILINE_THRESHOLD) {
|
||||
return "[" + String.join(", ", entries) + "]";
|
||||
} else {
|
||||
var out = new StringJoiner(",\n", "[\n", "\n]");
|
||||
entries.forEach(e -> out.add(indent(e)));
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull String indent(@NotNull String string) {
|
||||
if (string.isEmpty()) return "";
|
||||
|
||||
StringBuilder out = new StringBuilder();
|
||||
|
||||
Iterator<String> it = string.lines().iterator();
|
||||
while (it.hasNext()) {
|
||||
out.append(Util.PRETTY_PRINT_INDENT).append(it.next());
|
||||
if (it.hasNext()) out.append('\n');
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package eu.jonahbauer.json;
|
||||
|
||||
import eu.jonahbauer.json.tokenizer.token.JsonToken;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Java representation of a JSON boolean. Note, that JSON does not distinguish between integers and floating point
|
||||
* numbers and therefore all numbers are stored as {@code double}.
|
||||
*/
|
||||
public record JsonNumber(double value) implements JsonValue, JsonToken {
|
||||
public static final @NotNull JsonNumber ZERO = new JsonNumber(0);
|
||||
public static final @NotNull JsonNumber ONE = new JsonNumber(1);
|
||||
|
||||
/**
|
||||
* Converts the given int to a JSON number.
|
||||
* @param i an int
|
||||
* @return a new JSON number
|
||||
*/
|
||||
public static @NotNull JsonNumber valueOf(int i) {
|
||||
return new JsonNumber(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given long to a JSON number.
|
||||
* @param l a long
|
||||
* @return a new JSON number
|
||||
* @throws IllegalArgumentException if conversion from {@code long} to {@code double} is lossy
|
||||
*/
|
||||
public static @NotNull JsonNumber valueOf(long l) {
|
||||
if ((long) (double) l != l) {
|
||||
throw new IllegalArgumentException("lossy conversion from long to double for value " + l);
|
||||
}
|
||||
return new JsonNumber(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given double to a JSON number.
|
||||
* @param d a double
|
||||
* @return a new JSON number
|
||||
* @throws IllegalArgumentException if {@code d} is not {@linkplain Double#isFinite(double) finite}
|
||||
*/
|
||||
public static @NotNull JsonNumber valueOf(double d) {
|
||||
return new JsonNumber(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given number to a JSON number using {@link Number#doubleValue()}. The conversion to a JSON number
|
||||
* might be lossy.
|
||||
* @param number a number
|
||||
* @return a new JSON number
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonNumber valueOf(@Nullable Number number) {
|
||||
return number == null ? null : new JsonNumber(number.doubleValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return (long) value == value ? Long.toString((long) value) : Double.toString(value);
|
||||
}
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
package eu.jonahbauer.json;
|
||||
|
||||
import eu.jonahbauer.json.exceptions.JsonConversionException;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Java representation of a JSON object.
|
||||
* @param entries the object's entries
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public record JsonObject(@NotNull Map<@NotNull String, @Nullable JsonValue> entries) implements JsonValue, Map<@NotNull String, @Nullable JsonValue> {
|
||||
public static final @NotNull JsonObject EMPTY = new JsonObject(Map.of());
|
||||
|
||||
public JsonObject {
|
||||
Objects.requireNonNull(entries, "entries");
|
||||
entries = Util.defensiveCopy(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON object containing one mapping.
|
||||
* @param k1 the key
|
||||
* @param v1 the value
|
||||
* @return a new JSON object containing the specified mapping
|
||||
*/
|
||||
public static @NotNull JsonObject of(
|
||||
@NotNull String k1, @Nullable JsonValue v1
|
||||
) {
|
||||
var map = new LinkedHashMap<String, JsonValue>();
|
||||
map.put(Objects.requireNonNull(k1), v1);
|
||||
return new JsonObject(Util.trusted(Collections.unmodifiableSequencedMap(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON object containing two mapping.
|
||||
* @param k1 the first key
|
||||
* @param v1 the first value
|
||||
* @param k2 the second key
|
||||
* @param v2 the second value
|
||||
* @return a new JSON object containing the specified mappings
|
||||
*/
|
||||
public static @NotNull JsonObject of(
|
||||
@NotNull String k1, @Nullable JsonValue v1,
|
||||
@NotNull String k2, @Nullable JsonValue v2
|
||||
) {
|
||||
var map = new LinkedHashMap<String, JsonValue>();
|
||||
map.put(Objects.requireNonNull(k1), v1);
|
||||
map.put(Objects.requireNonNull(k2), v2);
|
||||
return new JsonObject(Util.trusted(Collections.unmodifiableSequencedMap(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON object containing three mapping.
|
||||
* @param k1 the first key
|
||||
* @param v1 the first value
|
||||
* @param k2 the second key
|
||||
* @param v2 the second value
|
||||
* @param k3 the third key
|
||||
* @param v3 the third value
|
||||
* @return a new JSON object containing the specified mappings
|
||||
*/
|
||||
public static @NotNull JsonObject of(
|
||||
@NotNull String k1, @Nullable JsonValue v1,
|
||||
@NotNull String k2, @Nullable JsonValue v2,
|
||||
@NotNull String k3, @Nullable JsonValue v3
|
||||
) {
|
||||
var map = new LinkedHashMap<String, JsonValue>();
|
||||
map.put(Objects.requireNonNull(k1), v1);
|
||||
map.put(Objects.requireNonNull(k2), v2);
|
||||
map.put(Objects.requireNonNull(k3), v3);
|
||||
return new JsonObject(Util.trusted(Collections.unmodifiableSequencedMap(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON object containing four mapping.
|
||||
* @param k1 the first key
|
||||
* @param v1 the first value
|
||||
* @param k2 the second key
|
||||
* @param v2 the second value
|
||||
* @param k3 the third key
|
||||
* @param v3 the third value
|
||||
* @param k4 the fourth key
|
||||
* @param v4 the fourth value
|
||||
* @return a new JSON object containing the specified mappings
|
||||
*/
|
||||
public static @NotNull JsonObject of(
|
||||
@NotNull String k1, @Nullable JsonValue v1,
|
||||
@NotNull String k2, @Nullable JsonValue v2,
|
||||
@NotNull String k3, @Nullable JsonValue v3,
|
||||
@NotNull String k4, @Nullable JsonValue v4
|
||||
) {
|
||||
var map = new LinkedHashMap<String, JsonValue>();
|
||||
map.put(Objects.requireNonNull(k1), v1);
|
||||
map.put(Objects.requireNonNull(k2), v2);
|
||||
map.put(Objects.requireNonNull(k3), v3);
|
||||
map.put(Objects.requireNonNull(k4), v4);
|
||||
return new JsonObject(Util.trusted(Collections.unmodifiableSequencedMap(map)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON object from the given map, using {@link JsonValue#valueOf(Object)} to convert the values to
|
||||
* {@link JsonValue}s. The keys are expected to be instances of {@link CharSequence}.
|
||||
* @param map a map
|
||||
* @return a new JSON object
|
||||
* @throws JsonConversionException if the given map or its entries cannot be converted to json
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
public static @Nullable JsonObject valueOf(@Nullable Map<?, ?> map) {
|
||||
if (map == null) return null;
|
||||
if (map instanceof JsonObject json) return json;
|
||||
|
||||
var out = new LinkedHashMap<String, JsonValue>();
|
||||
map.forEach((key, value) -> out.put(keyOf(key), JsonValue.valueOf(value)));
|
||||
return new JsonObject(out);
|
||||
}
|
||||
|
||||
private static @NotNull String keyOf(@Nullable Object object) {
|
||||
return switch (object) {
|
||||
case CharSequence chars -> chars.toString();
|
||||
case null -> throw new JsonConversionException("cannot use null as json key");
|
||||
default -> throw new JsonConversionException("cannot convert object of type " + object.getClass() + " to json key");
|
||||
};
|
||||
}
|
||||
|
||||
//<editor-fold desc="Map" defaultstate="collapsed">
|
||||
@Override
|
||||
public int size() {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return entries.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
Objects.requireNonNull(key, "key");
|
||||
return entries.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return entries.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonValue get(Object key) {
|
||||
Objects.requireNonNull(key, "key");
|
||||
return entries.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonValue put(@NotNull String key, @Nullable JsonValue value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JsonValue remove(Object key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends @NotNull String, ? extends @Nullable JsonValue> m) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<@NotNull String> keySet() {
|
||||
return entries.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<@Nullable JsonValue> values() {
|
||||
return entries.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Entry<@NotNull String, @Nullable JsonValue>> entrySet() {
|
||||
return entries.entrySet();
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="get...(String key)" defaultstate="collapsed">
|
||||
/**
|
||||
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
|
||||
* @throws ClassCastException if the value is not an instance of {@link JsonString}
|
||||
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
|
||||
*/
|
||||
public @NotNull JsonString getString(@NotNull String key) {
|
||||
return get(key, JsonString.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
|
||||
* @throws ClassCastException if the value is not an instance of {@link JsonNumber}
|
||||
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
|
||||
*/
|
||||
public @NotNull JsonNumber getNumber(@NotNull String key) {
|
||||
return get(key, JsonNumber.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
|
||||
* @throws ClassCastException if the value is not an instance of {@link JsonBoolean}
|
||||
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
|
||||
*/
|
||||
public @NotNull JsonBoolean getBoolean(@NotNull String key) {
|
||||
return get(key, JsonBoolean.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
|
||||
* @throws ClassCastException if the value is not an instance of {@link JsonArray}
|
||||
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
|
||||
*/
|
||||
public @NotNull JsonArray getArray(@NotNull String key) {
|
||||
return get(key, JsonArray.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the value to which the specified key is mapped, or <code>null</code> if this object contains no mapping for the key}
|
||||
* @throws ClassCastException if the value is not an instance of {@link JsonObject}
|
||||
* @throws NullPointerException if there is no mapping for the key or the key is mapped to {@code null}
|
||||
*/
|
||||
public @NotNull JsonObject getObject(@NotNull String key) {
|
||||
return get(key, JsonObject.class);
|
||||
}
|
||||
|
||||
private <T extends JsonValue> @NotNull T get(@NotNull String key, @NotNull Class<T> type) {
|
||||
var value = entries().get(key);
|
||||
if (value == null) {
|
||||
if (entries().containsKey(key)) {
|
||||
throw new NullPointerException("Key " + key + " is mapped to null.");
|
||||
} else {
|
||||
throw new NullPointerException("No mapping for key " + key);
|
||||
}
|
||||
} else {
|
||||
return type.cast(value);
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
//<editor-fold desc="get...OrDefault(String key, ...)" defaultstate="collapsed">
|
||||
// getStringOrDefault(String, JsonString) omitted because JsonString implements CharSequence
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or
|
||||
* {@link JsonString#valueOf(CharSequence) JsonString.valueOf(defaultValue)} if this object contains no mapping for
|
||||
* the key or the key is mapped to a value that is not an instance of {@link JsonString}.
|
||||
* @return the value to which the specified key is mapped falling back to the default value
|
||||
*/
|
||||
public @Nullable JsonString getStringOrDefault(@NotNull String key, @Nullable CharSequence defaultValue) {
|
||||
return getOrDefault(key, defaultValue, JsonString.class, JsonString::valueOf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or {@code defaultValue} if this object contains no mapping
|
||||
* for the key or the key is mapped to a value that is not an instance of {@link JsonNumber}.
|
||||
* @return the value to which the specified key is mapped falling back to the default value
|
||||
*/
|
||||
public @Nullable JsonNumber getNumberOrDefault(@NotNull String key, @Nullable JsonNumber defaultValue) {
|
||||
return getOrDefault(key, defaultValue, JsonNumber.class, Function.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or
|
||||
* {@link JsonNumber#valueOf(Number) JsonNumber.valueOf(defaultValue)} if this object contains no mapping for the
|
||||
* key or the key is mapped to a value that is not an instance of {@link JsonNumber}.
|
||||
* @return the value to which the specified key is mapped falling back to the default value
|
||||
*/
|
||||
public @Nullable JsonNumber getNumberOrDefault(@NotNull String key, @Nullable Number defaultValue) {
|
||||
return getOrDefault(key, defaultValue, JsonNumber.class, JsonNumber::valueOf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or {@code defaultValue} if this object contains no mapping
|
||||
* for the key or the key is mapped to a value that is not an instance of {@link JsonBoolean}.
|
||||
* @return the value to which the specified key is mapped falling back to the default value
|
||||
*/
|
||||
public @Nullable JsonBoolean getBooleanOrDefault(@NotNull String key, @Nullable JsonBoolean defaultValue) {
|
||||
return getOrDefault(key, defaultValue, JsonBoolean.class, Function.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or
|
||||
* {@link JsonBoolean#valueOf(Boolean) JsonBoolean.valueOf(defaultValue)} if this object contains no mapping
|
||||
* for the key or the key is mapped to a value that is not an instance of {@link JsonBoolean}.
|
||||
* @return the value to which the specified key is mapped falling back to the default value
|
||||
*/
|
||||
public @Nullable JsonBoolean getBooleanOrDefault(@NotNull String key, @Nullable Boolean defaultValue) {
|
||||
return getOrDefault(key, defaultValue, JsonBoolean.class, JsonBoolean::valueOf);
|
||||
}
|
||||
|
||||
// getArrayOrDefault(String, JsonArray) omitted because JsonArray implements List<?>
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or
|
||||
* {@link JsonArray#valueOf(List) JsonArray.valueOf(defaultValue)} if this object contains no mapping for
|
||||
* the key or the key is mapped to a value that is not an instance of {@link JsonArray}.
|
||||
* @return the value to which the specified key is mapped falling back to the default value
|
||||
*/
|
||||
public @Nullable JsonArray getArrayOrDefault(@NotNull String key, @Nullable List<?> defaultValue) {
|
||||
return getOrDefault(key, defaultValue, JsonArray.class, JsonArray::valueOf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or
|
||||
* {@link JsonArray#valueOf(Object...) JsonArray.valueOf(defaultValue)} if this object contains no mapping for the
|
||||
* key or the key is mapped to a value that is not an instance of {@link JsonArray}.
|
||||
* @return the value to which the specified key is mapped falling back to the default value
|
||||
*/
|
||||
public @Nullable JsonArray getArrayOrDefault(@NotNull String key, @Nullable Object @Nullable... defaultValue) {
|
||||
return getOrDefault(key, defaultValue, JsonArray.class, JsonArray::valueOf);
|
||||
}
|
||||
|
||||
// getObjectOrDefault(String, JsonObject) omitted because JsonObject implements Map<?, ?>
|
||||
|
||||
/**
|
||||
* Returns the value to which the specified key is mapped, or
|
||||
* {@link JsonObject#valueOf(Map) JsonObject.valueOf(defaultValue)} if this object contains no mapping for the key
|
||||
* or the key is mapped to a value that is not an instance of {@link JsonObject}.
|
||||
* @return the value to which the specified key is mapped falling back to the default value
|
||||
*/
|
||||
public @Nullable JsonObject getObjectOrDefault(@NotNull String key, @Nullable Map<?, ?> defaultValue) {
|
||||
return getOrDefault(key, defaultValue, JsonObject.class, JsonObject::valueOf);
|
||||
}
|
||||
|
||||
private <T extends JsonValue, S> @Nullable T getOrDefault(@NotNull String key, @Nullable S defaultValue, @NotNull Class<T> type, @NotNull Function<S, T> converter) {
|
||||
var value = entries().get(key);
|
||||
if (type.isInstance(value)) {
|
||||
return type.cast(value);
|
||||
} else {
|
||||
return converter.apply(defaultValue);
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
StringJoiner out = new StringJoiner(",", "{", "}");
|
||||
entries.forEach((key, value) -> out.add(JsonString.quote(key) + ":" + JsonValue.toJsonString(value)));
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toPrettyJsonString(int depth) {
|
||||
StringJoiner out = new StringJoiner(",\n" + Util.PRETTY_PRINT_INDENT, "{\n" + Util.PRETTY_PRINT_INDENT, "\n}");
|
||||
entries.forEach((key, value) -> {
|
||||
var keyString = JsonString.quote(key);
|
||||
var valueIdent = depth + Util.PRETTY_PRINT_INDENT.length() + keyString.length() + 3; // +3 for ": " and ","
|
||||
var valueString = JsonValue.toPrettyJsonString(value, valueIdent);
|
||||
out.add(keyString + ": " + indent(valueString));
|
||||
});
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static @NotNull String indent(@NotNull String string) {
|
||||
if (string.isEmpty()) return "";
|
||||
|
||||
StringBuilder out = new StringBuilder();
|
||||
|
||||
Iterator<String> it = string.lines().iterator();
|
||||
out.append(it.next());
|
||||
|
||||
while (it.hasNext()) {
|
||||
String next = it.next();
|
||||
out.append('\n').append(Util.PRETTY_PRINT_INDENT).append(next);
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package eu.jonahbauer.json;
|
||||
|
||||
import eu.jonahbauer.json.exceptions.JsonConversionException;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings("preview")
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class JsonTemplateProcessor<T extends JsonValue> implements StringTemplate.Processor<T, RuntimeException> {
|
||||
/**
|
||||
* Parses the string template as a JSON using {@link JsonValue#parse(StringTemplate)}.
|
||||
*/
|
||||
public static final @NotNull JsonTemplateProcessor<JsonValue> JSON = new JsonTemplateProcessor<>(JsonValue.class, true);
|
||||
/**
|
||||
* Parses the string template as a JSON using {@link JsonValue#parse(StringTemplate)}. Throws a
|
||||
* {@link JsonConversionException} if the result is not a JSON object.
|
||||
*/
|
||||
public static final @NotNull JsonTemplateProcessor<@NotNull JsonObject> JSON_OBJECT = new JsonTemplateProcessor<>(JsonObject.class, false);
|
||||
/**
|
||||
* Parses the string template as a JSON using {@link JsonValue#parse(StringTemplate)}. Throws a
|
||||
* {@link JsonConversionException} if the result is not a JSON array.
|
||||
*/
|
||||
public static final @NotNull JsonTemplateProcessor<@NotNull JsonArray> JSON_ARRAY = new JsonTemplateProcessor<>(JsonArray.class, false);
|
||||
|
||||
private final @NotNull Class<T> clazz;
|
||||
private final boolean allowNull;
|
||||
|
||||
@Override
|
||||
public T process(@NotNull StringTemplate template) throws RuntimeException {
|
||||
var result = JsonValue.parse(template);
|
||||
if (result == null && !allowNull || result != null && !clazz.isInstance(result)) {
|
||||
throw new JsonConversionException("The template does not represent a " + clazz.getSimpleName());
|
||||
}
|
||||
return clazz.cast(result);
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package eu.jonahbauer.json;
|
||||
|
||||
import eu.jonahbauer.json.exceptions.JsonConversionException;
|
||||
import eu.jonahbauer.json.exceptions.JsonParserException;
|
||||
import eu.jonahbauer.json.parser.JsonParser;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public sealed interface JsonValue extends Serializable permits JsonObject, JsonArray, JsonBoolean, JsonNumber, JsonString {
|
||||
|
||||
/**
|
||||
* {@return the JSON representation of this value}
|
||||
*/
|
||||
default @NotNull String toJsonString() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a prettified JSON representation of this value}
|
||||
*/
|
||||
default @NotNull String toPrettyJsonString() {
|
||||
return toPrettyJsonString(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a prettified JSON representation of this value}
|
||||
* @param depth indicates the depth at which this value appears (no formal definition given)
|
||||
*/
|
||||
default @NotNull String toPrettyJsonString(int depth) {
|
||||
return toJsonString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the JSON representation of the given value}
|
||||
* @param value a {@link JsonValue} (possibly {@code null})
|
||||
*/
|
||||
static @NotNull String toJsonString(@Nullable JsonValue value) {
|
||||
return value == null ? "null" : value.toJsonString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the prettified JSON representation of the given value}
|
||||
* @param value a {@link JsonValue} (possibly {@code null})
|
||||
*/
|
||||
static @NotNull String toPrettyJsonString(@Nullable JsonValue value) {
|
||||
return toPrettyJsonString(value, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the prettified JSON representation of the given value}
|
||||
* @param value a {@link JsonValue} (possibly {@code null})
|
||||
* @param depth indicates the depth at which the value appears (no formal definition given)
|
||||
*/
|
||||
static @NotNull String toPrettyJsonString(@Nullable JsonValue value, int depth) {
|
||||
return value == null ? "null" : value.toPrettyJsonString(depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string as a JSON. The string must adhere to the <a href="https://www.json.org">JSON grammar</a>.
|
||||
* @param json a string
|
||||
* @return the result of parsing the string as a JSON
|
||||
* @throws JsonParserException if the string cannot be parsed as JSON
|
||||
*/
|
||||
static @Nullable JsonValue parse(@NotNull String json) {
|
||||
var parser = new JsonParser(json);
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string template as a JSON. The string template must adhere to the
|
||||
* <a href="https://www.json.org">standard JSON grammar</a> with the following additional rules:
|
||||
* <pre>{@code
|
||||
* value
|
||||
* placeholder
|
||||
*
|
||||
* member
|
||||
* ws placeholder ws ':' element
|
||||
*
|
||||
* characters
|
||||
* placeholder characters
|
||||
* }</pre>
|
||||
* where <code>placeholder</code> represents an object embedded in the string template.
|
||||
* <p>
|
||||
* A placeholder will be converted to JSON depending on the context it appears in.
|
||||
* When used as a value, {@link JsonValue#valueOf(Object)} will be used.
|
||||
* When used as an object key, only instances or {@link CharSequence} are allowed, which will be converted using
|
||||
* {@link CharSequence#toString()}.
|
||||
* When used in a string, {@link Objects#toString(Object)} will be used.
|
||||
*
|
||||
* @param json a string template
|
||||
* @return the result of parsing the string template as a JSON
|
||||
* @throws JsonParserException if the string template cannot be parsed as JSON
|
||||
* @throws JsonConversionException if a placeholder cannot be converted to JSON
|
||||
*/
|
||||
@SuppressWarnings("preview")
|
||||
static @Nullable JsonValue parse(@NotNull StringTemplate json) {
|
||||
var parser = new JsonParser(json);
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to a JSON value:
|
||||
* <ul>
|
||||
* <li>{@link JsonValue}s and {@code null} are returned as is</li>
|
||||
* <li>{@link Boolean}s are converted with {@link JsonBoolean#valueOf(Boolean)}</li>
|
||||
* <li>{@link Number}s are converted with {@link JsonNumber#valueOf(Number)}</li>
|
||||
* <li>{@link Character}s are converted with {@link JsonString#valueOf(Character)}</li>
|
||||
* <li>{@link CharSequence}s are converted with {@link JsonString#valueOf(CharSequence)}</li>
|
||||
* <li>{@link List}s are converted with {@link JsonArray#valueOf(List)}</li>
|
||||
* <li>arrays are converted with the respective overload of {@link JsonArray#valueOf(Object...)}</li>
|
||||
* <li>{@link Map}s are converted with {@link JsonObject#valueOf(Map)}</li>
|
||||
* </ul>
|
||||
* @param object an object
|
||||
* @return a {@link JsonValue} representing the object
|
||||
* @throws JsonConversionException if the object cannot be converted to a {@link JsonValue}
|
||||
*/
|
||||
@Contract("null -> null; !null -> !null")
|
||||
static @Nullable JsonValue valueOf(@Nullable Object object) {
|
||||
return switch (object) {
|
||||
case JsonValue json -> json;
|
||||
case Boolean bool -> JsonBoolean.valueOf(bool);
|
||||
case Number number -> JsonNumber.valueOf(number);
|
||||
case Character chr -> JsonString.valueOf(String.valueOf(chr));
|
||||
case CharSequence chars -> JsonString.valueOf(chars);
|
||||
case List<?> list -> JsonArray.valueOf(list);
|
||||
case Object[] array -> JsonArray.valueOf(array);
|
||||
case boolean[] array -> JsonArray.valueOf(array);
|
||||
case byte[] array -> JsonArray.valueOf(array);
|
||||
case short[] array -> JsonArray.valueOf(array);
|
||||
case char[] array -> JsonArray.valueOf(array);
|
||||
case int[] array -> JsonArray.valueOf(array);
|
||||
case long[] array -> JsonArray.valueOf(array);
|
||||
case float[] array -> JsonArray.valueOf(array);
|
||||
case double[] array -> JsonArray.valueOf(array);
|
||||
case Map<?, ?> map -> JsonObject.valueOf(map);
|
||||
case null -> null;
|
||||
default -> throw new JsonConversionException("cannot convert object of type " + object.getClass() + " to json value");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package eu.jonahbauer.json;
|
||||
|
||||
import lombok.experimental.Delegate;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@UtilityClass
|
||||
class Util {
|
||||
static final @NotNull String PRETTY_PRINT_INDENT = " ";
|
||||
static final int PRETTY_PRINT_ARRAY_MULTILINE_THRESHOLD = 80;
|
||||
|
||||
private static final @NotNull Class<?> LIST_1_2 = List.of(1).getClass();
|
||||
private static final @NotNull Class<?> LIST_N = List.of(1, 2, 3).getClass();
|
||||
private static final @NotNull Class<?> SUB_LIST = List.of(1, 2, 3).subList(0, 2).getClass();
|
||||
private static final @NotNull Class<?> MAP_1 = Map.of(1, 2).getClass();
|
||||
private static final @NotNull Class<?> MAP_N = Map.of(1, 2, 3, 4).getClass();
|
||||
|
||||
/**
|
||||
* Performs a defensive copy of the given list unless it is known to be an immutable list.
|
||||
*/
|
||||
public static <T> @NotNull List<T> defensiveCopy(@NotNull List<T> list) {
|
||||
if (LIST_1_2.isInstance(list) || LIST_N.isInstance(list) || SUB_LIST.isInstance(list)) {
|
||||
return list;
|
||||
} else if (list instanceof TrustedList<T>(var delegate)) {
|
||||
return delegate;
|
||||
} else {
|
||||
return list.stream().toList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a defensive copy of the given map unless it is known to be an immutable map.
|
||||
* If the given map implements {@link SequencedMap} as will the returned one.
|
||||
*/
|
||||
public static <K, V> @NotNull Map<K, V> defensiveCopy(@NotNull Map<@NotNull K, V> map) {
|
||||
if (MAP_1.isInstance(map) || MAP_N.isInstance(map)) {
|
||||
return map;
|
||||
} else if (map instanceof TrustedMap<K, V>(var delegate)) {
|
||||
return delegate;
|
||||
} else if (map instanceof SequencedMap<K,V> sequenced) {
|
||||
var out = new LinkedHashMap<K, V>();
|
||||
sequenced.forEach((key, value) -> out.put(Objects.requireNonNull(key, "key"), value));
|
||||
return Collections.unmodifiableSequencedMap(out);
|
||||
} else {
|
||||
var out = new HashMap<K, V>();
|
||||
map.forEach((key, value) -> out.put(Objects.requireNonNull(key, "key"), value));
|
||||
return Collections.unmodifiableMap(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list that will be assumed immutable when given to {@link #defensiveCopy(List)}.
|
||||
* {@return a list that will be assumed immutable}
|
||||
*/
|
||||
public static <T> @NotNull List<T> trusted(@NotNull List<T> list) {
|
||||
return new TrustedList<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map that will be assumed immutable when given to {@link #defensiveCopy(Map)}.
|
||||
* {@return a map that will be assumed immutable}
|
||||
*/
|
||||
public static <K, V> @NotNull Map<K, V> trusted(@NotNull Map<@NotNull K, V> map) {
|
||||
return new TrustedMap<>(map);
|
||||
}
|
||||
|
||||
private record TrustedList<T>(@Delegate @NotNull List<T> delegate) implements List<T> {}
|
||||
|
||||
private record TrustedMap<K, V>(@Delegate @NotNull Map<K, V> delegate) implements Map<K, V> {}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package eu.jonahbauer.json.exceptions;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class JsonParserException extends JsonReaderException {
|
||||
|
||||
public JsonParserException(int fragment, int line, int column, @NotNull String message) {
|
||||
super(fragment, line, column, message);
|
||||
}
|
||||
|
||||
public JsonParserException(int fragment, int line, int column, @NotNull String message, @NotNull Throwable cause) {
|
||||
super(fragment, line, column, message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package eu.jonahbauer.json.exceptions;
|
||||
|
||||
/**
|
||||
* Parent class for exceptions related to json processing.
|
||||
*/
|
||||
public abstract class JsonProcessingException extends RuntimeException {
|
||||
public JsonProcessingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public JsonProcessingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package eu.jonahbauer.json.exceptions;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Getter
|
||||
abstract class JsonReaderException extends JsonProcessingException {
|
||||
private final int fragment;
|
||||
private final int lineNumber;
|
||||
private final int columnNumber;
|
||||
|
||||
public JsonReaderException(int fragment, int line, int column, @NotNull String message) {
|
||||
super(message);
|
||||
this.fragment = fragment;
|
||||
this.lineNumber = line;
|
||||
this.columnNumber = column;
|
||||
}
|
||||
|
||||
public JsonReaderException(int fragment, int line, int column, @NotNull String message, @NotNull Throwable cause) {
|
||||
super(message, cause);
|
||||
this.fragment = fragment;
|
||||
this.lineNumber = line;
|
||||
this.columnNumber = column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getMessage() {
|
||||
if (getFragment() == 0) {
|
||||
return super.getMessage() + " at line " + getLineNumber() + ", column " + getColumnNumber();
|
||||
} else {
|
||||
return super.getMessage() + " at fragment " + getFragment() + ", line " + getLineNumber() + ", column " + getColumnNumber();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package eu.jonahbauer.json.parser;
|
||||
|
||||
import eu.jonahbauer.json.*;
|
||||
import eu.jonahbauer.json.exceptions.JsonConversionException;
|
||||
import eu.jonahbauer.json.exceptions.JsonParserException;
|
||||
import eu.jonahbauer.json.exceptions.JsonTokenizerException;
|
||||
import eu.jonahbauer.json.tokenizer.JsonTokenizer;
|
||||
import eu.jonahbauer.json.tokenizer.JsonTokenizerImpl;
|
||||
import eu.jonahbauer.json.tokenizer.token.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.*;
|
||||
|
||||
public final class JsonParser {
|
||||
private static final String TT_JSON_STRING = "JSON_STRING";
|
||||
private static final String TT_JSON_NUMBER = "JSON_NUMBER";
|
||||
private static final String TT_JSON_STRING_TEMPLATE = "JSON_STRING_TEMPLATE";
|
||||
private static final String TT_JSON_PLACEHOLDER = "JSON_PLACEHOLDER";
|
||||
|
||||
private final @NotNull JsonTokenizer tokenizer;
|
||||
|
||||
@SuppressWarnings("preview")
|
||||
public JsonParser(@NotNull StringTemplate json) {
|
||||
this(new JsonTokenizerImpl(json));
|
||||
}
|
||||
|
||||
public JsonParser(@NotNull String json) {
|
||||
this(new JsonTokenizerImpl(json));
|
||||
}
|
||||
|
||||
public JsonParser(@NotNull Reader reader) {
|
||||
this(new JsonTokenizerImpl(reader));
|
||||
}
|
||||
|
||||
public JsonParser(@NotNull JsonTokenizer tokenizer) {
|
||||
this.tokenizer = Objects.requireNonNull(tokenizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the input as a JSON value.
|
||||
* @return the JSON value
|
||||
* @throws JsonParserException if the input could not be parsed as JSON
|
||||
* @throws UncheckedIOException if an I/O error occurs
|
||||
*/
|
||||
public @Nullable JsonValue parse() throws JsonParserException {
|
||||
try {
|
||||
var out = parse0();
|
||||
if (tokenizer.next() != null) {
|
||||
throw new JsonParserException(tokenizer.getFragment(), tokenizer.getLineNumber(), tokenizer.getColumnNumber(), "unexpected trailing entries");
|
||||
}
|
||||
return out;
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
} catch (JsonTokenizerException ex) {
|
||||
throw new JsonParserException(ex.getFragment(), ex.getLineNumber(), ex.getColumnNumber(), ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable JsonValue parse0() throws IOException {
|
||||
// Use a stack of Context objects to store the objects and arrays which are currently being parsed.
|
||||
// This avoids the risk of stack overflow which would exist when having one nested calls to parse0.
|
||||
var stack = new ArrayDeque<@NotNull Context>();
|
||||
|
||||
while (true) {
|
||||
JsonValue value;
|
||||
|
||||
var token = tokenizer.next();
|
||||
if (!stack.isEmpty() && token == stack.peek().end()) {
|
||||
value = stack.pop().build();
|
||||
} else {
|
||||
// consume value separator
|
||||
if (stack.peek() instanceof Context context && !context.isEmpty()) {
|
||||
if (token != JsonPunctuation.VALUE_SEPARATOR) {
|
||||
throw unexpectedToken(token, context.end(), JsonPunctuation.VALUE_SEPARATOR);
|
||||
}
|
||||
token = tokenizer.next();
|
||||
}
|
||||
|
||||
// read object key and consume name separator
|
||||
if (stack.peek() instanceof ObjectContext object) {
|
||||
var key = parseJsonObjectKey(object, token);
|
||||
if ((token = tokenizer.next()) != JsonPunctuation.NAME_SEPARATOR) {
|
||||
throw unexpectedToken(token, JsonPunctuation.NAME_SEPARATOR);
|
||||
}
|
||||
token = tokenizer.next();
|
||||
object.setKey(key);
|
||||
}
|
||||
|
||||
if (token == JsonPunctuation.BEGIN_OBJECT) {
|
||||
stack.push(new ObjectContext());
|
||||
continue;
|
||||
} else if (token == JsonPunctuation.BEGIN_ARRAY) {
|
||||
stack.push(new ArrayContext());
|
||||
continue;
|
||||
} else {
|
||||
// read json value
|
||||
value = switch (token) {
|
||||
case JsonPunctuation.BEGIN_ARRAY, JsonPunctuation.BEGIN_OBJECT -> throw new AssertionError();
|
||||
case JsonNull.NULL -> null;
|
||||
case JsonValue v -> v;
|
||||
case JsonStringTemplate template -> template.asString();
|
||||
case JsonPlaceholder(var object) -> JsonValue.valueOf(object);
|
||||
case JsonPunctuation _ -> throw unexpectedStartOfValue(token);
|
||||
case null -> throw unexpectedEndOfFile();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.peek() instanceof Context context) {
|
||||
context.add(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull String parseJsonObjectKey(@NotNull ObjectContext context, @Nullable JsonToken token) {
|
||||
return switch (token) {
|
||||
case JsonString string -> string.value();
|
||||
case JsonStringTemplate template -> template.asString().value();
|
||||
case JsonPlaceholder(var object) -> switch (object) {
|
||||
case CharSequence chars -> chars.toString();
|
||||
case null -> throw new JsonConversionException("cannot use null as json object key");
|
||||
default -> throw new JsonConversionException("cannot convert object of type " + object.getClass() + " to json object key");
|
||||
};
|
||||
case null -> throw unexpectedEndOfFile();
|
||||
default -> throw unexpectedStartOfKey(context, token);
|
||||
};
|
||||
}
|
||||
|
||||
private @NotNull JsonParserException unexpectedStartOfKey(@NotNull ObjectContext context, @Nullable JsonToken token) {
|
||||
var expected = new ArrayList<>(4);
|
||||
expected.add(TT_JSON_STRING);
|
||||
|
||||
if (context.isEmpty()) {
|
||||
// if the context is not empty, we have already consumed a VALUE_SEPARATOR(,) and standard JSON does not
|
||||
// allow trailing commas
|
||||
expected.add(JsonPunctuation.END_OBJECT);
|
||||
}
|
||||
|
||||
if (tokenizer.getFragment() != 0) {
|
||||
// when the input is a StringTemplate, additional tokens are allowed
|
||||
expected.add(TT_JSON_STRING_TEMPLATE);
|
||||
expected.add(TT_JSON_PLACEHOLDER);
|
||||
}
|
||||
|
||||
return unexpectedToken(token, expected.toArray());
|
||||
}
|
||||
|
||||
private @NotNull JsonParserException unexpectedStartOfValue(@Nullable JsonToken token) {
|
||||
var expected = new ArrayList<>(9);
|
||||
expected.addAll(Arrays.asList(
|
||||
JsonPunctuation.BEGIN_OBJECT, JsonPunctuation.BEGIN_ARRAY, JsonBoolean.TRUE, JsonBoolean.FALSE,
|
||||
JsonNull.NULL, TT_JSON_STRING, TT_JSON_NUMBER
|
||||
));
|
||||
if (tokenizer.getFragment() != 0) {
|
||||
expected.add(TT_JSON_STRING_TEMPLATE);
|
||||
expected.add(TT_JSON_PLACEHOLDER);
|
||||
}
|
||||
throw unexpectedToken(token, expected);
|
||||
}
|
||||
|
||||
private @NotNull JsonParserException unexpectedEndOfFile() {
|
||||
return new JsonParserException(tokenizer.getFragment(), tokenizer.getLineNumber(), tokenizer.getColumnNumber(), "unexpected end of file");
|
||||
}
|
||||
|
||||
private @NotNull JsonParserException unexpectedToken(@Nullable JsonToken token, @NotNull Object @NotNull... expected) {
|
||||
if (expected.length == 1) {
|
||||
throw new JsonParserException(
|
||||
tokenizer.getFragment(), tokenizer.getLineNumber(), tokenizer.getColumnNumber(),
|
||||
"unexpected token: " + token + " (expected " + expected[0] + ")"
|
||||
);
|
||||
} else {
|
||||
throw new JsonParserException(
|
||||
tokenizer.getFragment(), tokenizer.getLineNumber(), tokenizer.getColumnNumber(),
|
||||
"unexpected token: " + token + " (expected one of " + Arrays.toString(expected) + ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an JSON object or array that is currently being parsed.
|
||||
*/
|
||||
private sealed interface Context {
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Adds a nested JSON value to this context
|
||||
* @param value the value
|
||||
*/
|
||||
void add(@Nullable JsonValue value);
|
||||
|
||||
|
||||
/**
|
||||
* Builds and returns the JSON value represented by this context.
|
||||
* @return a JSON value
|
||||
*/
|
||||
@NotNull JsonValue build();
|
||||
|
||||
/**
|
||||
* {@return the token that indicates the end of this context}
|
||||
*/
|
||||
@NotNull JsonPunctuation end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a JSON object that is currently being parsed.
|
||||
*/
|
||||
private static final class ObjectContext implements Context {
|
||||
private final @NotNull SequencedMap<@NotNull String, @Nullable JsonValue> map = new LinkedHashMap<>();
|
||||
|
||||
private @Nullable String key;
|
||||
|
||||
public void setKey(@Nullable String key) {
|
||||
if (this.key != null) throw new IllegalStateException();
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(@Nullable JsonValue value) {
|
||||
if (key == null) throw new IllegalStateException();
|
||||
map.put(key, value);
|
||||
key = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JsonValue build() {
|
||||
if (key != null) throw new IllegalStateException();
|
||||
return new JsonObject(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JsonPunctuation end() {
|
||||
return JsonPunctuation.END_OBJECT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a JSON array that is currently being parsed.
|
||||
*/
|
||||
private static final class ArrayContext implements Context {
|
||||
private final @NotNull List<@Nullable JsonValue> list = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void add(@Nullable JsonValue value) {
|
||||
list.add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JsonArray build() {
|
||||
return new JsonArray(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JsonPunctuation end() {
|
||||
return JsonPunctuation.END_ARRAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package eu.jonahbauer.json.tokenizer;
|
||||
|
||||
import eu.jonahbauer.json.tokenizer.token.JsonToken;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public interface JsonTokenizer extends Iterable<JsonToken> {
|
||||
|
||||
/**
|
||||
* {@return the next token or <code>null</code> if the end of the input has been reached}
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Nullable JsonToken next() throws IOException;
|
||||
|
||||
/**
|
||||
* {@return the fragment at which the previously read token appeared}
|
||||
*/
|
||||
@Range(from = 0, to = Integer.MAX_VALUE) int getFragment();
|
||||
|
||||
/**
|
||||
* {@return the line at which the previously read token appeared}
|
||||
*/
|
||||
@Range(from = 1, to = Integer.MAX_VALUE) int getLineNumber();
|
||||
|
||||
/**
|
||||
* {@return the column at which the previously read token appeared}
|
||||
*/
|
||||
@Range(from = 1, to = Integer.MAX_VALUE) int getColumnNumber();
|
||||
|
||||
/**
|
||||
* {@return an iterator over the tokens} The {@link Iterator#next()} and {@link Iterator#hasNext()} methods
|
||||
* may throw an {@link UncheckedIOException} if an I/O error occurs.
|
||||
*/
|
||||
@Override
|
||||
default @NotNull Iterator<@NotNull JsonToken> iterator() {
|
||||
class JsonTokenizerIterator implements Iterator<@NotNull JsonToken> {
|
||||
private @Nullable JsonToken next;
|
||||
private boolean isValid = false;
|
||||
|
||||
private void ensureValid() {
|
||||
if (isValid) return;
|
||||
|
||||
try {
|
||||
next = JsonTokenizer.this.next();
|
||||
isValid = true;
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
ensureValid();
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull JsonToken next() {
|
||||
ensureValid();
|
||||
if (next == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
var token = next;
|
||||
next = null;
|
||||
isValid = false;
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonTokenizerIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a stream of tokens} When an I/O error occurs, the corresponding {@link IOException} is wrapped in an
|
||||
* {@link UncheckedIOException} exception.
|
||||
*/
|
||||
default @NotNull Stream<@NotNull JsonToken> stream() {
|
||||
return StreamSupport.stream(this.spliterator(), false);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package eu.jonahbauer.json.tokenizer.reader;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface PushbackReader extends AutoCloseable {
|
||||
int EOF = -1;
|
||||
int PLACEHOLDER = -2;
|
||||
|
||||
/**
|
||||
* Reads the next character.
|
||||
* @return the next character or {@link #EOF} if the end of the stream has been reached
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Range(from = -2, to = Character.MAX_VALUE) int read() throws IOException;
|
||||
|
||||
/**
|
||||
* Pushes the reader back, making {@link #read()} return the same character as before.
|
||||
* @throws IllegalStateException when called before the first read or more than once after each read
|
||||
*/
|
||||
void pushback();
|
||||
|
||||
/**
|
||||
* {@return the index of the fragment the current character belongs to} Returns 0 unless a {@code StringTemplate} is
|
||||
* being read.
|
||||
* @throws IllegalStateException when called before the first read
|
||||
*/
|
||||
@Range(from = 0, to = Integer.MAX_VALUE) int getFragment();
|
||||
|
||||
/**
|
||||
* {@return the line number of the current character}
|
||||
* @throws IllegalStateException when called before the first read
|
||||
*/
|
||||
@Range(from = 1, to = Integer.MAX_VALUE) int getLineNumber();
|
||||
|
||||
/**
|
||||
* {@return the column number of the current character}
|
||||
* @throws IllegalStateException when called before the first read
|
||||
*/
|
||||
@Range(from = 1, to = Integer.MAX_VALUE) int getColumnNumber();
|
||||
|
||||
/**
|
||||
* Return the object that has been read after {@link #read()} returned {@link #PLACEHOLDER}.
|
||||
* @throws IllegalStateException the previous call to read did not return {@link #PLACEHOLDER}
|
||||
* @return the object that has been read
|
||||
*/
|
||||
@Nullable Object getObject();
|
||||
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package eu.jonahbauer.json.tokenizer.reader;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.util.*;
|
||||
|
||||
public class PushbackReaderImpl implements PushbackReader {
|
||||
private @Nullable Input input;
|
||||
private @Nullable Reader reader;
|
||||
|
||||
private int current = Integer.MIN_VALUE;
|
||||
private Object object;
|
||||
|
||||
private int fragment;
|
||||
private int lineNumber;
|
||||
private int columnNumber;
|
||||
|
||||
private boolean pushback;
|
||||
|
||||
public PushbackReaderImpl(@NotNull Reader reader) {
|
||||
this.input = new Input(List.of(reader).iterator(), Collections.emptyIterator());
|
||||
this.fragment = -1; // fragment will be incremented to 0 when the first character is read
|
||||
}
|
||||
|
||||
@SuppressWarnings("preview")
|
||||
public PushbackReaderImpl(@NotNull StringTemplate template) {
|
||||
this.input = new Input(
|
||||
template.fragments().stream().<Reader>map(StringReader::new).iterator(),
|
||||
template.values().iterator()
|
||||
);
|
||||
this.fragment = 0; // fragment will be incremented to 1 when the first character is read
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFragment() {
|
||||
if (current == Integer.MIN_VALUE) {
|
||||
throw new IllegalStateException("No character has been read so far.");
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineNumber() {
|
||||
if (current == Integer.MIN_VALUE) {
|
||||
throw new IllegalStateException("No character has been read so far.");
|
||||
}
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnNumber() {
|
||||
if (current == Integer.MIN_VALUE) {
|
||||
throw new IllegalStateException("No character has been read so far.");
|
||||
}
|
||||
return columnNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushback() {
|
||||
if (current == Integer.MIN_VALUE) {
|
||||
throw new IllegalStateException("No character has been read so far.");
|
||||
} else if (pushback) {
|
||||
throw new IllegalStateException("Cannot push back more than one character at a time.");
|
||||
}
|
||||
pushback = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (pushback) {
|
||||
pushback = false;
|
||||
return current;
|
||||
}
|
||||
|
||||
var input = this.input;
|
||||
if (input == null) {
|
||||
throw new IOException("closed");
|
||||
}
|
||||
|
||||
var reader = this.reader;
|
||||
if (reader == null && input.readers().hasNext()) {
|
||||
this.reader = reader = input.readers().next();
|
||||
fragment++;
|
||||
lineNumber = 1;
|
||||
columnNumber = 0;
|
||||
} else if (reader == null) {
|
||||
return EOF;
|
||||
}
|
||||
|
||||
int result = reader.read();
|
||||
|
||||
if (current == '\n' && result != '\n' || current == '\r' && result != '\n' && result != '\r') {
|
||||
lineNumber++;
|
||||
columnNumber = 1;
|
||||
} else {
|
||||
columnNumber++;
|
||||
}
|
||||
|
||||
if (result == EOF) {
|
||||
reader.close();
|
||||
this.reader = null;
|
||||
|
||||
if (input.values().hasNext()) {
|
||||
this.object = input.values().next();
|
||||
return this.current = PLACEHOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
this.object = null;
|
||||
return this.current = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getObject() {
|
||||
if (current != PLACEHOLDER) {
|
||||
throw new IllegalStateException("Currently not at a placeholder.");
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
var input = this.input;
|
||||
var reader = this.reader;
|
||||
this.input = null;
|
||||
this.reader = null;
|
||||
close(Arrays.asList(input, reader).iterator());
|
||||
}
|
||||
|
||||
private static void close(@NotNull Iterator<? extends @Nullable AutoCloseable> closeables) throws ClosingException {
|
||||
ClosingException exception = null;
|
||||
while (closeables.hasNext()) {
|
||||
var closeable = closeables.next();
|
||||
if (closeable == null) continue;
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Throwable t) {
|
||||
if (exception == null) exception = new ClosingException();
|
||||
exception.addSuppressed(t);
|
||||
}
|
||||
}
|
||||
if (exception != null) throw exception;
|
||||
}
|
||||
|
||||
private record Input(@NotNull Iterator<@NotNull Reader> readers, @NotNull Iterator<Object> values) implements AutoCloseable {
|
||||
|
||||
@Override
|
||||
public void close() throws ClosingException {
|
||||
PushbackReaderImpl.close(readers);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ClosingException extends IOException { }
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package eu.jonahbauer.json.tokenizer.token;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* The JSON token {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("java:S6548")
|
||||
public enum JsonNull implements JsonToken {
|
||||
NULL;
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package eu.jonahbauer.json.tokenizer.token;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record JsonPlaceholder(@Nullable Object object) implements JsonToken {
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package eu.jonahbauer.json.tokenizer.token;
|
||||
|
||||
import eu.jonahbauer.json.JsonBoolean;
|
||||
import eu.jonahbauer.json.JsonNumber;
|
||||
import eu.jonahbauer.json.JsonString;
|
||||
|
||||
/**
|
||||
* Represents a JSON token.
|
||||
*/
|
||||
public sealed interface JsonToken permits JsonBoolean, JsonNumber, JsonString, JsonNull, JsonPlaceholder, JsonPunctuation, JsonStringTemplate { }
|
||||
@@ -1 +0,0 @@
|
||||
["é"]
|
||||
@@ -1 +0,0 @@
|
||||
["日ш�"]
|
||||
@@ -1 +0,0 @@
|
||||
["�"]
|
||||
@@ -1 +0,0 @@
|
||||
["�"]
|
||||
@@ -1 +0,0 @@
|
||||
["�"]
|
||||
@@ -1 +0,0 @@
|
||||
["�"]
|
||||
@@ -1 +0,0 @@
|
||||
["����"]
|
||||
@@ -1 +0,0 @@
|
||||
["��"]
|
||||
@@ -1 +0,0 @@
|
||||
["������"]
|
||||
@@ -1 +0,0 @@
|
||||
["������"]
|
||||
@@ -1 +0,0 @@
|
||||
["��"]
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
[a�]
|
||||
@@ -1 +0,0 @@
|
||||
[�]
|
||||
@@ -1 +0,0 @@
|
||||
[123�]
|
||||
@@ -1 +0,0 @@
|
||||
[1e1�]
|
||||
@@ -1 +0,0 @@
|
||||
[0�]
|
||||
@@ -1 +0,0 @@
|
||||
[1e�]
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"�":"0",}
|
||||
@@ -1 +0,0 @@
|
||||
["\u�"]
|
||||
@@ -1 +0,0 @@
|
||||
["\�"]
|
||||
@@ -1 +0,0 @@
|
||||
�{}
|
||||
@@ -1 +0,0 @@
|
||||
�
|
||||
@@ -1 +0,0 @@
|
||||
�
|
||||
@@ -1,11 +1,14 @@
|
||||
[versions]
|
||||
annotations = "24.1.0"
|
||||
junit = "5.12.1"
|
||||
junit-launcher = "1.12.1"
|
||||
lombok = "1.18.32"
|
||||
junit = "5.10.1"
|
||||
lombok = "1.18.30"
|
||||
|
||||
[libraries]
|
||||
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
|
||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
||||
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-launcher" }
|
||||
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
|
||||
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
|
||||
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
|
||||
|
||||
[bundles]
|
||||
junit = ["junit-jupiter", "junit-jupiter-api", "junit-jupiter-params"]
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
-30
@@ -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;
|
||||
}
|
||||
}
|
||||
-52
@@ -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) { }
|
||||
}
|
||||
-30
@@ -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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user