diff --git a/runtime/json.el b/runtime/json.el new file mode 100644 index 0000000..b7a0db9 --- /dev/null +++ b/runtime/json.el @@ -0,0 +1,173 @@ +// 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_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 +}