js-on-sx: baseline commit (278/280 unit, 148/148 slice, runner stub)

Initial commit of the lib/js/ tree and plans/ directory. A previous
session left template-string work in progress — 278/280 unit tests pass
(2 failing: tpl part-count off-by-one, escaped-backtick ident lookup).
test262-runner.py and scoreboard are placeholders (0/8 with 7 timeouts);
fixing the runner is the next queue item.
This commit is contained in:
2026-04-23 19:42:16 +00:00
parent 14b6586e41
commit 9e568ad886
310 changed files with 7056 additions and 0 deletions

1
lib/js/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
test262-upstream/

130
lib/js/conformance.sh Executable file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env bash
# Cherry-picked test262 conformance runner for JS-on-SX.
# Walks lib/js/test262-slice/**/*.js, evaluates each via js-eval,
# and compares against the sibling .expected file (substring match).
#
# Usage:
# bash lib/js/conformance.sh # summary only
# bash lib/js/conformance.sh -v # per-test pass/fail
set -uo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: $SX_SERVER not found. Run: cd hosts/ocaml && dune build"
exit 1
fi
VERBOSE="${1:-}"
SLICE_DIR="lib/js/test262-slice"
PASS=0
FAIL=0
ERRORS=""
# Find all .js fixtures (sorted for stable output).
# Skip README.md and similar.
mapfile -t FIXTURES < <(find "$SLICE_DIR" -type f -name '*.js' | sort)
if [ ${#FIXTURES[@]} -eq 0 ]; then
echo "No fixtures found in $SLICE_DIR"
exit 1
fi
# Build one big batch script: load everything once, then one epoch per
# fixture. Avoids the ~200ms boot cost of starting the server for each
# test.
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT
{
echo '(epoch 1)'
echo '(load "lib/r7rs.sx")'
echo '(epoch 2)'
echo '(load "lib/js/lexer.sx")'
echo '(epoch 3)'
echo '(load "lib/js/parser.sx")'
echo '(epoch 4)'
echo '(load "lib/js/transpile.sx")'
echo '(epoch 5)'
echo '(load "lib/js/runtime.sx")'
epoch=100
for f in "${FIXTURES[@]}"; do
# Read source, strip trailing newline, then escape for *two*
# nested SX string literals: the outer epoch `(eval "…")` and
# the inner `(js-eval "…")` that it wraps.
#
# Source char → final stream char
# \ → \\\\ (outer: becomes \\ ; inner: becomes \)
# " → \\\" (outer: becomes \" ; inner: becomes ")
# nl → \\n (SX newline escape, survives both levels)
src=$(python3 -c '
import sys
s = open(sys.argv[1], "r", encoding="utf-8").read().rstrip("\n")
# Two nested SX string literals: outer eval wraps inner js-eval.
# Escape once for inner (JS source → SX inner string literal):
inner = s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
# Escape the result again for the outer SX string literal:
outer = inner.replace("\\", "\\\\").replace("\"", "\\\"")
sys.stdout.write(outer)
' "$f")
echo "(epoch $epoch)"
echo "(eval \"(js-eval \\\"$src\\\")\")"
epoch=$((epoch + 1))
done
} > "$TMPFILE"
OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
# Iterate fixtures with the same epoch sequence and check each.
epoch=100
for f in "${FIXTURES[@]}"; do
expected=$(cat "${f%.js}.expected" | sed -e 's/[[:space:]]*$//' | head -n 1)
name="${f#${SLICE_DIR}/}"
name="${name%.js}"
# Actual output lives on the line after "(ok-len $epoch N)" or on
# "(ok $epoch VAL)" for short values. Errors surface as "(error …)".
actual=$(echo "$OUTPUT" | awk -v e="$epoch" '
$0 ~ ("^\\(ok-len "e" ") { getline; print; exit }
$0 ~ ("^\\(ok "e" ") { sub("^\\(ok "e" ", ""); sub(")$", ""); print; exit }
$0 ~ ("^\\(error "e" ") { print; exit }
')
[ -z "$actual" ] && actual="<no output>"
if echo "$actual" | grep -qF -- "$expected"; then
PASS=$((PASS + 1))
[ "$VERBOSE" = "-v" ] && echo "$name"
else
FAIL=$((FAIL + 1))
ERRORS+="$name
expected: $expected
actual: $actual
"
[ "$VERBOSE" = "-v" ] && echo "$name (expected: $expected, actual: $actual)"
fi
epoch=$((epoch + 1))
done
TOTAL=$((PASS + FAIL))
PCT=$(awk "BEGIN{printf \"%.1f\", ($PASS/$TOTAL)*100}")
echo
if [ $FAIL -eq 0 ]; then
echo "$PASS/$TOTAL test262-slice tests passed ($PCT%)"
else
echo "$PASS/$TOTAL passed, $FAIL failed ($PCT%):"
[ "$VERBOSE" != "-v" ] && echo "$ERRORS"
fi
# Phase 5 target: ≥50% pass.
TARGET=50
if (( $(echo "$PCT >= $TARGET" | bc -l 2>/dev/null || python3 -c "print($PCT >= $TARGET)") )); then
exit 0
else
echo "(below target of ${TARGET}%)"
exit 1
fi

519
lib/js/lexer.sx Normal file
View File

@@ -0,0 +1,519 @@
;; lib/js/lexer.sx — JavaScript source → token stream
;;
;; Tokens: {:type T :value V :pos P}
;; Types:
;; "number" — numeric literals (decoded into value as number)
;; "string" — string literals (decoded, escape sequences processed)
;; "template"— template literal body (no interpolation split yet — deferred)
;; "ident" — identifier (not a reserved word)
;; "keyword" — reserved word
;; "punct" — ( ) [ ] { } , ; : . ...
;; "op" — all operator tokens (incl. = == === !== < > etc.)
;; "eof" — end of input
;;
;; NOTE: `cond` clauses take exactly ONE body expression — multi-body
;; clauses must wrap their body in `(do ...)`.
;; ── Token constructor ─────────────────────────────────────────────
(define js-make-token (fn (type value pos) {:pos pos :value value :type type}))
;; ── Character predicates ──────────────────────────────────────────
(define js-digit? (fn (c) (and (>= c "0") (<= c "9"))))
(define
js-hex-digit?
(fn
(c)
(or
(js-digit? c)
(and (>= c "a") (<= c "f"))
(and (>= c "A") (<= c "F")))))
(define
js-letter?
(fn (c) (or (and (>= c "a") (<= c "z")) (and (>= c "A") (<= c "Z")))))
(define js-ident-start? (fn (c) (or (js-letter? c) (= c "_") (= c "$"))))
(define js-ident-char? (fn (c) (or (js-ident-start? c) (js-digit? c))))
(define js-ws? (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
;; ── Reserved words ────────────────────────────────────────────────
(define
js-keywords
(list
"break"
"case"
"catch"
"class"
"const"
"continue"
"debugger"
"default"
"delete"
"do"
"else"
"export"
"extends"
"false"
"finally"
"for"
"function"
"if"
"import"
"in"
"instanceof"
"new"
"null"
"return"
"super"
"switch"
"this"
"throw"
"true"
"try"
"typeof"
"undefined"
"var"
"void"
"while"
"with"
"yield"
"let"
"static"
"async"
"await"
"of"))
(define js-keyword? (fn (word) (contains? js-keywords word)))
;; ── Main tokenizer ────────────────────────────────────────────────
(define
js-tokenize
(fn
(src)
(let
((tokens (list)) (pos 0) (src-len (len src)))
(define
js-peek
(fn
(offset)
(if (< (+ pos offset) src-len) (nth src (+ pos offset)) nil)))
(define cur (fn () (js-peek 0)))
(define advance! (fn (n) (set! pos (+ pos n))))
(define
at?
(fn
(s)
(let
((sl (len s)))
(and (<= (+ pos sl) src-len) (= (slice src pos (+ pos sl)) s)))))
(define
js-emit!
(fn
(type value start)
(append! tokens (js-make-token type value start))))
(define
skip-line-comment!
(fn
()
(when
(and (< pos src-len) (not (= (cur) "\n")))
(do (advance! 1) (skip-line-comment!)))))
(define
skip-block-comment!
(fn
()
(cond
((>= pos src-len) nil)
((and (= (cur) "*") (< (+ pos 1) src-len) (= (js-peek 1) "/"))
(advance! 2))
(else (do (advance! 1) (skip-block-comment!))))))
(define
skip-ws!
(fn
()
(cond
((>= pos src-len) nil)
((js-ws? (cur)) (do (advance! 1) (skip-ws!)))
((and (= (cur) "/") (< (+ pos 1) src-len) (= (js-peek 1) "/"))
(do (advance! 2) (skip-line-comment!) (skip-ws!)))
((and (= (cur) "/") (< (+ pos 1) src-len) (= (js-peek 1) "*"))
(do (advance! 2) (skip-block-comment!) (skip-ws!)))
(else nil))))
(define
read-ident
(fn
(start)
(do
(when
(and (< pos src-len) (js-ident-char? (cur)))
(do (advance! 1) (read-ident start)))
(slice src start pos))))
(define
read-decimal-digits!
(fn
()
(when
(and (< pos src-len) (js-digit? (cur)))
(do (advance! 1) (read-decimal-digits!)))))
(define
read-hex-digits!
(fn
()
(when
(and (< pos src-len) (js-hex-digit? (cur)))
(do (advance! 1) (read-hex-digits!)))))
(define
read-exp-part!
(fn
()
(when
(and (< pos src-len) (or (= (cur) "e") (= (cur) "E")))
(let
((p1 (js-peek 1)))
(when
(or
(and (not (= p1 nil)) (js-digit? p1))
(and
(or (= p1 "+") (= p1 "-"))
(< (+ pos 2) src-len)
(js-digit? (js-peek 2))))
(do
(advance! 1)
(when
(and
(< pos src-len)
(or (= (cur) "+") (= (cur) "-")))
(advance! 1))
(read-decimal-digits!)))))))
(define
read-number
(fn
(start)
(cond
((and (= (cur) "0") (< (+ pos 1) src-len) (or (= (js-peek 1) "x") (= (js-peek 1) "X")))
(do
(advance! 2)
(read-hex-digits!)
(let
((raw (slice src (+ start 2) pos)))
(parse-number (str "0x" raw)))))
(else
(do
(read-decimal-digits!)
(when
(and
(< pos src-len)
(= (cur) ".")
(< (+ pos 1) src-len)
(js-digit? (js-peek 1)))
(do (advance! 1) (read-decimal-digits!)))
(read-exp-part!)
(parse-number (slice src start pos)))))))
(define
read-dot-number
(fn
(start)
(do
(advance! 1)
(read-decimal-digits!)
(read-exp-part!)
(parse-number (slice src start pos)))))
(define
read-string
(fn
(quote-char)
(let
((chars (list)))
(advance! 1)
(define
loop
(fn
()
(cond
((>= pos src-len) nil)
((= (cur) "\\")
(do
(advance! 1)
(when
(< pos src-len)
(let
((ch (cur)))
(do
(cond
((= ch "n") (append! chars "\n"))
((= ch "t") (append! chars "\t"))
((= ch "r") (append! chars "\r"))
((= ch "\\") (append! chars "\\"))
((= ch "'") (append! chars "'"))
((= ch "\"") (append! chars "\""))
((= ch "`") (append! chars "`"))
((= ch "0") (append! chars "\\0"))
((= ch "b") (append! chars "\\b"))
((= ch "f") (append! chars "\\f"))
((= ch "v") (append! chars "\\v"))
(else (append! chars ch)))
(advance! 1))))
(loop)))
((= (cur) quote-char) (advance! 1))
(else (do (append! chars (cur)) (advance! 1) (loop))))))
(loop)
(join "" chars))))
(define
read-template
(fn
()
(let
((parts (list)) (chars (list)))
(advance! 1)
(define
flush-chars!
(fn
()
(when
(> (len chars) 0)
(do
(append! parts (list "str" (join "" chars)))
(set! chars (list))))))
(define
read-expr-source!
(fn
()
(let
((buf (list)) (depth 1))
(define
expr-loop
(fn
()
(cond
((>= pos src-len) nil)
((and (= (cur) "}") (= depth 1)) (advance! 1))
((= (cur) "}")
(do
(append! buf (cur))
(set! depth (- depth 1))
(advance! 1)
(expr-loop)))
((= (cur) "{")
(do
(append! buf (cur))
(set! depth (+ depth 1))
(advance! 1)
(expr-loop)))
((or (= (cur) "\"") (= (cur) "'"))
(let
((q (cur)))
(do
(append! buf q)
(advance! 1)
(define
sloop
(fn
()
(cond
((>= pos src-len) nil)
((= (cur) "\\")
(do
(append! buf (cur))
(advance! 1)
(when
(< pos src-len)
(do
(append! buf (cur))
(advance! 1)))
(sloop)))
((= (cur) q)
(do (append! buf (cur)) (advance! 1)))
(else
(do
(append! buf (cur))
(advance! 1)
(sloop))))))
(sloop)
(expr-loop))))
(else
(do (append! buf (cur)) (advance! 1) (expr-loop))))))
(expr-loop)
(join "" buf))))
(define
loop
(fn
()
(cond
((>= pos src-len) nil)
((= (cur) "`") (advance! 1))
((and (= (cur) "$") (< (+ pos 1) src-len) (= (js-peek 1) "{"))
(do
(flush-chars!)
(advance! 2)
(let
((src (read-expr-source!)))
(append! parts (list "expr" src)))
(loop)))
((= (cur) "\\")
(do
(advance! 1)
(when
(< pos src-len)
(let
((ch (cur)))
(do
(cond
((= ch "n") (append! chars "\n"))
((= ch "t") (append! chars "\t"))
((= ch "r") (append! chars "\r"))
((= ch "\\") (append! chars "\\"))
((= ch "'") (append! chars "'"))
((= ch "\"") (append! chars "\""))
((= ch "`") (append! chars "`"))
((= ch "$") (append! chars "$"))
((= ch "0") (append! chars "0"))
((= ch "b") (append! chars "b"))
((= ch "f") (append! chars "f"))
((= ch "v") (append! chars "v"))
(else (append! chars ch)))
(advance! 1))))
(loop)))
(else (do (append! chars (cur)) (advance! 1) (loop))))))
(loop)
(flush-chars!)
(if
(= (len parts) 0)
""
(if
(and (= (len parts) 1) (= (nth (nth parts 0) 0) "str"))
(nth (nth parts 0) 1)
parts)))))
(define
try-op-4!
(fn
(start)
(cond
((at? ">>>=")
(do (js-emit! "op" ">>>=" start) (advance! 4) true))
(else false))))
(define
try-op-3!
(fn
(start)
(cond
((at? "===")
(do (js-emit! "op" "===" start) (advance! 3) true))
((at? "!==")
(do (js-emit! "op" "!==" start) (advance! 3) true))
((at? "**=")
(do (js-emit! "op" "**=" start) (advance! 3) true))
((at? "<<=")
(do (js-emit! "op" "<<=" start) (advance! 3) true))
((at? ">>=")
(do (js-emit! "op" ">>=" start) (advance! 3) true))
((at? ">>>")
(do (js-emit! "op" ">>>" start) (advance! 3) true))
((at? "&&=")
(do (js-emit! "op" "&&=" start) (advance! 3) true))
((at? "||=")
(do (js-emit! "op" "||=" start) (advance! 3) true))
((at? "??=")
(do (js-emit! "op" "??=" start) (advance! 3) true))
((at? "...")
(do (js-emit! "punct" "..." start) (advance! 3) true))
(else false))))
(define
try-op-2!
(fn
(start)
(cond
((at? "==") (do (js-emit! "op" "==" start) (advance! 2) true))
((at? "!=") (do (js-emit! "op" "!=" start) (advance! 2) true))
((at? "<=") (do (js-emit! "op" "<=" start) (advance! 2) true))
((at? ">=") (do (js-emit! "op" ">=" start) (advance! 2) true))
((at? "&&") (do (js-emit! "op" "&&" start) (advance! 2) true))
((at? "||") (do (js-emit! "op" "||" start) (advance! 2) true))
((at? "??") (do (js-emit! "op" "??" start) (advance! 2) true))
((at? "=>") (do (js-emit! "op" "=>" start) (advance! 2) true))
((at? "**") (do (js-emit! "op" "**" start) (advance! 2) true))
((at? "<<") (do (js-emit! "op" "<<" start) (advance! 2) true))
((at? ">>") (do (js-emit! "op" ">>" start) (advance! 2) true))
((at? "++") (do (js-emit! "op" "++" start) (advance! 2) true))
((at? "--") (do (js-emit! "op" "--" start) (advance! 2) true))
((at? "+=") (do (js-emit! "op" "+=" start) (advance! 2) true))
((at? "-=") (do (js-emit! "op" "-=" start) (advance! 2) true))
((at? "*=") (do (js-emit! "op" "*=" start) (advance! 2) true))
((at? "/=") (do (js-emit! "op" "/=" start) (advance! 2) true))
((at? "%=") (do (js-emit! "op" "%=" start) (advance! 2) true))
((at? "&=") (do (js-emit! "op" "&=" start) (advance! 2) true))
((at? "|=") (do (js-emit! "op" "|=" start) (advance! 2) true))
((at? "^=") (do (js-emit! "op" "^=" start) (advance! 2) true))
((at? "?.") (do (js-emit! "op" "?." start) (advance! 2) true))
(else false))))
(define
emit-one-op!
(fn
(ch start)
(cond
((= ch "(") (do (js-emit! "punct" "(" start) (advance! 1)))
((= ch ")") (do (js-emit! "punct" ")" start) (advance! 1)))
((= ch "[") (do (js-emit! "punct" "[" start) (advance! 1)))
((= ch "]") (do (js-emit! "punct" "]" start) (advance! 1)))
((= ch "{") (do (js-emit! "punct" "{" start) (advance! 1)))
((= ch "}") (do (js-emit! "punct" "}" start) (advance! 1)))
((= ch ",") (do (js-emit! "punct" "," start) (advance! 1)))
((= ch ";") (do (js-emit! "punct" ";" start) (advance! 1)))
((= ch ":") (do (js-emit! "punct" ":" start) (advance! 1)))
((= ch ".") (do (js-emit! "punct" "." start) (advance! 1)))
((= ch "?") (do (js-emit! "op" "?" start) (advance! 1)))
((= ch "+") (do (js-emit! "op" "+" start) (advance! 1)))
((= ch "-") (do (js-emit! "op" "-" start) (advance! 1)))
((= ch "*") (do (js-emit! "op" "*" start) (advance! 1)))
((= ch "/") (do (js-emit! "op" "/" start) (advance! 1)))
((= ch "%") (do (js-emit! "op" "%" start) (advance! 1)))
((= ch "=") (do (js-emit! "op" "=" start) (advance! 1)))
((= ch "<") (do (js-emit! "op" "<" start) (advance! 1)))
((= ch ">") (do (js-emit! "op" ">" start) (advance! 1)))
((= ch "!") (do (js-emit! "op" "!" start) (advance! 1)))
((= ch "&") (do (js-emit! "op" "&" start) (advance! 1)))
((= ch "|") (do (js-emit! "op" "|" start) (advance! 1)))
((= ch "^") (do (js-emit! "op" "^" start) (advance! 1)))
((= ch "~") (do (js-emit! "op" "~" start) (advance! 1)))
(else (advance! 1)))))
(define
scan!
(fn
()
(do
(skip-ws!)
(when
(< pos src-len)
(let
((ch (cur)) (start pos))
(cond
((or (= ch "\"") (= ch "'"))
(do (js-emit! "string" (read-string ch) start) (scan!)))
((= ch "`")
(do (js-emit! "template" (read-template) start) (scan!)))
((js-digit? ch)
(do
(js-emit! "number" (read-number start) start)
(scan!)))
((and (= ch ".") (< (+ pos 1) src-len) (js-digit? (js-peek 1)))
(do
(js-emit! "number" (read-dot-number start) start)
(scan!)))
((js-ident-start? ch)
(do
(let
((word (read-ident start)))
(js-emit!
(if (js-keyword? word) "keyword" "ident")
word
start))
(scan!)))
((try-op-4! start) (scan!))
((try-op-3! start) (scan!))
((try-op-2! start) (scan!))
(else (do (emit-one-op! ch start) (scan!)))))))))
(scan!)
(js-emit! "eof" nil pos)
tokens)))

1097
lib/js/parser.sx Normal file

File diff suppressed because it is too large Load Diff

1596
lib/js/runtime.sx Normal file

File diff suppressed because it is too large Load Diff

1156
lib/js/test.sh Executable file

File diff suppressed because it is too large Load Diff

711
lib/js/test262-runner.py Normal file
View File

@@ -0,0 +1,711 @@
#!/usr/bin/env python3
"""
test262-runner — run the official TC39 test262 suite against our JS-on-SX runtime.
Walks lib/js/test262-upstream/test/**/*.js, parses YAML-ish frontmatter, batches
tests through sx_server.exe, and emits a JSON + Markdown scoreboard.
Usage:
python3 lib/js/test262-runner.py # full run
python3 lib/js/test262-runner.py --limit 2000 # first 2000 tests only
python3 lib/js/test262-runner.py --filter built-ins/Math
python3 lib/js/test262-runner.py --batch-size 200 # tests per sx_server boot
Outputs:
lib/js/test262-scoreboard.json — per-category stats + top failure modes
lib/js/test262-scoreboard.md — human-readable summary (worst first)
Pinned to commit (see test262-upstream/.git/HEAD after clone). Update:
rm -rf lib/js/test262-upstream
git -C lib/js clone --depth 1 https://github.com/tc39/test262.git test262-upstream
Timeouts:
per-test wallclock: 5s
per-batch wallclock: 120s
"""
from __future__ import annotations
import argparse
import dataclasses
import json
import os
import re
import subprocess
import sys
import time
from collections import Counter, defaultdict
from pathlib import Path
REPO = Path(__file__).resolve().parents[2]
SX_SERVER = REPO / "hosts" / "ocaml" / "_build" / "default" / "bin" / "sx_server.exe"
UPSTREAM = REPO / "lib" / "js" / "test262-upstream"
TEST_ROOT = UPSTREAM / "test"
HARNESS_DIR = UPSTREAM / "harness"
# Default harness files every test implicitly gets (per INTERPRETING.md).
DEFAULT_HARNESS = ["assert.js", "sta.js"]
# Per-batch timeout (seconds). Each batch runs N tests; if sx_server hangs on
# one, we kill the whole batch and mark remaining as timeout.
BATCH_TIMEOUT_S = 120
# Per-test wallclock is enforced by slicing batches: if a batch of N tests
# takes > PER_TEST_S * N + slack, it's killed. We also record elapsed time
# per test by parsing the output stream.
PER_TEST_S = 5
# Target batch size — tune to balance sx_server startup cost (~500ms) against
# memory / risk of one bad test killing many.
DEFAULT_BATCH_SIZE = 200
# ---------------------------------------------------------------------------
# Frontmatter parsing
# ---------------------------------------------------------------------------
FRONTMATTER_RE = re.compile(r"/\*---(.*?)---\*/", re.DOTALL)
@dataclasses.dataclass
class Frontmatter:
description: str = ""
flags: list[str] = dataclasses.field(default_factory=list)
includes: list[str] = dataclasses.field(default_factory=list)
features: list[str] = dataclasses.field(default_factory=list)
negative_phase: str | None = None
negative_type: str | None = None
esid: str | None = None
es5id: str | None = None
es6id: str | None = None
def _parse_yaml_list(s: str) -> list[str]:
"""Parse a `[a, b, c]` style list. Loose — test262 YAML uses this form almost exclusively."""
s = s.strip()
if s.startswith("[") and s.endswith("]"):
s = s[1:-1]
return [item.strip().strip('"').strip("'") for item in s.split(",") if item.strip()]
def parse_frontmatter(src: str) -> Frontmatter:
"""Parse test262 YAML-ish frontmatter. Lenient — handles the subset actually in use."""
fm = Frontmatter()
m = FRONTMATTER_RE.search(src)
if not m:
return fm
body = m.group(1)
# Walk lines, tracking indent for nested negative: {phase, type}.
lines = body.split("\n")
i = 0
current_key = None
while i < len(lines):
line = lines[i]
stripped = line.strip()
if not stripped or stripped.startswith("#"):
i += 1
continue
# Top-level key: value
m2 = re.match(r"^([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*(.*)$", line)
if m2 and not line.startswith(" ") and not line.startswith("\t"):
key, value = m2.group(1), m2.group(2).strip()
if key == "description":
# Multi-line description supported via `>` or `|`
if value in (">", "|"):
desc_lines: list[str] = []
j = i + 1
while j < len(lines):
nxt = lines[j]
if nxt.startswith(" ") or nxt.startswith("\t") or not nxt.strip():
desc_lines.append(nxt.strip())
j += 1
else:
break
fm.description = " ".join(d for d in desc_lines if d)
i = j
continue
fm.description = value
elif key == "flags":
fm.flags = _parse_yaml_list(value)
elif key == "includes":
fm.includes = _parse_yaml_list(value)
elif key == "features":
fm.features = _parse_yaml_list(value)
elif key == "negative":
# Either `negative: {phase: parse, type: SyntaxError}` (inline)
# or spans two indented lines.
if value.startswith("{"):
# Inline dict
inner = value.strip("{}")
for part in inner.split(","):
if ":" in part:
pk, pv = part.split(":", 1)
pk = pk.strip()
pv = pv.strip().strip('"').strip("'")
if pk == "phase":
fm.negative_phase = pv
elif pk == "type":
fm.negative_type = pv
else:
current_key = "negative"
elif key == "esid":
fm.esid = value
elif key == "es5id":
fm.es5id = value
elif key == "es6id":
fm.es6id = value
i += 1
continue
# Indented continuation — e.g., negative: {phase:..., type:...}
if current_key == "negative":
m3 = re.match(r"^\s+([a-zA-Z_]+)\s*:\s*(.*)$", line)
if m3:
pk, pv = m3.group(1), m3.group(2).strip().strip('"').strip("'")
if pk == "phase":
fm.negative_phase = pv
elif pk == "type":
fm.negative_type = pv
else:
current_key = None
i += 1
return fm
# ---------------------------------------------------------------------------
# Harness loading
# ---------------------------------------------------------------------------
_HARNESS_CACHE: dict[str, str] = {}
def load_harness(name: str) -> str:
if name not in _HARNESS_CACHE:
p = HARNESS_DIR / name
if p.exists():
_HARNESS_CACHE[name] = p.read_text(encoding="utf-8")
else:
_HARNESS_CACHE[name] = ""
return _HARNESS_CACHE[name]
# ---------------------------------------------------------------------------
# Categories
# ---------------------------------------------------------------------------
def test_category(test_path: Path) -> str:
"""Derive a category like 'built-ins/Math' from the test path."""
rel = test_path.relative_to(TEST_ROOT).as_posix()
parts = rel.split("/")
# Use at most 2 levels; e.g. built-ins/Math/abs/foo.js → built-ins/Math
if len(parts) >= 2:
return "/".join(parts[:2])
return parts[0]
# ---------------------------------------------------------------------------
# SX escaping
# ---------------------------------------------------------------------------
def sx_escape_double(s: str) -> str:
"""Escape for a single SX string literal. Turn bytes that break SX parsing into escapes."""
return (
s.replace("\\", "\\\\")
.replace('"', '\\"')
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t")
)
def sx_double_escape(s: str) -> str:
"""Escape a JS source string for the nested `(eval "(js-eval \"...\")")` form.
Two levels of SX string-literal escaping. Matches conformance.sh.
"""
inner = sx_escape_double(s)
# The inner string gets consumed by the outer `(eval "...")`, so we need
# to escape backslashes and quotes again.
outer = inner.replace("\\", "\\\\").replace('"', '\\"')
return outer
# ---------------------------------------------------------------------------
# Test assembly
# ---------------------------------------------------------------------------
# A tiny helper we prepend so assert.X = function syntax has a hope. The real
# test262 assert.js does `assert.sameValue = function(...){}` which requires
# function-property support. Our runtime doesn't have that yet, so many tests
# will fail — that's the point of the scoreboard.
#
# We don't patch. We run the real harness as-is so the numbers reflect reality.
def assemble_source(test_src: str, includes: list[str]) -> str:
"""Assemble the full JS source for a test: harness preludes + test."""
chunks: list[str] = []
for h in DEFAULT_HARNESS:
chunks.append(load_harness(h))
for inc in includes:
chunks.append(load_harness(inc))
chunks.append(test_src)
return "\n".join(chunks)
# ---------------------------------------------------------------------------
# Output parsing
# ---------------------------------------------------------------------------
# Output from sx_server looks like:
# (ready)
# (ok 1 2) -- short value: (ok EPOCH VALUE)
# (ok-len 100 42) -- long value: next line has the value
# NEXT_LINE_WITH_VALUE
# (error 101 "msg") -- epoch errored
#
# For our purposes, each test has an epoch. We look up the ok/error result
# and classify as pass/fail.
def parse_output(output: str) -> dict[int, tuple[str, str]]:
"""Return {epoch: (kind, payload)} where kind is 'ok' | 'error' | 'missing'."""
results: dict[int, tuple[str, str]] = {}
lines = output.split("\n")
i = 0
while i < len(lines):
line = lines[i]
m_ok = re.match(r"^\(ok (\d+) (.*)\)$", line)
m_oklen = re.match(r"^\(ok-len (\d+) \d+\)$", line)
m_err = re.match(r"^\(error (\d+) (.*)\)$", line)
if m_ok:
epoch = int(m_ok.group(1))
results[epoch] = ("ok", m_ok.group(2))
elif m_oklen:
epoch = int(m_oklen.group(1))
val = lines[i + 1] if i + 1 < len(lines) else ""
results[epoch] = ("ok", val)
i += 1
elif m_err:
epoch = int(m_err.group(1))
results[epoch] = ("error", m_err.group(2))
i += 1
return results
# ---------------------------------------------------------------------------
# Classification
# ---------------------------------------------------------------------------
def classify_error(msg: str) -> str:
"""Bucket an error message into a failure mode."""
m = msg.lower()
if "syntaxerror" in m or "parse" in m or "expected" in m and "got" in m:
return "SyntaxError (parse/unsupported syntax)"
if "referenceerror" in m or "undefined symbol" in m or "unbound" in m:
return "ReferenceError (undefined symbol)"
if "typeerror" in m and "not a function" in m:
return "TypeError: not a function"
if "typeerror" in m:
return "TypeError (other)"
if "rangeerror" in m:
return "RangeError"
if "test262error" in m:
return "Test262Error (assertion failed)"
if "timeout" in m:
return "Timeout"
if "killed" in m or "crash" in m:
return "Crash"
if "unhandled exception" in m:
# Could be almost anything — extract the inner message.
inner = re.search(r"Unhandled exception:\s*\\?\"([^\"]{0,80})", msg)
if inner:
return f"Unhandled: {inner.group(1)[:60]}"
return "Unhandled exception"
return f"Other: {msg[:80]}"
def classify_negative_result(
fm: Frontmatter, kind: str, payload: str
) -> tuple[bool, str]:
"""For negative tests: pass if the right error was thrown."""
expected_type = fm.negative_type or ""
if kind == "error":
# We throw; check if it matches. Our error messages look like:
# Unhandled exception: "...TypeError..."
if expected_type and expected_type.lower() in payload.lower():
return True, f"negative: threw {expected_type} as expected"
# Also consider "Test262Error" a match for anything (assertion failed
# instead of throw) — some negative tests assert more than just the throw.
return False, f"negative: expected {expected_type}, got: {payload[:100]}"
# ok → the test ran without throwing; that's a fail for negative tests
return False, f"negative: expected {expected_type}, but test completed normally"
def classify_positive_result(kind: str, payload: str) -> tuple[bool, str]:
"""For positive tests: pass if no error thrown."""
if kind == "ok":
return True, "passed"
return False, classify_error(payload)
# ---------------------------------------------------------------------------
# Batch execution
# ---------------------------------------------------------------------------
@dataclasses.dataclass
class TestCase:
path: Path
rel: str
category: str
fm: Frontmatter
src: str # Test source (pre-harness); full source assembled at run time.
@dataclasses.dataclass
class TestResult:
rel: str
category: str
status: str # pass | fail | skip | timeout
reason: str
elapsed_ms: int = 0
def build_batch_script(tests: list[TestCase], start_epoch: int) -> tuple[str, list[int]]:
"""Build one big SX script that loads the kernel once, then runs each test
in its own epoch. Returns (script, [epoch_per_test])."""
lines = []
lines.append("(epoch 1)")
lines.append('(load "lib/r7rs.sx")')
lines.append("(epoch 2)")
lines.append('(load "lib/js/lexer.sx")')
lines.append("(epoch 3)")
lines.append('(load "lib/js/parser.sx")')
lines.append("(epoch 4)")
lines.append('(load "lib/js/transpile.sx")')
lines.append("(epoch 5)")
lines.append('(load "lib/js/runtime.sx")')
epochs: list[int] = []
epoch = start_epoch
for t in tests:
full_src = assemble_source(t.src, t.fm.includes)
escaped = sx_double_escape(full_src)
lines.append(f"(epoch {epoch})")
lines.append(f'(eval "(js-eval \\"{escaped}\\")")')
epochs.append(epoch)
epoch += 1
return "\n".join(lines) + "\n", epochs
def run_batch(
tests: list[TestCase], start_epoch: int, timeout_s: int
) -> tuple[dict[int, tuple[str, str]], bool, float]:
"""Run a batch; return (results, timed_out, elapsed_s)."""
script, epochs = build_batch_script(tests, start_epoch)
start = time.monotonic()
try:
proc = subprocess.run(
[str(SX_SERVER)],
input=script,
capture_output=True,
text=True,
timeout=timeout_s,
cwd=str(REPO),
)
elapsed = time.monotonic() - start
return parse_output(proc.stdout), False, elapsed
except subprocess.TimeoutExpired as e:
elapsed = time.monotonic() - start
# Partial output may still be parseable
stdout = (e.stdout or b"").decode("utf-8", errors="replace") if isinstance(e.stdout, bytes) else (e.stdout or "")
return parse_output(stdout), True, elapsed
# ---------------------------------------------------------------------------
# Main loop
# ---------------------------------------------------------------------------
def discover_tests(filter_prefix: str | None) -> list[Path]:
"""Walk test262/test/**/*.js, skipping _FIXTURE files and _FIXTURE dirs."""
tests: list[Path] = []
for p in TEST_ROOT.rglob("*.js"):
if p.name.endswith("_FIXTURE.js"):
continue
if "_FIXTURE" in p.parts:
continue
if filter_prefix:
rel = p.relative_to(TEST_ROOT).as_posix()
if not rel.startswith(filter_prefix):
continue
tests.append(p)
tests.sort()
return tests
def load_test(path: Path) -> TestCase | None:
"""Load + parse frontmatter. Returns None on read error."""
try:
src = path.read_text(encoding="utf-8")
except Exception:
return None
fm = parse_frontmatter(src)
return TestCase(
path=path,
rel=path.relative_to(TEST_ROOT).as_posix(),
category=test_category(path),
fm=fm,
src=src,
)
def should_skip(t: TestCase) -> tuple[bool, str]:
"""Skip tests we know we can't run or are explicitly excluded."""
# Strict-mode tests — we don't support strict mode, so these are noise.
if "onlyStrict" in t.fm.flags:
return True, "strict-mode only (not supported)"
# module flag — ESM tests not supported
if "module" in t.fm.flags:
return True, "ESM module (not supported)"
# async tests time out easily without a proper event loop
if "async" in t.fm.flags:
# Let them run; the executor handles timeouts per-batch.
pass
# raw tests — they don't load the harness; we can't use assert.* at all.
# Still run them — some raw tests just check syntax via parse.
return False, ""
def aggregate(results: list[TestResult]) -> dict:
"""Build the scoreboard dict."""
by_cat: dict[str, dict] = defaultdict(
lambda: {"pass": 0, "fail": 0, "skip": 0, "timeout": 0, "total": 0, "failures": Counter()}
)
totals = {"pass": 0, "fail": 0, "skip": 0, "timeout": 0, "total": 0}
failure_modes: Counter[str] = Counter()
for r in results:
cat = by_cat[r.category]
cat[r.status] += 1
cat["total"] += 1
totals[r.status] += 1
totals["total"] += 1
if r.status == "fail":
cat["failures"][r.reason] += 1
failure_modes[r.reason] += 1
# Build the scoreboard
categories = []
for name, stats in sorted(by_cat.items()):
total = stats["total"]
passed = stats["pass"]
runnable = total - stats["skip"]
pass_rate = (passed / runnable * 100.0) if runnable else 0.0
categories.append(
{
"category": name,
"total": total,
"pass": passed,
"fail": stats["fail"],
"skip": stats["skip"],
"timeout": stats["timeout"],
"pass_rate": round(pass_rate, 1),
"top_failures": stats["failures"].most_common(5),
}
)
pass_rate = (totals["pass"] / (totals["total"] - totals["skip"]) * 100.0) if totals["total"] - totals["skip"] else 0.0
return {
"totals": {**totals, "pass_rate": round(pass_rate, 1)},
"categories": categories,
"top_failure_modes": failure_modes.most_common(20),
}
def write_markdown(scoreboard: dict, path: Path, pinned_commit: str) -> None:
t = scoreboard["totals"]
lines = [
"# test262 scoreboard",
"",
f"Pinned commit: `{pinned_commit}`",
"",
f"**Total:** {t['pass']}/{t['total']} passed ({t['pass_rate']}%), "
f"{t['fail']} failed, {t['skip']} skipped, {t['timeout']} timeouts.",
"",
"## Top failure modes",
"",
]
for mode, count in scoreboard["top_failure_modes"]:
lines.append(f"- **{count}x** {mode}")
lines.extend(["", "## Categories (worst pass-rate first)", ""])
lines.append("| Category | Pass | Fail | Skip | Timeout | Total | Pass % |")
lines.append("|---|---:|---:|---:|---:|---:|---:|")
# Sort: worst pass rate first, breaking ties by total desc
cats = sorted(scoreboard["categories"], key=lambda c: (c["pass_rate"], -c["total"]))
for c in cats:
lines.append(
f"| {c['category']} | {c['pass']} | {c['fail']} | {c['skip']} | "
f"{c['timeout']} | {c['total']} | {c['pass_rate']}% |"
)
lines.append("")
lines.append("## Per-category top failures")
lines.append("")
for c in cats:
if not c["top_failures"]:
continue
lines.append(f"### {c['category']}")
lines.append("")
for reason, count in c["top_failures"]:
lines.append(f"- **{count}x** {reason}")
lines.append("")
path.write_text("\n".join(lines), encoding="utf-8")
def main(argv: list[str]) -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--limit", type=int, default=0, help="max tests to run (0 = all)")
ap.add_argument("--filter", type=str, default=None, help="path prefix filter")
ap.add_argument("--batch-size", type=int, default=DEFAULT_BATCH_SIZE)
ap.add_argument(
"--output-json",
type=str,
default=str(REPO / "lib" / "js" / "test262-scoreboard.json"),
)
ap.add_argument(
"--output-md",
type=str,
default=str(REPO / "lib" / "js" / "test262-scoreboard.md"),
)
ap.add_argument("--progress", action="store_true", help="print per-batch progress")
args = ap.parse_args(argv)
if not SX_SERVER.exists():
print(f"ERROR: sx_server.exe not found at {SX_SERVER}", file=sys.stderr)
print("Build with: cd hosts/ocaml && dune build", file=sys.stderr)
return 1
if not UPSTREAM.exists():
print(f"ERROR: test262-upstream not found at {UPSTREAM}", file=sys.stderr)
print(
"Clone with: cd lib/js && git clone --depth 1 "
"https://github.com/tc39/test262.git test262-upstream",
file=sys.stderr,
)
return 1
pinned_commit = ""
try:
pinned_commit = subprocess.check_output(
["git", "-C", str(UPSTREAM), "rev-parse", "HEAD"], text=True
).strip()
except Exception:
pass
all_paths = discover_tests(args.filter)
if args.limit:
all_paths = all_paths[: args.limit]
print(f"Discovered {len(all_paths)} test files.", file=sys.stderr)
# Load all (parse frontmatter, decide skips up front)
tests: list[TestCase] = []
skipped: list[TestResult] = []
for p in all_paths:
t = load_test(p)
if not t:
continue
skip, why = should_skip(t)
if skip:
skipped.append(
TestResult(rel=t.rel, category=t.category, status="skip", reason=why)
)
continue
tests.append(t)
print(
f"Will run {len(tests)} tests ({len(skipped)} skipped up front).",
file=sys.stderr,
)
results: list[TestResult] = list(skipped)
batch_size = args.batch_size
epoch_start = 100
n_batches = (len(tests) + batch_size - 1) // batch_size
t_run_start = time.monotonic()
for bi in range(n_batches):
batch = tests[bi * batch_size : (bi + 1) * batch_size]
timeout_s = min(BATCH_TIMEOUT_S, max(30, len(batch) * PER_TEST_S))
epoch_map, timed_out, elapsed = run_batch(batch, epoch_start, timeout_s)
for idx, t in enumerate(batch):
epoch = epoch_start + idx
res = epoch_map.get(epoch)
if res is None:
# No result for this epoch — batch probably timed out before
# reaching it, or sx_server died.
status = "timeout" if timed_out else "fail"
reason = "batch timeout before epoch" if timed_out else "no result from sx_server"
results.append(
TestResult(
rel=t.rel, category=t.category, status=status, reason=reason
)
)
continue
kind, payload = res
if t.fm.negative_phase:
ok, why = classify_negative_result(t.fm, kind, payload)
else:
ok, why = classify_positive_result(kind, payload)
results.append(
TestResult(
rel=t.rel,
category=t.category,
status="pass" if ok else "fail",
reason=why,
)
)
epoch_start += batch_size
if args.progress or bi % 10 == 0:
done_n = min((bi + 1) * batch_size, len(tests))
pass_so_far = sum(1 for r in results if r.status == "pass")
print(
f" [batch {bi + 1}/{n_batches}] {done_n}/{len(tests)} tests "
f"{elapsed:.1f}s{' TIMEOUT' if timed_out else ''} "
f"running-pass={pass_so_far}",
file=sys.stderr,
)
t_run_elapsed = time.monotonic() - t_run_start
print(f"\nFinished run in {t_run_elapsed:.1f}s", file=sys.stderr)
scoreboard = aggregate(results)
scoreboard["pinned_commit"] = pinned_commit
scoreboard["elapsed_seconds"] = round(t_run_elapsed, 1)
# Per-test detail is too large — omit from JSON by default; the aggregated
# scoreboard is what's useful.
out_json = Path(args.output_json)
out_json.parent.mkdir(parents=True, exist_ok=True)
out_json.write_text(json.dumps(scoreboard, indent=2), encoding="utf-8")
out_md = Path(args.output_md)
write_markdown(scoreboard, out_md, pinned_commit)
t = scoreboard["totals"]
print(
f"\nScoreboard: {t['pass']}/{t['total']} passed ({t['pass_rate']}%) "
f"fail={t['fail']} skip={t['skip']} timeout={t['timeout']}",
file=sys.stderr,
)
print(f"JSON: {out_json}", file=sys.stderr)
print(f"MD: {out_md}", file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

View File

@@ -0,0 +1,35 @@
{
"totals": {
"pass": 0,
"fail": 1,
"skip": 0,
"timeout": 7,
"total": 8,
"pass_rate": 0.0
},
"categories": [
{
"category": "built-ins/Math",
"total": 8,
"pass": 0,
"fail": 1,
"skip": 0,
"timeout": 7,
"pass_rate": 0.0,
"top_failures": [
[
"SyntaxError (parse/unsupported syntax)",
1
]
]
}
],
"top_failure_modes": [
[
"SyntaxError (parse/unsupported syntax)",
1
]
],
"pinned_commit": "d5e73fc8d2c663554fb72e2380a8c2bc1a318a33",
"elapsed_seconds": 40.1
}

View File

@@ -0,0 +1,21 @@
# test262 scoreboard
Pinned commit: `d5e73fc8d2c663554fb72e2380a8c2bc1a318a33`
**Total:** 0/8 passed (0.0%), 1 failed, 0 skipped, 7 timeouts.
## Top failure modes
- **1x** SyntaxError (parse/unsupported syntax)
## Categories (worst pass-rate first)
| Category | Pass | Fail | Skip | Timeout | Total | Pass % |
|---|---:|---:|---:|---:|---:|---:|
| built-ins/Math | 0 | 1 | 0 | 7 | 8 | 0.0% |
## Per-category top failures
### built-ins/Math
- **1x** SyntaxError (parse/unsupported syntax)

View File

@@ -0,0 +1,31 @@
# JS-on-SX cherry-picked conformance slice
A hand-picked slice inspired by test262 expression tests. Each test is one
JS expression in a `.js` file, paired with an `.expected` file containing
the SX-printed result that `js-eval` should produce.
Run via:
bash lib/js/conformance.sh
The slice intentionally avoids anything not yet implemented (statements,
`var`/`let`, `function`, regex, template strings, prototypes, `new`,
`this`, classes, async). Those land in later phases.
## Expected value format
`js-eval` returns SX values. The epoch protocol prints them thus:
| JS value | Expected file contents |
|------------------|-----------------------|
| `42` | `42` |
| `3.14` | `3.14` |
| `true` / `false` | `true` / `false` |
| `"hi"` | `"hi"` |
| `null` | `nil` |
| `undefined` | `"js-undefined"` |
| `[1,2,3]` | `(1 2 3)` |
| `{}` | `{}` |
The runner does a substring match — the `.expected` file can contain just
the distinguishing part of the result.

View File

@@ -0,0 +1 @@
3

View File

@@ -0,0 +1 @@
1 + 2

View File

@@ -0,0 +1 @@
5

View File

@@ -0,0 +1 @@
1 + 2 * 3 - 4 / 2

View File

@@ -0,0 +1 @@
-6

View File

@@ -0,0 +1 @@
~5

View File

@@ -0,0 +1 @@
10

View File

@@ -0,0 +1 @@
1 + 2 + 3 + 4

View File

@@ -0,0 +1 @@
3

View File

@@ -0,0 +1 @@
12 / 4

View File

@@ -0,0 +1 @@
"12"

View File

@@ -0,0 +1 @@
1 + "2"

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1 @@
10 % 3

View File

@@ -0,0 +1 @@
-5

View File

@@ -0,0 +1 @@
-5

View File

@@ -0,0 +1 @@
9

View File

@@ -0,0 +1 @@
(1 + 2) * 3

View File

@@ -0,0 +1 @@
5

View File

@@ -0,0 +1 @@
+5

View File

@@ -0,0 +1 @@
1024

View File

@@ -0,0 +1 @@
2 ** 10

View File

@@ -0,0 +1 @@
512

View File

@@ -0,0 +1 @@
2 ** 3 ** 2

View File

@@ -0,0 +1 @@
7

View File

@@ -0,0 +1 @@
1 + 2 * 3

View File

@@ -0,0 +1 @@
"ab"

View File

@@ -0,0 +1 @@
"a" + "b"

View File

@@ -0,0 +1 @@
6

View File

@@ -0,0 +1 @@
10 - 4

View File

@@ -0,0 +1 @@
42

View File

@@ -0,0 +1,5 @@
var double = async x => x * 2;
var r = null;
double(21).then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
42

View File

@@ -0,0 +1,5 @@
var add = async (a, b) => a + b;
var r = null;
add(10, 32).then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
42

View File

@@ -0,0 +1,5 @@
async function f() { return 42; }
var r = null;
f().then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
nope

View File

@@ -0,0 +1,5 @@
async function f() { throw "nope"; }
var r = null;
f().catch(e => { r = e; });
__drain();
r

View File

@@ -0,0 +1 @@
33

View File

@@ -0,0 +1,7 @@
async function inner(x) { return x * 2; }
async function middle(x) { var r = await inner(x); return r + 1; }
async function outer(x) { var r = await middle(x); return r * 3; }
var result = null;
outer(5).then(v => { result = v; });
__drain();
result

View File

@@ -0,0 +1 @@
100

View File

@@ -0,0 +1,5 @@
async function wrap(x) { return Promise.resolve(x); }
var r = null;
wrap(100).then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
70

View File

@@ -0,0 +1,9 @@
async function add(a, b) { return a + b; }
async function main() {
var r = await add(3, 4);
return r * 10;
}
var result = null;
main().then(v => { result = v; });
__drain();
result

View File

@@ -0,0 +1 @@
3

View File

@@ -0,0 +1,11 @@
async function step() { return 1; }
async function main() {
var a = await step();
var b = await step();
var c = await step();
return a + b + c;
}
var r = null;
main().then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
5

View File

@@ -0,0 +1,12 @@
async function one() { return 1; }
async function main() {
var sum = 0;
for (var i = 0; i < 5; i = i + 1) {
sum = sum + await one();
}
return sum;
}
var r = null;
main().then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
43

View File

@@ -0,0 +1,8 @@
async function main() {
var x = await 42;
return x + 1;
}
var r = null;
main().then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
6

View File

@@ -0,0 +1,8 @@
async function main() {
var vs = await Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]);
return vs[0] + vs[1] + vs[2];
}
var r = null;
main().then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
caught:no

View File

@@ -0,0 +1,9 @@
async function bad() { throw "no"; }
async function main() {
try { await bad(); return "unreachable"; }
catch (e) { return "caught:" + e; }
}
var r = null;
main().then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
bad

View File

@@ -0,0 +1,9 @@
async function failing() { throw new Error("bad"); }
async function main() {
try { await failing(); return "unreachable"; }
catch (e) { return e.message; }
}
var r = null;
main().then(v => { r = v; });
__drain();
r

View File

@@ -0,0 +1 @@
44

View File

@@ -0,0 +1 @@
function mk(n) { return x => x + n; } let add7 = mk(7); add7(10) + add7(20)

View File

@@ -0,0 +1 @@
4

View File

@@ -0,0 +1 @@
function mkc() { let n = 0; return () => { n = n + 1; return n; }; } let c = mkc(); c(); c(); c(); c()

View File

@@ -0,0 +1 @@
42

View File

@@ -0,0 +1 @@
function mkPair() { let x = 0; let y = 0; let setX = v => { x = v; }; let setY = v => { y = v; }; let get = () => x + y; setX(17); setY(25); return get(); } mkPair()

View File

@@ -0,0 +1 @@
3

View File

@@ -0,0 +1 @@
function outer() { let a = 1; function inner() { let b = 2; function deepest() { return a + b; } return deepest(); } return inner(); } outer()

View File

@@ -0,0 +1 @@
55

View File

@@ -0,0 +1 @@
function sumSq(xs) { let t = 0; for (let i = 0; i < xs.length; i = i + 1) t = t + xs[i] * xs[i]; return t; } sumSq([1,2,3,4,5])

View File

@@ -0,0 +1 @@
"n=42"

View File

@@ -0,0 +1 @@
"n=" + 42

View File

@@ -0,0 +1 @@
true

View File

@@ -0,0 +1 @@
"5" == 5

View File

@@ -0,0 +1 @@
"boolean"

View File

@@ -0,0 +1 @@
typeof true

View File

@@ -0,0 +1 @@
"function"

View File

@@ -0,0 +1 @@
typeof (x => x)

View File

@@ -0,0 +1 @@
"object"

View File

@@ -0,0 +1 @@
typeof null

View File

@@ -0,0 +1 @@
"number"

View File

@@ -0,0 +1 @@
typeof 42

View File

@@ -0,0 +1 @@
"string"

View File

@@ -0,0 +1 @@
typeof "x"

View File

@@ -0,0 +1 @@
"undefined"

View File

@@ -0,0 +1 @@
typeof undefined

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
[].length

View File

@@ -0,0 +1 @@
20

View File

@@ -0,0 +1 @@
[10,20,30][1]

View File

@@ -0,0 +1 @@
3

View File

@@ -0,0 +1 @@
[1,2,3].length

View File

@@ -0,0 +1 @@
3

View File

@@ -0,0 +1 @@
[[1,2],[3,4]][1][0]

Some files were not shown because too many files have changed in this diff Show More