///|
/// Evaluate source to Go-style JSON text or an unescaped string. Both modes
/// append one newline. The byte budget excludes that final newline.
/// Options and fresh evaluation state match `evaluate`.
///
/// ```mbt check
/// test {
/// assert_eq(
/// @moonjsonnet.evaluate_text("'hello'", string_output=true),
/// "hello\n",
/// )
/// }
/// ```
pub fn evaluate_text(
source : String,
string_output? : Bool = false,
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(),
) -> String raise JsonnetError {
Program::parse(source, filename~, anonymous~, limits~).evaluate_text(
string_output~,
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_text`; source filename and anonymous mode were fixed when parsed.
pub fn Program::evaluate_text(
self : Program,
string_output? : 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(),
) -> String raise JsonnetError {
evaluate_with_output(
self,
(vm, value, loc) => vm.render_text(value, loc, string_output),
ext_vars~,
ext_code~,
ext_programs~,
tla_vars~,
tla_code~,
tla_programs~,
native_functions~,
trace~,
resolver?,
binary_resolver?,
limits~,
)
}
///|
/// Evaluate a top-level array to JSON documents, each ending in a newline.
/// All visible values are manifested before checking the top-level type.
/// The output byte budget is shared across document payloads; final newlines
/// are excluded. String output mode does not apply to streams.
///
/// ```mbt check
/// test {
/// assert_eq(@moonjsonnet.evaluate_stream("[1,'x']"), ["1\n", "\"x\"\n"])
/// }
/// ```
pub fn evaluate_stream(
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(),
) -> Array[String] raise JsonnetError {
Program::parse(source, filename~, anonymous~, limits~).evaluate_stream(
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_stream`; source filename and anonymous mode were fixed when parsed.
pub fn Program::evaluate_stream(
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(),
) -> Array[String] raise JsonnetError {
evaluate_with_output(
self,
(vm, value, loc) => vm.render_stream(value, loc),
ext_vars~,
ext_code~,
ext_programs~,
tla_vars~,
tla_code~,
tla_programs~,
native_functions~,
trace~,
resolver?,
binary_resolver?,
limits~,
)
}
///|
fn cli_json(json : Json, limit : Int, loc : Location) -> String raise {
let output = ManifestText::new(limit, loc)
write_cli_json(json, output, "")
output.text()
}
///|
fn write_cli_json(
json : Json,
output : ManifestText,
indent : String,
) -> Unit raise {
let next = indent + " "
match json {
Array(items) =>
if items.is_empty() {
output.write("[ ]")
} else {
output.write("[\n")
for i, item in items {
if i > 0 {
output.write(",\n")
}
output.write(next)
write_cli_json(item, output, next)
}
output.write("\n" + indent + "]")
}
Object(fields) => {
let keys = fields.keys().collect()
keys.sort_by(compare_unicode)
if keys.is_empty() {
output.write("{ }")
} else {
output.write("{\n")
for i, key in keys {
if i > 0 {
output.write(",\n")
}
output.write(next + manifest_text_quote(key) + ": ")
write_cli_json(fields[key], output, next)
}
output.write("\n" + indent + "}")
}
}
Number(n, ..) => {
let text = if n.is_inf() || n.is_nan() {
manifest_number(n)
} else {
@number.format(n)
}
if text.is_empty() {
raise problem(Runtime, output.loc, "cannot format output number")
}
output.write(text)
}
String(text) => output.write(manifest_text_quote(text))
_ => output.write(json.stringify())
}
}
///|
fn Vm::render_text(
vm : Vm,
value : Value,
loc : Location,
string_output : Bool,
) -> String raise {
if string_output {
guard value is Str(text) else {
raise problem(
Type,
loc,
"expected string result, got: " + value_type(value),
)
}
vm.account_output(@utf8.encode(text).length(), loc)
text + "\n"
} else {
let json = vm.manifest(value, loc, 0)
cli_json(json, vm.limits.output_bytes, loc) + "\n"
}
}
///|
fn Vm::render_stream(
vm : Vm,
value : Value,
loc : Location,
) -> Array[String] raise {
let json = vm.manifest(value, loc, 0)
guard json is Array(documents) else {
raise problem(
Type,
loc,
"stream mode: top-level object was a " +
value_type(value) +
", should be an array whose elements hold the JSON for each document in the stream.",
)
}
vm.output_bytes = 0
documents.map(document => {
let text = cli_json(document, vm.limits.output_bytes - vm.output_bytes, loc)
vm.account_output(@utf8.encode(text).length(), loc)
text + "\n"
})
}