///| Utility functions for YAML processing
/// Ported from js-yaml v3.13.1:
/// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
/// Copyright 2011-2015 by Vitaly Puzrin. All rights reserved. MIT license.
/// Copyright 2018-2025 the Deno authors. MIT license.
///|
/// Clone a YamlValue
pub fn clone_yaml_value(value : YamlValue) -> YamlValue {
match value {
Null => Null
Bool(b) => Bool(b)
Int(i) => Int(i)
Float(f) => Float(f)
String(s) => String(s)
Array(arr) => {
let new_arr = []
for i = 0; i < arr.length(); i = i + 1 {
new_arr.push(clone_yaml_value(arr[i]))
}
Array(new_arr)
}
Object(obj) => {
let new_obj = Map::new()
obj.each(fn(key, value) { new_obj.set(key, clone_yaml_value(value)) })
Object(new_obj)
}
}
}
///|
/// Sanitize input string for parsing
/// Handles BOM, line endings, and ensures proper termination
pub fn sanitize_input(input : String) -> String {
let mut result = input
// Convert input to string if it isn't already
result = result.to_string()
if result.length() > 0 {
// Add trailing newline if not exists
let last_char = result[result.length() - 1]
if !is_eol(last_char) {
result = result + "\n"
}
// Strip BOM (Byte Order Mark)
if result[0] == BOM {
result = result.substring(start=1)
}
}
// Use 0 as string terminator for easier bounds checking
result + "\u{0000}"
}
///|
/// Normalize line endings to LF
pub fn normalize_line_endings(input : String) -> String {
let mut result = ""
let mut i = 0
while i < input.length() {
let ch = input[i]
if ch == CARRIAGE_RETURN {
if i + 1 < input.length() && input[i + 1] == LINE_FEED {
// CRLF -> LF
result += "\n"
i += 2
} else {
// CR -> LF
result += "\n"
i += 1
}
} else {
result += Char::from_int(ch).to_string()
i += 1
}
}
result
}
///|
/// Check if string is a valid YAML document start (---)
pub fn is_document_start(line : String) -> Bool {
let trimmed = trim_whitespace(line)
trimmed == "---" || trimmed.starts_with("--- ")
}
///|
/// Check if string is a YAML document end (...)
pub fn is_document_end(line : String) -> Bool {
let trimmed = trim_whitespace(line)
trimmed == "..." || trimmed.starts_with("... ")
}
///|
/// Trim leading and trailing whitespace
pub fn trim_whitespace(s : String) -> String {
let mut start = 0
let mut end = s.length()
// Find first non-whitespace character
while start < end && is_whitespace_or_eol(s[start]) {
start += 1
}
// Find last non-whitespace character
while end > start && is_whitespace_or_eol(s[end - 1]) {
end -= 1
}
if start >= end {
""
} else {
s.substring(start~, end~)
}
}
///|
/// Count leading whitespace characters
pub fn count_leading_whitespace(s : String) -> Int {
let mut count = 0
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
if ch == SPACE {
count += 1
} else if ch == TAB {
count += 8 // Treat tab as 8 spaces
} else {
break
}
}
count
}
///|
/// Check if a line is empty or contains only whitespace
pub fn is_empty_line(line : String) -> Bool {
for i = 0; i < line.length(); i = i + 1 {
if !is_whitespace_or_eol(line[i]) {
return false
}
}
true
}
///|
/// Split string into lines, preserving line ending information
pub fn split_lines(input : String) -> Array[String] {
let lines = []
let mut current = ""
let mut i = 0
while i < input.length() {
let ch = input[i]
if ch == LINE_FEED {
lines.push(current)
current = ""
i += 1
} else if ch == CARRIAGE_RETURN {
lines.push(current)
current = ""
i += 1
// Skip following LF if present
if i < input.length() && input[i] == LINE_FEED {
i += 1
}
} else {
current += Char::from_int(ch).to_string()
i += 1
}
}
// Add last line if not empty
if current.length() > 0 {
lines.push(current)
}
lines
}
///|
/// Escape special characters for YAML output
pub fn escape_yaml_string(s : String) -> String {
let mut result = ""
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
match ch {
DOUBLE_QUOTE => result += "\\\""
BACKSLASH => result += "\\\\"
LINE_FEED => result += "\\n"
CARRIAGE_RETURN => result += "\\r"
TAB => result += "\\t"
0x08 => result += "\\b" // backspace
0x0c => result += "\\f" // form feed
0x07 => result += "\\a" // bell
0x0b => result += "\\v" // vertical tab
0x00 => result += "\\0" // null
0x1b => result += "\\e" // escape
_ =>
if ch < 32 || ch == 0x7f {
// Non-printable ASCII characters
result += "\\x" + format_hex_byte(ch)
} else if ch > 0x7f && ch < 0xa0 {
// Control characters in Latin-1 supplement
result += "\\x" + format_hex_byte(ch)
} else if ch > 0xffff {
// Characters beyond BMP need \U notation
result += "\\U" + format_hex_unicode(ch)
} else if ch > 0x7f {
// Unicode characters in BMP can use \u notation
result += "\\u" + format_hex_unicode_short(ch)
} else {
result += Char::from_int(ch).to_string()
}
}
}
result
}
///|
/// Format byte as 2-digit hex
fn format_hex_byte(byte : Int) -> String {
let hex_digits = "0123456789abcdef"
let high = byte / 16 % 16
let low = byte % 16
Char::from_int(hex_digits[high]).to_string() +
Char::from_int(hex_digits[low]).to_string()
}
///|
/// Format Unicode code point as 8-digit hex (for \U)
fn format_hex_unicode(codepoint : Int) -> String {
let hex_digits = "0123456789abcdef"
let mut result = ""
let mut val = codepoint
for i = 0; i < 8; i = i + 1 {
let digit = val % 16
result = Char::from_int(hex_digits[digit]).to_string() + result
val /= 16
}
result
}
///|
/// Format Unicode code point as 4-digit hex (for \u)
fn format_hex_unicode_short(codepoint : Int) -> String {
let hex_digits = "0123456789abcdef"
let mut result = ""
let mut val = codepoint
for i = 0; i < 4; i = i + 1 {
let digit = val % 16
result = Char::from_int(hex_digits[digit]).to_string() + result
val /= 16
}
result
}
///|
/// Unescape YAML string (decode escape sequences)
pub fn unescape_yaml_string(s : String) -> String {
let mut result = ""
let mut i = 0
while i < s.length() {
let ch = s[i]
if ch == BACKSLASH && i + 1 < s.length() {
let next_ch = s[i + 1]
match next_ch {
0x22 => { // \"
result += "\""
i += 2
}
0x5c => { // \\
result += "\\"
i += 2
}
0x6e => { // \n
result += "\n"
i += 2
}
0x72 => { // \r
result += "\r"
i += 2
}
0x74 => { // \t
result += "\t"
i += 2
}
0x62 => { // \b
result += "\u{08}"
i += 2
}
0x66 => { // \f
result += "\u{0c}"
i += 2
}
0x61 => { // \a
result += "\u{07}"
i += 2
}
0x76 => { // \v
result += "\u{0b}"
i += 2
}
0x30 => { // \0
result += "\u{00}"
i += 2
}
0x65 => { // \e
result += "\u{1b}"
i += 2
}
0x78 => // \x (hex escape)
if i + 3 < s.length() {
let hex_str = s.substring(start=i + 2, end=i + 4)
match parse_hex_byte(hex_str) {
Some(byte_val) => {
result += Char::from_int(byte_val).to_string()
i += 4
}
None => {
result += Char::from_int(ch).to_string()
i += 1
}
}
} else {
result += Char::from_int(ch).to_string()
i += 1
}
0x75 => // \u (unicode escape)
if i + 5 < s.length() {
let hex_str = s.substring(start=i + 2, end=i + 6)
match parse_hex_unicode(hex_str) {
Some(unicode_val) => {
result += Char::from_int(unicode_val).to_string()
i += 6
}
None => {
result += Char::from_int(ch).to_string()
i += 1
}
}
} else {
result += Char::from_int(ch).to_string()
i += 1
}
0x55 => // \U (long unicode escape)
if i + 9 < s.length() {
let hex_str = s.substring(start=i + 2, end=i + 10)
match parse_hex_unicode(hex_str) {
Some(unicode_val) => {
result += codepoint_to_string(unicode_val)
i += 10
}
None => {
result += Char::from_int(ch).to_string()
i += 1
}
}
} else {
result += Char::from_int(ch).to_string()
i += 1
}
_ => {
result += Char::from_int(ch).to_string()
i += 1
}
}
} else {
result += Char::from_int(ch).to_string()
i += 1
}
}
result
}
///|
/// Parse hex byte string to integer
fn parse_hex_byte(hex_str : String) -> Int? {
if hex_str.length() != 2 {
return None
}
let mut result = 0
for i = 0; i < hex_str.length(); i = i + 1 {
let ch = hex_str[i]
let digit = if ch >= 48 && ch <= 57 { // 0-9
ch - 48
} else if ch >= 65 && ch <= 70 { // A-F
ch - 65 + 10
} else if ch >= 97 && ch <= 102 { // a-f
ch - 97 + 10
} else {
return None
}
result = result * 16 + digit
}
Some(result)
}
///|
/// Parse hex unicode string to integer
fn parse_hex_unicode(hex_str : String) -> Int? {
if hex_str.length() != 4 && hex_str.length() != 8 {
return None
}
let mut result = 0
for i = 0; i < hex_str.length(); i = i + 1 {
let ch = hex_str[i]
let digit = if ch >= 48 && ch <= 57 { // 0-9
ch - 48
} else if ch >= 65 && ch <= 70 { // A-F
ch - 65 + 10
} else if ch >= 97 && ch <= 102 { // a-f
ch - 97 + 10
} else {
return None
}
result = result * 16 + digit
}
Some(result)
}
///|
/// Check if string needs quoting in YAML
pub fn needs_quoting(s : String) -> Bool {
if s.length() == 0 {
return true
}
// Check for special YAML values
let lower = s.to_lower()
if lower == "true" ||
lower == "false" ||
lower == "null" ||
lower == "yes" ||
lower == "no" ||
lower == "on" ||
lower == "off" ||
lower == "~" {
return true
}
// Check if it looks like a number
if is_numeric_string(s) {
return true
}
// Check for special characters
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
if is_flow_indicator(ch) ||
ch == COLON ||
ch == SHARP ||
ch == ASTERISK ||
ch == AMPERSAND ||
ch == EXCLAMATION ||
ch == VERTICAL_LINE ||
ch == GREATER_THAN ||
ch == SMALLER_THAN ||
ch == QUESTION ||
ch == COMMERCIAL_AT ||
ch == GRAVE_ACCENT ||
ch == PERCENT {
return true
}
}
false
}
///|
/// Check if string looks like a number
pub fn is_numeric_string(s : String) -> Bool {
if s.length() == 0 {
return false
}
let mut has_digit = false
let mut has_dot = false
let mut start = 0
// Check for sign
if s[0] == PLUS || s[0] == MINUS {
start = 1
}
for i = start; i < s.length(); i = i + 1 {
let ch = s[i]
if is_decimal_digit(ch) {
has_digit = true
} else if ch == DOT && !has_dot {
has_dot = true
} else {
return false
}
}
has_digit
}
///|
/// Process folded string (YAML >)
/// Folds newlines into spaces while preserving paragraph breaks
pub fn process_folded_string(s : String) -> String {
let lines = split_lines(s)
let mut result = ""
let mut i = 0
while i < lines.length() {
let line = lines[i]
let trimmed = trim_whitespace(line)
if trimmed.length() == 0 {
// Empty line - preserve as paragraph break
result += "\n"
} else {
// Non-empty line
if result.length() > 0 && !string_ends_with(result, "\n") {
result += " " // Fold previous line ending into space
}
result += trimmed
}
i += 1
}
result
}
///|
/// Process literal string (YAML |)
/// Preserves line breaks exactly as written
pub fn process_literal_string(s : String) -> String {
// Remove common leading indentation
let lines = split_lines(s)
if lines.length() == 0 {
return ""
}
// Find minimum indentation (excluding empty lines)
let mut min_indent = -1
for i = 0; i < lines.length(); i = i + 1 {
let line = lines[i]
if !is_empty_line(line) {
let indent = count_leading_whitespace(line)
if min_indent == -1 || indent < min_indent {
min_indent = indent
}
}
}
if min_indent <= 0 {
return join(lines, "\n")
}
// Remove common indentation
let processed_lines = []
for i = 0; i < lines.length(); i = i + 1 {
let line = lines[i]
if is_empty_line(line) {
processed_lines.push("")
} else {
let spaces_to_remove = min_indent.min(count_leading_whitespace(line))
processed_lines.push(line.substring(start=spaces_to_remove))
}
}
join(processed_lines, "\n")
}
///|
/// Determine if string should use literal (|) or folded (>) style
pub fn suggest_block_style(s : String) -> StyleVariant {
if s.contains("\n\n") {
// Has paragraph breaks - literal style preserves them better
Literal
} else if s.contains("\n") {
// Has line breaks but no paragraphs - folded might be better
Folded
} else {
// Single line - use plain style
Plain
}
}
///|
/// Smart quote detection for strings
pub fn determine_quote_style(s : String) -> StyleVariant {
// Check if string needs quoting at all
if !needs_quoting(s) {
return Plain
}
// Count quote types to choose the best one
let mut double_quotes = 0
let mut single_quotes = 0
let mut has_escapes = false
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
match ch {
DOUBLE_QUOTE => double_quotes += 1
SINGLE_QUOTE => single_quotes += 1
BACKSLASH | LINE_FEED | CARRIAGE_RETURN | TAB => has_escapes = true
_ => if ch < 32 || ch == 0x7f { has_escapes = true }
}
}
if has_escapes {
// Need escape sequences - must use double quotes
DoubleQuoted
} else if double_quotes > 0 && single_quotes == 0 {
// Has double quotes but no single quotes
SingleQuoted
} else if single_quotes > 0 && double_quotes == 0 {
// Has single quotes but no double quotes
DoubleQuoted
} else if single_quotes <= double_quotes {
// Equal or fewer single quotes
SingleQuoted
} else {
// Fewer double quotes
DoubleQuoted
}
}
///|
/// Check if string ends with suffix
pub fn string_ends_with(s : String, suffix : String) -> Bool {
if suffix.length() > s.length() {
false
} else {
let start = s.length() - suffix.length()
s.substring(start~) == suffix
}
}
///|
/// Pad string to specified width with spaces
pub fn pad_string(s : String, width : Int) -> String {
let padding_needed = width - s.length()
if padding_needed <= 0 {
s
} else {
s + " ".repeat(padding_needed)
}
}
///|
/// Wrap text to specified line width
pub fn wrap_text(text : String, width : Int) -> Array[String] {
if width <= 0 {
return [text]
}
let words = text.split(" ")
let lines = []
let mut current_line = ""
for i = 0; i < words.length(); i = i + 1 {
let word = words[i]
let potential_line = if current_line.length() == 0 {
word
} else {
current_line + " " + word
}
if potential_line.length() <= width {
current_line = potential_line
} else {
if current_line.length() > 0 {
lines.push(current_line)
}
current_line = word
}
}
if current_line.length() > 0 {
lines.push(current_line)
}
lines
}
///|
/// Get string representation of a YAML value for debugging
pub fn yaml_value_to_debug_string(value : YamlValue) -> String {
match value {
Null => "null"
Bool(b) => b.to_string()
Int(i) => i.to_string()
Float(f) => f.to_string()
String(s) => "\"" + s + "\""
Array(arr) => {
let items = []
for i = 0; i < arr.length(); i = i + 1 {
items.push(yaml_value_to_debug_string(arr[i]))
}
"[" + items.join(", ") + "]"
}
Object(obj) => {
let items = []
obj.each(fn(k, v) {
items.push("\"" + k + "\": " + yaml_value_to_debug_string(v))
})
"{" + items.join(", ") + "}"
}
}
}
///|
/// Convert YamlValue to String if possible
pub fn yaml_value_to_string(value : YamlValue) -> String? {
match value {
String(s) => Some(s)
_ => None
}
}
///|
/// Convert YamlValue to Int if possible
pub fn yaml_value_to_int(value : YamlValue) -> Int? {
match value {
Int(i) => Some(i)
_ => None
}
}
///|
/// Convert YamlValue to Bool if possible
pub fn yaml_value_to_bool(value : YamlValue) -> Bool? {
match value {
Bool(b) => Some(b)
_ => None
}
}
///|
/// Convert YamlValue to Double if possible
pub fn yaml_value_to_double(value : YamlValue) -> Double? {
match value {
Float(f) => Some(f)
Int(i) => Some(i.to_double())
_ => None
}
}
///|
/// Convert YamlValue to Array if possible
pub fn yaml_value_to_array(value : YamlValue) -> Array[YamlValue]? {
match value {
Array(arr) => Some(arr)
_ => None
}
}
///|
/// Convert YamlValue to Object if possible
pub fn yaml_value_to_object(value : YamlValue) -> Map[String, YamlValue]? {
match value {
Object(obj) => Some(obj)
_ => None
}
}
///|
/// Check if YamlValue is null
pub fn yaml_value_is_null(value : YamlValue) -> Bool {
match value {
Null => true
_ => false
}
}
///|
/// Deep equality check for YAML values
pub fn yaml_values_equal(a : YamlValue, b : YamlValue) -> Bool {
match (a, b) {
(Null, Null) => true
(Bool(a_val), Bool(b_val)) => a_val == b_val
(Int(a_val), Int(b_val)) => a_val == b_val
(Float(a_val), Float(b_val)) => a_val == b_val
(String(a_val), String(b_val)) => a_val == b_val
(Array(a_arr), Array(b_arr)) =>
if a_arr.length() != b_arr.length() {
false
} else {
let mut equal = true
for i = 0; i < a_arr.length(); i = i + 1 {
if !yaml_values_equal(a_arr[i], b_arr[i]) {
equal = false
break
}
}
equal
}
(Object(a_obj), Object(b_obj)) =>
if a_obj.size() != b_obj.size() {
false
} else {
let mut equal = true
a_obj.each(fn(key, a_val) {
match b_obj.get(key) {
Some(b_val) => if !yaml_values_equal(a_val, b_val) { equal = false }
None => equal = false
}
})
equal
}
_ => false
}
}
///|
/// Convert YAML value to JSON-like string representation
pub fn yaml_value_to_json_string(value : YamlValue) -> String {
match value {
Null => "null"
Bool(b) => b.to_string()
Int(i) => i.to_string()
Float(f) =>
if f != f { // NaN
"null"
} else if f == infinity() || f == -infinity() {
"null"
} else {
f.to_string()
}
String(s) => "\"" + escape_json_string(s) + "\""
Array(arr) => {
let items = []
for i = 0; i < arr.length(); i = i + 1 {
items.push(yaml_value_to_json_string(arr[i]))
}
"[" + join(items, ", ") + "]"
}
Object(obj) => {
let items = []
obj.each(fn(k, v) {
items.push(
"\"" + escape_json_string(k) + "\": " + yaml_value_to_json_string(v),
)
})
"{" + join(items, ", ") + "}"
}
}
}
///|
/// Escape string for JSON output
fn escape_json_string(s : String) -> String {
let mut result = ""
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
match ch {
DOUBLE_QUOTE => result += "\\\""
BACKSLASH => result += "\\\\"
LINE_FEED => result += "\\n"
CARRIAGE_RETURN => result += "\\r"
TAB => result += "\\t"
0x08 => result += "\\b" // backspace
0x0c => result += "\\f" // form feed
_ =>
if ch < 32 {
result += "\\u" + format_hex_unicode_short(ch)
} else {
result += Char::from_int(ch).to_string()
}
}
}
result
}
///|
/// Merge two YAML objects (deep merge)
pub fn merge_yaml_objects(target : YamlValue, source : YamlValue) -> YamlValue {
match (target, source) {
(Object(target_obj), Object(source_obj)) => {
let result = Map::new()
// Copy all target properties
target_obj.each(fn(k, v) { result[k] = v })
// Merge source properties
source_obj.each(fn(k, v) {
match target_obj.get(k) {
Some(existing) =>
// Deep merge if both are objects
match (existing, v) {
(Object(_), Object(_)) =>
result[k] = merge_yaml_objects(existing, v)
_ => result[k] = v // Override with source value
}
None => result[k] = v // Add new property
}
})
Object(result)
}
(_, source) => source // Non-object target, just return source
}
}
///|
/// Get all keys from a YAML object (including nested)
pub fn get_all_keys(value : YamlValue, prefix? : String = "") -> Array[String] {
let keys = []
match value {
Object(obj) =>
obj.each(fn(k, v) {
let full_key = if prefix.length() == 0 { k } else { prefix + "." + k }
keys.push(full_key)
let nested_keys = get_all_keys(v, prefix=full_key)
for i = 0; i < nested_keys.length(); i = i + 1 {
keys.push(nested_keys[i])
}
})
Array(arr) =>
for i = 0; i < arr.length(); i = i + 1 {
let index_key = if prefix.length() == 0 {
i.to_string()
} else {
prefix + "[" + i.to_string() + "]"
}
keys.push(index_key)
let nested_keys = get_all_keys(arr[i], prefix=index_key)
for j = 0; j < nested_keys.length(); j = j + 1 {
keys.push(nested_keys[j])
}
}
_ => () // Scalar values have no nested keys
}
keys
}
///|
/// Get value at path (dot notation)
pub fn get_value_at_path(value : YamlValue, path : String) -> YamlValue? {
if path.length() == 0 {
return Some(value)
}
let parts = path.split(".")
let mut current = value
for i = 0; i < parts.length(); i = i + 1 {
let part = parts[i]
match current {
Object(obj) =>
match obj.get(part) {
Some(next_value) => current = next_value
None => return None
}
Array(arr) =>
// Try to parse part as array index
match parse_array_index(part) {
Some(index) =>
if index >= 0 && index < arr.length() {
current = arr[index]
} else {
return None
}
None => return None
}
_ => return None // Can't traverse into scalar values
}
}
Some(current)
}
///|
/// Parse string as array index
fn parse_array_index(s : String) -> Int? {
if s.length() == 0 {
return None
}
// Check if all characters are digits
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
if ch < 48 || ch > 57 { // Not 0-9
return None
}
}
Some(parse_decimal(s))
}
///|
/// Set value at path (dot notation)
pub fn set_value_at_path(
value : YamlValue,
path : String,
new_value : YamlValue,
) -> YamlValue {
if path.length() == 0 {
return new_value
}
let parts = path.split(".")
match value {
Object(obj) => {
let result = Map::new()
obj.each(fn(k, v) { result[k] = v }) // Copy existing properties
if parts.length() == 1 {
result[parts[0]] = new_value
} else {
let first_part = parts[0]
let remaining_path = join(parts.slice(start=1), ".")
let existing = obj.get(first_part).or(Object(Map::new()))
result[first_part] = set_value_at_path(
existing, remaining_path, new_value,
)
}
Object(result)
}
_ => {
// For non-objects, create new object with the path
let result = Map::new()
if parts.length() == 1 {
result[parts[0]] = new_value
} else {
let first_part = parts[0]
let remaining_path = join(parts.slice(start=1), ".")
result[first_part] = set_value_at_path(
Object(Map::new()),
remaining_path,
new_value,
)
}
Object(result)
}
}
}
///|
/// Get the type name of a YAML value
pub fn get_yaml_type_name(value : YamlValue) -> String {
match value {
Null => "null"
Bool(_) => "boolean"
Int(_) => "integer"
Float(_) => "float"
String(_) => "string"
Array(_) => "array"
Object(_) => "object"
}
}
///|
/// Flatten nested YAML object to dot notation
pub fn flatten_yaml_object(
value : YamlValue,
prefix? : String = "",
) -> Map[String, YamlValue] {
let result = Map::new()
match value {
Object(obj) =>
obj.each(fn(k, v) {
let key = if prefix.length() == 0 { k } else { prefix + "." + k }
match v {
Object(_) => {
let nested = flatten_yaml_object(v, prefix=key)
nested.each(fn(nested_k, nested_v) { result[nested_k] = nested_v })
}
_ => result[key] = v
}
})
_ => {
let key = if prefix.length() == 0 { "value" } else { prefix }
result[key] = value
}
}
result
}
///|
/// Unflatten dot notation back to nested YAML object
pub fn unflatten_yaml_object(flat : Map[String, YamlValue]) -> YamlValue {
let result = Map::new()
flat.each(fn(path, value) {
let mut current = result
let parts = path.split(".")
for i = 0; i < parts.length() - 1; i = i + 1 {
let part = parts[i]
match current.get(part) {
Some(Object(nested)) =>
// Continue with existing nested object
current = nested
_ => {
// Create new nested object
let new_nested = Map::new()
current[part] = Object(new_nested)
current = new_nested
}
}
}
// Set the final value
if parts.length() > 0 {
current[parts[parts.length() - 1]] = value
}
})
Object(result)
}
///|
/// Calculate size (number of elements) in YAML value
pub fn calculate_yaml_size(value : YamlValue) -> Int {
match value {
Array(arr) => arr.length()
Object(obj) => obj.size()
_ => 1 // Scalar values have size 1
}
}
///|
/// Check if YAML value is empty
pub fn is_yaml_empty(value : YamlValue) -> Bool {
match value {
Null => true
String(s) => s.length() == 0
Array(arr) => arr.length() == 0
Object(obj) => obj.size() == 0
_ => false
}
}
///|
/// Create a simple helper for infinity detection
fn infinity() -> Double {
1.0 / 0.0
}