kopia lustrzana https://github.com/onthegomap/planetiler
200 wiersze
7.3 KiB
Java
200 wiersze
7.3 KiB
Java
package com.onthegomap.planetiler.custommap.expression.stdlib;
|
|
|
|
import static org.projectnessie.cel.checker.Decls.newOverload;
|
|
|
|
import com.google.api.expr.v1alpha1.Type;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.function.DoubleBinaryOperator;
|
|
import java.util.function.LongBinaryOperator;
|
|
import java.util.stream.IntStream;
|
|
import java.util.stream.Stream;
|
|
import org.projectnessie.cel.checker.Decls;
|
|
import org.projectnessie.cel.common.types.BoolT;
|
|
import org.projectnessie.cel.common.types.DoubleT;
|
|
import org.projectnessie.cel.common.types.Err;
|
|
import org.projectnessie.cel.common.types.IntT;
|
|
import org.projectnessie.cel.common.types.NullT;
|
|
import org.projectnessie.cel.common.types.StringT;
|
|
import org.projectnessie.cel.common.types.ref.Val;
|
|
import org.projectnessie.cel.common.types.traits.Lister;
|
|
import org.projectnessie.cel.common.types.traits.Mapper;
|
|
import org.projectnessie.cel.interpreter.functions.Overload;
|
|
|
|
/**
|
|
* Built-in functions to expose to all dynamic expression used in planetiler configs.
|
|
*/
|
|
public class PlanetilerStdLib extends PlanetilerLib {
|
|
private static final int VARARG_LIMIT = 32;
|
|
private static final Type T = Decls.newTypeParamType("T");
|
|
private static final Type K = Decls.newTypeParamType("K");
|
|
private static final Type V = Decls.newTypeParamType("V");
|
|
|
|
public PlanetilerStdLib() {
|
|
super(List.of(
|
|
// coalesce(a, b, c...) -> first non-null value
|
|
new BuiltInFunction(
|
|
Decls.newFunction("coalesce",
|
|
IntStream.range(0, VARARG_LIMIT)
|
|
.mapToObj(
|
|
i -> newOverload("coalesce_" + i, IntStream.range(0, i).mapToObj(d -> Decls.Any).toList(), Decls.Any))
|
|
.toList()
|
|
),
|
|
Overload.overload("coalesce",
|
|
null,
|
|
null,
|
|
(a, b) -> a == null || a instanceof NullT ? b : a,
|
|
args -> {
|
|
for (var arg : args) {
|
|
if (!(arg instanceof NullT)) {
|
|
return arg;
|
|
}
|
|
}
|
|
return NullT.NullValue;
|
|
})
|
|
),
|
|
|
|
// nullif(a, b) -> null if a == b, otherwise a
|
|
new BuiltInFunction(
|
|
Decls.newFunction("nullif",
|
|
Decls.newOverload("nullif", List.of(T, T), T)
|
|
),
|
|
Overload.binary("nullif", (a, b) -> Objects.equals(a, b) ? NullT.NullValue : a)
|
|
),
|
|
|
|
// string.replaceRegex(regex, replacement) -> replaces all matches for regex in string with replacement
|
|
new BuiltInFunction(
|
|
Decls.newFunction("replaceRegex",
|
|
Decls.newInstanceOverload("replaceRegex", List.of(Decls.String, Decls.String, Decls.String), Decls.String)
|
|
),
|
|
Overload.function("replaceRegex", values -> {
|
|
try {
|
|
String string = ((String) values[0].value());
|
|
String regexp = ((String) values[1].value());
|
|
String replace = ((String) values[2].value());
|
|
return StringT.stringOf(string.replaceAll(regexp, replace));
|
|
} catch (RuntimeException e) {
|
|
return Err.newErr(e, "%s", e.getMessage());
|
|
}
|
|
})
|
|
),
|
|
|
|
// map.has(key) -> true if key is present in map
|
|
// map.has(key, value...) true if the value for key is in the list of values provided
|
|
new BuiltInFunction(
|
|
Decls.newFunction("has",
|
|
IntStream.range(0, VARARG_LIMIT)
|
|
.mapToObj(
|
|
i -> Decls.newInstanceOverload("map_has_" + i, Stream.concat(
|
|
Stream.of(Decls.newMapType(K, V), K),
|
|
IntStream.range(0, i).mapToObj(n -> V)
|
|
).toList(), Decls.Bool)
|
|
).toList()
|
|
),
|
|
Overload.overload("has",
|
|
null,
|
|
null,
|
|
(map, key) -> {
|
|
try {
|
|
return getFromMap(map, key) != null ? BoolT.True : BoolT.False;
|
|
} catch (RuntimeException e) {
|
|
return Err.newErr(e, "%s", e.getMessage());
|
|
}
|
|
},
|
|
args -> {
|
|
try {
|
|
Val elem = getFromMap(args[0], args[1]);
|
|
if (elem == null) {
|
|
return BoolT.False;
|
|
}
|
|
for (int i = 2; i < args.length; i++) {
|
|
if (args[i].equals(elem)) {
|
|
return BoolT.True;
|
|
}
|
|
}
|
|
return BoolT.False;
|
|
} catch (RuntimeException e) {
|
|
return Err.newErr(e, "%s", e.getMessage());
|
|
}
|
|
})
|
|
),
|
|
|
|
// map.get(key) -> the value for key, or null if missing
|
|
new BuiltInFunction(
|
|
Decls.newFunction("get", Decls.newInstanceOverload("get", List.of(Decls.newMapType(K, V), K), V)),
|
|
Overload.binary("get", (map, key) -> {
|
|
try {
|
|
var value = getFromMap(map, key);
|
|
return value == null ? NullT.NullValue : value;
|
|
} catch (RuntimeException e) {
|
|
return Err.newErr(e, "%s", e.getMessage());
|
|
}
|
|
})
|
|
),
|
|
|
|
// map.getOrDefault(key, default) -> the value for key, or default if missing
|
|
new BuiltInFunction(
|
|
Decls.newFunction("getOrDefault",
|
|
Decls.newInstanceOverload("getOrDefault", List.of(Decls.newMapType(K, V), K, V), V)),
|
|
Overload.function("getOrDefault", args -> {
|
|
try {
|
|
var value = getFromMap(args[0], args[1]);
|
|
return value == null ? args[2] : value;
|
|
} catch (RuntimeException e) {
|
|
return Err.newErr(e, "%s", e.getMessage());
|
|
}
|
|
})
|
|
),
|
|
|
|
// min(list) -> the minimum value from the list, or null if empty
|
|
new BuiltInFunction(
|
|
Decls.newFunction("min",
|
|
Decls.newOverload("min_int", List.of(Decls.newListType(Decls.Int)), Decls.Int),
|
|
Decls.newOverload("min_double", List.of(Decls.newListType(Decls.Double)), Decls.Double)
|
|
),
|
|
Overload.unary("min", list -> reduceNumeric(list, Math::min, Math::min))
|
|
),
|
|
|
|
// max(list) -> the maximum value from the list, or null if empty
|
|
new BuiltInFunction(
|
|
Decls.newFunction("max",
|
|
Decls.newOverload("max_int", List.of(Decls.newListType(Decls.Int)), Decls.Int),
|
|
Decls.newOverload("max_double", List.of(Decls.newListType(Decls.Double)), Decls.Double)
|
|
),
|
|
Overload.unary("max", list -> reduceNumeric(list, Math::max, Math::max))
|
|
)
|
|
));
|
|
}
|
|
|
|
private static Val getFromMap(Val map, Val key) {
|
|
return map instanceof Mapper mapper ? mapper.find(key) : null;
|
|
}
|
|
|
|
private static Val reduceNumeric(Val list, LongBinaryOperator intFn, DoubleBinaryOperator doubleFn) {
|
|
try {
|
|
var iterator = ((Lister) list).iterator();
|
|
if (!iterator.hasNext().booleanValue()) {
|
|
return NullT.NullValue;
|
|
}
|
|
var next = iterator.next();
|
|
if (next instanceof IntT intT) {
|
|
long acc = intT.intValue();
|
|
while (iterator.hasNext().booleanValue()) {
|
|
acc = intFn.applyAsLong(iterator.next().convertToNative(Long.class), acc);
|
|
}
|
|
return IntT.intOf(acc);
|
|
} else if (next instanceof DoubleT doubleT) {
|
|
double acc = doubleT.convertToNative(Double.class);
|
|
while (iterator.hasNext().booleanValue()) {
|
|
acc = doubleFn.applyAsDouble(iterator.next().convertToNative(Double.class), acc);
|
|
}
|
|
return DoubleT.doubleOf(acc);
|
|
} else {
|
|
return Err.newErr("Bad element of list for min(): %s", next);
|
|
}
|
|
} catch (RuntimeException e) {
|
|
return Err.newErr(e, "%s", e.getMessage());
|
|
}
|
|
}
|
|
}
|