///|
/// PKL-153e: yaml.Renderer's `mode = "1.2"` toggle. The renderer
/// quoting decision walks string scalars and asks "could this round-
/// trip as a non-string?". In compat mode the answer is yes for the
/// full set of 1.1 + 1.2 patterns (`y`, `yes`, `0b1001`, `1_2_3`,
/// base-60, etc.); in 1.2 mode it's narrowed to just what 1.2's
/// core schema produces (`true/null/0o/0x/decimals/floats`). The
/// renderer entry points stash the mode in `yaml_render_mode_ref`
/// for `render_yaml_string` to consult — saves threading the
/// parameter through every helper in the chain.
let yaml_render_mode_ref : Ref[String] = { val: "compat" }
///|
fn yaml_render_mode_current() -> String {
yaml_render_mode_ref.val
}
///|
pub fn render_value_as_yaml(value : Value) -> String {
render_value_as_yaml_with_options(value, 2, false)
}
///|
/// Mode-aware entry point used by the runtime renderer dispatch.
/// External callers should prefer this when the YamlRenderer's
/// `mode` slot matters; `render_value_as_yaml_with_options` keeps
/// the historical default-"compat" behaviour intact.
pub fn render_value_as_yaml_with_mode(
value : Value,
indent_width : Int,
is_stream : Bool,
mode : String,
) -> String {
let prev = yaml_render_mode_ref.val
yaml_render_mode_ref.val = mode
let result = render_value_as_yaml_with_options(value, indent_width, is_stream)
yaml_render_mode_ref.val = prev
result
}
///|
pub fn render_value_as_yaml_fragment_with_mode(
value : Value,
indent_width : Int,
is_stream : Bool,
mode : String,
) -> String {
let prev = yaml_render_mode_ref.val
yaml_render_mode_ref.val = mode
let result = render_value_as_yaml_fragment_with_options(
value, indent_width, is_stream,
)
yaml_render_mode_ref.val = prev
result
}
///|
pub fn render_value_as_yaml_with_indent_width(
value : Value,
indent_width : Int,
) -> String {
render_value_as_yaml_with_options(value, indent_width, false)
}
///|
pub fn render_value_as_yaml_stream(value : Value, indent_width : Int) -> String {
render_value_as_yaml_with_options(value, indent_width, true)
}
///|
pub fn render_value_as_yaml_with_options(
value : Value,
indent_width : Int,
is_stream : Bool,
) -> String {
let buf = StringBuilder::new()
let step = yaml_normalize_indent_width(indent_width)
if is_stream {
render_yaml_stream_root(value, step, buf)
} else {
render_yaml_root(value, step, buf)
}
buf.to_string()
}
///|
pub fn render_value_as_yaml_fragment(value : Value) -> String {
render_value_as_yaml_fragment_with_options(value, 2, false)
}
///|
pub fn render_value_as_yaml_fragment_with_options(
value : Value,
indent_width : Int,
is_stream : Bool,
) -> String {
let text = render_value_as_yaml_with_options(value, indent_width, is_stream)
if text.has_suffix("\n") {
String::unsafe_substring(text, start=0, end=text.length() - 1)
} else {
text
}
}
///|
fn yaml_normalize_indent_width(indent_width : Int) -> Int {
if indent_width < 1 {
1
} else {
indent_width
}
}
///|
fn render_yaml_stream_root(
value : Value,
indent_width : Int,
buf : StringBuilder,
) -> Unit {
match value {
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements)
| SetValue(elements) =>
for i = 0; i < elements.length(); i = i + 1 {
if i > 0 {
match elements[i] {
ObjectValue(_)
| MappingValue(_)
| DefaultedMappingValue(_, _, _)
| MapValue(_)
| ListingValue(_)
| DefaultedListingValue(_, _, _)
| ListValue(_)
| SetValue(_) => buf.write_string("---\n")
_ => buf.write_string("--- ")
}
}
if i > 0 {
match elements[i] {
ObjectValue(_)
| MappingValue(_)
| DefaultedMappingValue(_, _, _)
| MapValue(_)
| ListingValue(_)
| DefaultedListingValue(_, _, _)
| ListValue(_)
| SetValue(_) => render_yaml_root(elements[i], indent_width, buf)
_ => {
render_yaml_scalar(elements[i], buf)
buf.write_char('\n')
}
}
} else {
render_yaml_root(elements[i], indent_width, buf)
}
}
_ => render_yaml_root(value, indent_width, buf)
}
}
///|
fn render_yaml_root(
value : Value,
indent_width : Int,
buf : StringBuilder,
) -> Unit {
match render_directive_text(value) {
Some(text) => {
buf.write_string(text)
buf.write_char('\n')
return
}
None => ()
}
match value {
ObjectValue(members) =>
match dynamic_listing_elements_from_members(members) {
Some(elements) => render_yaml_sequence(elements, 0, indent_width, buf)
None =>
match dynamic_mapping_entries_from_members(members) {
Some(entries) =>
if entries.length() == 0 {
buf.write_string(" {}\n")
} else {
render_yaml_mapping_entries(entries, 0, indent_width, buf)
}
None => {
let visible = visible_members(members)
if visible.length() == 0 {
buf.write_string(" {}\n")
} else {
render_yaml_mapping_members(visible, 0, indent_width, buf)
}
}
}
}
MappingValue(entries)
| DefaultedMappingValue(_, entries, _)
| MapValue(entries) =>
if entries.length() == 0 {
buf.write_string(" {}\n")
} else {
render_yaml_mapping_entries(entries, 0, indent_width, buf)
}
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements)
| SetValue(elements) =>
if elements.length() == 0 {
buf.write_string(" []\n")
} else {
render_yaml_sequence(elements, 0, indent_width, buf)
}
_ => {
render_yaml_scalar(value, buf)
buf.write_char('\n')
}
}
}
///|
fn render_yaml_mapping_members(
members : Array[ValueMember],
indent : Int,
indent_width : Int,
buf : StringBuilder,
) -> Unit {
let visible = visible_members(members)
for i = 0; i < visible.length(); i = i + 1 {
write_yaml_indent(buf, indent)
render_yaml_member_key(visible[i].name, visible[i].value, buf)
buf.write_char(':')
render_yaml_block_value(visible[i].value, indent, indent_width, buf)
}
}
///|
fn render_yaml_mapping_entries(
entries : Array[ValueEntry],
indent : Int,
indent_width : Int,
buf : StringBuilder,
) -> Unit {
for i = 0; i < entries.length(); i = i + 1 {
render_yaml_mapping_entry(entries[i], indent, indent_width, buf)
}
}
///|
fn render_yaml_mapping_entry(
entry : ValueEntry,
indent : Int,
indent_width : Int,
buf : StringBuilder,
) -> Unit {
match entry.key {
StringValue(s) if yaml_string_key_needs_explicit(s) => {
write_yaml_indent(buf, indent)
buf.write_char('?')
match yaml_string_block_scalar_info(s, indent_width) {
Some((indicator, lines, trailing)) =>
render_yaml_block_scalar(
indicator,
lines,
trailing,
indent + indent_width,
buf,
)
None => {
buf.write_char(' ')
render_yaml_key_string(s, false, buf)
buf.write_char('\n')
}
}
write_yaml_indent(buf, indent)
buf.write_char(':')
render_yaml_block_value(entry.value, indent, indent_width, buf)
}
_ => {
write_yaml_indent(buf, indent)
render_yaml_entry_key(entry.key, buf)
buf.write_char(':')
render_yaml_block_value(entry.value, indent, indent_width, buf)
}
}
}
///|
fn render_yaml_block_value(
value : Value,
indent : Int,
indent_width : Int,
buf : StringBuilder,
) -> Unit {
match render_directive_text(value) {
Some(text) => {
buf.write_string(text)
buf.write_char('\n')
return
}
None => ()
}
match value {
ObjectValue(members) =>
match dynamic_listing_elements_from_members(members) {
Some(elements) => {
buf.write_char('\n')
render_yaml_sequence(elements, indent, indent_width, buf)
}
None =>
match dynamic_mapping_entries_from_members(members) {
Some(entries) =>
if entries.length() == 0 {
buf.write_string(" {}\n")
} else {
buf.write_char('\n')
render_yaml_mapping_entries(
entries,
indent + indent_width,
indent_width,
buf,
)
}
None => {
let visible = visible_members(members)
if visible.length() == 0 {
buf.write_string(" {}\n")
} else {
buf.write_char('\n')
render_yaml_mapping_members(
visible,
indent + indent_width,
indent_width,
buf,
)
}
}
}
}
MappingValue(entries) | DefaultedMappingValue(_, entries, _) =>
if entries.length() == 0 {
buf.write_string(" {}\n")
} else {
buf.write_char('\n')
render_yaml_mapping_entries(
entries,
indent + indent_width,
indent_width,
buf,
)
}
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements) =>
if elements.length() == 0 {
buf.write_string(" []\n")
} else {
buf.write_char('\n')
render_yaml_sequence(elements, indent, indent_width, buf)
}
// PKL-119a: Pair flows through the same block-sequence path as
// a 2-element Listing so YAML emits it as the canonical
// `- first\n- second` form rather than the PCF inline fallback.
PairValue(first, second) => {
buf.write_char('\n')
render_yaml_sequence([first, second], indent, indent_width, buf)
}
// PKL-119b: IntSeq materializes and reuses the sequence path so
// the YAML output matches what a hand-written `new Listing { ... }`
// of the same elements would render to.
IntSeqValue(start, end_v, step) => {
let elements = intseq_materialize(start, end_v, step)
if elements.length() == 0 {
buf.write_string(" []\n")
} else {
buf.write_char('\n')
render_yaml_sequence(elements, indent, indent_width, buf)
}
}
// PKL-119c: Set reuses the sequence path; uniqueness is upstream
// semantics, not a YAML output concern.
SetValue(elements) =>
if elements.length() == 0 {
buf.write_string(" []\n")
} else {
buf.write_char('\n')
render_yaml_sequence(elements, indent, indent_width, buf)
}
// PKL-119d: Map reuses the mapping-entries path so the YAML
// output shape matches a hand-written `new Mapping { ... }` of
// the same entries.
MapValue(entries) =>
if entries.length() == 0 {
buf.write_string(" {}\n")
} else {
buf.write_char('\n')
render_yaml_mapping_entries(
entries,
indent + indent_width,
indent_width,
buf,
)
}
StringValue(s) =>
match yaml_string_block_scalar_info(s, indent_width) {
Some((indicator, lines, trailing)) =>
render_yaml_block_scalar(
indicator,
lines,
trailing,
indent + indent_width,
buf,
)
None => {
buf.write_char(' ')
render_yaml_scalar(value, buf)
buf.write_char('\n')
}
}
_ => {
buf.write_char(' ')
render_yaml_scalar(value, buf)
buf.write_char('\n')
}
}
}
///|
fn render_yaml_sequence(
elements : Array[Value],
indent : Int,
indent_width : Int,
buf : StringBuilder,
) -> Unit {
for i = 0; i < elements.length(); i = i + 1 {
write_yaml_indent(buf, indent)
buf.write_char('-')
render_yaml_seq_item(elements[i], indent + indent_width, indent_width, buf)
}
}
///|
fn render_yaml_seq_item(
value : Value,
indent : Int,
indent_width : Int,
buf : StringBuilder,
) -> Unit {
// The first sub-member or sub-element shares the `- ` line; the rest are
// continued at `indent` (two spaces past the `- `).
match render_directive_text(value) {
Some(text) => {
buf.write_string(text)
buf.write_char('\n')
return
}
None => ()
}
match value {
ObjectValue(members) => {
let visible = visible_members(members)
if visible.length() == 0 {
buf.write_string(" {}\n")
} else {
write_yaml_inline_padding(buf, indent_width)
render_yaml_member_key(visible[0].name, visible[0].value, buf)
buf.write_char(':')
render_yaml_block_value(visible[0].value, indent, indent_width, buf)
for i = 1; i < visible.length(); i = i + 1 {
write_yaml_indent(buf, indent)
render_yaml_member_key(visible[i].name, visible[i].value, buf)
buf.write_char(':')
render_yaml_block_value(visible[i].value, indent, indent_width, buf)
}
}
}
MappingValue(entries) | DefaultedMappingValue(_, entries, _) =>
if entries.length() == 0 {
buf.write_string(" {}\n")
} else {
write_yaml_inline_padding(buf, indent_width)
render_yaml_entry_key(entries[0].key, buf)
buf.write_char(':')
render_yaml_block_value(entries[0].value, indent, indent_width, buf)
for i = 1; i < entries.length(); i = i + 1 {
write_yaml_indent(buf, indent)
render_yaml_entry_key(entries[i].key, buf)
buf.write_char(':')
render_yaml_block_value(entries[i].value, indent, indent_width, buf)
}
}
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements) =>
if elements.length() == 0 {
buf.write_string(" []\n")
} else {
write_yaml_inline_padding(buf, indent_width)
buf.write_char('-')
render_yaml_seq_item(
elements[0],
indent + indent_width,
indent_width,
buf,
)
for i = 1; i < elements.length(); i = i + 1 {
write_yaml_indent(buf, indent)
buf.write_char('-')
render_yaml_seq_item(
elements[i],
indent + indent_width,
indent_width,
buf,
)
}
}
SetValue(elements) =>
if elements.length() == 0 {
buf.write_string(" []\n")
} else {
write_yaml_inline_padding(buf, indent_width)
buf.write_char('-')
render_yaml_seq_item(
elements[0],
indent + indent_width,
indent_width,
buf,
)
for i = 1; i < elements.length(); i = i + 1 {
write_yaml_indent(buf, indent)
buf.write_char('-')
render_yaml_seq_item(
elements[i],
indent + indent_width,
indent_width,
buf,
)
}
}
MapValue(entries) =>
if entries.length() == 0 {
buf.write_string(" {}\n")
} else {
write_yaml_inline_padding(buf, indent_width)
render_yaml_entry_key(entries[0].key, buf)
buf.write_char(':')
render_yaml_block_value(entries[0].value, indent, indent_width, buf)
for i = 1; i < entries.length(); i = i + 1 {
write_yaml_indent(buf, indent)
render_yaml_entry_key(entries[i].key, buf)
buf.write_char(':')
render_yaml_block_value(entries[i].value, indent, indent_width, buf)
}
}
StringValue(s) =>
match yaml_string_block_scalar_info(s, indent_width) {
Some((indicator, lines, trailing)) =>
render_yaml_block_scalar(indicator, lines, trailing, indent, buf)
None => {
buf.write_char(' ')
render_yaml_scalar(value, buf)
buf.write_char('\n')
}
}
_ => {
buf.write_char(' ')
render_yaml_scalar(value, buf)
buf.write_char('\n')
}
}
}
///|
/// PKL-125: decide whether `s` qualifies for a YAML literal block
/// scalar (`|`, `|-`, `|+`). A string is eligible when:
/// - it has either an internal newline or trailing newlines, so the
/// block form preserves line structure without double-quoted
/// escapes;
/// - it carries no control characters other than `\n`,
/// which would otherwise need `\uXXXX` escapes that the block
/// form cannot express;
/// - the first content line gets an explicit indentation indicator
/// (`|2`, `|4`, ...) when it starts with whitespace.
///
/// The chomping indicator follows the trailing-newline count:
/// - exactly 1 trailing `\n` → `|` (clip — single final newline)
/// - 0 trailing `\n` → `|-` (strip — no final newline)
/// - 2+ trailing `\n` → `|+` (keep — preserve all)
///
/// Returns `Some((indicator, content_lines, trailing))` on
/// eligibility, where `content_lines` are the lines stripped of
/// every trailing newline and `trailing` is the original trailing
/// newline count (the `|+` path needs it to emit the extra blank
/// lines that preserve the run).
fn yaml_string_block_scalar_info(
s : String,
indent_width : Int,
) -> (String, Array[String], Int)? {
if s.length() == 0 {
return None
}
// Count trailing `\n` characters.
let mut trailing = 0
while trailing < s.length() && s[s.length() - 1 - trailing].to_int() == 0x0A {
trailing = trailing + 1
}
let core_len = s.length() - trailing
let core = String::unsafe_substring(s, start=0, end=core_len)
// Need at least one line break to beat the inline double-quote form.
let mut has_internal_newline = false
for c in core {
if c == '\n' {
has_internal_newline = true
break
}
}
if !has_internal_newline && trailing == 0 {
return None
}
// Reject anything with control chars other than `\n`.
for c in core {
if c < ' ' && c != '\n' {
return None
}
if c == '\u{7F}' {
return None
}
}
// Split into lines. When the first content line starts with whitespace,
// YAML needs an explicit indentation indicator (`|2-`, `|4-`, ...),
// otherwise the leading spaces would be confused with the scalar's own
// indentation. Later lines can carry extra indentation without the
// indicator.
let lines : Array[String] = []
for piece in core.split("\n") {
lines.push(piece.to_owned())
}
let mut needs_indent_indicator = false
if lines.length() > 0 && lines[0].length() > 0 {
let first = lines[0][0].to_int().unsafe_to_char()
if first == ' ' || first == '\t' {
needs_indent_indicator = true
}
}
let indent_indicator = if needs_indent_indicator {
"\{indent_width}"
} else {
""
}
let keep_all_newlines = core_len == 0 && trailing > 0
let indicator = if keep_all_newlines {
"|\{indent_indicator}+"
} else if trailing == 1 {
"|\{indent_indicator}"
} else if trailing == 0 {
"|\{indent_indicator}-"
} else {
"|\{indent_indicator}+"
}
Some((indicator, lines, trailing))
}
///|
/// PKL-125: emit a literal block scalar. The leading space and
/// indicator share the same line as the preceding `key:` / `-`;
/// each content line follows at `content_indent` spaces. Blank
/// content lines emit as a bare `\n` (YAML literal block scalars
/// allow empty lines to be undented). For the keep chomping (`|+`)
/// the source had ≥2 trailing newlines; the natural line break
/// after the last content line covers one of them, so `(trailing -
/// 1)` extra bare newlines preserve the rest.
fn render_yaml_block_scalar(
indicator : String,
lines : Array[String],
trailing : Int,
content_indent : Int,
buf : StringBuilder,
) -> Unit {
buf.write_char(' ')
buf.write_string(indicator)
buf.write_char('\n')
for line in lines {
if line.length() == 0 {
buf.write_char('\n')
} else {
write_yaml_indent(buf, content_indent)
buf.write_string(line)
buf.write_char('\n')
}
}
if indicator.has_suffix("+") {
let mut extras = trailing - 1
while extras > 0 {
buf.write_char('\n')
extras = extras - 1
}
}
}
///|
fn render_yaml_scalar(value : Value, buf : StringBuilder) -> Unit {
match value {
IntValue(n) => buf.write_string("\{n}")
FloatValue(d) => buf.write_string(render_float_text(d))
BoolValue(true) => buf.write_string("true")
BoolValue(false) => buf.write_string("false")
NullValue => buf.write_string("null")
StringValue(s) => render_yaml_string(s, buf)
DurationValue(n, unit) => render_yaml_string("\{n}.\{unit}", buf)
DataSizeValue(n, unit) => render_yaml_string("\{n}.\{unit}", buf)
RegexValue(pattern) => render_yaml_string(pattern, buf)
BytesValue(bytes) => {
buf.write_string("!!binary ")
let encoded = @base64.encode(bytes[:])
if encoded.length() == 0 {
render_yaml_string(encoded, buf)
} else {
buf.write_string(encoded)
}
}
_ =>
// Composite values reach this path only via the same renderer fallback
// that PCF / JSON use, so produce a deterministic flow form rather than
// panic. Anything richer is handled by the block-mode entry points.
render_pcf_inline(value, 0, false, buf)
}
}
///|
fn render_yaml_member_key(
name : String,
value : Value,
buf : StringBuilder,
) -> Unit {
render_yaml_key_string(
yaml_unquote_pkl_identifier_name(name),
yaml_value_starts_with_percent(value),
buf,
)
}
///|
fn render_yaml_key_string(
s : String,
force_single_quote : Bool,
buf : StringBuilder,
) -> Unit {
if s == "" {
buf.write_string("''")
return
}
if yaml_string_needs_double_quote(s) {
render_yaml_double_quoted(s, buf)
return
}
if force_single_quote ||
yaml_string_needs_single_quote(s) ||
yaml_key_needs_single_quote(s) {
render_yaml_single_quoted(s, buf)
return
}
buf.write_string(s)
}
///|
fn render_yaml_entry_key(value : Value, buf : StringBuilder) -> Unit {
match render_directive_text(value) {
Some(text) => {
buf.write_string(text)
return
}
None => ()
}
match value {
StringValue(s) => render_yaml_key_string(s, false, buf)
IntValue(_) | FloatValue(_) | BoolValue(_) | NullValue =>
render_yaml_scalar(value, buf)
_ => render_yaml_key_string(yaml_coerce_key(value), false, buf)
}
}
///|
fn render_yaml_string(s : String, buf : StringBuilder) -> Unit {
if s == "" {
buf.write_string("''")
return
}
if yaml_string_needs_double_quote(s) {
render_yaml_double_quoted(s, buf)
return
}
if yaml_string_needs_single_quote(s) {
render_yaml_single_quoted(s, buf)
return
}
buf.write_string(s)
}
///|
fn yaml_unquote_pkl_identifier_name(name : String) -> String {
if name.length() >= 2 && name.has_prefix("`") && name.has_suffix("`") {
String::unsafe_substring(name, start=1, end=name.length() - 1)
} else {
name
}
}
///|
fn yaml_value_starts_with_percent(value : Value) -> Bool {
match value {
StringValue(s) => {
for c in s {
return c == '%'
}
false
}
_ => false
}
}
///|
fn yaml_key_needs_single_quote(s : String) -> Bool {
for c in s {
match c {
'[' | ']' | '{' | '}' | ',' => return true
_ => ()
}
}
false
}
///|
fn yaml_string_key_needs_explicit(s : String) -> Bool {
if s.length() > 1024 {
return true
}
for c in s {
if c == '\n' {
return true
}
}
false
}
///|
fn yaml_string_needs_double_quote(s : String) -> Bool {
for c in s {
if c < ' ' ||
c == '\u{7F}' ||
c == '\u{85}' ||
c == '\u{A0}' ||
c == '\u{2028}' ||
c == '\u{2029}' ||
c == '\\' {
return true
}
}
false
}
///|
fn yaml_string_needs_single_quote(s : String) -> Bool {
// Reserved YAML 1.1 plain-scalar keywords.
if yaml_is_reserved_keyword(s) {
return true
}
if yaml_looks_like_number(s) {
return true
}
let chars : Array[Char] = []
for c in s {
chars.push(c)
}
if chars.length() == 0 {
return false
}
let first = chars[0]
if first == ' ' || first == '\t' {
return true
}
if first == '-' || first == '?' || first == ':' {
if chars.length() == 1 || chars[1] == ' ' || chars[1] == '\t' {
return true
}
}
// Leading indicator characters that would otherwise alter parsing.
match first {
','
| '['
| ']'
| '{'
| '}'
| '#'
| '&'
| '*'
| '!'
| '|'
| '>'
| '\''
| '"'
| '%'
| '@'
| '`' => return true
_ => ()
}
// Trailing whitespace.
let mut last : Char = first
for c in s {
last = c
}
if last == ' ' || last == '\t' || last == ':' {
return true
}
// Embedded `: ` or ` #` flips a plain scalar into something the YAML parser
// would re-interpret, so reach for single quotes.
if yaml_contains_pair(s, ':', ' ') || yaml_contains_pair(s, ' ', '#') {
return true
}
false
}
///|
fn yaml_is_reserved_keyword(s : String) -> Bool {
// 1.2 core schema: only `true|false|null` (and `~` for null) are
// reserved scalars. 1.1 / compat additionally treat the y/yes/on
// boolean aliases as reserved.
let mode = yaml_render_mode_current()
match s {
"true"
| "True"
| "TRUE"
| "false"
| "False"
| "FALSE"
| "null"
| "Null"
| "NULL"
| "~" => true
_ =>
if mode == "1.2" {
false
} else {
match s {
"yes"
| "Yes"
| "YES"
| "no"
| "No"
| "NO"
| "on"
| "On"
| "ON"
| "off"
| "Off"
| "OFF"
| "y"
| "Y"
| "n"
| "N" => true
_ => false
}
}
}
}
///|
fn yaml_looks_like_number(s : String) -> Bool {
let mode = yaml_render_mode_current()
if mode == "1.2" {
return yaml_looks_like_number_v12(s) || yaml_looks_like_special_float_v12(s)
}
// YAML 1.1 does not recognise the 1.2 `0o` spelling, nor an exponent
// without an explicit sign. Compat is deliberately the union of both
// schemas, while strict 1.1 must leave those spellings plain.
if mode == "1.1" &&
(yaml_is_v12_octal_spelling(s) || yaml_is_v12_unsigned_exponent_spelling(s)) {
return false
}
yaml_looks_like_number_v12(s) ||
yaml_looks_like_special_float_v12(s) ||
yaml_looks_like_v11_only_number(s)
}
///|
fn yaml_is_v12_octal_spelling(s : String) -> Bool {
let len = s.length()
if len == 0 {
return false
}
let idx = if s[0] == '+' || s[0] == '-' { 1 } else { 0 }
idx + 1 < len && s[idx] == '0' && (s[idx + 1] == 'o' || s[idx + 1] == 'O')
}
///|
fn yaml_is_v12_unsigned_exponent_spelling(s : String) -> Bool {
let len = s.length()
if len == 0 {
return false
}
let prefix = if s[0] == '+' || s[0] == '-' { 1 } else { 0 }
if prefix + 1 < len &&
s[prefix] == '0' &&
(s[prefix + 1] == 'x' || s[prefix + 1] == 'X') {
return false
}
for i in 0..<(len - 1) {
if (s[i] == 'e' || s[i] == 'E') && s[i + 1] is ('0'..='9') {
return true
}
}
false
}
///|
/// Matches the YAML 1.2 core-schema integer / float regex grammar.
/// - decimal int: `[-+]?[0-9]+`
/// - octal: `0o[0-7]+` (unsigned only)
/// - hex: `0x[0-9a-fA-F]+` (unsigned only)
/// - float: `[-+]? ( \.[0-9]+ | [0-9]+ (\.[0-9]*)? ) ( [eE] [-+]? [0-9]+ )?`
fn yaml_looks_like_number_v12(s : String) -> Bool {
let len = s.length()
if len == 0 {
return false
}
if len >= 2 && s[0] == '0' && (s[1] == 'o' || s[1] == 'O') {
if len == 2 {
return false
}
for i in 2..= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') {
if len == 2 {
return false
}
for i in 2.. Bool {
match s {
".inf"
| ".Inf"
| ".INF"
| "+.inf"
| "+.Inf"
| "+.INF"
| "-.inf"
| "-.Inf"
| "-.INF"
| ".nan"
| ".NaN"
| ".NAN" => true
_ => false
}
}
///|
/// 1.1-only number-looking patterns that compat / 1.1 modes should
/// still quote when they appear as YAML output strings:
/// - binary `[-+]?0b[01]+`
/// - signed octal / hex `[-+](0o[0-7]+|0x[0-9a-fA-F]+)`
/// - base-60 `[-+]?[0-9]+(:[0-5]?[0-9])+(\.[0-9]*)?`
/// - underscore-separated `[0-9]+(_[0-9]+)*` (with optional `.frac`)
/// - bare leading `0` octal `0[0-7]+`
fn yaml_looks_like_v11_only_number(s : String) -> Bool {
let len = s.length()
if len == 0 {
return false
}
// YAML 1.1 treats a bare dot, optionally followed only by numeric
// separators, as zero. Quote it so a rendered String round-trips.
if s[0] == '.' {
let mut zero_float = true
for i in 1..= 2 && s[1] is ('0'..='9') && yaml_string_all_digits(s) {
return true
}
false
}
///|
fn yaml_string_all_digits(s : String) -> Bool {
for c in s {
if !(c is ('0'..='9')) {
return false
}
}
true
}
///|
fn yaml_looks_like_base60(s : String) -> Bool {
// Pattern: optional sign, digits, then one or more `:digits`
// groups, optionally followed by `.digits`. Used by YAML 1.1.
let len = s.length()
if len == 0 {
return false
}
let mut idx = 0
if s[idx] == '+' || s[idx] == '-' {
idx = idx + 1
}
let head_start = idx
while idx < len && s[idx] is ('0'..='9') {
idx = idx + 1
}
if idx == head_start {
return false
}
if idx >= len || s[idx] != ':' {
return false
}
while idx < len && s[idx] == ':' {
idx = idx + 1
let group_start = idx
while idx < len && s[idx] is ('0'..='9') {
idx = idx + 1
}
if idx == group_start {
return false
}
}
if idx < len && s[idx] == '.' {
idx = idx + 1
while idx < len && s[idx] is ('0'..='9') {
idx = idx + 1
}
}
idx == len
}
///|
fn yaml_contains_pair(s : String, a : Char, b : Char) -> Bool {
let mut prev : Char = '\u{0}'
let mut have_prev = false
for c in s {
if have_prev && prev == a && c == b {
return true
}
prev = c
have_prev = true
}
false
}
///|
fn render_yaml_single_quoted(s : String, buf : StringBuilder) -> Unit {
buf.write_char('\'')
for c in s {
if c == '\'' {
buf.write_string("''")
} else {
buf.write_char(c)
}
}
buf.write_char('\'')
}
///|
fn render_yaml_double_quoted(s : String, buf : StringBuilder) -> Unit {
buf.write_char('"')
for c in s {
match c {
'"' => buf.write_string("\\\"")
'\\' => buf.write_string("\\\\")
'\n' => buf.write_string("\\n")
'\r' => buf.write_string("\\r")
'\t' => buf.write_string("\\t")
'\u{00}' => buf.write_string("\\0")
'\u{07}' => buf.write_string("\\a")
'\u{08}' => buf.write_string("\\b")
'\u{0B}' => buf.write_string("\\v")
'\u{0C}' => buf.write_string("\\f")
'\u{1B}' => buf.write_string("\\e")
'\u{85}' => buf.write_string("\\N")
'\u{A0}' => buf.write_string("\\_")
'\u{2028}' => buf.write_string("\\L")
'\u{2029}' => buf.write_string("\\P")
_ =>
if c < ' ' || c == '\u{7F}' {
let code = c.to_int()
let hex = json_hex4(code)
buf.write_string("\\u")
buf.write_string(hex)
} else {
buf.write_char(c)
}
}
}
buf.write_char('"')
}
///|
fn yaml_coerce_key(value : Value) -> String {
match value {
StringValue(s) => s
IntValue(n) => "\{n}"
BoolValue(true) => "true"
BoolValue(false) => "false"
NullValue => "null"
_ => {
let buf = StringBuilder::new()
render_pcf_inline(value, 0, false, buf)
buf.to_string()
}
}
}
///|
fn write_yaml_indent(buf : StringBuilder, indent : Int) -> Unit {
for i = 0; i < indent; i = i + 1 {
buf.write_char(' ')
}
}
///|
fn write_yaml_inline_padding(buf : StringBuilder, indent_width : Int) -> Unit {
for i = 1; i < indent_width; i = i + 1 {
buf.write_char(' ')
}
}
///|
/// PKL-126a: Render a Value as an Apple plist XML document, matching
/// the upstream `PListRenderer` output. The output starts with the
/// XML 1.0 prolog and the Apple PLIST 1.0 DOCTYPE, then wraps the
/// rendered value in a `` element.
///
/// Value mapping:
/// - Int → `N`
/// - Float → `D`
/// - Bool → `` / ``
/// - String → `...` (XML-escaped)
/// - Null → skipped inside `` (Apple `omitNullProperties`
/// behaviour); inside `` it would error upstream,
/// but PKL-126a's slice elides it silently — the
/// error-on-null-in-array surface lands with PKL-127 so
/// the diagnostic flows through the same converter path
/// as the rest of the renderer-side errors.
/// - Object / Mapping → `` with `` + value pairs.
/// - Listing → `` of values.
/// - Duration / DataSize → `N unit` (space-separated
/// numeric + unit, matching Apple PListRenderer rather
/// than the `.` form JSON / YAML use for these values).
/// - Regex → `pattern`.
/// - Bytes → `base64`.
///
/// Indentation defaults to two spaces; the eventual converter-aware
/// path (PKL-127) will honor the `indent` property when it threads
/// renderer config through to this entry point.