214 lines
8.3 KiB
EmacsLisp
214 lines
8.3 KiB
EmacsLisp
// 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<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_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
|
||
}
|