refactor(x): per-process scratch dirs, Ludic ports of the LSP test and Forgejo release

- every scratch file lives in `$TMPDIR/x_<pid>/` (tmp_dir/tmp_path in
  prelude.ludic), removed by main's new dispatch() → tmp_cleanup();
  X_KEEP_TMP=1 keeps it. `x test` and `x check-*` may now run together.
- `x test-lsp` (tools/x/lsp_test.ludic) replaces tools/test-lsp.py: the
  whole request stream is framed into one stdin file, the server runs to
  `exit`, and the response stream is parsed back by request id; adds a
  check that ludicc's own error is published on save
- tools/x/forgejo.ludic replaces tools/ci/forgejo_release.py (curl with a
  0600 header file; the token is no longer on the command line;
  LUDIC_FORGEJO_API for forks)
- `x docs-palette --check` regenerates into scratch and compares, so the
  drift guard judges the working tree rather than git HEAD; the generator
  no longer emits a trailing blank line the formatter rejects
- `x test-tools`: exit 2 from test-lsp / test-grammar.js is a visible
  skip, never a pass; the widget-prop test uses the `id: Root` syntax
- tools/test-grammar.js: current vocabulary (property/model/handler/
  prefab/scene/event/become/@Queries), LUDIC_NODE_MODULES, exit 2 on skip
- tools/atlas.ludic rewritten in the current language (it did not compile)
- operators.ludic / os.ludic registered in the suite
- json.ludic: j_quote() writer helper

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-05 01:12:26 +03:00
parent 647dfec334
commit 8fed9add66
19 changed files with 1154 additions and 614 deletions

View file

@ -26,18 +26,18 @@ var jpos: int = 0
var jerr: bool = false
function j_ws() -> void {
while jbuf[jpos] == 32 or jbuf[jpos] == 9 or jbuf[jpos] == 10 or jbuf[jpos] == 13 { jpos = jpos + 1 }
while jbuf[jpos] == ' ' or jbuf[jpos] == '\t' or jbuf[jpos] == '\n' or jbuf[jpos] == '\r' { jpos += 1 }
}
function j_hex(c: int) -> int {
if c >= 48 and c <= 57 { return c - 48 }
if c >= 97 and c <= 102 { return c - 97 + 10 }
if c >= 65 and c <= 70 { return c - 65 + 10 }
if c >= '0' and c <= '9' { return c - 48 }
if c >= 'a' and c <= 'f' { return c - 97 + 10 }
if c >= 'A' and c <= 'F' { return c - 65 + 10 }
return 0
}
function j_hex4() -> int {
var v = 0; var i = 0
while i < 4 { v = v * 16 + j_hex(jbuf[jpos]); jpos = jpos + 1; i = i + 1 }
while i < 4 { v = v * 16 + j_hex(jbuf[jpos]); jpos += 1; i += 1 }
return v
}
@ -52,32 +52,32 @@ function utf8_put(b: pointer, n: int, cp: int) -> int {
# parse a JSON string starting at the opening quote; returns a fresh buffer
function j_string() -> pointer {
jpos = jpos + 1 # opening quote
jpos += 1 # opening quote
# over-allocate: escapes only ever shrink the byte count
let cap = slen(jbuf) - jpos + 1
let out = bytes(cap + 4)
var n = 0
while jbuf[jpos] != 0 and jbuf[jpos] != 34 {
if jbuf[jpos] == 92 {
jpos = jpos + 1
let c = jbuf[jpos]; jpos = jpos + 1
if c == 110 { out[n] = 10; n = n + 1 }
else { if c == 116 { out[n] = 9; n = n + 1 }
else { if c == 114 { out[n] = 13; n = n + 1 }
else { if c == 98 { out[n] = 8; n = n + 1 }
else { if c == 102 { out[n] = 12; n = n + 1 }
else { if c == 117 {
while jbuf[jpos] != 0 and jbuf[jpos] != '"' {
if jbuf[jpos] == '\\' {
jpos += 1
let c = jbuf[jpos]; jpos += 1
if c == 'n' { out[n] = '\n'; n += 1 }
else { if c == 't' { out[n] = '\t'; n += 1 }
else { if c == 'r' { out[n] = '\r'; n += 1 }
else { if c == 'b' { out[n] = 8; n += 1 }
else { if c == 'f' { out[n] = 12; n += 1 }
else { if c == 'u' {
var cp = j_hex4()
if cp >= 55296 and cp < 56320 and jbuf[jpos] == 92 and jbuf[jpos + 1] == 117 {
jpos = jpos + 2
if cp >= 55296 and cp < 56320 and jbuf[jpos] == '\\' and jbuf[jpos + 1] == 'u' {
jpos += 2
let lo = j_hex4()
cp = 65536 + ((cp - 55296) << 10) + (lo - 56320)
}
n = utf8_put(out, n, cp)
} else { out[n] = c; n = n + 1 } } } } } }
} else { out[n] = jbuf[jpos]; n = n + 1; jpos = jpos + 1 }
} else { out[n] = c; n += 1 } } } } } }
} else { out[n] = jbuf[jpos]; n += 1; jpos += 1 }
}
if jbuf[jpos] == 34 { jpos = jpos + 1 }
if jbuf[jpos] == '"' { jpos += 1 }
out[n] = 0
return out
}
@ -85,55 +85,55 @@ function j_string() -> pointer {
function j_value() -> JVal {
j_ws()
let c = jbuf[jpos]
if c == 123 { # '{'
jpos = jpos + 1
if c == '{' { # '{'
jpos += 1
let v = jval_new(JV_OBJ)
j_ws()
if jbuf[jpos] == 125 { jpos = jpos + 1; return v }
if jbuf[jpos] == '}' { jpos += 1; return v }
while true {
j_ws()
var key = ""
if jbuf[jpos] == 34 { key = j_string() } else { jerr = true }
j_ws(); if jbuf[jpos] == 58 { jpos = jpos + 1 } else { jerr = true }
if jbuf[jpos] == '"' { key = j_string() } else { jerr = true }
j_ws(); if jbuf[jpos] == ':' { jpos += 1 } else { jerr = true }
let kid = j_value()
push(v.keys, key); push(v.kids, kid)
j_ws()
if jbuf[jpos] == 44 { jpos = jpos + 1 } else { if jbuf[jpos] == 125 { jpos = jpos + 1; return v } else { return v } }
if jbuf[jpos] == ',' { jpos += 1 } else { if jbuf[jpos] == '}' { jpos += 1; return v } else { return v } }
}
return v
}
if c == 91 { # '['
jpos = jpos + 1
if c == '[' { # '['
jpos += 1
let v = jval_new(JV_ARR)
j_ws()
if jbuf[jpos] == 93 { jpos = jpos + 1; return v }
if jbuf[jpos] == ']' { jpos += 1; return v }
while true {
let kid = j_value()
push(v.kids, kid)
j_ws()
if jbuf[jpos] == 44 { jpos = jpos + 1 } else { if jbuf[jpos] == 93 { jpos = jpos + 1; return v } else { return v } }
if jbuf[jpos] == ',' { jpos += 1 } else { if jbuf[jpos] == ']' { jpos += 1; return v } else { return v } }
}
return v
}
if c == 34 { # '"'
if c == '"' { # '"'
let v = jval_new(JV_STR)
v.s = j_string()
return v
}
if c == 116 { # true
jpos = jpos + 4; let v = jval_new(JV_BOOL); v.b = 1; return v
if c == 't' { # true
jpos += 4; let v = jval_new(JV_BOOL); v.b = 1; return v
}
if c == 102 { # false
jpos = jpos + 5; let v = jval_new(JV_BOOL); v.b = 0; return v
if c == 'f' { # false
jpos += 5; let v = jval_new(JV_BOOL); v.b = 0; return v
}
if c == 110 { # null
jpos = jpos + 4; return jval_new(JV_NULL)
if c == 'n' { # null
jpos += 4; return jval_new(JV_NULL)
}
# number
let start = jpos
if jbuf[jpos] == 45 { jpos = jpos + 1 }
if jbuf[jpos] < 48 or jbuf[jpos] > 57 { jerr = true; if jbuf[jpos] != 0 { jpos = jpos + 1 }; return jval_new(JV_NULL) }
while (jbuf[jpos] >= 48 and jbuf[jpos] <= 57) or jbuf[jpos] == 46 or jbuf[jpos] == 101 or jbuf[jpos] == 69 or jbuf[jpos] == 43 or jbuf[jpos] == 45 { jpos = jpos + 1 }
if jbuf[jpos] == '-' { jpos += 1 }
if jbuf[jpos] < '0' or jbuf[jpos] > '9' { jerr = true; if jbuf[jpos] != 0 { jpos += 1 }; return jval_new(JV_NULL) }
while (jbuf[jpos] >= '0' and jbuf[jpos] <= '9') or jbuf[jpos] == '.' or jbuf[jpos] == 'e' or jbuf[jpos] == 'E' or jbuf[jpos] == '+' or jbuf[jpos] == '-' { jpos += 1 }
let v = jval_new(JV_NUM)
v.s = sslice(jbuf, start, jpos)
v.num = str_to_int(v.s)
@ -158,17 +158,43 @@ function json_valid(text: pointer) -> bool {
# integer value of a decimal string (ignores any fractional part)
function str_to_int(s: pointer) -> int {
var i = 0; var neg = false
if s[0] == 45 { neg = true; i = 1 }
if s[0] == '-' { neg = true; i = 1 }
var v = 0
while s[i] >= 48 and s[i] <= 57 { v = v * 10 + (s[i] - 48); i = i + 1 }
if neg { return 0 - v }
while s[i] >= '0' and s[i] <= '9' { v = v * 10 + (s[i] - 48); i += 1 }
if neg { return -v }
return v
}
# the lowercase hex digit for 0..15
function j_hexch(v: int) -> int {
if v < 10 { return '0' + v }
return 'a' + (v - 10)
}
# `s` as a JSON string literal: quoted, with " \\ and control bytes escaped
function j_quote(s: pointer) -> pointer {
let b = sb_new()
sb_putc(b, '"')
var i = 0
while s[i] != 0 {
let c = s[i]
if c == '"' { sb_puts(b, "\\\"") }
else if c == '\\' { sb_puts(b, "\\\\") }
else if c == '\n' { sb_puts(b, "\\n") }
else if c == '\r' { sb_puts(b, "\\r") }
else if c == '\t' { sb_puts(b, "\\t") }
else if c >= 0 and c < ' ' { sb_puts(b, "\\u00"); sb_putc(b, j_hexch(c / 16)); sb_putc(b, j_hexch(c % 16)) }
else { sb_putc(b, c) }
i += 1
}
sb_putc(b, '"')
return sb_str(b)
}
# object member lookup by key (JV_NULL if absent or not an object)
function j_get(v: JVal, key: pointer) -> JVal {
if v.t != JV_OBJ { return jval_new(JV_NULL) }
var i = 0
while i < len(v.keys) { if v.keys[i] == key { return v.kids[i] }; i = i + 1 }
while i < len(v.keys) { if v.keys[i] == key { return v.kids[i] }; i += 1 }
return jval_new(JV_NULL)
}