sjsonnet.Val.scala Maven / Gradle / Ivy
package sjsonnet
import java.util
import java.util.Arrays
import sjsonnet.Expr.Member.Visibility
import sjsonnet.Expr.Params
import scala.annotation.tailrec
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.reflect.ClassTag
/**
* [[Lazy]] models lazy evaluation within a Jsonnet program. Lazily
* evaluated dictionary values, array contents, or function parameters
* are all wrapped in [[Lazy]] and only truly evaluated on-demand
*/
abstract class Lazy {
protected[this] var cached: Val = null
def compute(): Val
final def force: Val = {
if(cached == null) cached = compute()
cached
}
}
/**
* [[Val]]s represented Jsonnet values that are the result of evaluating
* a Jsonnet program. The [[Val]] data structure is essentially a JSON tree,
* except evaluation of object attributes and array contents are lazy, and
* the tree can contain functions.
*/
sealed abstract class Val extends Lazy {
cached = this // avoid a megamorphic call to compute() when forcing
final def compute() = this
def pos: Position
def prettyName: String
def cast[T: ClassTag: PrettyNamed] =
if (implicitly[ClassTag[T]].runtimeClass.isInstance(this)) this.asInstanceOf[T]
else Error.fail("Expected " + implicitly[PrettyNamed[T]].s + ", found " + prettyName)
private[this] def failAs(err: String): Nothing =
Error.fail("Wrong parameter type: expected " + err + ", got " + prettyName)
def asString: String = failAs("String")
def asBoolean: Boolean = failAs("Boolean")
def asInt: Int = failAs("Int")
def asDouble: Double = failAs("Number")
def asObj: Val.Obj = failAs("Object")
def asArr: Val.Arr = failAs("Array")
def asFunc: Val.Func = failAs("Function")
}
class PrettyNamed[T](val s: String)
object PrettyNamed{
implicit def strName: PrettyNamed[Val.Str] = new PrettyNamed("string")
implicit def numName: PrettyNamed[Val.Num] = new PrettyNamed("number")
implicit def arrName: PrettyNamed[Val.Arr] = new PrettyNamed("array")
implicit def objName: PrettyNamed[Val.Obj] = new PrettyNamed("object")
implicit def funName: PrettyNamed[Val.Func] = new PrettyNamed("function")
}
object Val{
abstract class Literal extends Val with Expr
abstract class Bool extends Literal {
override def asBoolean: Boolean = this.isInstanceOf[True]
}
def bool(pos: Position, b: Boolean) = if (b) True(pos) else False(pos)
case class True(pos: Position) extends Bool {
def prettyName = "boolean"
}
case class False(pos: Position) extends Bool {
def prettyName = "boolean"
}
case class Null(pos: Position) extends Literal {
def prettyName = "null"
}
case class Str(pos: Position, value: String) extends Literal {
def prettyName = "string"
override def asString: String = value
}
case class Num(pos: Position, value: Double) extends Literal {
def prettyName = "number"
override def asInt: Int = value.toInt
override def asDouble: Double = value
}
class Arr(val pos: Position, private val value: Array[_ <: Lazy]) extends Literal {
def prettyName = "array"
override def asArr: Arr = this
def length: Int = value.length
def force(i: Int) = value(i).force
def asLazy(i: Int) = value(i)
def asLazyArray: Array[Lazy] = value.asInstanceOf[Array[Lazy]]
def asStrictArray: Array[Val] = value.map(_.force)
def concat(newPos: Position, rhs: Arr): Arr =
new Arr(newPos, value ++ rhs.value)
def iterator: Iterator[Val] = value.iterator.map(_.force)
def foreach[U](f: Val => U) = {
var i = 0
while(i < value.length) {
f(value(i).force)
i += 1
}
}
def forall(f: Val => Boolean): Boolean = {
var i = 0
while(i < value.length) {
if(!f(value(i).force)) return false
i += 1
}
true
}
}
object Obj{
abstract class Member(val add: Boolean, val visibility: Visibility, val cached: Boolean = true) {
def invoke(self: Obj, sup: Obj, fs: FileScope, ev: EvalScope): Val
}
class ConstMember(add: Boolean, visibility: Visibility, v: Val, cached: Boolean = true)
extends Member(add, visibility, cached) {
def invoke(self: Obj, sup: Obj, fs: FileScope, ev: EvalScope): Val = v
}
def mk(pos: Position, members: (String, Obj.Member)*): Obj = {
val m = new util.LinkedHashMap[String, Obj.Member]()
for((k, v) <- members) m.put(k, v)
new Obj(pos, m, false, null, null)
}
}
final class Obj(val pos: Position,
private[this] var value0: util.LinkedHashMap[String, Obj.Member],
static: Boolean,
triggerAsserts: Val.Obj => Unit,
`super`: Obj,
valueCache: mutable.HashMap[Any, Val] = mutable.HashMap.empty[Any, Val],
private[this] var allKeys: util.LinkedHashMap[String, java.lang.Boolean] = null) extends Literal with Expr.ObjBody {
var asserting: Boolean = false
def getSuper = `super`
private[this] def getValue0: util.LinkedHashMap[String, Obj.Member] = {
if(value0 == null) {
val value0 = new java.util.LinkedHashMap[String, Val.Obj.Member]
allKeys.forEach { (k, _) =>
value0.put(k, new Val.Obj.ConstMember(false, Visibility.Normal, valueCache(k)))
}
// Only assign to field after initialization is complete to allow unsynchronized multi-threaded use:
this.value0 = value0
}
value0
}
@tailrec def triggerAllAsserts(obj: Val.Obj): Unit = {
if(triggerAsserts != null) triggerAsserts(obj)
if(`super` != null) `super`.triggerAllAsserts(obj)
}
def addSuper(pos: Position, lhs: Val.Obj): Val.Obj = {
`super` match{
case null => new Val.Obj(pos, getValue0, false, triggerAsserts, lhs)
case x => new Val.Obj(pos, getValue0, false, triggerAsserts, x.addSuper(pos, lhs))
}
}
def prettyName = "object"
override def asObj: Val.Obj = this
private def gatherKeys(mapping: util.LinkedHashMap[String, java.lang.Boolean]): Unit = {
if(static) mapping.putAll(allKeys)
else {
if(`super` != null) `super`.gatherKeys(mapping)
getValue0.forEach { (k, m) =>
val vis = m.visibility
if(!mapping.containsKey(k)) mapping.put(k, vis == Visibility.Hidden)
else if(vis == Visibility.Hidden) mapping.put(k, true)
else if(vis == Visibility.Unhide) mapping.put(k, false)
}
}
}
private def getAllKeys = {
if(allKeys == null) {
allKeys = new util.LinkedHashMap[String, java.lang.Boolean]
gatherKeys(allKeys)
}
allKeys
}
@inline def hasKeys = !getAllKeys.isEmpty
@inline def containsKey(k: String): Boolean = getAllKeys.containsKey(k)
@inline def containsVisibleKey(k: String): Boolean = getAllKeys.get(k) == java.lang.Boolean.FALSE
lazy val allKeyNames: Array[String] = getAllKeys.keySet().toArray(new Array[String](getAllKeys.size()))
lazy val visibleKeyNames: Array[String] = if(static) allKeyNames else {
val buf = mutable.ArrayBuilder.make[String]
getAllKeys.forEach((k, b) => if(b == java.lang.Boolean.FALSE) buf += k)
buf.result()
}
def value(k: String,
pos: Position,
self: Obj = this)
(implicit evaluator: EvalScope): Val = {
if(static) {
valueCache.getOrElse(k, null) match {
case null => Error.fail("Field does not exist: " + k, pos)
case x => x
}
} else {
val cacheKey = if(self eq this) k else (k, self)
valueCache.getOrElse(cacheKey, {
valueRaw(k, self, pos, valueCache, cacheKey) match {
case null => Error.fail("Field does not exist: " + k, pos)
case x => x
}
})
}
}
private def renderString(v: Val)(implicit evaluator: EvalScope): String =
evaluator.materialize(v).transform(new Renderer()).toString
def mergeMember(l: Val,
r: Val,
pos: Position)
(implicit evaluator: EvalScope) = {
val lStr = l.isInstanceOf[Val.Str]
val rStr = r.isInstanceOf[Val.Str]
if(lStr || rStr) {
val ll = if(lStr) l.asInstanceOf[Val.Str].value else renderString(l)
val rr = if(rStr) r.asInstanceOf[Val.Str].value else renderString(r)
Val.Str(pos, ll ++ rr)
} else if(l.isInstanceOf[Val.Num] && r.isInstanceOf[Val.Num]) {
val ll = l.asInstanceOf[Val.Num].value
val rr = r.asInstanceOf[Val.Num].value
Val.Num(pos, ll + rr)
} else if(l.isInstanceOf[Val.Arr] && r.isInstanceOf[Val.Arr]) {
val ll = l.asInstanceOf[Val.Arr].asLazyArray
val rr = r.asInstanceOf[Val.Arr].asLazyArray
new Val.Arr(pos, ll ++ rr)
} else if(l.isInstanceOf[Val.Obj] && r.isInstanceOf[Val.Obj]) {
val ll = l.asInstanceOf[Val.Obj]
val rr = r.asInstanceOf[Val.Obj]
rr.addSuper(pos, ll)
} else throw new MatchError((l, r))
}
def valueRaw(k: String,
self: Obj,
pos: Position,
addTo: mutable.HashMap[Any, Val] = null,
addKey: Any = null)
(implicit evaluator: EvalScope): Val = {
if(static) {
val v = valueCache.getOrElse(k, null)
if(addTo != null && v != null) addTo(addKey) = v
v
} else {
val s = this.`super`
getValue0.get(k) match{
case null =>
if(s == null) null else s.valueRaw(k, self, pos, addTo, addKey)
case m =>
val vv = m.invoke(self, s, pos.fileScope, evaluator)
val v = if(s != null && m.add) {
s.valueRaw(k, self, pos, null, null) match {
case null => vv
case supValue => mergeMember(supValue, vv, pos)
}
} else vv
if(addTo != null && m.cached) addTo(addKey) = v
v
}
}
}
def foreachElement(sort: Boolean, pos: Position)(f: (String, Val) => Unit)(implicit ev: EvalScope): Unit = {
val keys = if (sort) visibleKeyNames.sorted else visibleKeyNames
for(k <- keys) {
val v = value(k, pos)
f(k, v)
}
}
}
final class StaticObjectFieldSet(protected val keys: Array[String]) {
override def hashCode(): Int = {
Arrays.hashCode(keys.asInstanceOf[Array[Object]])
}
override def equals(obj: scala.Any): Boolean = {
obj match {
case that: StaticObjectFieldSet =>
keys.sameElements(that.keys)
case _ => false
}
}
}
def staticObject(
pos: Position,
fields: Array[Expr.Member.Field],
internedKeyMaps: mutable.HashMap[StaticObjectFieldSet, java.util.LinkedHashMap[String, java.lang.Boolean]],
internedStrings: mutable.HashMap[String, String]): Obj = {
// Set the initial capacity to the number of fields divided by the default load factor + 1 -
// this ensures that we can fill up the map to the total number of fields without resizing.
// From JavaDoc - true for both Scala & Java HashMaps
val hashMapDefaultLoadFactor = 0.75f
val capacity = (fields.length / hashMapDefaultLoadFactor).toInt + 1
val cache = mutable.HashMap.empty[Any, Val]
val allKeys = new util.LinkedHashMap[String, java.lang.Boolean](capacity, hashMapDefaultLoadFactor)
val keys = new Array[String](fields.length)
var idx = 0
fields.foreach {
case Expr.Member.Field(_, Expr.FieldName.Fixed(k), _, _, _, rhs: Val.Literal) =>
val uniqueKey = internedStrings.getOrElseUpdate(k, k)
cache.put(uniqueKey, rhs)
allKeys.put(uniqueKey, false)
keys(idx) = uniqueKey
idx += 1
}
val fieldSet = new StaticObjectFieldSet(keys)
new Val.Obj(pos, null, true, null, null, cache, internedKeyMaps.getOrElseUpdate(fieldSet, allKeys))
}
abstract class Func(val pos: Position,
val defSiteValScope: ValScope,
val params: Params) extends Val with Expr {
def evalRhs(scope: ValScope, ev: EvalScope, fs: FileScope, pos: Position): Val
def evalDefault(expr: Expr, vs: ValScope, es: EvalScope): Val = null
def prettyName = "function"
override def exprErrorString: String = "Function"
override def asFunc: Func = this
def apply(argsL: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val = {
val simple = namedNames == null && params.names.length == argsL.length
val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope }
//println(s"apply: argsL: ${argsL.length}, namedNames: $namedNames, paramNames: ${params.names.mkString(",")}")
if(simple) {
val newScope = defSiteValScope.extendSimple(argsL)
evalRhs(newScope, ev, funDefFileScope, outerPos)
} else {
val newScopeLen = math.max(params.names.length, argsL.length)
// Initialize positional args
val base = defSiteValScope.length
val newScope = defSiteValScope.extendBy(newScopeLen)
val argVals = newScope.bindings
val posArgs = if(namedNames == null) argsL.length else argsL.length - namedNames.length
System.arraycopy(argsL, 0, argVals, base, posArgs)
if(namedNames != null) { // Add named args
var i = 0
var j = posArgs
while(i < namedNames.length) {
val idx = params.paramMap.getOrElse(namedNames(i),
Error.fail(s"Function has no parameter ${namedNames(i)}", outerPos))
if(argVals(base+idx) != null)
Error.fail(s"binding parameter a second time: ${namedNames(i)}", outerPos)
argVals(base+idx) = argsL(j)
i += 1
j += 1
}
}
if(argsL.length > params.names.length)
Error.fail("Too many args, function has " + params.names.length + " parameter(s)", outerPos)
if(params.names.length != argsL.length) { // Args missing -> add defaults
var missing: ArrayBuffer[String] = null
var i = posArgs
var j = base + posArgs
while(j < argVals.length) {
if(argVals(j) == null) {
val default = params.defaultExprs(i)
if(default != null) {
argVals(j) = () => evalDefault(default, newScope, ev)
} else {
if(missing == null) missing = new ArrayBuffer
missing.+=(params.names(i))
}
}
i += 1
j += 1
}
if(missing != null) {
val plural = if(missing.size > 1) "s" else ""
Error.fail(s"Function parameter$plural ${missing.mkString(", ")} not bound in call", outerPos)
}
//println(argVals.mkString(s"params: ${params.names.length}, argsL: ${argsL.length}, argVals: [", ", ", "]"))
}
evalRhs(newScope, ev, funDefFileScope, outerPos)
}
}
def apply0(outerPos: Position)(implicit ev: EvalScope): Val = {
if(params.names.length != 0) apply(Evaluator.emptyLazyArray, null, outerPos)
else {
val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope }
evalRhs(defSiteValScope, ev, funDefFileScope, outerPos)
}
}
def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = {
if(params.names.length != 1) apply(Array(argVal), null, outerPos)
else {
val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope }
val newScope: ValScope = defSiteValScope.extendSimple(argVal)
evalRhs(newScope, ev, funDefFileScope, outerPos)
}
}
def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = {
if(params.names.length != 2) apply(Array(argVal1, argVal2), null, outerPos)
else {
val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope }
val newScope: ValScope = defSiteValScope.extendSimple(argVal1, argVal2)
evalRhs(newScope, ev, funDefFileScope, outerPos)
}
}
def apply3(argVal1: Lazy, argVal2: Lazy, argVal3: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = {
if(params.names.length != 3) apply(Array(argVal1, argVal2, argVal3), null, outerPos)
else {
val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope }
val newScope: ValScope = defSiteValScope.extendSimple(argVal1, argVal2, argVal3)
evalRhs(newScope, ev, funDefFileScope, outerPos)
}
}
}
/** Superclass for standard library functions */
abstract class Builtin(paramNames: Array[String], defaults: Array[Expr] = null)
extends Func(null, ValScope.empty, Params(paramNames,
if(defaults == null) new Array[Expr](paramNames.length) else defaults)) {
override final def evalDefault(expr: Expr, vs: ValScope, es: EvalScope): Val = expr.asInstanceOf[Val]
final def evalRhs(scope: ValScope, ev: EvalScope, fs: FileScope, pos: Position): Val = {
val args = new Array[Val](params.names.length)
var i = 0
var j = scope.length - args.length
while(i < args.length) {
args(i) = scope.bindings(i).force
i += 1
}
evalRhs(args, ev, pos)
}
def evalRhs(args: Array[Val], ev: EvalScope, pos: Position): Val
override def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope): Val =
if(params.names.length != 1) apply(Array(argVal), null, outerPos)
else evalRhs(Array(argVal.force), ev, outerPos)
override def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope): Val =
if(params.names.length != 2) apply(Array(argVal1, argVal2), null, outerPos)
else evalRhs(Array(argVal1.force, argVal2.force), ev, outerPos)
/** Specialize a call to this function in the optimizer. Must return either `null` to leave the
* call-site as it is or a pair of a (possibly different) `Builtin` and the arguments to pass
* to it (usually a subset of the supplied arguments).
* @param args the positional arguments for this function call. Named arguments and defaults have
* already been resolved. */
def specialize(args: Array[Expr]): (Builtin, Array[Expr]) = null
/** Is this builtin safe to use in static evaluation */
def staticSafe: Boolean = true
}
abstract class Builtin1(pn1: String, def1: Expr = null) extends Builtin(Array(pn1), if(def1 == null) null else Array(def1)) {
final def evalRhs(args: Array[Val], ev: EvalScope, pos: Position): Val =
evalRhs(args(0), ev, pos)
def evalRhs(arg1: Val, ev: EvalScope, pos: Position): Val
override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val =
if(namedNames == null && argVals.length == 1) evalRhs(argVals(0).force, ev, outerPos)
else super.apply(argVals, namedNames, outerPos)
override def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope): Val =
if(params.names.length == 1) evalRhs(argVal.force, ev, outerPos)
else super.apply(Array(argVal), null, outerPos)
}
abstract class Builtin2(pn1: String, pn2: String, defs: Array[Expr] = null) extends Builtin(Array(pn1, pn2), defs) {
final def evalRhs(args: Array[Val], ev: EvalScope, pos: Position): Val =
evalRhs(args(0), args(1), ev, pos)
def evalRhs(arg1: Val, arg2: Val, ev: EvalScope, pos: Position): Val
override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val =
if(namedNames == null && argVals.length == 2)
evalRhs(argVals(0).force, argVals(1).force, ev, outerPos)
else super.apply(argVals, namedNames, outerPos)
override def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope): Val =
if(params.names.length == 2) evalRhs(argVal1.force, argVal2.force, ev, outerPos)
else super.apply(Array(argVal1, argVal2), null, outerPos)
}
abstract class Builtin3(pn1: String, pn2: String, pn3: String, defs: Array[Expr] = null) extends Builtin(Array(pn1, pn2, pn3), defs) {
final def evalRhs(args: Array[Val], ev: EvalScope, pos: Position): Val =
evalRhs(args(0), args(1), args(2), ev, pos)
def evalRhs(arg1: Val, arg2: Val, arg3: Val, ev: EvalScope, pos: Position): Val
override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val =
if(namedNames == null && argVals.length == 3)
evalRhs(argVals(0).force, argVals(1).force, argVals(2).force, ev, outerPos)
else super.apply(argVals, namedNames, outerPos)
}
abstract class Builtin4(pn1: String, pn2: String, pn3: String, pn4: String, defs: Array[Expr] = null) extends Builtin(Array(pn1, pn2, pn3, pn4), defs) {
final def evalRhs(args: Array[Val], ev: EvalScope, pos: Position): Val =
evalRhs(args(0), args(1), args(2), args(3), ev, pos)
def evalRhs(arg1: Val, arg2: Val, arg3: Val, arg4: Val, ev: EvalScope, pos: Position): Val
override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val =
if(namedNames == null && argVals.length == 4)
evalRhs(argVals(0).force, argVals(1).force, argVals(2).force, argVals(3).force, ev, outerPos)
else super.apply(argVals, namedNames, outerPos)
}
}
/**
* [[EvalScope]] models the per-evaluator context that is propagated
* throughout the Jsonnet evaluation.
*/
abstract class EvalScope extends EvalErrorScope {
def visitExpr(expr: Expr)
(implicit scope: ValScope): Val
def materialize(v: Val): ujson.Value
def equal(x: Val, y: Val): Boolean
val emptyMaterializeFileScope = new FileScope(wd / "(materialize)")
val emptyMaterializeFileScopePos = new Position(emptyMaterializeFileScope, -1)
def settings: Settings
def warn(e: Error): Unit
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy