sjsonnet.Std.scala Maven / Gradle / Ivy
package sjsonnet
import java.io.StringWriter
import java.nio.charset.StandardCharsets.UTF_8
import java.util.Base64
import java.util
import java.util.regex.Pattern
import sjsonnet.Expr.Member.Visibility
import sjsonnet.Expr.BinaryOp
import scala.collection.mutable
import scala.util.matching.Regex
/**
* The Jsonnet standard library, `std`, with each builtin function implemented
* in Scala code. Uses `builtin` and other helpers to handle the common wrapper
* logic automatically
*/
class Std {
private val dummyPos: Position = new Position(null, 0)
private val emptyLazyArray = new Array[Lazy](0)
private object AssertEqual extends Val.Builtin2("a", "b") {
def evalRhs(v1: Val, v2: Val, ev: EvalScope, pos: Position): Val = {
val x1 = Materializer(v1)(ev)
val x2 = Materializer(v2)(ev)
if (x1 == x2) Val.True(pos)
else Error.fail("assertEqual failed: " + x1 + " != " + x2)
}
}
private object ToString extends Val.Builtin1("a") {
def evalRhs(v1: Val, ev: EvalScope, pos: Position): Val = Val.Str(pos, v1 match {
case Val.Str(_, s) => s
case v => Materializer.stringify(v)(ev)
})
}
private object Length extends Val.Builtin1("x") {
def evalRhs(x: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, x match {
case Val.Str(_, s) => s.length
case a: Val.Arr => a.length
case o: Val.Obj => o.visibleKeyNames.length
case o: Val.Func => o.params.names.length
case x => Error.fail("Cannot get length of " + x.prettyName)
})
override def specialize(args: Array[Expr]) = args match {
case Array(Expr.ApplyBuiltin2(_, Filter, f, a)) => (CountF, Array(f, a))
case _ => null
}
}
private object CountF extends Val.Builtin2("func", "arr") {
def evalRhs(_func: Val, arr: Val, ev: EvalScope, pos: Position): Val = {
val p = pos.noOffset
val a = arr.asArr.asLazyArray
var i = 0
val func = _func.asFunc
var res = 0
if(func.isInstanceOf[Val.Builtin] || func.params.names.length != 1) {
while(i < a.length) {
if(func.apply1(a(i), p)(ev).isInstanceOf[Val.True]) res += 1
i += 1
}
} else {
// Single-param non-builtin can benefit from scope reuse: We compute a strict boolean from
// the function, there's no risk of the scope leaking (and being invalid at a later point)
val funDefFileScope: FileScope = func.pos match { case null => p.fileScope case p => p.fileScope }
val newScope: ValScope = func.defSiteValScope.extendBy(1)
val scopeIdx = newScope.length-1
while(i < a.length) {
newScope.bindings(scopeIdx) = a(i)
if(func.evalRhs(newScope, ev, funDefFileScope, p).isInstanceOf[Val.True]) res += 1
i += 1
}
}
new Val.Num(pos, res)
}
}
private object Codepoint extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, str.asString.charAt(0).toInt)
}
private object ObjectHas extends Val.Builtin2("o", "f") {
def evalRhs(o: Val, f: Val, ev: EvalScope, pos: Position): Val =
Val.bool(pos, o.asObj.containsVisibleKey(f.asString))
override def specialize(args: Array[Expr]) = args match {
case Array(o, s: Val.Str) => (new SpecF(s.value), Array(o))
case _ => null
}
private class SpecF(f: String) extends Val.Builtin1("o") {
def evalRhs(o: Val, ev: EvalScope, pos: Position): Val =
Val.bool(pos, o.asObj.containsVisibleKey(f))
}
}
private object ObjectHasAll extends Val.Builtin2("o", "f") {
def evalRhs(o: Val, f: Val, ev: EvalScope, pos: Position): Val =
Val.bool(pos, o.asObj.containsKey(f.asString))
override def specialize(args: Array[Expr]) = args match {
case Array(o, s: Val.Str) => (new SpecF(s.value), Array(o))
case _ => null
}
class SpecF(f: String) extends Val.Builtin1("o") {
def evalRhs(o: Val, ev: EvalScope, pos: Position): Val =
Val.bool(pos, o.asObj.containsKey(f))
}
}
private object ObjectFields extends Val.Builtin1("o") {
def evalRhs(o: Val, ev: EvalScope, pos: Position): Val = {
val keys = getVisibleKeys(ev, o.asObj)
new Val.Arr(pos, keys.map(k => Val.Str(pos, k)))
}
}
private object ObjectFieldsAll extends Val.Builtin1("o") {
def evalRhs(o: Val, ev: EvalScope, pos: Position): Val = {
val keys = getAllKeys(ev, o.asObj)
new Val.Arr(pos, keys.map(k => Val.Str(pos, k)))
}
}
private object All extends Val.Builtin1("arr") {
def evalRhs(arr: Val, ev: EvalScope, pos: Position): Val = {
Val.bool(pos, arr.asArr.forall(v => v.asBoolean))
}
}
private object Any extends Val.Builtin1("arr") {
def evalRhs(arr: Val, ev: EvalScope, pos: Position): Val = {
Val.bool(pos, arr.asArr.iterator.exists(v => v.asBoolean))
}
}
private object Type extends Val.Builtin1("x") {
def evalRhs(x: Val, ev: EvalScope, pos: Position): Val = Val.Str(pos, x match {
case _: Val.Bool => "boolean"
case _: Val.Null => "null"
case _: Val.Obj => "object"
case _: Val.Arr => "array"
case _: Val.Func => "function"
case _: Val.Num => "number"
case _: Val.Str => "string"
})
}
private object Format_ extends Val.Builtin2("str", "vals") {
def evalRhs(str: Val, vals: Val, ev: EvalScope, pos: Position): Val =
new Val.Str(pos, Format.format(str.asString, vals, pos)(ev))
override def specialize(args: Array[Expr]) = args match {
case Array(str, fmt: Val.Str) =>
try { (new Format.PartialApplyFmt(fmt.value), Array(str)) } catch { case _: Exception => null }
case _ => null
}
}
private object Foldl extends Val.Builtin3("func", "arr", "init") {
def evalRhs(_func: Val, arr: Val, init: Val, ev: EvalScope, pos: Position): Val = {
val func = _func.asFunc
arr match{
case arr: Val.Arr =>
var current = init
for (item <- arr.asLazyArray) {
val c = current
current = func.apply2(c, item, pos.noOffset)(ev)
}
current
case s: Val.Str =>
var current = init
for (char <- s.value) {
val c = current
current = func.apply2(c, Val.Str(pos, new String(Array(char))), pos.noOffset)(ev)
}
current
}
}
}
private object Foldr extends Val.Builtin3("func", "arr", "init") {
def evalRhs(_func: Val, arr: Val, init: Val, ev: EvalScope, pos: Position): Val = {
val func = _func.asFunc
arr match {
case arr: Val.Arr =>
var current = init
for (item <- arr.asLazyArray.reverse) {
val c = current
current = func.apply2(item, c, pos.noOffset)(ev)
}
current
case s: Val.Str =>
var current = init
for (char <- s.value) {
val c = current
current = func.apply2(Val.Str(pos, new String(Array(char))), c, pos.noOffset)(ev)
}
current
}
}
}
private object IsString extends Val.Builtin1("v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Str])
}
private object IsBoolean extends Val.Builtin1("v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Bool])
}
private object IsNumber extends Val.Builtin1("v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Num])
}
private object IsObject extends Val.Builtin1("v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Obj])
}
private object IsArray extends Val.Builtin1("v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Arr])
}
private object IsFunction extends Val.Builtin1("v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Func])
}
private object Count extends Val.Builtin2("arr", "x") {
def evalRhs(arr: Val, x: Val, ev: EvalScope, pos: Position): Val = {
var count = 0
arr.asArr.foreach(v => if(ev.equal(v, x)) count += 1)
Val.Num(pos, count)
}
}
private object Filter extends Val.Builtin2("func", "arr") {
def evalRhs(_func: Val, arr: Val, ev: EvalScope, pos: Position): Val = {
val p = pos.noOffset
val a = arr.asArr.asLazyArray
var i = 0
val func = _func.asFunc
if(func.isInstanceOf[Val.Builtin] || func.params.names.length != 1) {
while(i < a.length) {
if(!func.apply1(a(i), p)(ev).isInstanceOf[Val.True]) {
var b = new Array[Lazy](a.length-1)
System.arraycopy(a, 0, b, 0, i)
var j = i+1
while(j < a.length) {
if(func.apply1(a(j), p)(ev).isInstanceOf[Val.True]) {
b(i) = a(j)
i += 1
}
j += 1
}
if(i != b.length) b = util.Arrays.copyOf(b, i)
return new Val.Arr(pos, b)
}
i += 1
}
} else {
// Single-param non-builtin can benefit from scope reuse: We compute a strict boolean from
// the function, there's no risk of the scope leaking (and being invalid at a later point)
val funDefFileScope: FileScope = func.pos match { case null => p.fileScope case p => p.fileScope }
val newScope: ValScope = func.defSiteValScope.extendBy(1)
val scopeIdx = newScope.length-1
while(i < a.length) {
newScope.bindings(scopeIdx) = a(i)
if(!func.evalRhs(newScope, ev, funDefFileScope, p).isInstanceOf[Val.True]) {
var b = new Array[Lazy](a.length-1)
System.arraycopy(a, 0, b, 0, i)
var j = i+1
while(j < a.length) {
newScope.bindings(scopeIdx) = a(j)
if(func.evalRhs(newScope, ev, funDefFileScope, p).isInstanceOf[Val.True]) {
b(i) = a(j)
i += 1
}
j += 1
}
if(i != b.length) b = util.Arrays.copyOf(b, i)
return new Val.Arr(pos, b)
}
i += 1
}
}
new Val.Arr(pos, a)
//new Val.Arr(pos, arr.asArr.asLazyArray.filter(v => func.apply1(v, pos.noOffset)(ev).isInstanceOf[Val.True]))
}
}
private object Map_ extends Val.Builtin2("func", "arr") {
def evalRhs(_func: Val, arr: Val, ev: EvalScope, pos: Position): Val = {
val func = _func.asFunc
new Val.Arr(pos, arr.asArr.asLazyArray.map(v => (() => func.apply1(v, pos.noOffset)(ev)): Lazy))
}
}
private object MapWithKey extends Val.Builtin2("func", "obj") {
def evalRhs(_func: Val, _obj: Val, ev: EvalScope, pos: Position): Val = {
val func = _func.asFunc
val obj = _obj.asObj
val allKeys = obj.allKeyNames
val m = new util.LinkedHashMap[String, Val.Obj.Member]()
var i = 0
while(i < allKeys.length) {
val k = allKeys(i)
val v = new Val.Obj.Member(false, Visibility.Normal) {
def invoke(self: Val.Obj, sup: Val.Obj, fs: FileScope, ev: EvalScope): Val =
func.apply2(Val.Str(pos, k), () => obj.value(k, pos.noOffset)(ev), pos.noOffset)(ev)
}
m.put(k, v)
i += 1
}
new Val.Obj(pos, m, false, null, null)
}
}
private object MapWithIndex extends Val.Builtin2("func", "arr") {
def evalRhs(_func: Val, _arr: Val, ev: EvalScope, pos: Position): Val = {
val func = _func.asFunc
val arr = _arr.asArr.asLazyArray
val a = new Array[Lazy](arr.length)
var i = 0
while(i < a.length) {
val x = arr(i)
val idx = Val.Num(pos, i)
a(i) = () => func.apply2(idx, x, pos.noOffset)(ev)
i += 1
}
new Val.Arr(pos, a)
}
}
private object Find extends Val.Builtin2("value", "arr") {
def evalRhs(value: Val, _arr: Val, ev: EvalScope, pos: Position): Val = {
val arr = _arr.asArr
val b = new mutable.ArrayBuilder.ofRef[Lazy]
var i = 0
while(i < arr.length) {
if(ev.equal(arr.force(i), value)) {
val finalI = i
b.+=(Val.Num(pos, finalI))
}
i += 1
}
new Val.Arr(pos, b.result())
}
}
private object EncodeUTF8 extends Val.Builtin1("s") {
def evalRhs(s: Val, ev: EvalScope, pos: Position): Val =
new Val.Arr(pos, s.asString.getBytes(UTF_8).map(i => Val.Num(pos, i & 0xff)))
}
private object DecodeUTF8 extends Val.Builtin1("arr") {
def evalRhs(arr: Val, ev: EvalScope, pos: Position): Val =
new Val.Str(pos, new String(arr.asArr.iterator.map(_.cast[Val.Num].value.toByte).toArray, UTF_8))
}
private object Substr extends Val.Builtin3("s", "from", "len") {
def evalRhs(_s: Val, from: Val, len: Val, ev: EvalScope, pos: Position): Val = {
val s = _s.asString
val safeOffset = math.min(from.asInt, s.length)
val safeLength = math.min(len.asInt, s.length - safeOffset)
Val.Str(pos, s.substring(safeOffset, safeOffset + safeLength))
}
}
private object StartsWith extends Val.Builtin2("a", "b") {
def evalRhs(a: Val, b: Val, ev: EvalScope, pos: Position): Val =
Val.bool(pos, a.asString.startsWith(b.asString))
}
private object EndsWith extends Val.Builtin2("a", "b") {
def evalRhs(a: Val, b: Val, ev: EvalScope, pos: Position): Val =
Val.bool(pos, a.asString.endsWith(b.asString))
}
private object Char_ extends Val.Builtin1("n") {
def evalRhs(n: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, n.asInt.toChar.toString)
}
private object StrReplace extends Val.Builtin3("str", "from", "to") {
def evalRhs(str: Val, from: Val, to: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, str.asString.replace(from.asString, to.asString))
}
private object StrReplaceAll extends Val.Builtin3("str", "from", "to") {
def evalRhs(str: Val, from: Val, to: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, str.asString.replaceAll(from.asString, to.asString))
override def specialize(args: Array[Expr]) = args match {
case Array(str, from: Val.Str, to) =>
try { (new SpecFrom(Pattern.compile(from.value)), Array(str, to)) } catch { case _: Exception => null }
case _ => null
}
private class SpecFrom(from: Pattern) extends Val.Builtin2("str", "to") {
def evalRhs(str: Val, to: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, from.matcher(str.asString).replaceAll(to.asString))
}
}
private object Join extends Val.Builtin2("sep", "arr") {
def evalRhs(sep: Val, _arr: Val, ev: EvalScope, pos: Position): Val = {
val arr = implicitly[ReadWriter[Val.Arr]].apply(_arr)
sep match {
case Val.Str(_, s) =>
val b = new java.lang.StringBuilder()
var i = 0
var added = false
while(i < arr.length) {
arr.force(i) match {
case _: Val.Null =>
case Val.Str(_, x) =>
if(added) b.append(s)
added = true
b.append(x)
case x => Error.fail("Cannot join " + x.prettyName)
}
i += 1
}
Val.Str(pos, b.toString)
case sep: Val.Arr =>
val out = new mutable.ArrayBuffer[Lazy]
var added = false
for(x <- arr){
x match{
case Val.Null(_) => // do nothing
case v: Val.Arr =>
if (added) out.appendAll(sep.asLazyArray)
added = true
out.appendAll(v.asLazyArray)
case x => Error.fail("Cannot join " + x.prettyName)
}
}
new Val.Arr(pos, out.toArray)
case x => Error.fail("Cannot join " + x.prettyName)
}
}
}
private object Member extends Val.Builtin2("arr", "x") {
def evalRhs(arr: Val, x: Val, ev: EvalScope, pos: Position): Val = {
Val.bool(pos, arr match {
case str: Val.Str =>
val secondArg = x match {
case Val.Str(_, value) => value
case n => Error.fail("std.member second argument must be a string, got " + x.prettyName)
}
str.value.contains(secondArg)
case a: Val.Arr =>
var c = 0
a.foreach(v => if(ev.equal(v, x)) c += 1)
c > 0
case x => Error.fail("std.member first argument must be an array or a string, got " + arr.prettyName)
})
}
}
private object FlattenArrays extends Val.Builtin1("arrs") {
def evalRhs(arrs: Val, ev: EvalScope, pos: Position): Val = {
val out = new mutable.ArrayBuffer[Lazy]
for(x <- arrs.asArr) {
x match{
case Val.Null(_) => // do nothing
case v: Val.Arr => out.appendAll(v.asLazyArray)
case x => Error.fail("Cannot call flattenArrays on " + x)
}
}
new Val.Arr(pos, out.toArray)
}
}
private object Reverse extends Val.Builtin1("arrs") {
def evalRhs(arrs: Val, ev: EvalScope, pos: Position): Val = {
new Val.Arr(pos, arrs.asArr.asLazyArray.reverse)
}
}
private object Split extends Val.Builtin2("str", "c") {
def evalRhs(_str: Val, _c: Val, ev: EvalScope, pos: Position): Val = {
val str = _str.asString
val cStr = _c.asString
if(cStr.length != 1) Error.fail("std.split second parameter should have length 1, got "+cStr.length)
val c = cStr.charAt(0)
val b = new mutable.ArrayBuilder.ofRef[Lazy]
var i = 0
var start = 0
while(i < str.length) {
if(str.charAt(i) == c) {
val finalStr = Val.Str(pos, str.substring(start, i))
b.+=(finalStr)
start = i+1
}
i += 1
}
b.+=(Val.Str(pos, str.substring(start, math.min(i, str.length))))
new Val.Arr(pos, b.result())
}
}
private object SplitLimit extends Val.Builtin3("str", "c", "maxSplits") {
def evalRhs(str: Val, c: Val, maxSplits: Val, ev: EvalScope, pos: Position): Val = {
new Val.Arr(pos, str.asString.split(java.util.regex.Pattern.quote(c.asString), maxSplits.asInt + 1).map(s => Val.Str(pos, s)))
}
}
private object StringChars extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
stringChars(pos, str.asString)
}
private object ParseInt extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, str.asString.toInt)
}
private object ParseOctal extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, Integer.parseInt(str.asString, 8))
}
private object ParseHex extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, Integer.parseInt(str.asString, 16))
}
private object MD5 extends Val.Builtin1("s") {
def evalRhs(s: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, Platform.md5(s.asString))
}
private object AsciiUpper extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, str.asString.toUpperCase)
}
private object AsciiLower extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, str.asString.toLowerCase)
}
private object Trace extends Val.Builtin2("str", "rest") {
def evalRhs(str: Val, rest: Val, ev: EvalScope, pos: Position): Val = {
System.err.println(s"TRACE: ${pos.fileScope.currentFileLastPathElement} " + str.asString)
rest
}
}
private object ExtVar extends Val.Builtin1("x") {
def evalRhs(_x: Val, ev: EvalScope, pos: Position): Val = {
val Val.Str(_, x) = _x
ev.visitExpr(ev.extVars(x).getOrElse(Error.fail("Unknown extVar: " + x)))(ValScope.empty)
}
override def staticSafe = false
}
private object ObjectValues extends Val.Builtin1("o") {
def evalRhs(_o: Val, ev: EvalScope, pos: Position): Val = {
val o = _o.asObj
val keys = getVisibleKeys(ev, o)
getObjValuesFromKeys(pos, ev, o, keys)
}
}
private object ObjectValuesAll extends Val.Builtin1("o") {
def evalRhs(_o: Val, ev: EvalScope, pos: Position): Val = {
val o = _o.asObj
val keys = getAllKeys(ev, o)
getObjValuesFromKeys(pos, ev, o, keys)
}
}
private object Lines extends Val.Builtin1("arr") {
def evalRhs(v1: Val, ev: EvalScope, pos: Position): Val = {
v1.asArr.foreach {
case _: Val.Str | _: Val.Null => // donothing
case x => Error.fail("Cannot call .lines on " + x.prettyName)
}
Val.Str(pos, Materializer.apply(v1)(ev).asInstanceOf[ujson.Arr]
.value
.filter(_ != ujson.Null)
.map{
case ujson.Str(s) => s + "\n"
case _ => ??? /* we ensure it's all strings above */
}
.mkString)
}
}
private object Range extends Val.Builtin2("from", "to") {
def evalRhs(from: Val, to: Val, ev: EvalScope, pos: Position): Val =
new Val.Arr(
pos,
(from.asInt to to.asInt).map(i => Val.Num(pos, i)).toArray
)
}
private object ManifestJson extends Val.Builtin1("v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, Materializer.apply0(v, new MaterializeJsonRenderer())(ev).toString)
}
private object ManifestJsonMinified extends Val.Builtin1("v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, Materializer.apply0(v, new MaterializeJsonRenderer(indent = -1))(ev).toString)
}
private object ManifestJsonEx extends Val.Builtin2("value", "indent") {
def evalRhs(v: Val, i: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, Materializer
.apply0(v, new MaterializeJsonRenderer(indent = i.asString.length))(ev)
.toString)
}
private object ParseJson extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
ujson.StringParser.transform(str.asString, new ValVisitor(pos))
}
private object ParseYaml extends Val.Builtin1("str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val = {
try {
ujson.StringParser.transform(Platform.yamlToJson(str.asString), new ValVisitor(pos))
} catch {
case _: Exception => null
}
}
}
private object Set_ extends Val.Builtin2("arr", "keyF", Array(null, Val.False(dummyPos))) {
def evalRhs(arr: Val, keyF: Val, ev: EvalScope, pos: Position): Val = {
uniqArr(pos, ev, sortArr(pos, ev, arr, keyF), keyF)
}
}
private object SetInter extends Val.Builtin3("a", "b", "keyF", Array(null, null, Val.False(dummyPos))) {
def isStr(a: Val.Arr) = a.forall(_.isInstanceOf[Val.Str])
def isNum(a: Val.Arr) = a.forall(_.isInstanceOf[Val.Num])
override def specialize(args: Array[Expr]): (Val.Builtin, Array[Expr]) = args match {
case Array(a: Val.Arr, b) if isStr(a) => (new Spec1Str(a), Array(b))
case Array(a, b: Val.Arr) if isStr(b) => (new Spec1Str(b), Array(a))
case args if args.length == 2 => (Spec2, args)
case _ => null
}
def asArray(a: Val): Array[Lazy] = a match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str => stringChars(pos, str.value).asLazyArray
case _ => Error.fail("Arguments must be either arrays or strings")
}
def evalRhs(_a: Val, _b: Val, _keyF: Val, ev: EvalScope, pos: Position): Val = {
if(_keyF.isInstanceOf[Val.False]) Spec2.evalRhs(_a, _b, ev, pos)
else {
val a = asArray(_a)
val b = asArray(_b)
val keyFFunc = _keyF.asInstanceOf[Val.Func]
val out = new mutable.ArrayBuffer[Lazy]
for (v <- a) {
val appliedX = keyFFunc.apply1(v, pos.noOffset)(ev)
if (b.exists(value => {
val appliedValue = keyFFunc.apply1(value, pos.noOffset)(ev)
ev.equal(appliedValue, appliedX)
}) && !out.exists(value => {
val mValue = keyFFunc.apply1(value, pos.noOffset)(ev)
ev.equal(mValue, appliedX)
})) {
out.append(v)
}
}
sortArr(pos, ev, new Val.Arr(pos, out.toArray), keyFFunc)
}
}
private object Spec2 extends Val.Builtin2("a", "b") {
def evalRhs(_a: Val, _b: Val, ev: EvalScope, pos: Position): Val = {
val a = asArray(_a)
val b = asArray(_b)
val out = new mutable.ArrayBuffer[Lazy](a.length)
for (v <- a) {
val vf = v.force
if (b.exists(value => {
ev.equal(value.force, vf)
}) && !out.exists(value => {
ev.equal(value.force, vf)
})) {
out.append(v)
}
}
sortArr(pos, ev, new Val.Arr(pos, out.toArray), null)
}
}
private class Spec1Str(_a: Val.Arr) extends Val.Builtin1("b") {
private[this] val a =
ArrayOps.sortInPlaceBy(ArrayOps.distinctBy(_a.asLazyArray)(_.asInstanceOf[Val.Str].value))(_.asInstanceOf[Val.Str].value)
// 2.13+: _a.asLazyArray.distinctBy(_.asInstanceOf[Val.Str].value).sortInPlaceBy(_.asInstanceOf[Val.Str].value)
def evalRhs(_b: Val, ev: EvalScope, pos: Position): Val = {
val b = asArray(_b)
val bs = new mutable.HashSet[String]
var i = 0
while(i < b.length) {
b(i).force match {
case s: Val.Str => bs.add(s.value)
case _ =>
}
i += 1
}
val out = new mutable.ArrayBuilder.ofRef[Lazy]
i = 0
while(i < a.length) {
val s = a(i).asInstanceOf[Val.Str]
if(bs.contains(s.value)) out.+=(s)
i += 1
}
new Val.Arr(pos, out.result())
}
}
}
val functions: Map[String, Val.Func] = Map(
"assertEqual" -> AssertEqual,
"toString" -> ToString,
"codepoint" -> Codepoint,
"length" -> Length,
"objectHas" -> ObjectHas,
"objectHasAll" -> ObjectHasAll,
"objectFields" -> ObjectFields,
"objectFieldsAll" -> ObjectFieldsAll,
"objectValues" -> ObjectValues,
"objectValuesAll" -> ObjectValuesAll,
"type" -> Type,
"lines" -> Lines,
"format" -> Format_,
"foldl" -> Foldl,
"foldr" -> Foldr,
"range" -> Range,
builtin("mergePatch", "target", "patch"){ (pos, ev, target: Val, patch: Val) =>
val mergePosition = pos
def createMember(v: => Val) = new Val.Obj.Member(false, Visibility.Unhide) {
def invoke(self: Val.Obj, sup: Val.Obj, fs: FileScope, ev: EvalScope): Val = v
}
def recPair(l: Val, r: Val): Val = (l, r) match{
case (l: Val.Obj, r: Val.Obj) =>
val kvs = for {
k <- (l.visibleKeyNames ++ r.visibleKeyNames).distinct
val lValue = Option(l.valueRaw(k, l, pos)(ev))
val rValue = Option(r.valueRaw(k, r, pos)(ev))
if !rValue.exists(_.isInstanceOf[Val.Null])
} yield (lValue, rValue) match{
case (Some(lChild), None) => k -> createMember{lChild}
case (Some(lChild: Val.Obj), Some(rChild: Val.Obj)) => k -> createMember{recPair(lChild, rChild)}
case (_, Some(rChild)) => k -> createMember{recSingle(rChild)}
}
Val.Obj.mk(mergePosition, kvs:_*)
case (_, _) => recSingle(r)
}
def recSingle(v: Val): Val = v match{
case obj: Val.Obj =>
val kvs = for{
k <- obj.visibleKeyNames
val value = obj.value(k, pos, obj)(ev)
if !value.isInstanceOf[Val.Null]
} yield (k, createMember{recSingle(value)})
Val.Obj.mk(obj.pos, kvs:_*)
case _ => v
}
recPair(target, patch)
},
builtin("sqrt", "x"){ (pos, ev, x: Double) =>
math.sqrt(x)
},
builtin("max", "a", "b"){ (pos, ev, a: Double, b: Double) =>
math.max(a, b)
},
builtin("min", "a", "b"){ (pos, ev, a: Double, b: Double) =>
math.min(a, b)
},
builtin("mod", "a", "b"){ (pos, ev, a: Int, b: Int) =>
a % b
},
builtin("clamp", "x", "minVal", "maxVal"){ (pos, ev, x: Double, minVal: Double, maxVal: Double) =>
math.max(minVal, math.min(x, maxVal))
},
builtin("slice", "indexable", "index", "end", "step"){ (pos, ev, indexable: Val, index: Int, end: Int, step: Int) =>
val res = indexable match {
case Val.Str(pos0, s) => Val.Str(pos, Util.sliceStr(s, index, end, step))
case arr: Val.Arr => new Val.Arr(pos, Util.sliceArr(arr.asLazyArray, index, end, step))
case _ => Error.fail("std.slice first argument must be indexable")
}
res: Val
},
builtin("makeArray", "sz", "func"){ (pos, ev, sz: Int, func: Val.Func) =>
new Val.Arr(
pos,
{
val a = new Array[Lazy](sz)
var i = 0
while(i < sz) {
val forcedI = i
a(i) = () => func.apply1(Val.Num(pos, forcedI), pos.noOffset)(ev)
i += 1
}
a
}
)
},
builtin("pow", "x", "n"){ (pos, ev, x: Double, n: Double) =>
math.pow(x, n)
},
builtin("floor", "x"){ (pos, ev, x: Double) =>
math.floor(x)
},
builtin("ceil", "x"){ (pos, ev, x: Double) =>
math.ceil(x)
},
builtin("abs", "x"){ (pos, ev, x: Double) =>
math.abs(x)
},
builtin("sin", "x"){ (pos, ev, x: Double) =>
math.sin(x)
},
builtin("cos", "x"){ (pos, ev, x: Double) =>
math.cos(x)
},
builtin("tan", "x"){ (pos, ev, x: Double) =>
math.tan(x)
},
builtin("asin", "x"){ (pos, ev, x: Double) =>
math.asin(x)
},
builtin("acos", "x"){ (pos, ev, x: Double) =>
math.acos(x)
},
builtin("atan", "x"){ (pos, ev, x: Double) =>
math.atan(x)
},
builtin("log", "x"){ (pos, ev, x: Double) =>
math.log(x)
},
builtin("exp", "x"){ (pos, ev, x: Double) =>
math.exp(x)
},
builtin("mantissa", "x"){ (pos, ev, x: Double) =>
val value = x
val exponent = (Math.log(value) / Math.log(2)).toInt + 1
val mantissa = value * Math.pow(2.0, -exponent)
mantissa
},
builtin("exponent", "x"){ (pos, ev, x: Double) =>
val value = x
val exponent = (Math.log(value) / Math.log(2)).toInt + 1
//val mantissa = value * Math.pow(2.0, -exponent)
exponent
},
"isString" -> IsString,
"isBoolean" -> IsBoolean,
"isNumber" -> IsNumber,
"isObject" -> IsObject,
"isArray" -> IsArray,
"isFunction" -> IsFunction,
"count" -> Count,
"filter" -> Filter,
"map" -> Map_,
"mapWithKey" -> MapWithKey,
"mapWithIndex" -> MapWithIndex,
builtin("flatMap", "func", "arr"){ (pos, ev, func: Val.Func, arr: Val) =>
val res: Val = arr match {
case a: Val.Arr =>
val arrResults = a.asLazyArray.flatMap {
v => {
val fres = func.apply1(v, pos.noOffset)(ev)
fres match {
case va: Val.Arr => va.asLazyArray
case unknown => Error.fail("flatMap func must return an array, not " + unknown)
}
}
}
new Val.Arr(pos, arrResults)
case s: Val.Str =>
val builder = new StringBuilder()
for (c: Char <- s.value) {
val fres = func.apply1(Val.Str(pos, c.toString), pos.noOffset)(ev)
builder.append(
fres match {
case fstr: Val.Str => fstr.value
case _: Val.Null => ""
case x => Error.fail("flatMap func must return string, got " + fres.asInstanceOf[Val].prettyName)
}
)
}
Val.Str(pos, builder.toString)
}
res
},
builtin("filterMap", "filter_func", "map_func", "arr"){ (pos, ev, filter_func: Val.Func, map_func: Val.Func, arr: Val.Arr) =>
new Val.Arr(
pos,
arr.asLazyArray.flatMap { i =>
i.force
if (!filter_func.apply1(i, pos.noOffset)(ev).isInstanceOf[Val.True]) None
else Some[Lazy](() => map_func.apply1(i, pos.noOffset)(ev))
}
)
},
"find" -> Find,
builtin("findSubstr", "pat", "str") { (pos, ev, pat: String, str: String) =>
if (pat.length == 0) new Val.Arr(pos, emptyLazyArray)
else {
val indices = mutable.ArrayBuffer[Int]()
var matchIndex = str.indexOf(pat)
while (0 <= matchIndex && matchIndex < str.length) {
indices.append(matchIndex)
matchIndex = str.indexOf(pat, matchIndex + 1)
}
new Val.Arr(pos, indices.map(x => Val.Num(pos, x)).toArray)
}
},
"substr" -> Substr,
"startsWith" -> StartsWith,
"endsWith" -> EndsWith,
"char" -> Char_,
"strReplace" -> StrReplace,
"strReplaceAll" -> StrReplaceAll,
builtin("rstripChars", "str", "chars"){ (pos, ev, str: String, chars: String) =>
str.replaceAll("[" + Regex.quote(chars) + "]+$", "")
},
builtin("lstripChars", "str", "chars"){ (pos, ev, str: String, chars: String) =>
str.replaceAll("^[" + Regex.quote(chars) + "]+", "")
},
builtin("stripChars", "str", "chars"){ (pos, ev, str: String, chars: String) =>
str.replaceAll("[" + Regex.quote(chars) + "]+$", "").replaceAll("^[" + Regex.quote(chars) + "]+", "")
},
"join" -> Join,
"member" -> Member,
builtin("repeat", "what", "count"){ (pos, ev, what: Val, count: Int) =>
val res: Val = what match {
case str: Val.Str =>
val builder = new StringBuilder
for (i <- 1 to count) {
builder.append(str.value)
}
Val.Str(pos, builder.toString())
case a: Val.Arr =>
val out = new mutable.ArrayBuffer[Lazy]
for (i <- 1 to count) {
out.appendAll(a.asLazyArray)
}
new Val.Arr(pos, out.toArray)
case x => Error.fail("std.repeat first argument must be an array or a string")
}
res
},
"flattenArrays" -> FlattenArrays,
"reverse" -> Reverse,
builtin("manifestIni", "v"){ (pos, ev, v: Val) =>
val materialized = Materializer(v)(ev)
def render(x: ujson.Value) = x match{
case ujson.Str(v) => v
case ujson.Num(v) => RenderUtils.renderDouble(v)
case ujson.Bool(v) => v.toString
case ujson.Null => "null"
case _ => x.transform(new sjsonnet.Renderer())
}
def sect(x: ujson.Obj) = {
x.value.flatMap{
case (k, ujson.Arr(vs)) => vs.map(x => k + " = " + render(x))
case (k, v) => Seq(k + " = " + render(v))
}
}
val lines = materialized.obj.get("main").fold(Iterable[String]())(x => sect(x.asInstanceOf[ujson.Obj])) ++
materialized.obj.get("sections").fold(Iterable[String]())(x =>
x.obj.flatMap{case (k, v) => Seq("[" + k + "]") ++ sect(v.asInstanceOf[ujson.Obj])}
)
lines.flatMap(Seq(_, "\n")).mkString
},
builtin("escapeStringJson", "str"){ (pos, ev, str: String) =>
val out = new StringWriter()
BaseRenderer.escape(out, str, unicode = true)
out.toString
},
builtin("escapeStringBash", "str"){ (pos, ev, str: String) =>
"'" + str.replace("'", """'"'"'""") + "'"
},
builtin("escapeStringDollars", "str"){ (pos, ev, str: String) =>
str.replace("$", "$$")
},
builtin("manifestPython", "v"){ (pos, ev, v: Val) =>
Materializer.apply0(v, new PythonRenderer())(ev).toString
},
"manifestJson" -> ManifestJson,
"manifestJsonMinified" -> ManifestJsonMinified,
"manifestJsonEx" -> ManifestJsonEx,
builtinWithDefaults("manifestYamlDoc",
"v" -> null,
"indent_array_in_object" -> Val.False(dummyPos)){ (args, pos, ev) =>
val v = args(0)
val indentArrayInObject = args(1) match {
case Val.False(_) => false
case Val.True(_) => true
case _ => Error.fail("indent_array_in_object has to be a boolean, got" + v.getClass)
}
Materializer.apply0(
v,
new YamlRenderer(indentArrayInObject = indentArrayInObject)
)(ev).toString
},
builtinWithDefaults("manifestYamlStream",
"v" -> null,
"indent_array_in_object" -> Val.False(dummyPos)){ (args, pos, ev) =>
val v = args(0)
val indentArrayInObject = args(1) match {
case Val.False(_) => false
case Val.True(_) => true
case _ => Error.fail("indent_array_in_object has to be a boolean, got" + v.getClass)
}
v match {
case arr: Val.Arr => arr.asLazyArray
.map { item =>
Materializer.apply0(
item.force,
new YamlRenderer(indentArrayInObject = indentArrayInObject)
)(ev).toString()
}
.mkString("---\n", "\n---\n", "\n...\n")
case _ => Error.fail("manifestYamlStream only takes arrays, got " + v.getClass)
}
},
builtin("manifestPythonVars", "v"){ (pos, ev, v: Val.Obj) =>
Materializer(v)(ev).obj
.map{case (k, v) => k + " = " + v.transform(new PythonRenderer()).toString + "\n"}
.mkString
},
builtin("manifestXmlJsonml", "value"){ (pos, ev, value: Val) =>
import scalatags.Text.all.{value => _, _}
def rec(v: ujson.Value): Frag = {
v match {
case ujson.Str(s) => s
case ujson.Arr(mutable.Seq(ujson.Str(t), attrs: ujson.Obj, children@_*)) =>
tag(t)(
attrs.value.map {
case (k, ujson.Str(v)) => attr(k) := v
// use ujson.write to make sure output number format is same as
// google/jsonnet, e.g. whole numbers are printed without the
// decimal point and trailing zero
case (k, ujson.Num(v)) => attr(k) := ujson.write(v)
case (k, v) => Error.fail("Cannot call manifestXmlJsonml on " + v.getClass)
}.toSeq,
children.map(rec)
)
case ujson.Arr(mutable.Seq(ujson.Str(t), children@_*)) =>
tag(t)(children.map(rec).toSeq)
case x =>
Error.fail("Cannot call manifestXmlJsonml on " + x.getClass)
}
}
rec(Materializer(value)(ev)).render
},
builtin("base64", "v"){ (pos, ev, v: Val) =>
v match{
case Val.Str(_, value) => Base64.getEncoder().encodeToString(value.getBytes)
case arr: Val.Arr => Base64.getEncoder().encodeToString(arr.iterator.map(_.cast[Val.Num].value.toByte).toArray)
case x => Error.fail("Cannot base64 encode " + x.prettyName)
}
},
builtin("base64Decode", "s"){ (pos, ev, s: String) =>
new String(Base64.getDecoder().decode(s))
},
builtin("base64DecodeBytes", "s"){ (pos, ev, s: String) =>
new Val.Arr(pos, Base64.getDecoder().decode(s).map(i => Val.Num(pos, i)))
},
builtin("gzip", "v"){ (pos, ev, v: Val) =>
v match{
case Val.Str(_, value) => Platform.gzipString(value)
case arr: Val.Arr => Platform.gzipBytes(arr.iterator.map(_.cast[Val.Num].value.toByte).toArray)
case x => Error.fail("Cannot gzip encode " + x.prettyName)
}
},
builtinWithDefaults("xz", "v" -> null, "compressionLevel" -> Val.Null(dummyPos)){ (args, pos, ev) =>
val compressionLevel: Option[Int] = args(1) match {
case Val.Null(_) =>
// Use default compression level if the user didn't set one
None
case Val.Num(_, n) =>
Some(n.toInt)
case x =>
Error.fail("Cannot xz encode with compression level " + x.prettyName)
}
args(0) match {
case Val.Str(_, value) => Platform.xzString(value, compressionLevel)
case arr: Val.Arr => Platform.xzBytes(arr.iterator.map(_.cast[Val.Num].value.toByte).toArray, compressionLevel)
case x => Error.fail("Cannot xz encode " + x.prettyName)
}
},
"encodeUTF8" -> EncodeUTF8,
"decodeUTF8" -> DecodeUTF8,
builtinWithDefaults("uniq", "arr" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
uniqArr(pos, ev, args(0), args(1))
},
builtinWithDefaults("sort", "arr" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
sortArr(pos, ev, args(0), args(1))
},
"set" -> Set_,
builtinWithDefaults("setUnion", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
val a = args(0) match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str => stringChars(pos, str.value).asLazyArray
case _ => Error.fail("Arguments must be either arrays or strings")
}
val b = args(1) match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str => stringChars(pos, str.value).asLazyArray
case _ => Error.fail("Arguments must be either arrays or strings")
}
val concat = new Val.Arr(pos, a ++ b)
uniqArr(pos, ev, sortArr(pos, ev, concat, args(2)), args(2))
},
"setInter" -> SetInter,
builtinWithDefaults("setDiff", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
val a = args(0) match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str => stringChars(pos, str.value).asLazyArray
case _ => Error.fail("Arguments must be either arrays or strings")
}
val b = args(1) match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str => stringChars(pos, str.value).asLazyArray
case _ => Error.fail("Arguments must be either arrays or strings")
}
val keyF = args(2)
val out = new mutable.ArrayBuffer[Lazy]
for (v <- a) {
if (keyF.isInstanceOf[Val.False]) {
val vf = v.force
if (!b.exists(value => {
ev.equal(value.force, vf)
}) && !out.exists(value => {
ev.equal(value.force, vf)
})) {
out.append(v)
}
} else {
val keyFFunc = keyF.asInstanceOf[Val.Func]
val appliedX = keyFFunc.apply1(v, pos.noOffset)(ev)
if (!b.exists(value => {
val appliedValue = keyFFunc.apply1(value, pos.noOffset)(ev)
ev.equal(appliedValue, appliedX)
}) && !out.exists(value => {
val mValue = keyFFunc.apply1(value, pos.noOffset)(ev)
ev.equal(mValue, appliedX)
})) {
out.append(v)
}
}
}
sortArr(pos, ev, new Val.Arr(pos, out.toArray), keyF)
},
builtinWithDefaults("setMember", "x" -> null, "arr" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
val keyF = args(2)
if (keyF.isInstanceOf[Val.False]) {
val ujson.Arr(mArr) = Materializer(args(1))(ev)
val mx = Materializer(args(0))(ev)
mArr.contains(mx)
} else {
val arr = args(1).asInstanceOf[Val.Arr].asLazyArray
val keyFFunc = keyF.asInstanceOf[Val.Func]
val appliedX = keyFFunc.apply1(args(0), pos.noOffset)(ev)
arr.exists(value => {
val appliedValue = keyFFunc.apply1(value, pos.noOffset)(ev)
ev.equal(appliedValue, appliedX)
})
}
},
"split" -> Split,
"splitLimit" -> SplitLimit,
"stringChars" -> StringChars,
"parseInt" -> ParseInt,
"parseOctal" -> ParseOctal,
"parseHex" -> ParseHex,
"parseJson" -> ParseJson,
"parseYaml" -> ParseYaml,
"md5" -> MD5,
builtin("prune", "x"){ (pos, ev, s: Val) =>
def filter(x: Val) = x match{
case c: Val.Arr if c.length == 0 => false
case c: Val.Obj if c.visibleKeyNames.length == 0 => false
case Val.Null(_) => false
case _ => true
}
def rec(x: Val): Val = x match{
case o: Val.Obj =>
val bindings = for{
k <- o.visibleKeyNames
v = rec(o.value(k, pos.fileScope.noOffsetPos)(ev))
if filter(v)
}yield (k, new Val.Obj.ConstMember(false, Visibility.Normal, v))
Val.Obj.mk(pos, bindings: _*)
case a: Val.Arr =>
new Val.Arr(pos, a.asStrictArray.map(rec).filter(filter).map(identity))
case _ => x
}
rec(s)
},
"asciiUpper" -> AsciiUpper,
"asciiLower" -> AsciiLower,
"trace" -> Trace,
"extVar" -> ExtVar,
builtinWithDefaults("get", "o" -> null, "f" -> null, "default" -> Val.Null(dummyPos), "inc_hidden" -> Val.True(dummyPos)) { (args, pos, ev) =>
val obj = args(0).asObj
val k = args(1).asString
val incHidden = args(3).asBoolean
if (incHidden && obj.containsKey(k)) {
obj.value(k, pos.noOffset, obj)(ev)
} else if (!incHidden && obj.containsVisibleKey(k)) {
obj.value(k, pos.noOffset, obj)(ev)
} else {
args(2)
}
},
"all" -> All,
"any" -> Any
)
val Std = Val.Obj.mk(
null,
functions.toSeq
.map{
case (k, v) =>
(
k,
new Val.Obj.ConstMember(false, Visibility.Hidden, v)
)
} ++ Seq(
(
"thisFile",
new Val.Obj.Member(false, Visibility.Hidden, cached = false) {
def invoke(self: Val.Obj, sup: Val.Obj, fs: FileScope, ev: EvalScope): Val =
Val.Str(self.pos, fs.currentFile.relativeToString(ev.wd))
}
)
): _*
)
def builtin[R: ReadWriter, T1: ReadWriter](name: String, p1: String)
(eval: (Position, EvalScope, T1) => R): (String, Val.Func) = {
(name, new Val.Builtin1(p1) {
def evalRhs(arg1: Val, ev: EvalScope, outerPos: Position): Val = {
//println("--- calling builtin: "+name)
val v1: T1 = implicitly[ReadWriter[T1]].apply(arg1)
implicitly[ReadWriter[R]].write(outerPos, eval(outerPos, ev, v1))
}
})
}
def builtin[R: ReadWriter, T1: ReadWriter, T2: ReadWriter](name: String, p1: String, p2: String)
(eval: (Position, EvalScope, T1, T2) => R): (String, Val.Func) = {
(name, new Val.Builtin2(p1, p2) {
def evalRhs(arg1: Val, arg2: Val, ev: EvalScope, outerPos: Position): Val = {
//println("--- calling builtin: "+name)
val v1: T1 = implicitly[ReadWriter[T1]].apply(arg1)
val v2: T2 = implicitly[ReadWriter[T2]].apply(arg2)
implicitly[ReadWriter[R]].write(outerPos, eval(outerPos, ev, v1, v2))
}
})
}
def builtin[R: ReadWriter, T1: ReadWriter, T2: ReadWriter, T3: ReadWriter](name: String, p1: String, p2: String, p3: String)
(eval: (Position, EvalScope, T1, T2, T3) => R): (String, Val.Func) = {
(name, new Val.Builtin3(p1, p2, p3) {
def evalRhs(arg1: Val, arg2: Val, arg3: Val, ev: EvalScope, outerPos: Position): Val = {
//println("--- calling builtin: "+name)
val v1: T1 = implicitly[ReadWriter[T1]].apply(arg1)
val v2: T2 = implicitly[ReadWriter[T2]].apply(arg2)
val v3: T3 = implicitly[ReadWriter[T3]].apply(arg3)
implicitly[ReadWriter[R]].write(outerPos, eval(outerPos, ev, v1, v2, v3))
}
})
}
def builtin[R: ReadWriter, T1: ReadWriter, T2: ReadWriter, T3: ReadWriter, T4: ReadWriter]
(name: String, p1: String, p2: String, p3: String, p4: String)
(eval: (Position, EvalScope, T1, T2, T3, T4) => R): (String, Val.Func) = {
(name, new Val.Builtin4(p1, p2, p3, p4) {
def evalRhs(arg1: Val, arg2: Val, arg3: Val, arg4: Val, ev: EvalScope, outerPos: Position): Val = {
//println("--- calling builtin: "+name)
val v1: T1 = implicitly[ReadWriter[T1]].apply(arg1)
val v2: T2 = implicitly[ReadWriter[T2]].apply(arg2)
val v3: T3 = implicitly[ReadWriter[T3]].apply(arg3)
val v4: T4 = implicitly[ReadWriter[T4]].apply(arg4)
implicitly[ReadWriter[R]].write(outerPos, eval(outerPos, ev, v1, v2, v3, v4))
}
})
}
/**
* Helper function that can define a built-in function with default parameters
*
* Arguments of the eval function are (args, ev)
*/
def builtinWithDefaults[R: ReadWriter](name: String, params: (String, Val.Literal)*)
(eval: (Array[Val], Position, EvalScope) => R): (String, Val.Func) = {
val indexedParamKeys = params.zipWithIndex.map{case ((k, v), i) => (k, i)}.toArray
name -> new Val.Builtin(params.map(_._1).toArray, params.map(_._2).toArray) {
def evalRhs(args: Array[Val], ev: EvalScope, pos: Position): Val =
implicitly[ReadWriter[R]].write(pos, eval(args, pos, ev))
}
}
def uniqArr(pos: Position, ev: EvalScope, arr: Val, keyF: Val) = {
val arrValue = arr match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str => stringChars(pos, str.value).asLazyArray
case _ => Error.fail("Argument must be either array or string")
}
val out = new mutable.ArrayBuffer[Lazy]
for (v <- arrValue) {
if (out.isEmpty) {
out.append(v)
} else if (keyF.isInstanceOf[Val.False]) {
if (!ev.equal(out.last.force, v.force)) {
out.append(v)
}
} else if (!keyF.isInstanceOf[Val.False]) {
val keyFFunc = keyF.asInstanceOf[Val.Func]
val o1Key = keyFFunc.apply1(v, pos.noOffset)(ev)
val o2Key = keyFFunc.apply1(out.last, pos.noOffset)(ev)
val o1KeyExpr = Materializer.toExpr(Materializer.apply(o1Key)(ev))(ev)
val o2KeyExpr = Materializer.toExpr(Materializer.apply(o2Key)(ev))(ev)
val comparisonExpr = Expr.BinaryOp(dummyPos, o1KeyExpr, BinaryOp.OP_!=, o2KeyExpr)
val exprResult = ev.visitExpr(comparisonExpr)(ValScope.empty)
val res = Materializer.apply(exprResult)(ev).asInstanceOf[ujson.Bool]
if (res.value) {
out.append(v)
}
}
}
new Val.Arr(pos, out.toArray)
}
def sortArr(pos: Position, ev: EvalScope, arr: Val, keyF: Val) = {
arr match{
case vs: Val.Arr =>
new Val.Arr(
pos,
if (vs.forall(_.isInstanceOf[Val.Str])){
vs.asStrictArray.map(_.cast[Val.Str]).sortBy(_.value)
}else if (vs.forall(_.isInstanceOf[Val.Num])) {
vs.asStrictArray.map(_.cast[Val.Num]).sortBy(_.value)
}else if (vs.forall(_.isInstanceOf[Val.Obj])){
if (keyF == null || keyF.isInstanceOf[Val.False]) {
Error.fail("Unable to sort array of objects without key function")
} else {
val objs = vs.asStrictArray.map(_.cast[Val.Obj])
val keyFFunc = keyF.asInstanceOf[Val.Func]
val keys = objs.map((v) => keyFFunc(Array(v), null, pos.noOffset)(ev))
if (keys.forall(_.isInstanceOf[Val.Str])){
objs.sortBy((v) => keyFFunc(Array(v), null, pos.noOffset)(ev).cast[Val.Str].value)
} else if (keys.forall(_.isInstanceOf[Val.Num])) {
objs.sortBy((v) => keyFFunc(Array(v), null, pos.noOffset)(ev).cast[Val.Num].value)
} else {
Error.fail("Cannot sort with key values that are " + keys(0).prettyName + "s")
}
}
}else {
???
}
)
case Val.Str(pos, s) => new Val.Arr(pos, s.sorted.map(c => Val.Str(pos, c.toString)).toArray)
case x => Error.fail("Cannot sort " + x.prettyName)
}
}
def stringChars(pos: Position, str: String): Val.Arr = {
val a = new Array[Lazy](str.length)
var i = 0
while(i < a.length) {
a(i) = new Val.Str(pos, String.valueOf(str.charAt(i)))
i += 1
}
new Val.Arr(pos, a)
}
def getVisibleKeys(ev: EvalScope, v1: Val.Obj): Array[String] =
maybeSortKeys(ev, v1.visibleKeyNames)
def getAllKeys(ev: EvalScope, v1: Val.Obj): Array[String] =
maybeSortKeys(ev, v1.allKeyNames)
@inline private[this] def maybeSortKeys(ev: EvalScope, keys: Array[String]): Array[String] =
if(ev.settings.preserveOrder) keys else keys.sorted
def getObjValuesFromKeys(pos: Position, ev: EvalScope, v1: Val.Obj, keys: Array[String]): Val.Arr =
new Val.Arr(pos, keys.map { k =>
(() => v1.value(k, pos.noOffset)(ev)): Lazy
})
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy