// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: builtins.go
// Source: interpreter.go
// Copyright 2017 Google Inc. All rights reserved.
// Copyright 2016 Google Inc. All rights reserved.
// Modifications: Ported native text-rendering and quoting paths to MoonBit values with output budgets.
// License and retained attribution: LICENSE and NOTICE at the module root.
///|
// Adapted from go-jsonnet v0.21.0 native YAML rendering. See NOTICE and LICENSE.
fn yaml_bare_safe(s : String) -> Bool {
let chars = s.to_array()
if chars.is_empty() {
return false
}
for c in chars {
if !((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' ||
c == '-' ||
c == '/' ||
c == '.') {
return false
}
}
let lower = s.to_lower()
if [
"true", "false", "yes", "no", "on", "off", "y", "n", ".nan", "-.inf", ".inf",
"null", "-", "---", "nan", "inf", "infinity", "-inf", "-infinity",
].contains(lower) {
return false
}
// The reference timestamp regexp allows even empty digit groups.
if chars.iter().all(c => (c >= '0' && c <= '9') || c == '-') &&
chars.iter().filter(c => c == '-').count() == 2 {
return false
}
let positive = if s.has_prefix("-") { s[1:].to_owned() } else { s }
if positive.has_prefix("0b") &&
positive.length() > 2 &&
positive[2:].iter().all(c => c == '0' || c == '1' || c == '_') {
return false
}
// Intentionally unanchored and case sensitive, like yamlHexPattern in Go.
for i in 0..<(chars.length() - 2) {
if chars[i] == '0' && chars[i + 1] == 'x' {
let c = chars[i + 2]
if (c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F') ||
c == '_' {
return false
}
}
}
!yaml_decimal(s)
}
///|
// ParseFloat accepts decimal underscores only between digits. Overflow returns
// ErrRange and therefore does not disqualify a bare key; underflow is accepted.
fn yaml_decimal(s : String) -> Bool {
let chars = s.to_array()
let n = chars.length()
let mut i = if chars[0] == '-' { 1 } else { 0 }
let digits = fn() {
let mut count = 0
while i < n && ((chars[i] >= '0' && chars[i] <= '9') || chars[i] == '_') {
if chars[i] == '_' {
if count == 0 ||
i + 1 == n ||
chars[i - 1] == '_' ||
chars[i + 1] < '0' ||
chars[i + 1] > '9' {
return -1
}
} else {
count += 1
}
i += 1
}
count
}
let before = digits()
if before < 0 {
return false
}
let after = if i < n && chars[i] == '.' {
i += 1
digits()
} else {
0
}
if after < 0 || before + after == 0 {
return false
}
if i < n && (chars[i] == 'e' || chars[i] == 'E') {
i += 1
if i < n && chars[i] == '-' {
i += 1
}
if digits() <= 0 {
return false
}
}
if i != n {
return false
}
let cleaned = s.replace(old="_", new="")
try @string.parse_double(cleaned[:]) catch {
_ => false
} noraise {
value => !value.is_inf()
}
}
///|
fn Vm::yaml_value(
self : Vm,
value : Value,
indent_arrays : Bool,
quote_keys : Bool,
current_indent : String,
depth : Int,
out : ManifestText,
loc : Location,
) -> Unit raise {
self.tick(loc)
if depth >= self.limits.depth {
raise problem(Limit, loc, "YAML depth limit exceeded")
}
match value {
Nil => out.write("null")
Bool(v) => out.write(if v { "true" } else { "false" })
Num(v) => out.write(manifest_number(v))
Str(s) =>
if s.has_suffix("\n") {
out.write("|")
for line in s[:s.length() - 1].split("\n") {
out.write("\n")
out.write(current_indent)
out.write(" ")
out.write(line.to_owned())
}
} else {
out.write(manifest_text_quote(s))
}
// Native Go omits a function case and returns an empty string.
Closure(_, _, _) | Builtin(_) | Native(_) => ()
ArrayValue(items) => {
if items.is_empty() {
out.write("[]")
return
}
for i, t in items {
if i > 0 {
out.write("\n")
out.write(current_indent)
}
let child = self.force(t, loc)
out.write("-")
if child is ArrayValue(a) && !a.is_empty() {
out.write("\n")
out.write(current_indent)
out.write(" ")
} else {
out.write(" ")
}
let child_indent = match child {
ArrayValue(_) | ObjectValue(_, _) => current_indent + " "
_ => current_indent
}
self.yaml_value(
child,
indent_arrays,
quote_keys,
child_indent,
depth + 1,
out,
loc,
)
}
}
ObjectValue(id, top) => {
let keys = self.keys(id, top, false)
if keys.is_empty() {
out.write("{}")
return
}
for i, key in keys {
let child = self.field(id, top, key, loc)
if i > 0 {
out.write("\n")
out.write(current_indent)
}
out.write(
if quote_keys || !yaml_bare_safe(key) {
manifest_text_quote(key)
} else {
key
},
)
out.write(":")
let nested = match child {
ArrayValue(a) => !a.is_empty()
ObjectValue(oid, otop) => !self.keys(oid, otop, false).is_empty()
_ => false
}
let child_indent = if nested {
if child is ArrayValue(_) && !indent_arrays {
current_indent
} else {
current_indent + " "
}
} else {
current_indent
}
if nested {
out.write("\n")
out.write(child_indent)
} else {
out.write(" ")
}
// Go discards this recursive call's error, keeping its partial output.
// Resource limits remain mandatory and cannot be swallowed.
self.yaml_value(
child,
indent_arrays,
quote_keys,
child_indent,
depth + 1,
out,
loc,
) catch {
Error(d) => if d.kind == Limit { raise Error(d) }
e => raise e
}
}
}
}
}