///|
/// Helper function to escape strings according to TOML spec
fn escape_toml_string(s : String) -> String {
let result = StringBuilder::new()
for char in s {
match char {
'\b' => result.write_string("\\b")
'\t' => result.write_string("\\t")
'\n' => result.write_string("\\n")
'\f' => result.write_string("\\f")
'\r' => result.write_string("\\r")
'"' => result.write_string("\\\"")
'\\' => result.write_string("\\\\")
// Control characters (U+0000 to U+001F) except the above
c if c >= '\u0000' && c <= '\u001F' => {
result.write_string("\\u")
let code = c.to_int()
// Format as 4-digit hex manually
let hex = StringBuilder::new()
for i = 3; i >= 0; i = i - 1 {
let digit = (code >> (i * 4)) & 0xF
if digit < 10 {
hex.write_char(Int::unsafe_to_char('0'.to_int() + digit))
} else {
hex.write_char(Int::unsafe_to_char('A'.to_int() + digit - 10))
}
}
result.write_string(hex.to_string())
}
// DEL character (U+007F)
'\u007F' => result.write_string("\\u007F")
c => result.write_char(c)
}
}
result.to_string()
}
///|
/// Check if a string needs to be quoted (contains special characters or starts/ends with whitespace)
fn needs_quoting(s : String) -> Bool {
if s.is_empty() {
return true
}
// Check for leading/trailing whitespace
if s is [' ' | '\t', ..] || s is [.., ' ' | '\t'] {
return true
}
// if s lexmatch? ("([ \t].*|.*[ \t])") {
// return true
// }
// Check for special characters
for char in s {
match char {
'\n'
| '\r'
| '\t'
| '"'
| '\\'
| '['
| ']'
| '{'
| '}'
| '='
| ','
| '#' => return true
c if c >= '\u0000' && c <= '\u001F' => return true
'\u007F' => return true
_ => continue
}
}
false
}
///|
test "needs_quoting tests" {
debug_inspect(needs_quoting("simplekey"), content="false")
debug_inspect(needs_quoting("key with spaces"), content="false")
debug_inspect(needs_quoting("key\nwith\nnewlines"), content="true")
debug_inspect(needs_quoting(" key"), content="true")
debug_inspect(needs_quoting("key "), content="true")
debug_inspect(needs_quoting(""), content="true")
debug_inspect(needs_quoting("key=with=equals"), content="true")
}
///|
/// Format a key for TOML output
fn format_toml_key(key : String) -> String {
if needs_quoting(key) {
"\"\{escape_toml_string(key)}\""
} else {
key
}
}
///|
/// Helper to check if an array should be formatted inline
fn should_format_inline(arr : Array[TomlValue]) -> Bool {
// Empty arrays or arrays with more than 5 elements should be multiline
if arr.is_empty() || arr.length() > 5 {
return arr.is_empty()
}
// Arrays containing tables should be multiline
for value in arr {
match value {
TomlTable(_) => return false
TomlArray(inner) if !should_format_inline(inner) => return false
_ => continue
}
}
true
}
///|
/// Convert a TomlValue to its string representation in TOML format
pub fn TomlValue::to_string(self : TomlValue) -> String {
let result = StringBuilder::new()
self.write_toml(result, [])
result.to_string()
}
///|
/// TODO: the logger interface is good?
pub impl Show for TomlValue with output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Internal helper to write TOML with proper formatting
fn TomlValue::write_toml(
self : TomlValue,
output : StringBuilder,
path : Array[String], // Current table path for nested tables
) -> Unit {
match self {
TomlString(s) => output.write_string("\"\{escape_toml_string(s)}\"")
TomlInteger(i) => output.write_string(i.to_string())
TomlFloat(f) =>
// Handle special float values
if f.is_nan() {
output.write_string("nan")
} else if f.is_inf() {
if f < 0.0 {
output.write_string("-inf")
} else {
output.write_string("inf")
}
} else {
// Ensure floats always have decimal point
let s = f.to_string()
if !s.contains(".") && !s.contains("e") && !s.contains("E") {
output.write_string(s + ".0")
} else {
output.write_string(s)
}
}
TomlBoolean(b) => output.write_string(if b { "true" } else { "false" })
TomlDateTime(dt) =>
match dt {
OffsetDateTime(s) | LocalDateTime(s) | LocalDate(s) | LocalTime(s) =>
output.write_string(s)
}
TomlArray(arr) =>
if should_format_inline(arr) {
// Inline array
output.write_string("[")
for i = 0; i < arr.length(); i = i + 1 {
if i > 0 {
output.write_string(", ")
}
arr[i].write_toml(output, path)
}
output.write_string("]")
} else {
// Multiline array
output.write_string("[\n")
for i = 0; i < arr.length(); i = i + 1 {
output.write_string(" ")
arr[i].write_toml(output, path)
if i < arr.length() - 1 {
output.write_string(",")
}
output.write_string("\n")
}
output.write_string("]")
}
TomlTable(table) =>
if path.is_empty() {
// Root table - format as top-level TOML document
write_table_contents(table, output, path)
} else {
// Inline table for nested tables
write_inline_table(table, output)
}
}
}
///|
/// Write table contents as key-value pairs
fn write_table_contents(
table : Map[String, TomlValue],
output : StringBuilder,
path : Array[String],
) -> Unit {
let simple_pairs : Array[(String, TomlValue)] = []
let array_pairs : Array[(String, TomlValue)] = []
let table_pairs : Array[(String, TomlValue)] = []
// Categorize entries
table.each(fn(key, value) {
match value {
TomlTable(_) => table_pairs.push((key, value))
TomlArray(arr) => {
// Check if it's an array of tables
let mut is_table_array = true
for item in arr {
match item {
TomlTable(_) => continue
_ => {
is_table_array = false
break
}
}
}
if is_table_array && arr.length() > 0 {
table_pairs.push((key, value))
} else {
array_pairs.push((key, value))
}
}
_ => simple_pairs.push((key, value))
}
})
// Write simple key-value pairs first
for i = 0; i < simple_pairs.length(); i = i + 1 {
let (key, value) = simple_pairs[i]
output.write_string(format_toml_key(key))
output.write_string(" = ")
value.write_toml(output, path)
output.write_string("\n")
}
// Write arrays
for i = 0; i < array_pairs.length(); i = i + 1 {
let (key, value) = array_pairs[i]
if i > 0 || simple_pairs.length() > 0 {
output.write_string("\n")
}
output.write_string(format_toml_key(key))
output.write_string(" = ")
value.write_toml(output, path)
output.write_string("\n")
}
// Write nested tables
for i = 0; i < table_pairs.length(); i = i + 1 {
let (key, value) = table_pairs[i]
if i > 0 || simple_pairs.length() > 0 || array_pairs.length() > 0 {
output.write_string("\n")
}
match value {
TomlArray(arr) =>
// Array of tables
for j = 0; j < arr.length(); j = j + 1 {
if j > 0 {
output.write_string("\n")
}
output.write_string("[[")
write_table_path(path, output)
if path.length() > 0 {
output.write_string(".")
}
output.write_string(format_toml_key(key))
output.write_string("]]\n")
match arr[j] {
TomlTable(t) => {
let new_path = path.copy()
new_path.push(key)
write_table_contents(t, output, new_path)
}
_ => () // Should not happen if properly validated
}
}
TomlTable(t) => {
// Regular nested table
output.write_string("[")
write_table_path(path, output)
if path.length() > 0 {
output.write_string(".")
}
output.write_string(format_toml_key(key))
output.write_string("]\n")
let new_path = path.copy()
new_path.push(key)
write_table_contents(t, output, new_path)
}
_ => () // Should not happen
}
}
}
///|
/// Write an inline table
fn write_inline_table(
table : Map[String, TomlValue],
output : StringBuilder,
) -> Unit {
output.write_string("{ ")
let mut first = true
table.each(fn(key, value) {
if !first {
output.write_string(", ")
}
first = false
output.write_string(format_toml_key(key))
output.write_string(" = ")
// For inline tables, nested tables should also be inline
match value {
TomlTable(t) => write_inline_table(t, output)
_ => value.write_toml(output, [])
}
})
output.write_string(" }")
}
///|
/// Write the table path for section headers
fn write_table_path(path : Array[String], output : StringBuilder) -> Unit {
for i = 0; i < path.length(); i = i + 1 {
if i > 0 {
output.write_string(".")
}
output.write_string(format_toml_key(path[i]))
}
}