// runtime/json.el — El JSON operations // // Thin El wrappers over seed JSON primitives, plus pure-El builders and // helpers. Each function here corresponds to (and replaces) a C function // from el-compiler/runtime/legacy/el_runtime.c (lines 2692–3333). // // Seed primitives consumed by this module: // __json_get(json, key) -> String (value as string) // __json_get_raw(json, key) -> String (raw JSON token) // __json_parse_map(s) -> Map // __json_stringify_val(v) -> String // __json_array_len(arr) -> Int // __json_array_get(arr, i) -> String (element as JSON fragment) // __json_array_get_string(arr, i) -> String (element as string value) // __json_set(json, key, value) -> String (JSON mutation) // __str_to_int(s) -> Int // __str_to_float(s) -> Float // --------------------------------------------------------------------------- // Core — thin wrappers that delegate directly to seed // --------------------------------------------------------------------------- // json_get — extract a value from a JSON object as a string. // Supports dot-path traversal ("a.b.c") and array indices ("items.0.name"). fn json_get(json: String, key: String) -> String { return __json_get(json, key) } // json_get_raw — extract a raw JSON token (the un-decoded fragment) for a key. // Useful when the caller wants to pass a sub-object to another JSON function. fn json_get_raw(json: String, key: String) -> String { return __json_get_raw(json, key) } // json_parse — parse a JSON string into a Map. // Arrays become ElList; objects become ElMap; scalars are typed values. fn json_parse(s: String) -> Map { return __json_parse_map(s) } // json_stringify — serialize an El value (ElMap, ElList, String, Int) to JSON. fn json_stringify(v: Any) -> String { return __json_stringify_val(v) } // json_array_len — return the number of elements in a JSON array string. fn json_array_len(arr: String) -> Int { return __json_array_len(arr) } // json_array_get — return the i-th element of a JSON array as a JSON fragment. // Nested objects and arrays are returned verbatim. Out-of-range -> "". fn json_array_get(arr: String, i: Int) -> String { return __json_array_get(arr, i) } // json_array_get_string — return the i-th element of a JSON array as a plain // string value (quotes and escape sequences removed). Non-string elements // and out-of-range indices yield "". fn json_array_get_string(arr: String, i: Int) -> String { return __json_array_get_string(arr, i) } // --------------------------------------------------------------------------- // Typed extractors — delegate to seed then convert // --------------------------------------------------------------------------- // json_get_string — extract a string value for a key. // Equivalent to json_get but named explicitly for readability. fn json_get_string(json: String, key: String) -> String { return __json_get(json, key) } // json_get_int — extract an integer value for a key. fn json_get_int(json: String, key: String) -> Int { let s: String = __json_get(json, key) return str_to_int(s) } // json_get_float — extract a floating-point value for a key. fn json_get_float(json: String, key: String) -> Float { let s: String = __json_get(json, key) return str_to_float(s) } // json_get_bool — extract a boolean value for a key. // Returns true only when the raw JSON token is the literal "true". fn json_get_bool(json: String, key: String) -> Bool { let s: String = __json_get(json, key) return str_eq(s, "true") } // --------------------------------------------------------------------------- // Mutation // --------------------------------------------------------------------------- // json_set — set or insert a key/value pair in a JSON object string. // If the key already exists its value is replaced in-place; otherwise the // pair is appended before the closing brace. The value must already be a // valid JSON-encoded string (e.g. a quoted string, number, or sub-object). fn json_set(json: String, key: String, value: String) -> String { return __json_set(json, key, value) } // --------------------------------------------------------------------------- // Pure-El builders — no seed call required // --------------------------------------------------------------------------- // json_build_object — build a JSON object from alternating key/value strings. // // keys_and_values must contain an even number of elements laid out as: // [key0, val0, key1, val1, ...] // // Both keys and values are assumed to be plain strings that will be // double-quoted and JSON-escaped by this function. Pass a pre-encoded // number or sub-object as the value if you need non-string JSON types. // // Example: // json_build_object(["name", "alice", "role", "admin"]) // -> {"name":"alice","role":"admin"} fn json_build_object(keys_and_values: [String]) -> String { let n: Int = el_list_len(keys_and_values) let result: String = "{" let i: Int = 0 while i < n - 1 { let key: String = el_list_get(keys_and_values, i) let val: String = el_list_get(keys_and_values, i + 1) let sep: String = if i == 0 { "" } else { "," } let escaped_key: String = json_escape_string(key) let escaped_val: String = json_escape_string(val) let result = result + sep + "\"" + escaped_key + "\":\"" + escaped_val + "\"" let i = i + 2 } return result + "}" } // json_build_array — build a JSON array from a list of already-JSON-encoded // strings. // // Each element in items must be a valid JSON fragment (quoted string, number, // object, array, or literal). The function joins them with commas and wraps // the result in brackets. // // Example: // json_build_array(["\"alice\"", "\"bob\""]) // -> ["alice","bob"] fn json_build_array(items: [String]) -> String { let n: Int = el_list_len(items) let result: String = "[" let i: Int = 0 while i < n { let item: String = el_list_get(items, i) let sep: String = if i == 0 { "" } else { "," } let result = result + sep + item let i = i + 1 } return result + "]" } // json_array_push — append a pre-encoded JSON element to a JSON array string. // elem must be a valid JSON fragment (e.g. "\"foo\"" or "42"). // Returns a new JSON array string with elem appended. // Example: json_array_push("[]", "\"alice\"") -> "[\"alice\"]" fn json_array_push(arr: String, elem: String) -> String { let n: Int = json_array_len(arr) if n == 0 { return "[" + elem + "]" } // arr ends with ']'; insert before it let inner_end: Int = str_last_index_of(arr, "]") if inner_end < 0 { return "[" + elem + "]" } let prefix: String = str_slice(arr, 0, inner_end) return prefix + "," + elem + "]" } // json_escape_string — escape a raw string so it can be safely embedded as a // JSON string value. // // Characters escaped: backslash, double-quote, newline, carriage return, tab. // The returned value does NOT include surrounding double-quotes; wrap it in // quotes if you need a complete JSON string literal. fn json_escape_string(s: String) -> String { let s1: String = str_replace(s, "\\", "\\\\") let s2: String = str_replace(s1, "\"", "\\\"") let s3: String = str_replace(s2, "\n", "\\n") let s4: String = str_replace(s3, "\r", "\\r") let s5: String = str_replace(s4, "\t", "\\t") return s5 } // --------------------------------------------------------------------------- // DHARMA byte decoding // --------------------------------------------------------------------------- // bytes_to_str — decode a JSON array of integer byte values back to a string. // "[104,105]" -> "hi" // Inverse of str_to_bytes (defined in string.el). Defined here because it // depends on json_array_len and json_array_get_string which live in this file. fn bytes_to_str(arr: String) -> String { let n: Int = json_array_len(arr) if n == 0 { return "" } let out: String = __str_alloc(n) let i: Int = 0 while i < n { let elem: String = json_array_get_string(arr, i) let b: Int = __str_to_int(elem) out = __str_set_char(out, i, b) i = i + 1 } return out }