lingmodeler
↓ 0 downloadsLingModeler: box modeling with quads and tris, rigging + weight painting, 2D vector illustration and animation, procedural textures and game assets; .lmesh / .ling native formats, OBJ, glTF and SVG import / export
Keywords 3d, modeling, vector, svg, gltf, obj, rigging, animation, textures, ling
lingfu install lingmodelerCode / Files (67)
ling/json.ling
# ━━━ json.ling — a small JSON reader/writer (for glTF). Nodes live in st lists; no maps needed ━━━
# Parse: js_parse(text) -> root node index (or -1). Node arrays (parallel):
# jn_t type: 0 null, 1 bool, 2 number, 3 string, 4 array, 5 object
# jn_v value (number / bool as 0,1 / string)
# jn_k0 first child slot, jn_kn child count (children in jc_node; object keys in jc_key)
# Tokenising is done with native str_split so large files stay fast (char-by-char slicing is O(n) each).
fn js_reset() {
st_set("jn_t", []) st_set("jn_v", []) st_set("jn_k0", []) st_set("jn_kn", [])
st_set("jc_node", []) st_set("jc_key", [])
}
fn js_type(n) { return list_get(st_get("jn_t", []), n) }
fn js_num(n) {
if n < 0.0 { return 0.0 }
bind t = js_type(n)
if t == 2.0 || t == 1.0 { return list_get(st_get("jn_v", []), n) }
return 0.0
}
fn js_str(n) {
if n < 0.0 { return "" }
if js_type(n) == 3.0 { return list_get(st_get("jn_v", []), n) }
return ""
}
fn js_len(n) { if n < 0.0 { return 0.0 } return list_get(st_get("jn_kn", []), n) }
# i-th child of an array (or object)
fn js_idx(n, i) {
if n < 0.0 { return 0.0 - 1.0 }
bind kn = list_get(st_get("jn_kn", []), n)
if i < 0.0 || i >= kn { return 0.0 - 1.0 }
return list_get(st_get("jc_node", []), list_get(st_get("jn_k0", []), n) + i)
}
fn js_key_at(n, i) { return list_get(st_get("jc_key", []), list_get(st_get("jn_k0", []), n) + i) }
# object member by key, -1 when missing
fn js_get(n, key) {
if n < 0.0 { return 0.0 - 1.0 }
if js_type(n) != 5.0 { return 0.0 - 1.0 }
bind k0 = list_get(st_get("jn_k0", []), n)
bind kn = list_get(st_get("jn_kn", []), n)
bind keys = st_get("jc_key", [])
bind nodes = st_get("jc_node", [])
bind i = 0.0
while i < kn {
if list_get(keys, k0 + i) == key { return list_get(nodes, k0 + i) }
bind i = i + 1.0
}
return 0.0 - 1.0
}
fn js_get_num(n, key, d) { bind c = js_get(n, key) if c < 0.0 { return d } return js_num(c) }
fn js_get_str(n, key, d) { bind c = js_get(n, key) if c < 0.0 { return d } return js_str(c) }
# number list from an array node
fn js_nums(n) {
bind out = list_new()
bind i = 0.0
while i < js_len(n) { bind out = list_push(out, js_num(js_idx(n, i))) bind i = i + 1.0 }
return out
}
# ── tokeniser: split on quotes, then on structural characters ────────────────
fn js_unescape(s) {
if str_find(s, "\\") < 0.0 { return s }
bind t = str_replace(s, "\\n", "\n")
bind t = str_replace(t, "\\t", "\t")
bind t = str_replace(t, "\\r", "\r")
bind t = str_replace(t, "\\/", "/")
bind t = str_replace(t, "\\\"", "\"")
bind t = str_replace(t, "\\\\", "\\")
return t
}
fn js_tokens(text) {
# pieces alternate: outside-string, inside-string, outside, ... (a backslash before the quote re-joins)
bind parts = str_split(text, "\"")
bind toks = list_new()
bind kinds = list_new()
bind inside = false
bind pending = ""
bind i = 0.0
while i < len(parts) {
bind p = list_get(parts, i)
if inside {
bind pending = pending + p
if str_ends_with(p, "\\") && !str_ends_with(p, "\\\\") {
# escaped quote inside the string
bind pending = str_slice(pending, 0.0, str_len(pending) - 1.0) + "\""
} else {
bind toks = list_push(toks, js_unescape(pending))
bind kinds = list_push(kinds, 1.0)
bind pending = ""
bind inside = false
}
} else {
# structure + literals: pad the punctuation then split on spaces
bind q = str_replace(p, "{", " { ")
bind q = str_replace(q, "}", " } ")
bind q = str_replace(q, "[", " [ ")
bind q = str_replace(q, "]", " ] ")
bind q = str_replace(q, ",", " , ")
bind q = str_replace(q, ":", " : ")
bind q = str_replace(q, "\n", " ")
bind q = str_replace(q, "\r", " ")
bind q = str_replace(q, "\t", " ")
bind ws = str_split(q, " ")
bind k = 0.0
while k < len(ws) {
bind w = list_get(ws, k)
if w != "" { bind toks = list_push(toks, w) bind kinds = list_push(kinds, 0.0) }
bind k = k + 1.0
}
bind inside = true
}
bind i = i + 1.0
}
st_set("js_toks", toks)
st_set("js_kinds", kinds)
}
fn js_new_node(t, v) {
bind idx = len(st_get("jn_t", []))
st_lpush("jn_t", t) st_lpush("jn_v", v) st_lpush("jn_k0", 0.0) st_lpush("jn_kn", 0.0)
return idx
}
# parse the value starting at token position p; returns [node, nextpos]
fn js_value(p) {
bind toks = st_get("js_toks", [])
bind kinds = st_get("js_kinds", [])
if p >= len(toks) { return [0.0 - 1.0, p] }
bind tk = list_get(toks, p)
if list_get(kinds, p) > 0.5 { return [js_new_node(3.0, tk), p + 1.0] }
if tk == "{" {
bind n = js_new_node(5.0, 0.0)
bind keys = list_new() bind kids = list_new()
bind q = p + 1.0
bind go = true
while go {
if q >= len(toks) { bind go = false }
else if list_get(toks, q) == "}" { bind q = q + 1.0 bind go = false }
else if list_get(toks, q) == "," { bind q = q + 1.0 }
else {
bind key = list_get(toks, q)
bind q = q + 1.0
if q < len(toks) { if list_get(toks, q) == ":" { bind q = q + 1.0 } }
bind r = js_value(q)
bind keys = list_push(keys, key)
bind kids = list_push(kids, list_get(r, 0))
bind q = list_get(r, 1)
}
}
bind k0 = len(st_get("jc_node", []))
bind i = 0.0
while i < len(kids) { st_lpush("jc_node", list_get(kids, i)) st_lpush("jc_key", list_get(keys, i)) bind i = i + 1.0 }
st_lset("jn_k0", n, k0)
st_lset("jn_kn", n, len(kids))
return [n, q]
}
if tk == "[" {
bind n = js_new_node(4.0, 0.0)
bind kids = list_new()
bind q = p + 1.0
bind go = true
while go {
if q >= len(toks) { bind go = false }
else if list_get(toks, q) == "]" { bind q = q + 1.0 bind go = false }
else if list_get(toks, q) == "," { bind q = q + 1.0 }
else {
bind r = js_value(q)
bind kids = list_push(kids, list_get(r, 0))
bind q = list_get(r, 1)
}
}
bind k0 = len(st_get("jc_node", []))
bind i = 0.0
while i < len(kids) { st_lpush("jc_node", list_get(kids, i)) st_lpush("jc_key", "") bind i = i + 1.0 }
st_lset("jn_k0", n, k0)
st_lset("jn_kn", n, len(kids))
return [n, q]
}
if tk == "true" { return [js_new_node(1.0, 1.0), p + 1.0] }
if tk == "false" { return [js_new_node(1.0, 0.0), p + 1.0] }
if tk == "null" { return [js_new_node(0.0, 0.0), p + 1.0] }
return [js_new_node(2.0, to_number(tk)), p + 1.0]
}
fn js_parse(text) {
js_reset()
js_tokens(text)
bind r = js_value(0.0)
return list_get(r, 0)
}
# ── writer helpers ───────────────────────────────────────────────────────────
fn js_q(s) {
bind t = str_replace(s, "\\", "\\\\")
bind t = str_replace(t, "\"", "\\\"")
bind t = str_replace(t, "\n", "\\n")
return "\"" + t + "\""
}
fn js_numlist(l) {
bind parts = list_new()
bind i = 0.0
while i < len(l) { bind parts = list_push(parts, mx_num(list_get(l, i))) bind i = i + 1.0 }
return "[" + p_join(parts, ",") + "]"
}