merge runtime/json — JSON operations in El

This commit is contained in:
Will Anderson
2026-05-03 15:37:41 -05:00
+173
View File
@@ -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 26923333).
//
// 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<String, Any>
// __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<String, Any>.
// Arrays become ElList; objects become ElMap; scalars are typed values.
fn json_parse(s: String) -> Map<String, Any> {
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
}