///|
/// Evaluate Jsonnet source into JSON. Imports are disabled unless a resolver is
/// provided. Each call has independent caches and resource counters.
/// `ext_code` and `tla_code` contain lazy Jsonnet expressions in clean scopes.
/// Parsed `ext_programs`/`tla_programs` override code, which overrides JSON for
/// duplicate names. Parsed parameters are lazy, with synthetic std.thisFile but
/// their original diagnostic location and import origin. A top-level function is
/// applied once with named TLAs; other top-level values ignore them.
/// With `anonymous=true`, filename is diagnostic-only: std.thisFile and the
/// entry import origin are empty, matching Go's EvaluateAnonymousSnippet.
///
/// ```mbt check
/// test {
/// let result = @moonjsonnet.evaluate("local x = 6; { answer: x * 7 }")
/// assert_eq(result.stringify(), "{\"answer\":42}")
/// }
/// ```
pub fn evaluate(
source : String,
filename? : String = "",
anonymous? : Bool = false,
ext_vars? : Map[String, Json] = Map([]),
ext_code? : Map[String, String] = Map([]),
ext_programs? : Map[String, Program] = Map([]),
tla_vars? : Map[String, Json] = Map([]),
tla_code? : Map[String, String] = Map([]),
tla_programs? : Map[String, Program] = Map([]),
native_functions? : Map[String, NativeFunction] = Map([]),
trace? : (Location, String) -> Unit = default_trace,
resolver? : (String, String) -> Source raise,
binary_resolver? : (String, String) -> BinarySource raise,
limits? : Limits = Limits::default(),
) -> Json raise JsonnetError {
Program::parse(source, filename~, anonymous~, limits~).evaluate(
ext_vars~,
ext_code~,
ext_programs~,
tla_vars~,
tla_code~,
tla_programs~,
native_functions~,
trace~,
resolver?,
binary_resolver?,
limits~,
)
}
///|
/// Execute this validated program with fresh evaluation state. Options and output
/// match `evaluate`; source filename and anonymous mode were fixed when parsed.
pub fn Program::evaluate(
self : Program,
ext_vars? : Map[String, Json] = Map([]),
ext_code? : Map[String, String] = Map([]),
ext_programs? : Map[String, Program] = Map([]),
tla_vars? : Map[String, Json] = Map([]),
tla_code? : Map[String, String] = Map([]),
tla_programs? : Map[String, Program] = Map([]),
native_functions? : Map[String, NativeFunction] = Map([]),
trace? : (Location, String) -> Unit = default_trace,
resolver? : (String, String) -> Source raise,
binary_resolver? : (String, String) -> BinarySource raise,
limits? : Limits = Limits::default(),
) -> Json raise JsonnetError {
evaluate_with_output(
self,
(vm, value, loc) => vm.manifest(value, loc, 0),
ext_vars~,
ext_code~,
ext_programs~,
tla_vars~,
tla_code~,
tla_programs~,
native_functions~,
trace~,
resolver?,
binary_resolver?,
limits~,
)
}
///|
fn[T] evaluate_with_output(
program : Program,
output : (Vm, Value, Location) -> T raise,
ext_vars? : Map[String, Json] = Map([]),
ext_code? : Map[String, String] = Map([]),
ext_programs? : Map[String, Program] = Map([]),
tla_vars? : Map[String, Json] = Map([]),
tla_code? : Map[String, String] = Map([]),
tla_programs? : Map[String, Program] = Map([]),
native_functions? : Map[String, NativeFunction] = Map([]),
trace? : (Location, String) -> Unit = default_trace,
resolver? : (String, String) -> Source raise,
binary_resolver? : (String, String) -> BinarySource raise,
limits? : Limits = Limits::default(),
session? : Evaluator,
) -> T raise JsonnetError {
let filename = program.filename
let anonymous = program.anonymous
let loc : Location = { file: filename, line: 1, column: 1, }
try {
check_limits(limits, loc)
if program.source_bytes > limits.source_bytes {
raise problem(Limit, loc, "source byte limit exceeded")
}
check_ast_depth(program.expression, limits.depth)
let previous : Vm? = match session {
Some(s) => s.state
None => None
}
let vm : Vm = {
library: previous
.map(vm => vm.library)
.unwrap_or_else(standard_library_expression),
thunks: previous.map(vm => vm.thunks).unwrap_or([]),
free_thunks: previous.map(vm => vm.free_thunks).unwrap_or([]),
free_objects: previous.map(vm => vm.free_objects).unwrap_or([]),
retries: Map([]),
persistent: session is Some(_),
accessed_sources: Map([]),
parsed_imports: session.map(s => s.parsed_imports).unwrap_or(Map([])),
objects: previous.map(vm => vm.objects).unwrap_or([]),
limits,
ext: Map([]),
native_functions,
trace,
eval_hook: session.bind(s => s.eval_hook),
hooks_suppressed: false,
hook_stack: [],
hook_serial: 0,
hook_failure_environment: None,
resolver,
binary_resolver,
binary_sources: session.map(s => s.binary_sources).unwrap_or(Map([])),
binary_imports: Map([]),
sources: session.map(s => s.sources).unwrap_or(Map([])),
imports: previous.map(vm => vm.imports).unwrap_or(Map([])),
steps: 0,
depth: 0,
frames: [],
current_trace: None,
imported_bytes: 0,
output_bytes: 0,
}
defer (if session is Some(owner) {
vm.retain_imports()
owner.state = Some(vm)
})
let ext = vm.prepare_parameters(ext_vars, ext_code, ext_programs, "extvar")
for name, id in ext {
vm.ext[name] = id
}
let expression = program.expression
let entry_env = vm.base_env(file=if anonymous { "" } else { filename })
let value = vm.with_trace(Synthetic("During evaluation"), () => {
vm.eval_frame(expression, entry_env)
})
let value = match value {
Closure(_, _, _) | Builtin(_) | Native(_) => {
let tla = vm.prepare_parameters(
tla_vars, tla_code, tla_programs, "top-level-arg",
)
let names = tla.keys().collect()
names.sort()
vm.with_trace(Synthetic("Top-level function call"), () => {
vm.call(value, names.map(name => (Some(name), tla[name])), loc)
})
}
_ => value
}
vm.output_bytes = 0
vm.with_trace(Synthetic("During manifestation"), () => {
output(vm, value, loc)
})
} catch {
Error(d) => raise Error(d)
e =>
raise Error({
kind: Runtime,
message: e.to_string(),
location: loc,
context: [],
trace: [],
})
}
}