js-on-sx: harness cache — precompute HARNESS_STUB SX once per run
Root cause: every sx_server worker session used js-eval on the 3.6KB HARNESS_STUB, paying ~15s for tokenize+parse+transpile even though every session does the same thing. Over a full scoreboard with periodic worker restarts that's minutes of wasted work. Fix: transpile once per Python process. Spin up a throwaway sx_server, run (inspect (js-transpile (js-parse (js-tokenize HARNESS_STUB)))), write the resulting SX source to lib/js/.harness-cache/stub.<fingerprint>.sx and a stable-name symlink-ish copy stub.sx. Every worker session then does a single (load .harness-cache/stub.sx) instead of re-running js-eval. Fingerprint: sha256(HARNESS_STUB + lexer.sx + parser.sx + transpile.sx). Transpiler edits invalidate the cache automatically. Runs back-to-back reuse the cache — only the first run after a transpiler change pays the ~15s precompute. Transpile had to gain a $-to-_js_dollar_ name-mangler: the SX tokenizer rejects $ in identifiers, which broke round-tripping via inspect. JS $DONOTEVALUATE → SX _js_dollar_DONOTEVALUATE. Internal JS-on-SX names are unaffected (none contain $). Measured: 300-test wide (Math+Number+String @ 100/cat, --per-test-timeout 5): 593.7s → 288.0s, 2.06x speedup. Scoreboard 114→115/300 (38.3%, noise band). Math 40%, Number 44%, String 30% — same shape as prior. Baselines: 520/522 unit, 148/148 slice — unchanged.
This commit is contained in:
1
lib/js/.gitignore
vendored
1
lib/js/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
test262-upstream/
|
||||
.harness-cache/
|
||||
|
||||
@@ -55,6 +55,10 @@ HARNESS_DIR = UPSTREAM / "harness"
|
||||
DEFAULT_PER_TEST_TIMEOUT_S = 5.0
|
||||
DEFAULT_BATCH_TIMEOUT_S = 120
|
||||
|
||||
# Cache dir for precomputed SX source of harness JS (one file per Python run).
|
||||
# Written once in main(), read via (load ...) by every worker session.
|
||||
HARNESS_CACHE_DIR = REPO / "lib" / "js" / ".harness-cache"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Harness stub — replaces assert.js + sta.js with something our parser handles.
|
||||
@@ -588,6 +592,175 @@ def load_test(path: Path):
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Harness cache — transpile HARNESS_STUB once, write SX to disk.
|
||||
# Every worker then loads the cached .sx (a few ms) instead of re-running
|
||||
# js-tokenize + js-parse + js-transpile (15+ s).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Remembered across the Python process. None until we've run the precompute.
|
||||
_HARNESS_CACHE_PATH: "Path | None" = None
|
||||
# Per-filename include cache: maps 'compareArray.js' -> Path of cached .sx.
|
||||
_EXTRA_HARNESS_CACHE: dict = {}
|
||||
|
||||
|
||||
def _harness_cache_rel_path() -> "str | None":
|
||||
if _HARNESS_CACHE_PATH is None:
|
||||
return None
|
||||
try:
|
||||
return _HARNESS_CACHE_PATH.relative_to(REPO).as_posix()
|
||||
except ValueError:
|
||||
return str(_HARNESS_CACHE_PATH)
|
||||
|
||||
|
||||
def _precompute_sx(js_source: str, timeout_s: float = 120.0) -> str:
|
||||
"""Run one throwaway sx_server to turn a chunk of JS into the SX text that
|
||||
js-eval would have evaluated. Returns the raw SX source (no outer quotes).
|
||||
"""
|
||||
proc = subprocess.Popen(
|
||||
[str(SX_SERVER)],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
cwd=str(REPO),
|
||||
bufsize=0,
|
||||
)
|
||||
fd = proc.stdout.fileno()
|
||||
os.set_blocking(fd, False)
|
||||
|
||||
buf = [b""]
|
||||
|
||||
def readline(timeout: float):
|
||||
deadline = time.monotonic() + timeout
|
||||
while True:
|
||||
nl = buf[0].find(b"\n")
|
||||
if nl >= 0:
|
||||
line = buf[0][: nl + 1]
|
||||
buf[0] = buf[0][nl + 1 :]
|
||||
return line
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
raise TimeoutError("precompute readline timeout")
|
||||
rlist, _, _ = select.select([fd], [], [], remaining)
|
||||
if not rlist:
|
||||
raise TimeoutError("precompute readline timeout")
|
||||
try:
|
||||
chunk = os.read(fd, 65536)
|
||||
except (BlockingIOError, InterruptedError):
|
||||
continue
|
||||
if not chunk:
|
||||
return None
|
||||
buf[0] += chunk
|
||||
|
||||
def run(epoch: int, cmd: str, to: float = 60.0):
|
||||
proc.stdin.write(f"(epoch {epoch})\n{cmd}\n".encode("utf-8"))
|
||||
proc.stdin.flush()
|
||||
deadline = time.monotonic() + to
|
||||
while time.monotonic() < deadline:
|
||||
line = readline(deadline - time.monotonic())
|
||||
if line is None:
|
||||
raise RuntimeError("precompute: sx_server closed stdout")
|
||||
m = RX_OK_INLINE.match(line.decode("utf-8", "replace"))
|
||||
if m and int(m.group(1)) == epoch:
|
||||
return "ok", m.group(2)
|
||||
m = RX_OK_LEN.match(line.decode("utf-8", "replace"))
|
||||
if m and int(m.group(1)) == epoch:
|
||||
val = readline(deadline - time.monotonic())
|
||||
return "ok", (val or b"").decode("utf-8", "replace").rstrip("\n")
|
||||
m = RX_ERR.match(line.decode("utf-8", "replace"))
|
||||
if m and int(m.group(1)) == epoch:
|
||||
return "error", m.group(2)
|
||||
raise TimeoutError(f"precompute epoch {epoch}")
|
||||
|
||||
try:
|
||||
# Wait for ready
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
line = readline(deadline - time.monotonic())
|
||||
if line is None:
|
||||
raise RuntimeError("precompute: sx_server closed before ready")
|
||||
if b"(ready)" in line:
|
||||
break
|
||||
# Load JS kernel
|
||||
run(1, '(load "lib/r7rs.sx")')
|
||||
run(2, '(load "lib/js/lexer.sx")')
|
||||
run(3, '(load "lib/js/parser.sx")')
|
||||
run(4, '(load "lib/js/transpile.sx")')
|
||||
# Transpile to SX source via inspect
|
||||
inner = js_source.replace("\\", "\\\\").replace('"', '\\"')
|
||||
inner = inner.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
|
||||
outer = inner.replace("\\", "\\\\").replace('"', '\\"')
|
||||
cmd = f'(eval "(inspect (js-transpile (js-parse (js-tokenize \\"{outer}\\"))))")'
|
||||
kind, payload = run(5, cmd, timeout_s)
|
||||
if kind != "ok":
|
||||
raise RuntimeError(f"precompute error: {payload[:200]}")
|
||||
# payload is an SX string-literal — peel one layer of quoting.
|
||||
import json as _json
|
||||
if payload.startswith('"') and payload.endswith('"'):
|
||||
return _json.loads(payload)
|
||||
return payload
|
||||
finally:
|
||||
try:
|
||||
proc.stdin.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
proc.terminate()
|
||||
proc.wait(timeout=3)
|
||||
except Exception:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _harness_fingerprint() -> str:
|
||||
import hashlib
|
||||
# Include the exact runtime/transpile source hash so a change to the
|
||||
# transpiler invalidates the cache automatically.
|
||||
h = hashlib.sha256()
|
||||
h.update(HARNESS_STUB.encode("utf-8"))
|
||||
for p in ("lib/js/lexer.sx", "lib/js/parser.sx", "lib/js/transpile.sx"):
|
||||
try:
|
||||
h.update((REPO / p).read_bytes())
|
||||
except Exception:
|
||||
pass
|
||||
return h.hexdigest()[:16]
|
||||
|
||||
|
||||
def precompute_harness_cache() -> Path:
|
||||
"""Populate _HARNESS_CACHE_PATH by transpiling HARNESS_STUB once and
|
||||
writing it to disk. Every worker session then does (load <path>) instead.
|
||||
|
||||
Reuses a prior cache file from a previous `python3 test262-runner.py`
|
||||
run when the fingerprint (harness text + transpiler source hash) still
|
||||
matches — that covers the common case of re-running scoreboards back-to-back
|
||||
without touching transpile.sx.
|
||||
"""
|
||||
global _HARNESS_CACHE_PATH
|
||||
HARNESS_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
fp = _harness_fingerprint()
|
||||
dst = HARNESS_CACHE_DIR / f"stub.{fp}.sx"
|
||||
stable = HARNESS_CACHE_DIR / "stub.sx"
|
||||
if dst.exists() and dst.stat().st_size > 0:
|
||||
# Expose both the canonical and fingerprinted names — sessions load
|
||||
# the canonical one.
|
||||
stable.write_bytes(dst.read_bytes())
|
||||
_HARNESS_CACHE_PATH = stable
|
||||
print(f"harness cache: reused {dst.name} ({dst.stat().st_size} bytes)",
|
||||
file=sys.stderr)
|
||||
return stable
|
||||
t0 = time.monotonic()
|
||||
sx = _precompute_sx(HARNESS_STUB)
|
||||
dst.write_text(sx, encoding="utf-8")
|
||||
stable.write_text(sx, encoding="utf-8")
|
||||
_HARNESS_CACHE_PATH = stable
|
||||
dt = time.monotonic() - t0
|
||||
print(f"harness cache: {len(HARNESS_STUB)} JS chars → {len(sx)} SX chars "
|
||||
f"at {stable.relative_to(REPO)} (fp={fp}, {dt:.2f}s)", file=sys.stderr)
|
||||
return stable
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Long-lived server session
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -625,13 +798,18 @@ class ServerSession:
|
||||
self._run_and_collect(3, '(load "lib/js/parser.sx")', timeout=60.0)
|
||||
self._run_and_collect(4, '(load "lib/js/transpile.sx")', timeout=60.0)
|
||||
self._run_and_collect(5, '(load "lib/js/runtime.sx")', timeout=60.0)
|
||||
# Preload the stub harness as one big js-eval
|
||||
stub_escaped = sx_escape_for_nested_eval(HARNESS_STUB)
|
||||
self._run_and_collect(
|
||||
6,
|
||||
f'(eval "(js-eval \\"{stub_escaped}\\")")',
|
||||
timeout=60.0,
|
||||
)
|
||||
# Preload the stub harness — use precomputed SX cache when available
|
||||
# (huge win: ~15s js-eval HARNESS_STUB → ~0s load precomputed .sx).
|
||||
cache_rel = _harness_cache_rel_path()
|
||||
if cache_rel is not None:
|
||||
self._run_and_collect(6, f'(load "{cache_rel}")', timeout=60.0)
|
||||
else:
|
||||
stub_escaped = sx_escape_for_nested_eval(HARNESS_STUB)
|
||||
self._run_and_collect(
|
||||
6,
|
||||
f'(eval "(js-eval \\"{stub_escaped}\\")")',
|
||||
timeout=60.0,
|
||||
)
|
||||
|
||||
def stop(self) -> None:
|
||||
if self.proc is not None:
|
||||
@@ -964,6 +1142,14 @@ def main(argv):
|
||||
all_paths = all_paths[: args.limit]
|
||||
print(f"Discovered {len(all_paths)} test files.", file=sys.stderr)
|
||||
|
||||
# Precompute harness cache once per run. Workers (forked) inherit module
|
||||
# globals, so the cache path is visible to every session.start() call.
|
||||
try:
|
||||
precompute_harness_cache()
|
||||
except Exception as e:
|
||||
print(f"harness cache precompute failed ({e}); falling back to js-eval per session",
|
||||
file=sys.stderr)
|
||||
|
||||
tests = []
|
||||
results = []
|
||||
per_cat_count = defaultdict(int)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"totals": {
|
||||
"pass": 118,
|
||||
"fail": 160,
|
||||
"pass": 115,
|
||||
"fail": 174,
|
||||
"skip": 1597,
|
||||
"timeout": 22,
|
||||
"timeout": 11,
|
||||
"total": 1897,
|
||||
"runnable": 300,
|
||||
"pass_rate": 39.3
|
||||
"pass_rate": 38.3
|
||||
},
|
||||
"categories": [
|
||||
{
|
||||
@@ -35,46 +35,42 @@
|
||||
{
|
||||
"category": "built-ins/Number",
|
||||
"total": 340,
|
||||
"pass": 48,
|
||||
"fail": 45,
|
||||
"pass": 44,
|
||||
"fail": 52,
|
||||
"skip": 240,
|
||||
"timeout": 7,
|
||||
"pass_rate": 48.0,
|
||||
"timeout": 4,
|
||||
"pass_rate": 44.0,
|
||||
"top_failures": [
|
||||
[
|
||||
"Test262Error (assertion failed)",
|
||||
42
|
||||
52
|
||||
],
|
||||
[
|
||||
"Timeout",
|
||||
7
|
||||
],
|
||||
[
|
||||
"ReferenceError (undefined symbol)",
|
||||
3
|
||||
4
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"category": "built-ins/String",
|
||||
"total": 1223,
|
||||
"pass": 30,
|
||||
"fail": 56,
|
||||
"pass": 31,
|
||||
"fail": 63,
|
||||
"skip": 1123,
|
||||
"timeout": 14,
|
||||
"pass_rate": 30.0,
|
||||
"timeout": 6,
|
||||
"pass_rate": 31.0,
|
||||
"top_failures": [
|
||||
[
|
||||
"Test262Error (assertion failed)",
|
||||
43
|
||||
53
|
||||
],
|
||||
[
|
||||
"Timeout",
|
||||
14
|
||||
6
|
||||
],
|
||||
[
|
||||
"ReferenceError (undefined symbol)",
|
||||
7
|
||||
2
|
||||
],
|
||||
[
|
||||
"Unhandled: Not callable: \\\\\\",
|
||||
@@ -100,19 +96,19 @@
|
||||
"top_failure_modes": [
|
||||
[
|
||||
"Test262Error (assertion failed)",
|
||||
108
|
||||
128
|
||||
],
|
||||
[
|
||||
"TypeError: not a function",
|
||||
36
|
||||
37
|
||||
],
|
||||
[
|
||||
"Timeout",
|
||||
22
|
||||
11
|
||||
],
|
||||
[
|
||||
"ReferenceError (undefined symbol)",
|
||||
10
|
||||
2
|
||||
],
|
||||
[
|
||||
"Unhandled: Not callable: \\\\\\",
|
||||
@@ -129,9 +125,13 @@
|
||||
[
|
||||
"Unhandled: Not callable: {:__proto__ {}} (kont=5 frames)\\",
|
||||
1
|
||||
],
|
||||
[
|
||||
"Unhandled: js-transpile-binop: unsupported op: >>>\\",
|
||||
1
|
||||
]
|
||||
],
|
||||
"pinned_commit": "d5e73fc8d2c663554fb72e2380a8c2bc1a318a33",
|
||||
"elapsed_seconds": 429.3,
|
||||
"elapsed_seconds": 288.0,
|
||||
"workers": 1
|
||||
}
|
||||
@@ -1,36 +1,37 @@
|
||||
# test262 scoreboard
|
||||
|
||||
Pinned commit: `d5e73fc8d2c663554fb72e2380a8c2bc1a318a33`
|
||||
Wall time: 429.3s
|
||||
Wall time: 288.0s
|
||||
|
||||
**Total:** 118/300 runnable passed (39.3%). Raw: pass=118 fail=160 skip=1597 timeout=22 total=1897.
|
||||
**Total:** 115/300 runnable passed (38.3%). Raw: pass=115 fail=174 skip=1597 timeout=11 total=1897.
|
||||
|
||||
## Top failure modes
|
||||
|
||||
- **108x** Test262Error (assertion failed)
|
||||
- **36x** TypeError: not a function
|
||||
- **22x** Timeout
|
||||
- **10x** ReferenceError (undefined symbol)
|
||||
- **128x** Test262Error (assertion failed)
|
||||
- **37x** TypeError: not a function
|
||||
- **11x** Timeout
|
||||
- **2x** ReferenceError (undefined symbol)
|
||||
- **2x** Unhandled: Not callable: \\\
|
||||
- **2x** Unhandled: Not callable: {:__proto__ {}} (kont=6 frames)\
|
||||
- **1x** SyntaxError (parse/unsupported syntax)
|
||||
- **1x** Unhandled: Not callable: {:__proto__ {}} (kont=5 frames)\
|
||||
- **1x** Unhandled: js-transpile-binop: unsupported op: >>>\
|
||||
|
||||
## Categories (worst pass-rate first, min 10 runnable)
|
||||
|
||||
| Category | Pass | Fail | Skip | Timeout | Total | Pass % |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| built-ins/String | 30 | 56 | 1123 | 14 | 1223 | 30.0% |
|
||||
| built-ins/String | 31 | 63 | 1123 | 6 | 1223 | 31.0% |
|
||||
| built-ins/Math | 40 | 59 | 227 | 1 | 327 | 40.0% |
|
||||
| built-ins/Number | 48 | 45 | 240 | 7 | 340 | 48.0% |
|
||||
| built-ins/Number | 44 | 52 | 240 | 4 | 340 | 44.0% |
|
||||
|
||||
## Per-category top failures (min 10 runnable, worst first)
|
||||
|
||||
### built-ins/String (30/100 — 30.0%)
|
||||
### built-ins/String (31/100 — 31.0%)
|
||||
|
||||
- **43x** Test262Error (assertion failed)
|
||||
- **14x** Timeout
|
||||
- **7x** ReferenceError (undefined symbol)
|
||||
- **53x** Test262Error (assertion failed)
|
||||
- **6x** Timeout
|
||||
- **2x** ReferenceError (undefined symbol)
|
||||
- **2x** Unhandled: Not callable: \\\
|
||||
- **2x** Unhandled: Not callable: {:__proto__ {}} (kont=6 frames)\
|
||||
|
||||
@@ -40,8 +41,7 @@ Wall time: 429.3s
|
||||
- **23x** Test262Error (assertion failed)
|
||||
- **1x** Timeout
|
||||
|
||||
### built-ins/Number (48/100 — 48.0%)
|
||||
### built-ins/Number (44/100 — 44.0%)
|
||||
|
||||
- **42x** Test262Error (assertion failed)
|
||||
- **7x** Timeout
|
||||
- **3x** ReferenceError (undefined symbol)
|
||||
- **52x** Test262Error (assertion failed)
|
||||
- **4x** Timeout
|
||||
|
||||
@@ -23,7 +23,46 @@
|
||||
|
||||
;; ── tiny helpers ──────────────────────────────────────────────────
|
||||
|
||||
(define js-sym (fn (name) (make-symbol name)))
|
||||
(define js-has-dollar? (fn (name) (js-has-dollar-loop? name 0 (len name))))
|
||||
(define
|
||||
js-has-dollar-loop?
|
||||
(fn
|
||||
(s i n)
|
||||
(cond
|
||||
((>= i n) false)
|
||||
((= (char-at s i) "$") true)
|
||||
(else (js-has-dollar-loop? s (+ i 1) n)))))
|
||||
|
||||
(define
|
||||
js-mangle-ident
|
||||
(fn
|
||||
(name)
|
||||
(if
|
||||
(js-has-dollar? name)
|
||||
(js-mangle-ident-loop name 0 (len name) "")
|
||||
name)))
|
||||
|
||||
;; ── main dispatcher ───────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
js-mangle-ident-loop
|
||||
(fn
|
||||
(s i n acc)
|
||||
(cond
|
||||
((>= i n) acc)
|
||||
((= (char-at s i) "$")
|
||||
(js-mangle-ident-loop s (+ i 1) n (str acc "_js_dollar_")))
|
||||
(else (js-mangle-ident-loop s (+ i 1) n (str acc (char-at s i)))))))
|
||||
|
||||
;; ── Identifier lookup ─────────────────────────────────────────────
|
||||
|
||||
;; `undefined` in JS is really a global binding. If the parser emits
|
||||
;; (js-undef) we handle that above. A bare `undefined` ident also maps
|
||||
;; to the same sentinel.
|
||||
(define js-sym (fn (name) (make-symbol (js-mangle-ident name))))
|
||||
|
||||
;; ── Unary ops ─────────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
js-tag?
|
||||
(fn
|
||||
@@ -34,9 +73,11 @@
|
||||
(= (type-of (first ast)) "symbol")
|
||||
(= (symbol-name (first ast)) tag))))
|
||||
|
||||
;; ── Binary ops ────────────────────────────────────────────────────
|
||||
|
||||
(define js-ast-tag (fn (ast) (symbol-name (first ast))))
|
||||
|
||||
;; ── main dispatcher ───────────────────────────────────────────────
|
||||
;; ── Member / index ────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
js-transpile
|
||||
@@ -146,11 +187,6 @@
|
||||
(else
|
||||
(error (str "js-transpile: unexpected value type: " (type-of ast)))))))
|
||||
|
||||
;; ── Identifier lookup ─────────────────────────────────────────────
|
||||
|
||||
;; `undefined` in JS is really a global binding. If the parser emits
|
||||
;; (js-undef) we handle that above. A bare `undefined` ident also maps
|
||||
;; to the same sentinel.
|
||||
(define
|
||||
js-transpile-ident
|
||||
(fn
|
||||
@@ -164,8 +200,10 @@
|
||||
((= name "Function") (js-sym "js-function-global"))
|
||||
(else (js-sym name)))))
|
||||
|
||||
;; ── Unary ops ─────────────────────────────────────────────────────
|
||||
;; ── Call ──────────────────────────────────────────────────────────
|
||||
|
||||
;; JS `f(a, b, c)` → `(f a b c)` after transpile. Works for both
|
||||
;; identifier calls and computed callee (arrow fn, member access).
|
||||
(define
|
||||
js-transpile-unop
|
||||
(fn
|
||||
@@ -196,7 +234,7 @@
|
||||
((= op "void") (list (js-sym "quote") :js-undefined))
|
||||
(else (error (str "js-transpile-unop: unsupported op: " op)))))))))
|
||||
|
||||
;; ── Binary ops ────────────────────────────────────────────────────
|
||||
;; ── Array literal ─────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
js-transpile-binop
|
||||
@@ -259,22 +297,25 @@
|
||||
(js-sym "_a"))))
|
||||
(else (error (str "js-transpile-binop: unsupported op: " op))))))
|
||||
|
||||
;; ── Member / index ────────────────────────────────────────────────
|
||||
;; ── Object literal ────────────────────────────────────────────────
|
||||
|
||||
;; Build a dict by `(dict)` + `dict-set!` inside a `let` that yields
|
||||
;; the dict as its final expression. This keeps keys in JS insertion
|
||||
;; order and allows computed values.
|
||||
(define
|
||||
js-transpile-member
|
||||
(fn (obj key) (list (js-sym "js-get-prop") (js-transpile obj) key)))
|
||||
|
||||
;; ── Conditional ───────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
js-transpile-index
|
||||
(fn
|
||||
(obj idx)
|
||||
(list (js-sym "js-get-prop") (js-transpile obj) (js-transpile idx))))
|
||||
|
||||
;; ── Call ──────────────────────────────────────────────────────────
|
||||
;; ── Arrow function ────────────────────────────────────────────────
|
||||
|
||||
;; JS `f(a, b, c)` → `(f a b c)` after transpile. Works for both
|
||||
;; identifier calls and computed callee (arrow fn, member access).
|
||||
(define
|
||||
js-transpile-call
|
||||
(fn
|
||||
@@ -320,8 +361,11 @@
|
||||
(js-transpile callee)
|
||||
(js-transpile-args args))))))
|
||||
|
||||
;; ── Array literal ─────────────────────────────────────────────────
|
||||
;; ── Assignment ────────────────────────────────────────────────────
|
||||
|
||||
;; `a = b` on an ident → (set! a b).
|
||||
;; `a += b` on an ident → (set! a (js-add a b)).
|
||||
;; `obj.k = v` / `obj[k] = v` → (js-set-prop obj "k" v).
|
||||
(define
|
||||
js-transpile-new
|
||||
(fn
|
||||
@@ -331,11 +375,6 @@
|
||||
(js-transpile callee)
|
||||
(cons (js-sym "list") (map js-transpile args)))))
|
||||
|
||||
;; ── Object literal ────────────────────────────────────────────────
|
||||
|
||||
;; Build a dict by `(dict)` + `dict-set!` inside a `let` that yields
|
||||
;; the dict as its final expression. This keeps keys in JS insertion
|
||||
;; order and allows computed values.
|
||||
(define
|
||||
js-transpile-array
|
||||
(fn
|
||||
@@ -354,8 +393,6 @@
|
||||
elts))
|
||||
(cons (js-sym "list") (map js-transpile elts)))))
|
||||
|
||||
;; ── Conditional ───────────────────────────────────────────────────
|
||||
|
||||
(define
|
||||
js-has-spread?
|
||||
(fn
|
||||
@@ -365,8 +402,9 @@
|
||||
((js-tag? (first lst) "js-spread") true)
|
||||
(else (js-has-spread? (rest lst))))))
|
||||
|
||||
;; ── Arrow function ────────────────────────────────────────────────
|
||||
;; ── End-to-end entry points ───────────────────────────────────────
|
||||
|
||||
;; Transpile + eval a single JS expression string.
|
||||
(define
|
||||
js-transpile-args
|
||||
(fn
|
||||
@@ -385,11 +423,8 @@
|
||||
args))
|
||||
(cons (js-sym "list") (map js-transpile args)))))
|
||||
|
||||
;; ── Assignment ────────────────────────────────────────────────────
|
||||
|
||||
;; `a = b` on an ident → (set! a b).
|
||||
;; `a += b` on an ident → (set! a (js-add a b)).
|
||||
;; `obj.k = v` / `obj[k] = v` → (js-set-prop obj "k" v).
|
||||
;; Transpile a JS expression string to SX source text (for inspection
|
||||
;; in tests). Useful for asserting the exact emitted tree.
|
||||
(define
|
||||
js-transpile-object
|
||||
(fn
|
||||
@@ -451,9 +486,6 @@
|
||||
(append inits (list (js-transpile body))))))))
|
||||
(list (js-sym "fn") param-syms body-tr))))
|
||||
|
||||
;; ── End-to-end entry points ───────────────────────────────────────
|
||||
|
||||
;; Transpile + eval a single JS expression string.
|
||||
(define
|
||||
js-transpile-tpl
|
||||
(fn
|
||||
@@ -465,8 +497,6 @@
|
||||
(else
|
||||
(cons (js-sym "js-template-concat") (js-transpile-tpl-parts parts))))))
|
||||
|
||||
;; Transpile a JS expression string to SX source text (for inspection
|
||||
;; in tests). Useful for asserting the exact emitted tree.
|
||||
(define
|
||||
js-transpile-tpl-parts
|
||||
(fn
|
||||
|
||||
Reference in New Issue
Block a user