// Simple TOML parser for cell modules // Supports basic TOML features needed for the module system function toml_unescape(s) { if (!is_text(s)) return null var r = replace(s, '\\"', '"') r = replace(r, '\\\\', '\\') return r } function toml_escape(s) { if (!is_text(s)) return null var r = replace(s, '\\', '\\\\') r = replace(r, '"', '\\"') return r } function parse_toml(toml_text) { if (!is_text(toml_text)) return null // Prefer Misty split if present; fall back to JS split. var lines = array(toml_text, '\n') if (lines == null) lines = array(toml_text, '\n') var result = {} var current_section = result var current_section_name = '' var i = 0 var line = null var inner = null var section_path = null var j = 0 var key = null var eq_index = null var key_part = null var value = null var unquoted = null for (i = 0; i < length(lines); i++) { line = trim(lines[i]) if (line == null) line = lines[i] // Skip empty lines and comments if (!line || starts_with(line, '#')) continue // Section header if (starts_with(line, '[') && ends_with(line, ']')) { inner = text(line, 1, -1) section_path = parse_key_path(inner) if (section_path == null) return null current_section = result current_section_name = text(section_path, '.') for (j = 0; j < length(section_path); j++) { key = section_path[j] // Only treat null as "missing"; do not clobber false/0/"" if (current_section[key] == null) { current_section[key] = {} } else if (!is_object(current_section[key])) { // Scalar/table collision like: a = 1 then [a.b] return null } current_section = current_section[key] } continue } // Key-value pair eq_index = search(line, '=') if (eq_index != null && eq_index > 0) { key_part = trim(text(line, 0, eq_index)) value = trim(text(line, eq_index + 1)) if (key_part == null) key_part = trim(text(line, 0, eq_index)) if (value == null) value = trim(text(line, eq_index + 1)) key = parse_key(key_part) if (key == null) return null if (starts_with(value, '"') && ends_with(value, '"')) { unquoted = text(value, 1, -1) current_section[key] = toml_unescape(unquoted) if (current_section[key] == null) return null } else if (starts_with(value, '[') && ends_with(value, ']')) { current_section[key] = parse_array(value) if (current_section[key] == null) return null } else if (value == 'true' || value == 'false') { current_section[key] = value == 'true' } else if (is_number(value)) { current_section[key] = Number(value) } else { // Unquoted string current_section[key] = value } } } return result } function parse_key(str) { if (!is_text(str)) return null var inner = null if (starts_with(str, '"') && ends_with(str, '"')) { inner = text(str, 1, -1) return toml_unescape(inner) } return str } // Split a key path by dots, respecting quotes function parse_key_path(str) { if (!is_text(str)) return null var parts = [] var current = '' var in_quote = false var i = 0 var c = null var piece = null for (i = 0; i < length(str); i++) { c = str[i] if (c == '"' && (i == 0 || str[i - 1] != '\\')) { in_quote = !in_quote } else if (c == '.' && !in_quote) { piece = trim(current) if (piece == null) piece = trim(current) push(parts, parse_key(piece)) current = '' continue } current = current + c } var tail = trim(current) if (tail == null) tail = trim(current) if (length(tail) > 0) push(parts, parse_key(tail)) return parts } function parse_array(str) { if (!is_text(str)) return null // Remove brackets and trim var s = text(str, 1, -1) s = trim(s) if (!s) return [] var items = [] var current = '' var in_quotes = false var i = 0 var ch = null var piece = null for (i = 0; i < length(s); i++) { ch = s[i] if (ch == '"' && (i == 0 || s[i - 1] != '\\')) { in_quotes = !in_quotes current = current + ch } else if (ch == ',' && !in_quotes) { piece = trim(current) if (piece == null) piece = trim(current) push(items, parse_value(piece)) current = '' } else { current = current + ch } } var last = trim(current) if (last == null) last = trim(current) if (last) push(items, parse_value(last)) return items } function parse_value(str) { if (!is_text(str)) return null if (starts_with(str, '"') && ends_with(str, '"')) { return toml_unescape(text(str, 1, -1)) } if (str == 'true' || str == 'false') return str == 'true' // Use your existing numeric test; TOML numeric formats are richer, but this keeps your "module TOML" scope. if (!isNaN(Number(str))) return Number(str) return str } function encode_toml(obj) { var result = [] function encode_value(value) { var items = null var i = 0 if (is_text(value)) return '"' + toml_escape(value) + '"' if (is_logical(value)) return value ? 'true' : 'false' if (is_number(value)) return text(value) if (is_array(value)) { items = [] for (i = 0; i < length(value); i++) push(items, encode_value(value[i])) return '[' + text(items, ', ') + ']' } return text(value) } function quote_key(k) { if (search(k, '.') != null || search(k, '"') != null || search(k, ' ') != null) { return '"' + toml_escape(k) + '"' } return k } // First pass: encode top-level simple values var keys = array(obj) var i = 0 var key = null var value = null for (i = 0; i < length(keys); i++) { key = keys[i] value = obj[key] if (!is_object(value)) push(result, quote_key(key) + ' = ' + encode_value(value)) } // Second pass: encode nested objects function encode_section(o, path) { var keys = array(o) var i = 0 var key = null var value = null var quoted = null var section_path = null var section_keys = null var j = 0 var sk = null var sv = null for (i = 0; i < length(keys); i++) { key = keys[i] value = o[key] if (is_object(value)) { quoted = quote_key(key) section_path = path ? path + '.' + quoted : quoted push(result, '[' + section_path + ']') // Direct properties section_keys = array(value) for (j = 0; j < length(section_keys); j++) { sk = section_keys[j] sv = value[sk] if (!is_object(sv)) push(result, quote_key(sk) + ' = ' + encode_value(sv)) } // Nested sections encode_section(value, section_path) } } } encode_section(obj, '') return text(result, '\n') } return { decode: parse_toml, encode: encode_toml }