///| Built-in YAML type handlers
/// 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.
///|
/// String type handler
pub fn str_type() -> YamlType[String] {
{
tag: "tag:yaml.org,2002:str",
kind: Scalar,
predicate: fn(value) {
match value {
String(_) => true
_ => false
}
},
represent: Some(fn(data, style) {
match style {
Some(SingleQuoted) => "'" + escape_single_quotes(data) + "'"
Some(DoubleQuoted) => "\"" + escape_double_quotes(data) + "\""
Some(Literal) => "|\\n " + data.replace(old="\n", new="\n ")
Some(Folded) => ">\\n " + data.replace(old="\n", new="\n ")
_ => data // Plain style
}
}),
default_style: Some(Plain),
resolve: fn(value) {
match value {
String(_) => true
_ => false
}
},
construct: fn(value) {
match value {
String(s) => s
_ => yaml_value_to_debug_string(value)
}
},
}
}
///|
/// Escape single quotes in string
fn escape_single_quotes(s : String) -> String {
s.replace(old="'", new="''")
}
///|
/// Escape double quotes in string
fn escape_double_quotes(s : String) -> String {
s
.replace(old="\"", new="\\\"")
.replace(old="\n", new="\\n")
.replace(old="\t", new="\\t")
}
///|
/// Boolean type handler
pub fn bool_type() -> YamlType[Bool] {
let yaml_true_values = ["true", "True", "TRUE"]
let yaml_false_values = ["false", "False", "FALSE"]
{
tag: "tag:yaml.org,2002:bool",
kind: Scalar,
predicate: fn(value) {
match value {
Bool(_) => true
_ => false
}
},
represent: Some(fn(data, style) {
match style {
Some(Uppercase) => if data { "TRUE" } else { "FALSE" }
Some(Camelcase) => if data { "True" } else { "False" }
_ => if data { "true" } else { "false" } // Default lowercase
}
}),
default_style: Some(Lowercase),
resolve: fn(value) {
match value {
String(s) =>
yaml_true_values.contains(s) || yaml_false_values.contains(s)
_ => false
}
},
construct: fn(value) {
match value {
String(s) => yaml_true_values.contains(s)
Bool(b) => b
_ => false
}
},
}
}
///|
/// Integer type handler
pub fn int_type() -> YamlType[Int] {
{
tag: "tag:yaml.org,2002:int",
kind: Scalar,
predicate: fn(value) {
match value {
Int(_) => true
_ => false
}
},
represent: Some(fn(data, style) {
match style {
Some(Binary) =>
if data >= 0 {
"0b" + to_base_string(data, 2)
} else {
"-0b" + to_base_string(-data, 2)
}
Some(Octal) =>
if data >= 0 {
"0" + to_base_string(data, 8)
} else {
"-0" + to_base_string(-data, 8)
}
Some(Hexadecimal) =>
if data >= 0 {
"0x" + to_uppercase(to_base_string(data, 16))
} else {
"-0x" + to_uppercase(to_base_string(-data, 16))
}
_ => data.to_string() // Default decimal
}
}),
default_style: Some(Decimal),
resolve: fn(value) {
match value {
String(s) => resolve_yaml_integer(s)
Int(_) => true
_ => false
}
},
construct: fn(value) {
match value {
String(s) => construct_yaml_integer(s)
Int(i) => i
_ => 0
}
},
}
}
///|
/// Float type handler
pub fn float_type() -> YamlType[Double] {
{
tag: "tag:yaml.org,2002:float",
kind: Scalar,
predicate: fn(value) {
match value {
Float(_) => true
Int(_) => true // Integers can be represented as floats
_ => false
}
},
represent: Some(fn(data, style) {
if data != data { // NaN check
".nan"
} else if data == infinity() {
".inf"
} else if data == -infinity() {
"-.inf"
} else {
data.to_string()
}
}),
default_style: None,
resolve: fn(value) {
match value {
String(s) => resolve_yaml_float(s)
Float(_) => true
Int(_) => true
_ => false
}
},
construct: fn(value) {
match value {
String(s) => construct_yaml_float(s)
Float(f) => f
Int(i) => i.to_double()
_ => 0.0
}
},
}
}
///|
/// Null type handler
pub fn null_type() -> YamlType[Unit] {
let yaml_null_values = ["null", "Null", "NULL", "~", ""]
{
tag: "tag:yaml.org,2002:null",
kind: Scalar,
predicate: fn(value) {
match value {
Null => true
_ => false
}
},
represent: Some(fn(data, style) {
match style {
Some(Uppercase) => "NULL"
Some(Camelcase) => "Null"
_ => "null" // Default lowercase
}
}),
default_style: Some(Lowercase),
resolve: fn(value) {
match value {
String(s) => yaml_null_values.contains(s)
Null => true
_ => false
}
},
construct: fn(value) {
match value {
_ => () // Always return unit/null
}
},
}
}
///|
/// Sequence type handler
pub fn seq_type() -> YamlType[Array[YamlValue]] {
{
tag: "tag:yaml.org,2002:seq",
kind: Sequence,
predicate: fn(value) {
match value {
Array(_) => true
_ => false
}
},
represent: None, // Complex representation handled elsewhere
default_style: None,
resolve: fn(value) {
match value {
Array(_) => true
_ => false
}
},
construct: fn(value) {
match value {
Array(arr) => arr
_ => [] // Default to empty array
}
},
}
}
///|
/// Mapping type handler
pub fn map_type() -> YamlType[Map[String, YamlValue]] {
{
tag: "tag:yaml.org,2002:map",
kind: Mapping,
predicate: fn(value) {
match value {
Object(_) => true
_ => false
}
},
represent: None, // Complex representation handled elsewhere
default_style: None,
resolve: fn(value) {
match value {
Object(_) => true
_ => false
}
},
construct: fn(value) {
match value {
Object(obj) => obj
_ => Map::new() // Default to empty map
}
},
}
}
///|
/// Binary type handler
/// Represents binary data encoded in base64
pub fn binary_type() -> YamlType[String] {
{
tag: "tag:yaml.org,2002:binary",
kind: Scalar,
predicate: fn(value) {
match value {
String(s) => is_base64_string(s)
_ => false
}
},
represent: Some(fn(data, style) {
// In a full implementation, this would encode to base64
data
}),
default_style: Some(Literal),
resolve: fn(value) {
match value {
String(s) => is_base64_string(s)
_ => false
}
},
construct: fn(value) {
match value {
String(s) => s // In a full implementation, this would decode base64
_ => ""
}
},
}
}
///|
/// Check if string looks like base64
fn is_base64_string(s : String) -> Bool {
if s.length() == 0 {
return false
}
// Check if all characters are valid base64
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
if !((ch >= 65 && ch <= 90) || // A-Z
(ch >= 97 && ch <= 122) || // a-z
(ch >= 48 && ch <= 57) || // 0-9
ch == 43 ||
ch == 47 || // + /
ch == 61) { // =
return false
}
}
// Check padding
let padding = s.length() % 4
padding == 0 || padding == 2 || padding == 3
}
///|
/// Ordered map (omap) type handler
/// Represents an ordered mapping as an array of key-value pairs
pub fn omap_type() -> YamlType[Array[(String, YamlValue)]] {
{
tag: "tag:yaml.org,2002:omap",
kind: Sequence,
predicate: fn(value) {
match value {
Array(_) => true // TODO: Check if it's array of key-value pairs
_ => false
}
},
represent: None, // Complex representation handled elsewhere
default_style: None,
resolve: fn(value) {
match value {
Array(_) => true
_ => false
}
},
construct: fn(value) {
match value {
Array(_) => [] // Simplified - should parse key-value pairs
_ => []
}
},
}
}
///|
/// Pairs type handler
/// Similar to omap but allows duplicate keys
pub fn pairs_type() -> YamlType[Array[(String, YamlValue)]] {
{
tag: "tag:yaml.org,2002:pairs",
kind: Sequence,
predicate: fn(value) {
match value {
Array(_) => true
_ => false
}
},
represent: None,
default_style: None,
resolve: fn(value) {
match value {
Array(_) => true
_ => false
}
},
construct: fn(value) {
match value {
Array(_) => []
_ => []
}
},
}
}
///|
/// Set type handler
/// Represents a set as a mapping with null values
pub fn set_type() -> YamlType[Array[String]] {
{
tag: "tag:yaml.org,2002:set",
kind: Mapping,
predicate: fn(value) {
match value {
Object(_) => true // TODO: Check if all values are null
_ => false
}
},
represent: None,
default_style: None,
resolve: fn(value) {
match value {
Object(_) => true
_ => false
}
},
construct: fn(value) {
match value {
Object(_) => [] // Simplified - should extract keys
_ => []
}
},
}
}
///|
/// Timestamp type handler
/// Represents date/time values
pub fn timestamp_type() -> YamlType[String] {
{
tag: "tag:yaml.org,2002:timestamp",
kind: Scalar,
predicate: fn(value) {
match value {
String(s) => is_timestamp_string(s)
_ => false
}
},
represent: Some(fn(data, style) { data }),
default_style: None,
resolve: fn(value) {
match value {
String(s) => is_timestamp_string(s)
_ => false
}
},
construct: fn(value) {
match value {
String(s) => s
_ => ""
}
},
}
}
///|
/// Check if string looks like a timestamp
fn is_timestamp_string(s : String) -> Bool {
// Very basic check for ISO 8601-like format
// Full implementation would use proper regex
s.contains("-") && (s.contains(":") || s.length() == 10)
}
///|
/// Merge type handler
/// Handles YAML merge keys (<<)
pub fn merge_type() -> YamlType[YamlValue] {
{
tag: "tag:yaml.org,2002:merge",
kind: Scalar,
predicate: fn(value) {
match value {
String(s) => s == "<<"
_ => false
}
},
represent: Some(fn(value, style) { "<<" }),
default_style: None,
resolve: fn(value) {
match value {
String(s) => s == "<<"
_ => false
}
},
construct: fn(value) { YamlValue::String("<>") },
}
}
// Helper functions for integer parsing
///|
/// Resolve YAML integer from string
fn resolve_yaml_integer(data : String) -> Bool {
let max = data.length()
if max == 0 {
return false
}
let mut index = 0
let mut has_digits = false
let mut ch = data[index]
// Handle sign
if ch == 45 || ch == 43 { // '-' or '+'
index = index + 1
if index >= max {
return false
}
ch = data[index]
}
// Handle zero prefix
if ch == 48 { // '0'
if index + 1 == max {
return true
} // Just "0"
index = index + 1
ch = data[index]
// Binary prefix
if ch == 98 { // 'b'
index = index + 1
for i = index; i < max; i = i + 1 {
let c = data[i]
if c == 95 { // '_'
continue
}
if c != 48 && c != 49 { // '0' and '1'
return false
}
has_digits = true
}
return has_digits
}
// Hex prefix
if ch == 120 { // 'x'
index = index + 1
for i = index; i < max; i = i + 1 {
let c = data[i]
if c == 95 { // '_'
continue
}
if !is_hex_digit(c) {
return false
}
has_digits = true
}
return has_digits
}
// Octal
for i = index; i < max; i = i + 1 {
let c = data[i]
if c == 95 { // '_'
continue
}
if !is_octal_digit(c) {
return false
}
has_digits = true
}
return has_digits
}
// Decimal
if ch == 95 { // '_'
return false
} // Can't start with underscore
for i = index; i < max; i = i + 1 {
let c = data[i]
if c == 95 { // '_'
continue
}
if !is_decimal_digit(c) {
return false
}
has_digits = true
}
has_digits
}
///|
/// Construct integer from YAML string
fn construct_yaml_integer(data : String) -> Int {
let mut value = data
// Remove underscores
value = value.replace(old="_", new="")
let mut sign = 1
let mut ch = if value.length() > 0 { value[0] } else { 0 }
// Handle sign
if ch == 45 || ch == 43 { // '-' or '+'
if ch == 45 { // '-'
sign = -1
}
value = value.substring(start=1)
if value.length() == 0 {
return 0
}
ch = value[0]
}
if value == "0" {
return 0
}
// Handle different bases
if ch == 48 && value.length() > 1 { // '0'
let prefix = value[1]
if prefix == 98 { // 'b'
// Binary - parse manually since MoonBit might not have built-in support
let binary_str = value.substring(start=2)
sign * parse_binary(binary_str)
} else if prefix == 120 { // 'x'
// Hexadecimal
sign * parse_hex(value)
} else {
// Octal
sign * parse_octal(value)
}
} else {
// Decimal
sign * parse_decimal(value)
}
}
///|
/// Resolve YAML float from string
fn resolve_yaml_float(data : String) -> Bool {
match data {
".nan" | ".NaN" | ".NAN" => true
".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => true
"-.inf" | "-.Inf" | "-.INF" => true
_ =>
// Try to parse as regular float
parse_double_check(data)
}
}
///|
/// Construct float from YAML string
fn construct_yaml_float(data : String) -> Double {
match data {
".nan" | ".NaN" | ".NAN" => 0.0 / 0.0 // NaN
".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => infinity()
"-.inf" | "-.Inf" | "-.INF" => -infinity()
_ => parse_double(data)
}
}
// Helper functions
///|
/// Convert string to uppercase
fn to_uppercase(s : String) -> String {
// Simplified implementation
let mut result = ""
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
if ch >= 97 && ch <= 122 { // 'a' to 'z'
result += Char::from_int(ch - 32).to_string()
} else {
result += Char::from_int(ch).to_string()
}
}
result
}
// Character validation helpers
///|
fn is_octal_digit(c : Int) -> Bool {
c >= 48 && c <= 55 // '0' to '7'
}
// Number parsing helpers (simplified implementations)
///|
fn parse_binary(s : String) -> Int {
let mut result = 0
for i = 0; i < s.length(); i = i + 1 {
let ch = s[i]
if ch == 48 { // '0'
result = result * 2
} else if ch == 49 { // '1'
result = result * 2 + 1
}
}
result
}
///|
fn parse_hex(s : String) -> Int {
let mut result = 0
let start = if s.starts_with("0x") || s.starts_with("0X") { 2 } else { 0 }
for i = start; i < s.length(); i = i + 1 {
let ch = s[i]
result = result * 16
if ch >= 48 && ch <= 57 { // '0' to '9'
result += ch - 48
} else if ch >= 65 && ch <= 70 { // 'A' to 'F'
result += ch - 65 + 10
} else if ch >= 97 && ch <= 102 { // 'a' to 'f'
result += ch - 97 + 10
}
}
result
}
///|
fn parse_octal(s : String) -> Int {
let mut result = 0
let start = if s.starts_with("0") { 1 } else { 0 }
for i = start; i < s.length(); i = i + 1 {
let ch = s[i]
if ch >= 48 && ch <= 55 { // '0' to '7'
result = result * 8 + (ch - 48)
}
}
result
}
///|
fn to_base_string(n : Int, base : Int) -> String {
if n == 0 {
return "0"
}
let mut num = n
let mut result = ""
let digits = "0123456789abcdef"
while num > 0 {
let digit = num % base
result = Char::from_int(digits[digit]).to_string() + result
num /= base
}
result
}
///|
fn parse_decimal(s : String) -> Int {
let mut result = 0
for i = 0; i < s.length(); i = i + 1 {
let c = s[i]
if c >= 48 && c <= 57 { // '0' to '9'
result = result * 10 + (c - 48)
}
}
result
}
///|
fn parse_double_check(s : String) -> Bool {
// Simple check if string looks like a float
let mut has_digit = false
let mut has_dot = false
let mut i = 0
// Skip leading sign
if i < s.length() && (s[i] == 43 || s[i] == 45) { // '+' or '-'
i = i + 1
}
while i < s.length() {
let c = s[i]
if c >= 48 && c <= 57 { // '0' to '9'
has_digit = true
} else if c == 46 && !has_dot { // '.'
has_dot = true
} else if c == 101 || c == 69 { // 'e' or 'E'
// Scientific notation - simplified check
has_digit = true
break
} else {
return false
}
i = i + 1
}
has_digit
}
///|
fn parse_double(s : String) -> Double {
// Simple double parsing implementation
let mut result = 0.0
let mut decimal_part = 0.0
let mut decimal_divisor = 1.0
let mut in_decimal = false
let mut i = 0
let mut sign = 1.0
// Handle sign
if i < s.length() && s[i] == 45 { // '-'
sign = -1.0
i += 1
} else if i < s.length() && s[i] == 43 { // '+'
i += 1
}
while i < s.length() {
let c = s[i]
if c >= 48 && c <= 57 { // '0' to '9'
let digit = (c - 48).to_double()
if in_decimal {
decimal_divisor *= 10.0
decimal_part = decimal_part * 10.0 + digit
} else {
result = result * 10.0 + digit
}
} else if c == 46 { // '.'
in_decimal = true
} else if c == 101 || c == 69 { // 'e' or 'E'
// Scientific notation - simplified handling
break
}
i += 1
}
sign * (result + decimal_part / decimal_divisor)
}