///|
pub fn render_value(value : Value) -> String {
render_value_with_custom_string_delimiters(value, false)
}
///|
fn render_value_with_custom_string_delimiters(
value : Value,
use_custom_string_delimiters : Bool,
) -> String {
// Preserve the document-vs-fragment contract after forcing an output
// thunk. Dispatching on the handle itself would route an ObjectValue
// through `render_pcf_inline` and incorrectly add a top-level `new {}`.
let value = force_eval_thunk(value)
let buf = StringBuilder::new()
if deferred_error_message(value) is Some(_) {
return "?"
}
// PKL-153f: a `RenderDirective` at the document root (`renderer.
// renderDocument(new RenderDirective {...})`) emits its `text`
// verbatim across every renderer, including PCF.
match render_directive_text(value) {
Some(text) => return text
None => ()
}
match value {
ObjectValue(members) =>
render_pcf_module(members, use_custom_string_delimiters, buf)
_ => render_pcf_inline(value, 0, use_custom_string_delimiters, buf)
}
buf.to_string()
}
///|
pub fn render_value_with_pcf_indent(
value : Value,
indent_text : String,
) -> String {
pcf_apply_indent_text(render_value(value), indent_text)
}
///|
pub fn render_value_with_pcf_options(
value : Value,
indent_text : String,
use_custom_string_delimiters : Bool,
) -> String {
pcf_apply_indent_text(
render_value_with_custom_string_delimiters(
value, use_custom_string_delimiters,
),
indent_text,
)
}
///|
pub fn render_value_as_pcf_document(value : Value) -> String {
render_value(value) + "\n"
}
///|
pub fn render_value_as_pcf_document_with_indent(
value : Value,
indent_text : String,
) -> String {
pcf_apply_indent_text(render_value_as_pcf_document(value), indent_text)
}
///|
pub fn render_value_as_pcf_document_with_options(
value : Value,
indent_text : String,
use_custom_string_delimiters : Bool,
) -> String {
pcf_apply_indent_text(
render_value_with_custom_string_delimiters(
value, use_custom_string_delimiters,
) +
"\n",
indent_text,
)
}
///|
pub fn render_value_as_pcf_fragment(value : Value) -> String {
render_value_as_pcf_fragment_with_custom_string_delimiters(value, false)
}
///|
fn render_value_as_pcf_fragment_with_custom_string_delimiters(
value : Value,
use_custom_string_delimiters : Bool,
) -> String {
// PKL-153f: directive shortcut — `renderValue(new RenderDirective
// { text = "…" })` emits the text raw across every renderer.
match render_directive_text(value) {
Some(text) => return text
None => ()
}
let buf = StringBuilder::new()
if pcf_is_block_value(value) {
render_pcf_block_body(value, 0, use_custom_string_delimiters, buf)
} else {
render_pcf_inline(value, 0, use_custom_string_delimiters, buf)
}
buf.to_string()
}
///|
pub fn render_value_as_pcf_fragment_with_indent(
value : Value,
indent_text : String,
) -> String {
pcf_apply_indent_text(render_value_as_pcf_fragment(value), indent_text)
}
///|
pub fn render_value_as_pcf_fragment_with_options(
value : Value,
indent_text : String,
use_custom_string_delimiters : Bool,
) -> String {
pcf_apply_indent_text(
render_value_as_pcf_fragment_with_custom_string_delimiters(
value, use_custom_string_delimiters,
),
indent_text,
)
}
///|
fn pcf_apply_indent_text(text : String, indent_text : String) -> String {
if indent_text == " " {
return text
}
let buf = StringBuilder::new()
let mut i = 0
let mut at_line_start = true
while i < text.length() {
if at_line_start {
let mut spaces = 0
while i < text.length() && text[i].to_int().unsafe_to_char() == ' ' {
spaces = spaces + 1
i = i + 1
}
for level = 0; level < spaces / 2; level = level + 1 {
buf.write_string(indent_text)
}
if spaces % 2 == 1 {
buf.write_char(' ')
}
at_line_start = false
if i >= text.length() {
break
}
}
let c = text[i].to_int().unsafe_to_char()
buf.write_char(c)
i = i + 1
if c == '\n' {
at_line_start = true
}
}
buf.to_string()
}
///|
fn escape_string(value : String) -> String {
let buf = StringBuilder::new()
for c in value {
match c {
'\n' => buf.write_string("\\n")
'\t' => buf.write_string("\\t")
'\r' => buf.write_string("\\r")
'"' => buf.write_string("\\\"")
'\\' => buf.write_string("\\\\")
_ => buf.write_char(c)
}
}
buf.to_string()
}
///|
fn pcf_is_block_value(value : Value) -> Bool {
let value = force_eval_thunk(value)
if render_directive_text(value) is Some(_) {
return false
}
if deferred_error_message(value) is Some(_) {
return false
}
if value is ObjectValue(members) && is_reference_value_members(members) {
return false
}
match value {
ObjectValue(_)
| ListingValue(_)
| DefaultedListingValue(_, _, _)
| MappingValue(_)
| DefaultedMappingValue(_, _, _)
| DeferredImportValue(_) => true
// PKL-148h: `ListValue` renders through the `List(...)` constructor
// form (handled in `render_pcf_scalar`), not as a `new { ... }`
// block — so callers must NOT prefix it with `new`.
_ => false
}
}
///|
fn write_pcf_indent(buf : StringBuilder, indent : Int) -> Unit {
for i = 0; i < indent; i = i + 1 {
buf.write_char(' ')
}
}
///|
fn render_float_text(d : Double) -> String {
if is_negative_zero(d) {
return "-0.0"
}
if d.is_nan() {
return "NaN"
}
if d.is_inf() {
if d < 0.0 {
return "-Infinity"
}
return "Infinity"
}
if d > 0.0 && d < 1.0e-323 {
return "4.9E-324"
}
if d < 0.0 && d > -1.0e-323 {
return "-4.9E-324"
}
if d >= 1.797693134862315e308 {
return "1.7976931348623157E308"
}
if d <= -1.797693134862315e308 {
return "-1.7976931348623157E308"
}
// PKL-092: render a Float deterministically. Apple Pkl emits Doubles in
// the shortest round-trip form (`1.5`, `12.3`, `1.0` — never `1`), so
// we anchor on `Double::to_string()` and append `.0` when the result
// has no fractional or exponent component (otherwise `42.0` would
// round-trip as `42` and pun as an Int).
let s = d.to_string()
match normalize_scientific_float_text(s) {
Some(text) => return text
None => ()
}
match render_large_decimal_float_scientific(s) {
Some(text) => return text
None => ()
}
match render_small_decimal_float_scientific(s) {
Some(text) => return text
None => ()
}
if s.find(".") is None &&
s.find("e") is None &&
s.find("E") is None &&
s != "Infinity" &&
s != "-Infinity" &&
s != "NaN" {
s + ".0"
} else {
s
}
}
///|
fn normalize_scientific_float_text(s : String) -> String? {
let mut exp_idx = -1
for i = 0; i < s.length(); i = i + 1 {
let c = s[i].to_int().unsafe_to_char()
if c == 'e' || c == 'E' {
exp_idx = i
break
}
}
if exp_idx < 0 {
return None
}
let mantissa = String::unsafe_substring(s, start=0, end=exp_idx)
let mut exp_start = exp_idx + 1
if exp_start < s.length() && s[exp_start].to_int().unsafe_to_char() == '+' {
exp_start = exp_start + 1
}
let mut negative_exp = false
if exp_start < s.length() && s[exp_start].to_int().unsafe_to_char() == '-' {
negative_exp = true
exp_start = exp_start + 1
}
while exp_start + 1 < s.length() &&
s[exp_start].to_int().unsafe_to_char() == '0' {
exp_start = exp_start + 1
}
let buf = StringBuilder::new()
buf.write_string(mantissa)
if mantissa.find(".") is None {
buf.write_string(".0")
}
buf.write_char('E')
if negative_exp {
buf.write_char('-')
}
buf.write_string(String::unsafe_substring(s, start=exp_start, end=s.length()))
Some(buf.to_string())
}
///|
fn render_large_decimal_float_scientific(s : String) -> String? {
let (negative, body) = if s.has_prefix("-") {
(true, String::unsafe_substring(s, start=1, end=s.length()))
} else {
(false, s)
}
if body.find("e") is Some(_) || body.find("E") is Some(_) {
return None
}
let mut dot_idx = body.length()
for i = 0; i < body.length(); i = i + 1 {
if body[i].to_int().unsafe_to_char() == '.' {
dot_idx = i
break
}
}
if dot_idx < 9 {
return None
}
let digits_buf = StringBuilder::new()
for i = 0; i < body.length(); i = i + 1 {
let c = body[i].to_int().unsafe_to_char()
if c >= '0' && c <= '9' {
digits_buf.write_char(c)
}
}
let digits_raw = digits_buf.to_string()
let mut end = digits_raw.length()
while end > 1 && digits_raw[end - 1].to_int().unsafe_to_char() == '0' {
end = end - 1
}
let digits = String::unsafe_substring(digits_raw, start=0, end~)
let buf = StringBuilder::new()
if negative {
buf.write_char('-')
}
buf.write_char(digits[0].to_int().unsafe_to_char())
if digits.length() == 1 {
buf.write_string(".0")
} else {
buf.write_char('.')
buf.write_string(
String::unsafe_substring(digits, start=1, end=digits.length()),
)
}
buf.write_char('E')
buf.write_string((dot_idx - 1).to_string())
Some(buf.to_string())
}
///|
fn render_small_decimal_float_scientific(s : String) -> String? {
let (negative, body) = if s.has_prefix("-") {
(true, String::unsafe_substring(s, start=1, end=s.length()))
} else {
(false, s)
}
if !body.has_prefix("0.") {
return None
}
let frac = String::unsafe_substring(body, start=2, end=body.length())
let mut zeros = 0
while zeros < frac.length() && frac[zeros].to_int().unsafe_to_char() == '0' {
zeros = zeros + 1
}
if zeros < 3 || zeros >= frac.length() {
return None
}
let significant = String::unsafe_substring(
frac,
start=zeros,
end=frac.length(),
)
let max_digits = 16
let digit_end = if significant.length() > max_digits {
max_digits
} else {
significant.length()
}
let mut end = digit_end
while end > 1 && significant[end - 1].to_int().unsafe_to_char() == '0' {
end = end - 1
}
let digits = String::unsafe_substring(significant, start=0, end~)
let buf = StringBuilder::new()
if negative {
buf.write_char('-')
}
buf.write_char(digits[0].to_int().unsafe_to_char())
if digits.length() > 1 {
buf.write_char('.')
buf.write_string(
String::unsafe_substring(digits, start=1, end=digits.length()),
)
}
buf.write_string("E-")
buf.write_string((zeros + 1).to_string())
Some(buf.to_string())
}
///|
fn render_pcf_scalar(value : Value, buf : StringBuilder) -> Unit {
render_pcf_scalar_with_indent(value, 0, false, buf)
}
///|
/// PKL-148bb: indent-aware scalar / constructor render. The element
/// values nested inside `Set(...)`, `List(...)`, `Map(...)`, `Pair(...)`
/// previously rendered at indent 0; when the element was itself a block
/// (`new Foo { ... }`), the inner body opened at column 2 regardless
/// of the enclosing context's depth. Apple Pkl indents the inner block
/// relative to the enclosing column so `classes/class2a` projects
/// `friends = Set(new { name = "Emma" ... })` with the body sitting at
/// the same depth as `friends`'s siblings.
fn render_pcf_scalar_with_indent(
value : Value,
indent : Int,
use_custom_string_delimiters : Bool,
buf : StringBuilder,
) -> Unit {
if deferred_error_message(value) is Some(_) {
buf.write_string("?")
return
}
match value {
ThunkValue(_) =>
render_pcf_scalar_with_indent(
force_eval_thunk(value),
indent,
use_custom_string_delimiters,
buf,
)
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")
StringValue(s) =>
if pcf_bare_marker_text(s) is Some(text) {
// PKL-080: snippet-test runner's line / column placeholder
// (`X`, `XX`, `XXXX`) lands in PCF position as a *bare* token
// — Apple Pkl's gold has `line = X` without quotes. We tag
// such placeholders with a `\u{001f}BARE:` sentinel and emit
// the trailing text raw so the diff matches.
buf.write_string(text)
} else {
render_pcf_string_value(s, indent, use_custom_string_delimiters, buf)
}
NullValue => buf.write_string("null")
DeferredImportValue(_) => buf.write_string("new {}")
FunctionValue(_, _, _, _, _) => buf.write_string("")
DurationValue(n, unit) => buf.write_string("\{n}.\{unit}")
DataSizeValue(n, unit) => buf.write_string("\{n}.\{unit}")
RegexValue(pattern) =>
if pattern.contains("\\") && !pattern.contains("#\"") {
buf.write_string("Regex(#\"")
buf.write_string(pattern)
buf.write_string("\"#)")
} else {
buf.write_string("Regex(\"")
buf.write_string(escape_string(pattern))
buf.write_string("\")")
}
BytesValue(bytes) =>
// Apple Pkl renders Bytes through the varargs constructor.
buf.write_string(render_bytes_value_inline(bytes))
// PKL-119a: PCF round-trips `Pair(a, b)` through Apple Pkl's
// constructor form. Element values flow through `render_pcf_inline`
// so nested composites stay valid PCF (`Pair(new { x = 1 }, 2)`).
PairValue(first, second) => {
buf.write_string("Pair(")
render_pcf_inline(first, indent, use_custom_string_delimiters, buf)
buf.write_string(", ")
render_pcf_inline(second, indent, use_custom_string_delimiters, buf)
buf.write_char(')')
}
// PKL-119b: PCF round-trips IntSeq through Apple Pkl's constructor
// form. Default `step = 1` stays implicit; any other step renders
// as a chained `.step(n)` call so re-parsing the output yields the
// same IntSeq.
IntSeqValue(start, end_v, step) => {
buf.write_string("IntSeq(\{start}, \{end_v})")
if step != 1 {
buf.write_string(".step(\{step})")
}
}
// PKL-119c: PCF round-trips through Apple Pkl's `Set(...)`
// constructor form. Element values flow through
// `render_pcf_inline` so nested composites stay valid PCF.
SetValue(elements) => {
buf.write_string("Set(")
for i = 0; i < elements.length(); i = i + 1 {
if i > 0 {
buf.write_string(", ")
}
render_pcf_inline(
elements[i],
indent,
use_custom_string_delimiters,
buf,
)
}
buf.write_char(')')
}
// PKL-148h: `ListValue` round-trips through `List(...)` to match
// upstream PCF (`new { 1; 2 }` would parse back as a Listing, not a
// List). Element values flow through `render_pcf_inline` so nested
// composites — including `new Listing { ... }` blocks inside a
// `List(...)` — stay valid PCF.
ListValue(elements) => {
buf.write_string("List(")
for i = 0; i < elements.length(); i = i + 1 {
if i > 0 {
buf.write_string(", ")
}
render_pcf_inline(
elements[i],
indent,
use_custom_string_delimiters,
buf,
)
}
buf.write_char(')')
}
// PKL-119d: PCF round-trips through Apple Pkl's `Map(...)`
// constructor form (alternating key, value, key, value, ...).
// Reparsing the output yields the same MapValue.
MapValue(entries) => {
buf.write_string("Map(")
for i = 0; i < entries.length(); i = i + 1 {
if i > 0 {
buf.write_string(", ")
}
render_pcf_inline(
entries[i].key,
indent,
use_custom_string_delimiters,
buf,
)
buf.write_string(", ")
render_pcf_inline(
entries[i].value,
indent,
use_custom_string_delimiters,
buf,
)
}
buf.write_char(')')
}
ObjectValue(members) =>
if !render_reference_constructor(members, buf) {
render_pcf_inline(value, 0, use_custom_string_delimiters, buf)
}
ListingValue(_)
| DefaultedListingValue(_, _, _)
| MappingValue(_)
| DefaultedMappingValue(_, _, _) =>
// Defensive: callers should route block values through
// render_pcf_inline / render_pcf_block_body. Falling back keeps
// diagnostics non-empty if we ever miss a case.
render_pcf_inline(value, 0, use_custom_string_delimiters, buf)
}
}
///|
fn render_pcf_inline(
value : Value,
indent : Int,
use_custom_string_delimiters : Bool,
buf : StringBuilder,
) -> Unit {
if pcf_is_block_value(value) {
buf.write_string("new ")
render_pcf_block_body(value, indent, use_custom_string_delimiters, buf)
} else if value is StringValue(s) {
render_pcf_string_value(s, indent, use_custom_string_delimiters, buf)
} else {
match render_directive_text(value) {
Some(text) => buf.write_string(text)
None =>
render_pcf_scalar_with_indent(
value, indent, use_custom_string_delimiters, buf,
)
}
}
}
///|
///|
/// PKL-080: when a `StringValue` carries a `\u{001f}BARE:` prefix
/// it's a snippet-test placeholder (`X` / `XX` / `XXXX` / etc.) that
/// must render *unquoted* in PCF position so the snippet-test gold
/// matches. Returns the bare text without the prefix when present.
fn pcf_bare_marker_text(s : String) -> String? {
let prefix = "__PKL_BARE__:"
if s.length() < prefix.length() {
return None
}
for i in 0.. Unit {
if use_custom_string_delimiters &&
pcf_should_use_custom_string_delimiters(s, indent > 0) {
if indent > 0 && s.contains("\n") {
render_pcf_raw_string_heredoc(s, indent, buf)
} else {
render_pcf_raw_string(s, buf)
}
} else if indent > 0 && s.contains("\n") {
// PKL-148al: block-position StringValue with at least one newline
// renders as a PCF triple-quoted heredoc. Apple Pkl emits the
// opening `"""` on the current line, content lines at the same
// indent as the opening for a bare element, and the closing
// `"""` at the same indent. inline-position contexts (indent ==
// 0, e.g. inside `List(...)` or `Pair(...)`) keep the
// single-line escape form.
render_pcf_string_heredoc(s, indent, buf)
} else {
buf.write_char('"')
buf.write_string(escape_string(s))
buf.write_char('"')
}
}
///|
fn pcf_should_use_custom_string_delimiters(
s : String,
block_position : Bool,
) -> Bool {
let has_newline = s.contains("\n")
for c in s {
if c == '\\' {
return true
}
if c == '"' && !has_newline {
return true
}
}
if block_position && has_newline && pcf_raw_heredoc_hash_count(s) > 1 {
return true
}
false
}
///|
fn render_pcf_raw_string(s : String, buf : StringBuilder) -> Unit {
let hashes = pcf_raw_string_hash_count(s)
write_hashes(buf, hashes)
buf.write_char('"')
let mut i = 0
if s.length() >= 2 &&
s[0].to_int().unsafe_to_char() == '"' &&
s[1].to_int().unsafe_to_char() == '"' {
buf.write_char('"')
buf.write_char('\\')
write_hashes(buf, hashes)
buf.write_char('"')
i = 2
}
while i < s.length() {
buf.write_char(s[i].to_int().unsafe_to_char())
i = i + 1
}
buf.write_char('"')
write_hashes(buf, hashes)
}
///|
fn render_pcf_raw_string_heredoc(
s : String,
indent : Int,
buf : StringBuilder,
) -> Unit {
let hashes = pcf_raw_heredoc_hash_count(s)
write_hashes(buf, hashes)
buf.write_string("\"\"\"")
let parts = string_split_newline(s)
for i = 0; i < parts.length(); i = i + 1 {
buf.write_char('\n')
write_pcf_indent(buf, indent)
buf.write_string(parts[i])
}
buf.write_char('\n')
write_pcf_indent(buf, indent)
buf.write_string("\"\"\"")
write_hashes(buf, hashes)
}
///|
fn pcf_raw_string_hash_count(s : String) -> Int {
pcf_hash_count_for_delimiters(s, false)
}
///|
fn pcf_raw_heredoc_hash_count(s : String) -> Int {
pcf_hash_count_for_delimiters(s, true)
}
///|
fn pcf_hash_count_for_delimiters(s : String, heredoc : Bool) -> Int {
let mut hashes = 1
let mut i = 0
while i < s.length() {
let c = s[i].to_int().unsafe_to_char()
if c == '\\' {
let run = count_hash_run(s, i + 1)
if run >= hashes {
hashes = run + 1
}
} else if c == '"' {
if heredoc {
if i + 2 < s.length() &&
s[i + 1].to_int().unsafe_to_char() == '"' &&
s[i + 2].to_int().unsafe_to_char() == '"' {
let run = count_hash_run(s, i + 3)
if run >= hashes {
hashes = run + 1
}
}
} else {
let run = count_hash_run(s, i + 1)
if run >= hashes {
hashes = run + 1
}
}
}
i = i + 1
}
hashes
}
///|
fn count_hash_run(s : String, start : Int) -> Int {
let mut i = start
while i < s.length() && s[i].to_int().unsafe_to_char() == '#' {
i = i + 1
}
i - start
}
///|
fn write_hashes(buf : StringBuilder, count : Int) -> Unit {
for i = 0; i < count; i = i + 1 {
buf.write_char('#')
}
}
///|
/// PKL-148al: PCF triple-quoted heredoc form. The body indent is the
/// caller-supplied value (one level past the surrounding member's name).
/// Apple Pkl preserves blank content lines as empty lines (no trailing
/// whitespace), so an empty segment between two `Flintstone`s emits a
/// bare newline rather than `\n`.
fn render_pcf_string_heredoc(
s : String,
indent : Int,
buf : StringBuilder,
) -> Unit {
buf.write_string("\"\"\"")
let parts = string_split_newline(s)
for i = 0; i < parts.length(); i = i + 1 {
buf.write_char('\n')
write_pcf_indent(buf, indent)
// PKL-148ao: each content line still needs the backslash / tab /
// CR escape pass so a literal `\` round-trips as `\\` and a literal
// tab character as `\t`. `\n` is already consumed by the line
// split. `"` doesn't need escaping inside a heredoc (the close
// delimiter is `"""`, not a single `"`).
buf.write_string(escape_string_heredoc(parts[i]))
}
buf.write_char('\n')
write_pcf_indent(buf, indent)
buf.write_string("\"\"\"")
}
///|
/// PKL-148ao: heredoc-position escape pass. Heredoc strings preserve
/// `"` verbatim (close delimiter is `"""`) but still need to escape
/// the actual `\`, `\t`, and `\r` characters so the rendered source
/// re-parses to the same value. `\n` is line-split before this fires,
/// so it never appears here.
fn escape_string_heredoc(value : String) -> String {
let buf = StringBuilder::new()
for c in value {
match c {
'\\' => buf.write_string("\\\\")
'\t' => buf.write_string("\\t")
'\r' => buf.write_string("\\r")
_ => buf.write_char(c)
}
}
buf.to_string()
}
///|
/// PKL-148al: split a string on `\n` boundaries without using the
/// stdlib `split` (which would also require a CharSet). Pure linear
/// walk that returns the segments (empty segments preserved for
/// consecutive newlines).
fn string_split_newline(s : String) -> Array[String] {
let parts : Array[String] = []
let buf = StringBuilder::new()
for i = 0; i < s.length(); i = i + 1 {
let c = s[i].to_int().unsafe_to_char()
if c == '\n' {
parts.push(buf.to_string())
buf.reset()
} else {
buf.write_char(c)
}
}
parts.push(buf.to_string())
parts
}
///|
fn render_pcf_block_body(
value : Value,
indent : Int,
use_custom_string_delimiters : Bool,
buf : StringBuilder,
) -> Unit {
match value {
ObjectValue([])
| ListingValue([])
| DefaultedListingValue(_, [], _)
| MappingValue([])
| DefaultedMappingValue(_, [], _)
| DeferredImportValue(_) => buf.write_string("{}")
ObjectValue(members) => {
let visible = visible_members(members)
if visible.length() == 0 {
buf.write_string("{}")
return
}
// PKL-148al: Apple Pkl's PCF projection of a Dynamic-shape body
// groups all named properties before any bare elements and
// mapping-style subscript entries; source-order rendering would
// emit `name = "barn owl"; "surfing"; age = 42` but the gold is
// `name = "barn owl"; age = 42; "surfing"`. Partition the
// visible members into (named, sentinel) buckets and emit named
// first, preserving each bucket's source-order. `@element$` and
// `@subscript$` are the dynamic-shape sentinel families; every
// other prefix-free name is a property.
// PKL-148bb: split the dynamic-shape sentinels further — Apple
// Pkl emits `[key] = ...` subscript entries before bare element
// entries inside the same body (`basic/newInsideIf` /
// `basic/newInsideLet` mix subscripts and `if`/`let` expression
// elements; the bare entry must follow the subscript even when
// it textually comes first in the source).
let named : Array[ValueMember] = []
let subscripts : Array[ValueMember] = []
let elements : Array[ValueMember] = []
let mut has_spread_sentinel = false
for field in visible {
if field.name.has_prefix("@subscript$spread") ||
field.name.has_prefix("@element$spread") {
has_spread_sentinel = true
}
if field.name.has_prefix("@subscript$") {
subscripts.push(field)
} else if field.name.has_prefix("@element$") {
elements.push(field)
} else {
named.push(field)
}
}
let ordered : Array[ValueMember] = if has_spread_sentinel {
// PKL-148ax: a spread-heavy Dynamic body preserves spread
// payload order across collection families. ObjectValue
// payloads are normalized before insertion; grouping the whole
// parent would hoist later named members ahead of earlier
// Mapping/List payloads.
visible
} else {
let result : Array[ValueMember] = []
for m in named {
result.push(m)
}
for m in subscripts {
result.push(m)
}
for m in elements {
result.push(m)
}
result
}
buf.write_string("{\n")
for i = 0; i < ordered.length(); i = i + 1 {
if i > 0 {
buf.write_char('\n')
}
write_pcf_indent(buf, indent + 2)
render_pcf_object_member(
ordered[i],
indent + 2,
use_custom_string_delimiters,
buf,
)
}
buf.write_char('\n')
write_pcf_indent(buf, indent)
buf.write_char('}')
}
ListingValue(elements) | DefaultedListingValue(_, elements, _) => {
buf.write_string("{\n")
for i = 0; i < elements.length(); i = i + 1 {
if i > 0 {
buf.write_char('\n')
}
write_pcf_indent(buf, indent + 2)
render_pcf_inline(
elements[i],
indent + 2,
use_custom_string_delimiters,
buf,
)
}
buf.write_char('\n')
write_pcf_indent(buf, indent)
buf.write_char('}')
}
MappingValue(entries) | DefaultedMappingValue(_, entries, _) => {
buf.write_string("{\n")
for i = 0; i < entries.length(); i = i + 1 {
if i > 0 {
buf.write_char('\n')
}
write_pcf_indent(buf, indent + 2)
render_pcf_mapping_entry(
entries[i],
indent + 2,
use_custom_string_delimiters,
buf,
)
}
buf.write_char('\n')
write_pcf_indent(buf, indent)
buf.write_char('}')
}
_ =>
render_pcf_scalar_with_indent(
value, indent, use_custom_string_delimiters, buf,
)
}
}
///|
fn render_pcf_object_member(
field : ValueMember,
indent : Int,
use_custom_string_delimiters : Bool,
buf : StringBuilder,
) -> Unit {
// PKL-148x: Dynamic-shape sentinel members render specially.
// `@element$` projects listing-style (no `name =` prefix);
// `@subscript$` decodes the synthetic `@key`/`@value`
// payload and projects `[key] = value` (or `[key] { ... }` for
// block values).
if field.name.has_prefix("@element$") {
// Use the `render_pcf_inline` form so nested ObjectValue / Listing
// / Mapping elements pick up the `new ` prefix (`new { ... }`),
// matching Apple Pkl's PCF projection of unnamed Dynamic elements.
render_pcf_inline(field.value, indent, use_custom_string_delimiters, buf)
return
}
if field.name.has_prefix("@subscript$") {
match field.value {
ObjectValue(pair_members) =>
match
(
lookup_member(pair_members, "@key"),
lookup_member(pair_members, "@value"),
) {
(Some(k), Some(v)) => {
buf.write_char('[')
render_pcf_inline(k, indent, use_custom_string_delimiters, buf)
buf.write_char(']')
if pcf_is_block_value(v) {
buf.write_char(' ')
render_pcf_block_body(
v, indent, use_custom_string_delimiters, buf,
)
} else {
buf.write_string(" = ")
render_pcf_scalar_with_indent(
v, indent, use_custom_string_delimiters, buf,
)
}
}
_ => ()
}
_ => ()
}
return
}
render_pcf_member_name(field.name, buf)
match render_directive_text(field.value) {
Some(text) => {
buf.write_char(' ')
buf.write_string(text)
return
}
None => ()
}
if pcf_is_block_value(field.value) {
buf.write_char(' ')
render_pcf_block_body(
field.value,
indent,
use_custom_string_delimiters,
buf,
)
} else if field.value is StringValue(s) {
buf.write_string(" = ")
match pcf_bare_marker_text(s) {
Some(text) => buf.write_string(text)
None =>
render_pcf_string_value(
s,
indent + 2,
use_custom_string_delimiters,
buf,
)
}
} else {
buf.write_string(" = ")
render_pcf_scalar_with_indent(
field.value,
indent,
use_custom_string_delimiters,
buf,
)
}
}
///|
fn render_pcf_member_name(name : String, buf : StringBuilder) -> Unit {
if pcf_is_regular_identifier(name) {
buf.write_string(name)
} else {
buf.write_char('`')
for c in name {
if c == '`' || c == '\\' {
buf.write_char('\\')
}
buf.write_char(c)
}
buf.write_char('`')
}
}
///|
fn pcf_is_regular_identifier(name : String) -> Bool {
if name.length() == 0 {
return false
}
let first = name[0].to_int().unsafe_to_char()
if !pcf_is_identifier_start(first) {
return false
}
for i = 1; i < name.length(); i = i + 1 {
let c = name[i].to_int().unsafe_to_char()
if !pcf_is_identifier_continue(c) {
return false
}
}
true
}
///|
fn pcf_is_identifier_start(c : Char) -> Bool {
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
c == '_' ||
c.to_int() >= 128
}
///|
fn pcf_is_identifier_continue(c : Char) -> Bool {
pcf_is_identifier_start(c) || (c >= '0' && c <= '9')
}
///|
fn render_pcf_mapping_entry(
entry : ValueEntry,
indent : Int,
use_custom_string_delimiters : Bool,
buf : StringBuilder,
) -> Unit {
buf.write_char('[')
render_pcf_inline(entry.key, indent, use_custom_string_delimiters, buf)
buf.write_char(']')
// PKL-153f: a RenderDirective value drops the `= ` separator so
// Apple Pkl's `["other"] 💰` shape lines up.
match render_directive_text(entry.value) {
Some(text) => {
buf.write_char(' ')
buf.write_string(text)
return
}
None => ()
}
if pcf_is_block_value(entry.value) {
buf.write_char(' ')
render_pcf_block_body(
entry.value,
indent,
use_custom_string_delimiters,
buf,
)
} else if entry.value is StringValue(s) {
buf.write_string(" = ")
render_pcf_string_value(s, indent + 2, use_custom_string_delimiters, buf)
} else {
buf.write_string(" = ")
render_pcf_scalar_with_indent(
entry.value,
indent,
use_custom_string_delimiters,
buf,
)
}
}
///|
fn value_member_is_output_configuration(field : ValueMember) -> Bool {
if strip_member_visibility_prefix(field.name) != "output" {
return false
}
match force_eval_thunk(field.value) {
ObjectValue(output_members) =>
lookup_member(output_members, "value") is Some(_) ||
lookup_member(output_members, "text") is Some(_) ||
lookup_member(output_members, "renderer") is Some(_)
_ => false
}
}
///|
fn visible_members(members : Array[ValueMember]) -> Array[ValueMember] {
// Hidden / local members carry the `hidden_member_prefix` marker; every
// renderer projects through this filter so the prefix never reaches the
// output. `lookup_member` still resolves bare-name reads against the
// prefixed entry, so omitting members here doesn't break runtime access.
let result : Array[ValueMember] = []
for field in members {
let forced_value = force_eval_thunk(field.value)
let omitted_reflect_default = field.name == "defaultValue" &&
forced_value is NullValue
let omitted_module_output = value_member_is_output_configuration(field)
if !is_invisible_member_name(field.name) &&
!omitted_reflect_default &&
!omitted_module_output {
result.push({
name: field.name,
value: forced_value,
source: field.source,
annotations: field.annotations,
})
}
}
result
}
///|
/// PKL-147: when the module declares
/// `output { renderer = new PcfRenderer { omitNullProperties = true } }`,
/// the PCF render drops every module-level property whose value is `null`.
/// snippetTest fixtures rely on this so the inherited stub `catch = null`
/// doesn't leak into the rendered output; without the omit pass each
/// snippetTest fixture's output would begin with `catch = null` and never
/// match upstream byte-for-byte.
fn pcf_omit_null_properties(members : Array[ValueMember]) -> Bool {
match lookup_member(members, "output") {
Some(ObjectValue(output_members)) =>
match lookup_member(output_members, "renderer") {
Some(ObjectValue(renderer_members)) =>
match lookup_member(renderer_members, "omitNullProperties") {
Some(BoolValue(value)) => value
_ => false
}
_ => false
}
_ => false
}
}
///|
fn render_pcf_module(
members : Array[ValueMember],
use_custom_string_delimiters : Bool,
buf : StringBuilder,
) -> Unit {
let omit_null = pcf_omit_null_properties(members)
let visible = pcf_module_order_members(visible_members(members))
let mut emitted = 0
for i = 0; i < visible.length(); i = i + 1 {
if omit_null && visible[i].value is NullValue {
continue
}
// The `output` member is the renderer/value configuration block,
// not data — Apple Pkl's PCF render strips it from the top-level
// output. Without this filter `pkl eval` on a snippetTest fixture
// would include the inherited `output { renderer = ... }` block.
if visible[i].name == "output" {
continue
}
if emitted > 0 {
buf.write_char('\n')
}
render_pcf_object_member(visible[i], 0, use_custom_string_delimiters, buf)
emitted = emitted + 1
}
}
///|
fn pcf_module_order_members(members : Array[ValueMember]) -> Array[ValueMember] {
let mut facts_idx = -1
let mut examples_idx = -1
for i = 0; i < members.length(); i = i + 1 {
if members[i].name == "facts" {
facts_idx = i
} else if members[i].name == "examples" {
examples_idx = i
}
}
if facts_idx < 0 || examples_idx < 0 || facts_idx < examples_idx {
return members
}
// snippetTest extends pkl:test, whose nullable slots are ordered as
// `facts` then `examples`. The local source often declares examples
// first, but the amended module renders in inherited slot order.
let ordered : Array[ValueMember] = []
for i = 0; i < members.length(); i = i + 1 {
if i == examples_idx {
ordered.push(members[facts_idx])
ordered.push(members[examples_idx])
} else if i != facts_idx {
ordered.push(members[i])
}
}
ordered
}
///|
/// Render a Pkl value as JSON matching `pkl eval -f json`.
///
/// - ObjectValue / MappingValue → JSON object (Mapping keys are coerced to strings).
/// - ListingValue → JSON array.
/// - IntValue / BoolValue / NullValue → JSON scalar.
/// - StringValue → JSON string with the standard `"`, `\\`, control-character
/// escapes.
/// - FunctionValue does not have a JSON projection and renders as `null` so
/// diagnostics still produce a parseable document; the typechecker rejects
/// functions earlier on the rendering path.
///
/// Indentation is fixed at two spaces, matching the upstream default for
/// `pkl eval -f json`.