///|
/// A host callback registered by name in `evaluate(native_functions=...)`.
/// Arguments are fully manifested JSON in parameter order. The callback may
/// raise an error, which becomes a runtime diagnostic at its call site.
pub(all) struct NativeFunction {
params : Array[String]
callback : (Array[Json]) -> Json raise
}
///|
fn default_trace(loc : Location, message : String) -> Unit {
@posix.stderr("TRACE: \{loc.file}:\{loc.line} \{message}")
}
///|
fn Vm::call_native(
self : Vm,
function : NativeFunction,
args : Array[(String?, Int)],
loc : Location,
) -> Value raise {
let env = self.call_env(
function.params.map(n => { name: n, default_value: None, range: None, }),
args,
{
parent: None,
names: Map([]),
import_from: "",
debug_visible: None,
diagnostic_context: ("$", "anonymous"),
diagnostic_suppressed: false,
},
loc,
)
// Native arguments have their own bounded manifestation. Restore the outer
// counter even when a callback is reached during final output traversal.
let saved_output = self.output_bytes
self.output_bytes = 0
defer {
self.output_bytes = saved_output
}
let values = function.params.map(n => {
self.manifest(self.force(env.names[n], loc), loc, 0)
})
let result = (function.callback)(values) catch {
e => raise problem(Runtime, loc, e.to_string())
}
// Go converts every returned object member eagerly, including members the
// Jsonnet caller will never read. Copy containers so later host mutations
// cannot change the value already returned to Jsonnet.
self.native_result(result, loc, 0)
}
///|
fn Vm::native_result(
self : Vm,
value : Json,
loc : Location,
depth : Int,
) -> Value raise {
self.tick(loc)
if depth >= self.limits.depth {
raise problem(Limit, loc, "native result depth limit exceeded")
}
match value {
Number(n, ..) => finite(n, loc)
Array(items) =>
ArrayValue(
items.map(item => {
self.store(Ready(self.native_result(item, loc, depth + 1)))
}),
)
Object(fields) => {
let env : Env = {
parent: None,
names: Map([]),
import_from: "",
debug_visible: None,
diagnostic_context: ("$", "anonymous"),
diagnostic_suppressed: false,
}
let result : Map[String, LayerField] = Map([])
for name, item in fields {
env.names[name] = self.store(
Ready(self.native_result(item, loc, depth + 1)),
)
result[name] = {
field: {
key: { kind: Literal(Json::string(name)), loc, end: None, },
value: { kind: Var(name), loc, end: None, },
visibility: 1,
additive: false,
},
env,
}
}
self.object_value([
{
fields: result,
locals: [],
assertions: [],
assertion_sites: [],
env,
},
])
}
Null => Nil
True => Bool(true)
False => Bool(false)
String(s) => Str(s)
}
}