
sjsonnet.Std.scala Maven / Gradle / Ivy
The newest version!
package sjsonnet
import java.io.StringWriter
import java.nio.charset.StandardCharsets.UTF_8
import java.util.Base64
import java.util
import sjsonnet.Expr.Member.Visibility
import scala.collection.Searching._
import scala.collection.mutable
/**
* 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 additionalNativeFunctions: Map[String, Val.Builtin] = Map.empty) {
private val dummyPos: Position = new Position(null, 0)
private val emptyLazyArray = new Array[Lazy](0)
private val leadingWhiteSpacePattern = Platform.getPatternFromCache("^[ \t\n\f\r\u0085\u00A0']+")
private val trailingWhiteSpacePattern = Platform.getPatternFromCache("[ \t\n\f\r\u0085\u00A0']+$")
private val builtinNativeFunctions = Map(
builtin("gzip", "v"){ (_, _, 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)
}
},
) ++ StdRegex.functions
require(builtinNativeFunctions.forall(k => !additionalNativeFunctions.contains(k._1)), "Conflicting native functions")
private val nativeFunctions = builtinNativeFunctions ++ additionalNativeFunctions
private object AssertEqual extends Val.Builtin2("assertEqual", "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("toString", "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("length", "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("length", "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("codepoint", "str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, str.asString.charAt(0).toLong)
}
private object ObjectHas extends Val.Builtin2("objectHas", "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("objectHas", "o") {
def evalRhs(o: Val, ev: EvalScope, pos: Position): Val =
Val.bool(pos, o.asObj.containsVisibleKey(f))
}
}
private object ObjectHasAll extends Val.Builtin2("objectHasAll", "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("objectHasAll", "o") {
def evalRhs(o: Val, ev: EvalScope, pos: Position): Val =
Val.bool(pos, o.asObj.containsKey(f))
}
}
private object ObjectFields extends Val.Builtin1("objectFields", "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("objectFieldsAll", "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("all", "arr") {
def evalRhs(arr: Val, ev: EvalScope, pos: Position): Val = {
Val.bool(pos, arr.asArr.forall(v => v.asBoolean))
}
}
private object Get extends Val.Builtin("get", Array("o", "f", "default", "inc_hidden"), Array(null, null, Val.Null(dummyPos), Val.True(dummyPos))) {
override def evalRhs(args: Array[_ <: Lazy], ev: EvalScope, pos: Position): Val = {
val obj = args(0).force.asObj
val k = args(1).force.asString
val incHidden = args(3).force.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).force
}
}
}
private object MinArray extends Val.Builtin("minArray", Array("arr", "keyF", "onEmpty"), Array(null, Val.False(dummyPos), Val.False(dummyPos))) {
override def evalRhs(args: Array[_ <: Lazy], ev: EvalScope, pos: Position): Val = {
val arr = args(0).force.asArr
val keyF = args(1).force
val onEmpty = args(2)
if (arr.length == 0) {
if (onEmpty.isInstanceOf[Val.False]) {
Error.fail("Expected at least one element in array. Got none")
} else {
onEmpty.force
}
} else if (keyF.isInstanceOf[Val.False]) {
arr.asStrictArray.min(ev)
} else {
arr.asStrictArray.map(v => keyF.asInstanceOf[Val.Func].apply1(v, pos.fileScope.noOffsetPos)(ev)).min(ev)
}
}
}
private object MaxArray extends Val.Builtin("maxArray", Array("arr", "keyF", "onEmpty"), Array(null, Val.False(dummyPos), Val.False(dummyPos))) {
override def evalRhs(args: Array[_ <: Lazy], ev: EvalScope, pos: Position): Val = {
val arr = args(0).force.asArr
val keyF = args(1).force
val onEmpty = args(2)
if (arr.length == 0) {
if (onEmpty.isInstanceOf[Val.False]) {
Error.fail("Expected at least one element in array. Got none")
} else {
onEmpty.force
}
} else if (keyF.isInstanceOf[Val.False]) {
arr.asStrictArray.max(ev)
} else {
arr.asStrictArray.map(v => keyF.asInstanceOf[Val.Func].apply1(v, pos.fileScope.noOffsetPos)(ev)).max(ev)
}
}
}
private object Any extends Val.Builtin1("any", "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("type", "x") {
def evalRhs(x: Val, ev: EvalScope, pos: Position): Val = Val.Str(pos, x.prettyName)
}
private object Format_ extends Val.Builtin2("format", "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("foldl", "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
case _ => Error.fail("Cannot call foldl on " + arr.prettyName)
}
}
}
private object Foldr extends Val.Builtin3("foldr", "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
case _ => Error.fail("Cannot call foldr on " + arr.prettyName)
}
}
}
private object IsString extends Val.Builtin1("isString", "v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Str])
}
private object IsBoolean extends Val.Builtin1("isBoolean", "v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Bool])
}
private object IsNumber extends Val.Builtin1("isNumber", "v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Num])
}
private object IsObject extends Val.Builtin1("isObject", "v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Obj])
}
private object IsArray extends Val.Builtin1("isArray", "v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Arr])
}
private object IsFunction extends Val.Builtin1("isFunction", "v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val = Val.bool(pos, v.isInstanceOf[Val.Func])
}
private object Count extends Val.Builtin2("count", "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("filter", "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("map", "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("mapWithKey", "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 = Util.preSizedJavaLinkedHashMap[String, Val.Obj.Member](allKeys.length)
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
}
val valueCache = Val.Obj.getEmptyValueCacheForObjWithoutSuper(allKeys.length)
new Val.Obj(pos, m, false, null, null, valueCache)
}
}
private object MapWithIndex extends Val.Builtin2("mapWithIndex", "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("find", "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("encodeUTF8", "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("decodeUTF8", "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("substr", "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("startsWith", "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("endsWith", "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("char", "n") {
def evalRhs(n: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, n.asLong.toChar.toString)
}
private object StrReplace extends Val.Builtin3("strReplace", "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("strReplaceAll", "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(from.value), Array(str, to)) } catch { case _: Exception => null }
case _ => null
}
private class SpecFrom(from: String) extends Val.Builtin2("strReplaceAll", "str", "to") {
private[this] val pattern = Platform.getPatternFromCache(from)
def evalRhs(str: Val, to: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, pattern.matcher(str.asString).replaceAll(to.asString))
}
}
private object StripUtils {
private def getLeadingPattern(chars: String): String = "^[" + Platform.regexQuote(chars) + "]+"
private def getTrailingPattern(chars: String): String = "[" + Platform.regexQuote(chars) + "]+$"
def unspecializedStrip(str: String, chars: String, left: Boolean, right: Boolean): String = {
var s = str
if (right) s = Platform.getPatternFromCache(getTrailingPattern(chars)).matcher(s).replaceAll("")
if (left) s = Platform.getPatternFromCache(getLeadingPattern(chars)).matcher(s).replaceAll("")
s
}
private class SpecStrip(
chars: String,
left: Boolean,
right: Boolean,
functionName: String
) extends Val.Builtin1(functionName, "str") {
private[this] val leftPattern = Platform.getPatternFromCache(getLeadingPattern(chars))
private[this] val rightPattern = Platform.getPatternFromCache(getTrailingPattern(chars))
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val = {
var s = str.asString
if (right) s = rightPattern.matcher(s).replaceAll("")
if (left) s = leftPattern.matcher(s).replaceAll("")
Val.Str(pos, s)
}
}
def trySpecialize(str: Expr, chars: Val.Str, left: Boolean, right: Boolean, name: String): (Val.Builtin, Array[Expr]) = {
try {
(new SpecStrip(chars.value, left, right, name), Array(str))
} catch {
case _: Exception => null
}
}
}
object StripChars extends Val.Builtin2("stripChars", "str", "chars") {
def evalRhs(str: Val, chars: Val, ev: EvalScope, pos: Position): Val = {
Val.Str(pos, StripUtils.unspecializedStrip(str.asString, chars.asString, left = true, right = true))
}
override def specialize(args: Array[Expr]): (Val.Builtin, Array[Expr]) = args match {
case Array(str, chars: Val.Str) =>
StripUtils.trySpecialize(str, chars, left = true, right = true, functionName)
case _ => null
}
}
object LStripChars extends Val.Builtin2("lstripChars", "str", "chars") {
def evalRhs(str: Val, chars: Val, ev: EvalScope, pos: Position): Val = {
Val.Str(pos, StripUtils.unspecializedStrip(str.asString, chars.asString, left = true, right = false))
}
override def specialize(args: Array[Expr]): (Val.Builtin, Array[Expr]) = args match {
case Array(str, chars: Val.Str) =>
StripUtils.trySpecialize(str, chars, left = true, right = false, functionName)
case _ => null
}
}
object RStripChars extends Val.Builtin2("rstripChars", "str", "chars") {
def evalRhs(str: Val, chars: Val, ev: EvalScope, pos: Position): Val = {
Val.Str(pos, StripUtils.unspecializedStrip(str.asString, chars.asString, left = false, right = true))
}
override def specialize(args: Array[Expr]): (Val.Builtin, Array[Expr]) = args match {
case Array(str, chars: Val.Str) =>
StripUtils.trySpecialize(str, chars, left = false, right = true, functionName)
case _ => null
}
}
private object Join extends Val.Builtin2("join", "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("member", "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 =>
a.asLazyArray.indexWhere(v => ev.equal(v.force, x)) >= 0
case _ => Error.fail("std.member first argument must be an array or a string, got " + arr.prettyName)
})
}
}
private object FlattenArrays extends Val.Builtin1("flattenArrays", "arrs") {
def evalRhs(arrs: Val, ev: EvalScope, pos: Position): Val = {
val out = new mutable.ArrayBuilder.ofRef[Lazy]
for(x <- arrs.asArr) {
x match{
case Val.Null(_) => // do nothing
case v: Val.Arr => out ++= v.asLazyArray
case x => Error.fail("Cannot call flattenArrays on " + x)
}
}
new Val.Arr(pos, out.result())
}
}
private object FlattenDeepArrays extends Val.Builtin1("flattenDeepArray", "value") {
def evalRhs(value: Val, ev: EvalScope, pos: Position): Val = {
val out = new mutable.ArrayBuilder.ofRef[Lazy]
val q = new java.util.ArrayDeque[Lazy]()
value.asArr.asLazyArray.foreach(q.add)
while (!q.isEmpty) {
q.removeFirst().force match {
case v: Val.Arr => v.asLazyArray.reverseIterator.foreach(q.push)
case x => out += x
}
}
new Val.Arr(pos, out.result())
}
}
private object DeepJoin extends Val.Builtin1("deepJoin", "arr") {
def evalRhs(value: Val, ev: EvalScope, pos: Position): Val = {
val out = new StringWriter()
val q = new java.util.ArrayDeque[Lazy]()
q.add(value)
while (!q.isEmpty) {
q.removeFirst().force match {
case v: Val.Arr => v.asLazyArray.reverseIterator.foreach(q.push)
case s: Val.Str => out.write(s.value)
}
}
Val.Str(pos, out.toString)
}
}
private object Reverse extends Val.Builtin1("reverse", "arrs") {
def evalRhs(arrs: Val, ev: EvalScope, pos: Position): Val = {
new Val.Arr(pos, arrs.asArr.asLazyArray.reverse)
}
}
private def splitLimit(pos: Position, str: String, cStr: String, maxSplits: Int): Array[Lazy] = {
val b = new mutable.ArrayBuilder.ofRef[Lazy]
var sz = 0
var i = 0
var start = 0
while (i <= str.length - cStr.length && (maxSplits < 0 || sz < maxSplits)) {
if (str.startsWith(cStr, i)) {
val finalStr = Val.Str(pos, str.substring(start, i))
b.+=(finalStr)
start = i + cStr.length
sz += 1
i += cStr.length
} else {
i += 1
}
}
b.+=(Val.Str(pos, str.substring(start)))
sz += 1
b.result()
}
private object Split extends Val.Builtin2("split", "str", "c") {
def evalRhs(str: Val, c: Val, ev: EvalScope, pos: Position): Val = {
new Val.Arr(pos, splitLimit(pos, str.asString, c.asString, -1))
}
}
private object SplitLimit extends Val.Builtin3("splitLimit", "str", "c", "maxSplits") {
def evalRhs(str: Val, c: Val, maxSplits: Val, ev: EvalScope, pos: Position): Val = {
new Val.Arr(pos, splitLimit(pos, str.asString, c.asString, maxSplits.asInt))
}
}
private object SplitLimitR extends Val.Builtin3("splitLimitR", "str", "c", "maxSplits") {
def evalRhs(str: Val, c: Val, maxSplits: Val, ev: EvalScope, pos: Position): Val = {
new Val.Arr(pos, splitLimit(pos, str.asString.reverse, c.asString.reverse, maxSplits.asInt)
.map(s => Val.Str(pos, s.force.asString.reverse)).reverse)
}
}
private object StringChars extends Val.Builtin1("stringChars", "str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
stringChars(pos, str.asString)
}
private object ParseInt extends Val.Builtin1("parseInt", "str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, str.asString.toLong)
}
private object ParseOctal extends Val.Builtin1("parseOctal", "str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, java.lang.Long.parseLong(str.asString, 8))
}
private object ParseHex extends Val.Builtin1("parseHex", "str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Num(pos, java.lang.Long.parseLong(str.asString, 16))
}
private object MD5 extends Val.Builtin1("md5", "s") {
def evalRhs(s: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, Platform.md5(s.asString))
}
private object AsciiUpper extends Val.Builtin1("asciiUpper", "str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, str.asString.toUpperCase)
}
private object AsciiLower extends Val.Builtin1("asciiLower", "str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, str.asString.toLowerCase)
}
private object Trace extends Val.Builtin2("trace", "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("extVar", "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("objectValues", "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("objectValuesAll", "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("lines", "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("range", "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("manifestJson", "v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, Materializer.apply0(v, MaterializeJsonRenderer())(ev).toString)
}
private object ManifestJsonMinified extends Val.Builtin1("manifestJsonMinified", "v") {
def evalRhs(v: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, Materializer.apply0(v, new MaterializeJsonRenderer(indent = -1, newline = "", keyValueSeparator = ":"))(ev).toString)
}
private object ManifestJsonEx extends Val.Builtin4("manifestJsonEx", "value", "indent", "newline", "key_val_sep", Array(null, null, Val.Str(dummyPos, "\n"), Val.Str(dummyPos, ": "))) {
def evalRhs(v: Val, i: Val, newline: Val, keyValSep: Val, ev: EvalScope, pos: Position): Val =
Val.Str(pos, Materializer
.apply0(v, MaterializeJsonRenderer(indent = i.asString.length, newline = newline.asString, keyValueSeparator = keyValSep.asString))(ev)
.toString)
}
private object ParseJson extends Val.Builtin1("parseJson", "str") {
def evalRhs(str: Val, ev: EvalScope, pos: Position): Val =
ujson.StringParser.transform(str.asString, new ValVisitor(pos))
}
private object ParseYaml extends Val.Builtin1("parseYaml", "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 ManifestTomlEx extends Val.Builtin2("manifestTomlEx", "value", "indent") {
private def isTableArray(v: Val) = v match {
case s: Val.Arr => s.length > 0 && s.asLazyArray.forall(_.isInstanceOf[Val.Obj])
case _ => false
}
private def isSection(v: Val) = v.isInstanceOf[Val.Obj] || isTableArray(v)
private def renderTableInternal(out: StringWriter, v: Val.Obj, cumulatedIndent: String, indent: String, path: Seq[String], indexedPath: Seq[String])(implicit ev : EvalScope): StringWriter = {
val (sections, nonSections) = v.visibleKeyNames.partition(k => isSection(v.value(k, v.pos)(ev)))
for (k <- nonSections.sorted) {
out.write(cumulatedIndent)
out.write(TomlRenderer.escapeKey(k))
out.write(" = ")
Materializer.apply0(v.value(k, v.pos)(ev), new TomlRenderer(out, cumulatedIndent, indent))(ev)
}
out.write('\n')
for (k <- sections.sorted) {
val v0 = v.value(k, v.pos, v)(ev)
if (isTableArray(v0)) {
for (i <- 0 until v0.asArr.length) {
out.write(cumulatedIndent)
renderTableArrayHeader(out, path :+ k)
out.write('\n')
renderTableInternal(out, v0.asArr.force(i).asObj, cumulatedIndent + indent, indent, path :+ k,
indexedPath ++ Seq(k, i.toString))
}
} else {
out.write(cumulatedIndent)
renderTableHeader(out, path :+ k)
out.write('\n')
renderTableInternal(out, v0.asObj, cumulatedIndent + indent, indent, path :+ k, indexedPath :+ k)
}
}
out
}
private def renderTableHeader(out: StringWriter, path: Seq[String]) = {
out.write('[')
out.write(path.map(TomlRenderer.escapeKey).mkString("."))
out.write(']')
out
}
private def renderTableArrayHeader(out: StringWriter, path: Seq[String]) = {
out.write('[')
renderTableHeader(out, path)
out.write(']')
out
}
def evalRhs(v: Val, indent: Val, ev: EvalScope, pos: Position): Val = {
val out = new StringWriter
renderTableInternal(out, v.force.asObj, "", indent.asString, Seq.empty[String], Seq.empty[String])(ev)
Val.Str(pos, out.toString.strip)
}
}
private object Set_ extends Val.Builtin2("set", "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)
}
}
val functions: Map[String, Val.Func] = Map(
builtin(AssertEqual),
builtin(ToString),
builtin(Codepoint),
builtin(Length),
builtin(ObjectHas),
builtin(ObjectHasAll),
builtin(ObjectFields),
builtin(ObjectFieldsAll),
builtin(ObjectValues),
builtin(ObjectValuesAll),
builtin(Type),
builtin(Lines),
builtin(Format_),
builtin(Foldl),
builtin(Foldr),
builtin(Range),
builtin("mergePatch", "target", "patch"){ (pos, ev, target: Val, patch: Val) =>
val mergePosition = pos
def createLazyMember(v: => Val) = new Val.Obj.Member(false, Visibility.Normal) {
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 keys: Array[String] = distinctKeys(l.visibleKeyNames, r.visibleKeyNames)
val kvs: Array[(String, Val.Obj.Member)] = new Array[(String, Val.Obj.Member)](keys.length)
var kvsIdx = 0
var i = 0
while (i < keys.length) {
val key = keys(i)
val lValue = if (l.containsVisibleKey(key)) l.valueRaw(key, l, pos)(ev) else null
val rValue = if (r.containsVisibleKey(key)) r.valueRaw(key, r, pos)(ev) else null
if (!rValue.isInstanceOf[Val.Null]) { // if we are not removing the key
if (lValue != null && rValue == null) {
// Preserve the LHS/target value:
kvs(kvsIdx) = (key, new Val.Obj.ConstMember(false, Visibility.Normal, lValue))
} else if (lValue.isInstanceOf[Val.Obj] && rValue.isInstanceOf[Val.Obj]) {
// Recursively merge objects:
kvs(kvsIdx) = (key, createLazyMember(recPair(lValue, rValue)))
} else if (rValue != null) {
// Use the RHS/patch value and recursively remove Null or hidden fields:
kvs(kvsIdx) = (key, createLazyMember(recSingle(rValue)))
} else {
Error.fail("std.mergePatch: This should never happen")
}
kvsIdx += 1
}
i += 1
}
val trimmedKvs = if (kvsIdx == i) kvs else kvs.slice(0, kvsIdx)
Val.Obj.mk(mergePosition, trimmedKvs)
case (_, _) => recSingle(r)
}
def recSingle(v: Val): Val = v match{
case obj: Val.Obj =>
val keys: Array[String] = obj.visibleKeyNames
val kvs: Array[(String, Val.Obj.Member)] = new Array[(String, Val.Obj.Member)](keys.length)
var kvsIdx = 0
var i = 0
while (i < keys.length) {
val key = keys(i)
val value = obj.value(key, pos, obj)(ev)
if (!value.isInstanceOf[Val.Null]) {
kvs(kvsIdx) = (key, createLazyMember(recSingle(value)))
kvsIdx += 1
}
i += 1
}
val trimmedKvs = if (kvsIdx == i) kvs else kvs.slice(0, kvsIdx)
Val.Obj.mk(obj.pos, trimmedKvs)
case _ => v
}
def distinctKeys(lKeys: Array[String], rKeys: Array[String]): Array[String] = {
// Fast path for small RHS size (the common case when merging a small
// patch into a large target object), avoiding the cost of constructing
// and probing a hash set: instead, perform a nested loop where the LHS
// is scanned and matching RHS entries are marked as null to be skipped.
// Via local microbenchmarks simulating a "worst-case" (RHS keys all new),
// the threshold of `8` was empirically determined to be a good tradeoff
// between allocation + hashing costs vs. nested loop array scans.
if (rKeys.length <= 8) {
val rKeysCopy = new Array[String](rKeys.length)
rKeys.copyToArray(rKeysCopy)
var i = 0
var numNewRKeys = rKeysCopy.length
while (i < lKeys.length) {
val lKey = lKeys(i)
var j = 0
while (j < rKeysCopy.length) {
// This LHS key is in the RHS, so mark it to be skipped in output:
if (lKey == rKeysCopy(j)) {
rKeysCopy(j) = null
numNewRKeys -= 1
}
j += 1
}
i += 1
}
// Combine lKeys with non-null elements of rKeysCopy:
if (numNewRKeys == 0) {
lKeys
} else {
val outArray = new Array[String](lKeys.length + numNewRKeys)
System.arraycopy(lKeys, 0, outArray, 0, lKeys.length)
var outIdx = lKeys.length
var j = 0
while (j < rKeysCopy.length) {
if (rKeysCopy(j) != null) {
outArray(outIdx) = rKeysCopy(j)
outIdx += 1
}
j += 1
}
outArray
}
} else {
// Fallback: Use hash-based deduplication for large RHS arrays:
(lKeys ++ rKeys).distinct
}
}
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("round", "x") { (pos, ev, x: Double) =>
math.round(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("isEven", "x"){ (_, _, x: Double) =>
math.round(x) % 2 == 0
},
builtin("isInteger", "x"){ (_, _, x: Double) =>
math.round(x).toDouble == x
},
builtin("isOdd", "x"){ (_, _, x: Double) =>
math.round(x) % 2 != 0
},
builtin("isDecimal", "x"){ (_, _, x: Double) =>
math.round(x).toDouble != 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)).toLong + 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)).toLong + 1
//val mantissa = value * Math.pow(2.0, -exponent)
exponent
},
builtin(IsString),
builtin(IsBoolean),
builtin(IsNumber),
builtin(IsObject),
builtin(IsArray),
builtin(IsFunction),
builtin(Count),
builtin(Filter),
builtin(Map_),
builtin(MapWithKey),
builtin(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)
case _ => Error.fail("Argument must be either array or string")
}
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))
}
)
},
builtin(Find),
builtin("findSubstr", "pat", "str") { (pos, ev, pat: String, str: String) =>
if (pat.length == 0) new Val.Arr(pos, emptyLazyArray)
else {
var matchIndex = str.indexOf(pat)
if (matchIndex == -1) new Val.Arr(pos, emptyLazyArray)
else {
val indices = new mutable.ArrayBuilder.ofRef[Val.Num]
while (0 <= matchIndex && matchIndex < str.length) {
indices.+=(Val.Num(pos, matchIndex))
matchIndex = str.indexOf(pat, matchIndex + 1)
}
new Val.Arr(pos, indices.result())
}
}
},
builtin(Substr),
builtin(StartsWith),
builtin(EndsWith),
builtin(Char_),
builtin(StrReplace),
builtin(StrReplaceAll),
builtin(RStripChars),
builtin(LStripChars),
builtin(StripChars),
builtin(Join),
builtin(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
},
builtin(FlattenArrays),
builtin(FlattenDeepArrays),
builtin(DeepJoin),
builtin(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("escapeStringXML", "str"){ (_, _, str: String) =>
val out = new StringWriter()
for (c <- str) {
c match {
case '<' => out.write("<")
case '>' => out.write(">")
case '&' => out.write("&")
case '"' => out.write(""")
case '\'' => out.write("'")
case _ => out.write(c)
}
}
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
},
builtin(ManifestJson),
builtin(ManifestJsonMinified),
builtin(ManifestJsonEx),
builtin("manifestToml", "value"){ (pos, ev, value: Val) =>
ManifestTomlEx.evalRhs(value, Val.Str(pos, ""), ev, pos)
},
builtin(ManifestTomlEx),
builtinWithDefaults("manifestYamlDoc",
"v" -> null,
"indent_array_in_object" -> Val.False(dummyPos),
"quote_keys" -> Val.True(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)
}
val quoteKeys = args(2) match {
case Val.False(_) => false
case Val.True(_) => true
case _ => Error.fail("quote_keys has to be a boolean, got " + v.getClass)
}
Materializer.apply0(
v,
new YamlRenderer(indentArrayInObject = indentArrayInObject, quoteKeys = quoteKeys)
)(ev).toString
},
builtinWithDefaults("manifestYamlStream",
"v" -> null,
"indent_array_in_object" -> Val.False(dummyPos),
"c_document_end" -> Val.True(dummyPos),
"quote_keys" -> Val.True(dummyPos)){ (args, _, 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)
}
val cDocumentEnd = args(2) match {
case Val.False(_) => false
case Val.True(_) => true
case _ => Error.fail("c_document_end has to be a boolean, got " + v.getClass)
}
val quoteKeys = args(3) match {
case Val.False(_) => false
case Val.True(_) => true
case _ => Error.fail("quote_keys 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, quoteKeys = quoteKeys)
)(ev).toString()
}
.mkString("---\n", "\n---\n", if (cDocumentEnd) "\n...\n" else "\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(EncodeUTF8),
builtin(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))
},
builtin(Set_),
builtinWithDefaults("setUnion", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
val a = toSetArrOrString(args, 0, pos, ev)
val b = toSetArrOrString(args, 1, pos, ev)
if (a.isEmpty) {
uniqArr(pos, ev, sortArr(pos, ev, args(1), args(2)), args(2))
} else if (b.isEmpty) {
uniqArr(pos, ev, sortArr(pos, ev, args(0), args(2)), args(2))
} else {
val concat = new Val.Arr(pos, a ++ b)
uniqArr(pos, ev, sortArr(pos, ev, concat, args(2)), args(2))
}
},
builtinWithDefaults("setInter", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
val keyF = args(2)
validateSet(ev, pos, keyF, args(0))
validateSet(ev, pos, keyF, args(1))
val a = toSetArrOrString(args, 0, pos, ev)
val b = toSetArrOrString(args, 1, pos, ev)
val out = new mutable.ArrayBuffer[Lazy]
// The intersection will always be, at most, the size of the smallest set.
val sets = if (b.length < a.length) (b, a) else (a, b)
for (v <- sets._1) {
if (existsInSet(ev, pos, keyF, sets._2, v.force) &&
(ev.settings.strictSetOperations || !existsInSet(ev, pos, keyF, out, v.force))) {
out.append(v)
}
}
if (ev.settings.strictSetOperations) {
new Val.Arr(pos, out.toArray)
} else {
sortArr(pos, ev, new Val.Arr(pos, out.toArray), keyF)
}
},
builtinWithDefaults("setDiff", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
val keyF = args(2)
validateSet(ev, pos, keyF, args(0))
validateSet(ev, pos, keyF, args(1))
val a = toSetArrOrString(args, 0, pos, ev)
val b = toSetArrOrString(args, 1, pos, ev)
val out = new mutable.ArrayBuffer[Lazy]
for (v <- a) {
if (!existsInSet(ev, pos, keyF, b, v.force) &&
(ev.settings.strictSetOperations || !existsInSet(ev, pos, keyF, out, v.force))) {
out.append(v)
}
}
if (ev.settings.strictSetOperations) {
new Val.Arr(pos, out.toArray)
} else {
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)
validateSet(ev, pos, keyF, args(1))
val arr = args(1).asArr.asLazyArray
existsInSet(ev, pos, keyF, arr, args(0))
},
builtin(Split),
builtin(SplitLimit),
builtin(SplitLimitR),
builtin(StringChars),
builtin(ParseInt),
builtin(ParseOctal),
builtin(ParseHex),
builtin(ParseJson),
builtin(ParseYaml),
builtin(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: Array[(String, Val.Obj.Member)] = 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)
},
builtin(AsciiUpper),
builtin(AsciiLower),
builtin(Trace),
builtin(ExtVar),
builtin(Get),
builtin(All),
builtin(Any),
builtin("isEmpty", "str") { (_, _, str: String) =>
str.isEmpty
},
builtin("trim", "str") { (_, _, str: String) =>
trailingWhiteSpacePattern.matcher(leadingWhiteSpacePattern.matcher(str).replaceAll("")).replaceAll("")
},
builtin("equalsIgnoreCase", "str1", "str2") { (_, _, str1: String, str2: String) =>
str1.equalsIgnoreCase(str2)
},
builtin("xor", "bool1", "bool2") { (_, _, bool1: Boolean, bool2: Boolean) =>
bool1 ^ bool2
},
builtin("xnor", "bool1", "bool2") { (_, _, bool1: Boolean, bool2: Boolean) =>
!(bool1 ^ bool2)
},
builtin("sha1", "str") { (_, _, str: String) =>
Platform.sha1(str)
},
builtin("sha256", "str") { (_, _, str: String) =>
Platform.sha256(str)
},
builtin("sha512", "str") { (_, _, str: String) =>
Platform.sha512(str)
},
builtin("sha3", "str") { (_, _, str: String) =>
Platform.sha3(str)
},
builtin("sum", "arr") { (_, _, arr: Val.Arr) =>
if (!arr.forall(_.isInstanceOf[Val.Num])) {
Error.fail("Argument must be an array of numbers")
}
arr.asLazyArray.map(_.force.asDouble).sum
},
builtin("avg", "arr") { (_, _, arr: Val.Arr) =>
if (!arr.forall(_.isInstanceOf[Val.Num])) {
Error.fail("Argument must be an array of numbers")
}
if (arr.length == 0) {
Error.fail("Cannot calculate average of an empty array")
}
arr.asLazyArray.map(_.force.asDouble).sum/arr.length
},
builtin("contains", "arr", "elem") { (_, ev, arr: Val.Arr, elem: Val) =>
arr.asLazyArray.indexWhere(s => ev.equal(s.force, elem)) != -1
},
builtin("remove", "arr", "elem") { (_, ev, arr: Val.Arr, elem: Val) =>
val idx = arr.asLazyArray.indexWhere(s => ev.equal(s.force, elem))
if (idx == -1) {
arr
} else {
new Val.Arr(arr.pos, arr.asLazyArray.slice(0, idx) ++ arr.asLazyArray.slice(idx + 1, arr.length))
}
},
builtin("removeAt", "arr", "idx") { (_, _, arr: Val.Arr, idx: Int) =>
if (!(0 <= idx && idx < arr.length)) {
Error.fail("index out of bounds: 0 <= " + idx + " < " + arr.length)
}
new Val.Arr(arr.pos, arr.asLazyArray.slice(0, idx) ++ arr.asLazyArray.slice(idx + 1, arr.length))
},
builtin("objectKeysValues", "o") { (pos, ev, o: Val.Obj) =>
val keys = getVisibleKeys(ev, o)
new Val.Arr(pos, keys.map(k => Val.Obj.mk(
pos.fileScope.noOffsetPos,
"key" -> new Val.Obj.ConstMember(false, Visibility.Normal, Val.Str(pos.fileScope.noOffsetPos, k)),
"value" -> new Val.Obj.ConstMember(false, Visibility.Normal, o.value(k, pos.fileScope.noOffsetPos)(ev))
)))
},
builtin("objectKeysValuesAll", "o") { (pos, ev, o: Val.Obj) =>
val keys = getAllKeys(ev, o)
new Val.Arr(pos, keys.map(k => Val.Obj.mk(
pos.fileScope.noOffsetPos,
"key" -> new Val.Obj.ConstMember(false, Visibility.Normal, Val.Str(pos.fileScope.noOffsetPos, k)),
"value" -> new Val.Obj.ConstMember(false, Visibility.Normal, o.value(k, pos.fileScope.noOffsetPos)(ev))
)))
},
builtin("objectRemoveKey", "obj", "key") { (pos, ev, o: Val.Obj, key: String) =>
val bindings: Array[(String, Val.Obj.Member)] = for{
k <- o.visibleKeyNames
v = o.value(k, pos.fileScope.noOffsetPos)(ev)
if k != key
}yield (k, new Val.Obj.ConstMember(false, Visibility.Normal, v))
Val.Obj.mk(pos, bindings)
},
builtin(MinArray),
builtin(MaxArray),
builtin("primitiveEquals", "x", "y") { (_, ev, x: Val, y: Val) =>
x.isInstanceOf[y.type] && ev.compare(x, y) == 0
},
builtin("native", "name") { (pos, ev, name: String) =>
if (nativeFunctions.contains(name)) {
nativeFunctions(name)
} else {
Error.fail("Native function " + name + " not found", pos)(ev)
}
},
) ++ builtinNativeFunctions
private def toSetArrOrString(args: Array[Val], idx: Int, pos: Position, ev: EvalScope) = {
args(idx) match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str if !ev.settings.strictSetOperations => stringChars(pos, str.value).asLazyArray
case _ if !ev.settings.strictSetOperations => Error.fail(f"Argument $idx must be either arrays or strings")
case _ => Error.fail(f"Argument $idx must be an array")
}
}
private def toArrOrString(arg: Val, pos: Position, ev: EvalScope) = {
arg match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str => stringChars(pos, str.value).asLazyArray
case _ => Error.fail(f"Argument must be either arrays or strings")
}
}
private def validateSet(ev: EvalScope, pos: Position, keyF: Val, arr: Val): Unit = {
if (ev.settings.throwErrorForInvalidSets) {
val sorted = uniqArr(pos.noOffset, ev, sortArr(pos.noOffset, ev, arr, keyF), keyF)
if (!ev.equal(arr, sorted)) {
val err = new Error("Set operation on " + arr.prettyName + " was called with a non-set")
if (ev.settings.strictSetOperations) {
throw err
} else {
ev.warn(err)
}
}
}
}
private def existsInSet(ev: EvalScope, pos: Position, keyF: Val, arr: mutable.IndexedSeq[_ <: Lazy], toFind: Val): Boolean = {
val appliedX = keyF match {
case keyFFunc: Val.Func => keyFFunc.apply1(toFind, pos.noOffset)(ev)
case _ => toFind
}
if (ev.settings.strictSetOperations) {
arr.search(appliedX.force)((toFind: Lazy, value: Lazy) => {
val appliedValue = keyF match {
case keyFFunc: Val.Func => keyFFunc.apply1(value, pos.noOffset)(ev)
case _ => value
}
ev.compare(toFind.force, appliedValue.force)
}).isInstanceOf[Found]
} else {
arr.exists(value => {
val appliedValue = keyF match {
case keyFFunc: Val.Func => keyFFunc.apply1(value, pos.noOffset)(ev)
case _ => value
}
ev.equal(appliedValue.force, appliedX.force)
})
}
}
val Std: Val.Obj = 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))
}
)
): _*
)
private def builtin(obj : Val.Builtin) = (obj.functionName, obj)
def builtin[R: ReadWriter, T1: ReadWriter](name: String, p1: String)
(eval: (Position, EvalScope, T1) => R): (String, Val.Func) = {
(name, new Val.Builtin1(name, 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(name, 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(name, 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(name, 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) = {
name -> new Val.Builtin(name, params.map(_._1).toArray, params.map(_._2).toArray) {
def evalRhs(args: Array[_ <: Lazy], ev: EvalScope, pos: Position): Val =
implicitly[ReadWriter[R]].write(pos, eval(args.map(_.force), pos, ev))
}
}
private def uniqArr(pos: Position, ev: EvalScope, arr: Val, keyF: Val): Val = {
val arrValue = toArrOrString(arr, pos, ev)
if (arrValue.length <= 1) {
return arr
}
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)
if (!ev.equal(o1Key, o2Key)) {
out.append(v)
}
}
}
new Val.Arr(pos, out.toArray)
}
private def sortArr(pos: Position, ev: EvalScope, arr: Val, keyF: Val) = {
val vs = toArrOrString(arr, pos, ev)
if (vs.length <= 1) {
arr
} else {
val keyFFunc = if (keyF == null || keyF.isInstanceOf[Val.False]) null else keyF.asInstanceOf[Val.Func]
new Val.Arr(pos, if (keyFFunc != null) {
val keys: Array[Val] = vs.map(v => keyFFunc(Array(v.force), null, pos.noOffset)(ev).force)
val keyType = keys(0).getClass
if (classOf[Val.Bool].isAssignableFrom(keyType)) {
Error.fail("Cannot sort with key values that are booleans")
}
if (!keys.forall(_.getClass == keyType)) {
Error.fail("Cannot sort with key values that are not all the same type")
}
val indices = Array.range(0, vs.length)
val sortedIndices = if (keyType == classOf[Val.Str]) {
indices.sortBy(i => keys(i).cast[Val.Str].asString)
} else if (keyType == classOf[Val.Num]) {
indices.sortBy(i => keys(i).cast[Val.Num].asDouble)
} else {
Error.fail("Cannot sort with key values that are " + keys(0).prettyName + "s")
}
sortedIndices.map(i => vs(i))
} else {
val keyType = vs(0).force.getClass
if (classOf[Val.Bool].isAssignableFrom(keyType)) {
Error.fail("Cannot sort with values that are booleans")
}
if (!vs.forall(_.force.getClass == keyType))
Error.fail("Cannot sort with values that are not all the same type")
if (keyType == classOf[Val.Str]) {
vs.map(_.force.cast[Val.Str]).sortBy(_.asString)
} else if (keyType == classOf[Val.Num]) {
vs.map(_.force.cast[Val.Num]).sortBy(_.asDouble)
} else if (keyType == classOf[Val.Obj]) {
Error.fail("Unable to sort array of objects without key function")
} else {
Error.fail("Cannot sort array of " + vs(0).force.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 - 2025 Weber Informatics LLC | Privacy Policy