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:
1
lib/js/.gitignore
vendored
Normal file
1
lib/js/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
test262-upstream/
|
||||
130
lib/js/conformance.sh
Executable file
130
lib/js/conformance.sh
Executable 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
519
lib/js/lexer.sx
Normal 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
1097
lib/js/parser.sx
Normal file
File diff suppressed because it is too large
Load Diff
1596
lib/js/runtime.sx
Normal file
1596
lib/js/runtime.sx
Normal file
File diff suppressed because it is too large
Load Diff
1156
lib/js/test.sh
Executable file
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
711
lib/js/test262-runner.py
Normal 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:]))
|
||||
35
lib/js/test262-scoreboard.json
Normal file
35
lib/js/test262-scoreboard.json
Normal 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
|
||||
}
|
||||
21
lib/js/test262-scoreboard.md
Normal file
21
lib/js/test262-scoreboard.md
Normal 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)
|
||||
31
lib/js/test262-slice/README.md
Normal file
31
lib/js/test262-slice/README.md
Normal 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.
|
||||
1
lib/js/test262-slice/arithmetic/add.expected
Normal file
1
lib/js/test262-slice/arithmetic/add.expected
Normal file
@@ -0,0 +1 @@
|
||||
3
|
||||
1
lib/js/test262-slice/arithmetic/add.js
Normal file
1
lib/js/test262-slice/arithmetic/add.js
Normal file
@@ -0,0 +1 @@
|
||||
1 + 2
|
||||
1
lib/js/test262-slice/arithmetic/big_expr.expected
Normal file
1
lib/js/test262-slice/arithmetic/big_expr.expected
Normal file
@@ -0,0 +1 @@
|
||||
5
|
||||
1
lib/js/test262-slice/arithmetic/big_expr.js
Normal file
1
lib/js/test262-slice/arithmetic/big_expr.js
Normal file
@@ -0,0 +1 @@
|
||||
1 + 2 * 3 - 4 / 2
|
||||
1
lib/js/test262-slice/arithmetic/bitnot.expected
Normal file
1
lib/js/test262-slice/arithmetic/bitnot.expected
Normal file
@@ -0,0 +1 @@
|
||||
-6
|
||||
1
lib/js/test262-slice/arithmetic/bitnot.js
Normal file
1
lib/js/test262-slice/arithmetic/bitnot.js
Normal file
@@ -0,0 +1 @@
|
||||
~5
|
||||
1
lib/js/test262-slice/arithmetic/chained.expected
Normal file
1
lib/js/test262-slice/arithmetic/chained.expected
Normal file
@@ -0,0 +1 @@
|
||||
10
|
||||
1
lib/js/test262-slice/arithmetic/chained.js
Normal file
1
lib/js/test262-slice/arithmetic/chained.js
Normal file
@@ -0,0 +1 @@
|
||||
1 + 2 + 3 + 4
|
||||
1
lib/js/test262-slice/arithmetic/div.expected
Normal file
1
lib/js/test262-slice/arithmetic/div.expected
Normal file
@@ -0,0 +1 @@
|
||||
3
|
||||
1
lib/js/test262-slice/arithmetic/div.js
Normal file
1
lib/js/test262-slice/arithmetic/div.js
Normal file
@@ -0,0 +1 @@
|
||||
12 / 4
|
||||
1
lib/js/test262-slice/arithmetic/mixed_concat.expected
Normal file
1
lib/js/test262-slice/arithmetic/mixed_concat.expected
Normal file
@@ -0,0 +1 @@
|
||||
"12"
|
||||
1
lib/js/test262-slice/arithmetic/mixed_concat.js
Normal file
1
lib/js/test262-slice/arithmetic/mixed_concat.js
Normal file
@@ -0,0 +1 @@
|
||||
1 + "2"
|
||||
1
lib/js/test262-slice/arithmetic/mod.expected
Normal file
1
lib/js/test262-slice/arithmetic/mod.expected
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
1
lib/js/test262-slice/arithmetic/mod.js
Normal file
1
lib/js/test262-slice/arithmetic/mod.js
Normal file
@@ -0,0 +1 @@
|
||||
10 % 3
|
||||
1
lib/js/test262-slice/arithmetic/neg.expected
Normal file
1
lib/js/test262-slice/arithmetic/neg.expected
Normal file
@@ -0,0 +1 @@
|
||||
-5
|
||||
1
lib/js/test262-slice/arithmetic/neg.js
Normal file
1
lib/js/test262-slice/arithmetic/neg.js
Normal file
@@ -0,0 +1 @@
|
||||
-5
|
||||
@@ -0,0 +1 @@
|
||||
9
|
||||
1
lib/js/test262-slice/arithmetic/paren_precedence.js
Normal file
1
lib/js/test262-slice/arithmetic/paren_precedence.js
Normal file
@@ -0,0 +1 @@
|
||||
(1 + 2) * 3
|
||||
1
lib/js/test262-slice/arithmetic/pos.expected
Normal file
1
lib/js/test262-slice/arithmetic/pos.expected
Normal file
@@ -0,0 +1 @@
|
||||
5
|
||||
1
lib/js/test262-slice/arithmetic/pos.js
Normal file
1
lib/js/test262-slice/arithmetic/pos.js
Normal file
@@ -0,0 +1 @@
|
||||
+5
|
||||
1
lib/js/test262-slice/arithmetic/pow.expected
Normal file
1
lib/js/test262-slice/arithmetic/pow.expected
Normal file
@@ -0,0 +1 @@
|
||||
1024
|
||||
1
lib/js/test262-slice/arithmetic/pow.js
Normal file
1
lib/js/test262-slice/arithmetic/pow.js
Normal file
@@ -0,0 +1 @@
|
||||
2 ** 10
|
||||
1
lib/js/test262-slice/arithmetic/pow_right_assoc.expected
Normal file
1
lib/js/test262-slice/arithmetic/pow_right_assoc.expected
Normal file
@@ -0,0 +1 @@
|
||||
512
|
||||
1
lib/js/test262-slice/arithmetic/pow_right_assoc.js
Normal file
1
lib/js/test262-slice/arithmetic/pow_right_assoc.js
Normal file
@@ -0,0 +1 @@
|
||||
2 ** 3 ** 2
|
||||
1
lib/js/test262-slice/arithmetic/precedence.expected
Normal file
1
lib/js/test262-slice/arithmetic/precedence.expected
Normal file
@@ -0,0 +1 @@
|
||||
7
|
||||
1
lib/js/test262-slice/arithmetic/precedence.js
Normal file
1
lib/js/test262-slice/arithmetic/precedence.js
Normal file
@@ -0,0 +1 @@
|
||||
1 + 2 * 3
|
||||
1
lib/js/test262-slice/arithmetic/string_concat.expected
Normal file
1
lib/js/test262-slice/arithmetic/string_concat.expected
Normal file
@@ -0,0 +1 @@
|
||||
"ab"
|
||||
1
lib/js/test262-slice/arithmetic/string_concat.js
Normal file
1
lib/js/test262-slice/arithmetic/string_concat.js
Normal file
@@ -0,0 +1 @@
|
||||
"a" + "b"
|
||||
1
lib/js/test262-slice/arithmetic/sub.expected
Normal file
1
lib/js/test262-slice/arithmetic/sub.expected
Normal file
@@ -0,0 +1 @@
|
||||
6
|
||||
1
lib/js/test262-slice/arithmetic/sub.js
Normal file
1
lib/js/test262-slice/arithmetic/sub.js
Normal file
@@ -0,0 +1 @@
|
||||
10 - 4
|
||||
1
lib/js/test262-slice/async/async_arrow.expected
Normal file
1
lib/js/test262-slice/async/async_arrow.expected
Normal file
@@ -0,0 +1 @@
|
||||
42
|
||||
5
lib/js/test262-slice/async/async_arrow.js
Normal file
5
lib/js/test262-slice/async/async_arrow.js
Normal file
@@ -0,0 +1,5 @@
|
||||
var double = async x => x * 2;
|
||||
var r = null;
|
||||
double(21).then(v => { r = v; });
|
||||
__drain();
|
||||
r
|
||||
@@ -0,0 +1 @@
|
||||
42
|
||||
5
lib/js/test262-slice/async/async_arrow_multiparam.js
Normal file
5
lib/js/test262-slice/async/async_arrow_multiparam.js
Normal file
@@ -0,0 +1,5 @@
|
||||
var add = async (a, b) => a + b;
|
||||
var r = null;
|
||||
add(10, 32).then(v => { r = v; });
|
||||
__drain();
|
||||
r
|
||||
1
lib/js/test262-slice/async/async_fn_basic.expected
Normal file
1
lib/js/test262-slice/async/async_fn_basic.expected
Normal file
@@ -0,0 +1 @@
|
||||
42
|
||||
5
lib/js/test262-slice/async/async_fn_basic.js
Normal file
5
lib/js/test262-slice/async/async_fn_basic.js
Normal file
@@ -0,0 +1,5 @@
|
||||
async function f() { return 42; }
|
||||
var r = null;
|
||||
f().then(v => { r = v; });
|
||||
__drain();
|
||||
r
|
||||
1
lib/js/test262-slice/async/async_fn_throws.expected
Normal file
1
lib/js/test262-slice/async/async_fn_throws.expected
Normal file
@@ -0,0 +1 @@
|
||||
nope
|
||||
5
lib/js/test262-slice/async/async_fn_throws.js
Normal file
5
lib/js/test262-slice/async/async_fn_throws.js
Normal file
@@ -0,0 +1,5 @@
|
||||
async function f() { throw "nope"; }
|
||||
var r = null;
|
||||
f().catch(e => { r = e; });
|
||||
__drain();
|
||||
r
|
||||
1
lib/js/test262-slice/async/async_nested_calls.expected
Normal file
1
lib/js/test262-slice/async/async_nested_calls.expected
Normal file
@@ -0,0 +1 @@
|
||||
33
|
||||
7
lib/js/test262-slice/async/async_nested_calls.js
Normal file
7
lib/js/test262-slice/async/async_nested_calls.js
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
100
|
||||
5
lib/js/test262-slice/async/async_returns_promise.js
Normal file
5
lib/js/test262-slice/async/async_returns_promise.js
Normal file
@@ -0,0 +1,5 @@
|
||||
async function wrap(x) { return Promise.resolve(x); }
|
||||
var r = null;
|
||||
wrap(100).then(v => { r = v; });
|
||||
__drain();
|
||||
r
|
||||
1
lib/js/test262-slice/async/await_basic.expected
Normal file
1
lib/js/test262-slice/async/await_basic.expected
Normal file
@@ -0,0 +1 @@
|
||||
70
|
||||
9
lib/js/test262-slice/async/await_basic.js
Normal file
9
lib/js/test262-slice/async/await_basic.js
Normal 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
|
||||
1
lib/js/test262-slice/async/await_chain.expected
Normal file
1
lib/js/test262-slice/async/await_chain.expected
Normal file
@@ -0,0 +1 @@
|
||||
3
|
||||
11
lib/js/test262-slice/async/await_chain.js
Normal file
11
lib/js/test262-slice/async/await_chain.js
Normal 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
|
||||
1
lib/js/test262-slice/async/await_in_loop.expected
Normal file
1
lib/js/test262-slice/async/await_in_loop.expected
Normal file
@@ -0,0 +1 @@
|
||||
5
|
||||
12
lib/js/test262-slice/async/await_in_loop.js
Normal file
12
lib/js/test262-slice/async/await_in_loop.js
Normal 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
|
||||
1
lib/js/test262-slice/async/await_nonpromise.expected
Normal file
1
lib/js/test262-slice/async/await_nonpromise.expected
Normal file
@@ -0,0 +1 @@
|
||||
43
|
||||
8
lib/js/test262-slice/async/await_nonpromise.js
Normal file
8
lib/js/test262-slice/async/await_nonpromise.js
Normal 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
|
||||
1
lib/js/test262-slice/async/await_promise_all.expected
Normal file
1
lib/js/test262-slice/async/await_promise_all.expected
Normal file
@@ -0,0 +1 @@
|
||||
6
|
||||
8
lib/js/test262-slice/async/await_promise_all.js
Normal file
8
lib/js/test262-slice/async/await_promise_all.js
Normal 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
|
||||
1
lib/js/test262-slice/async/await_rejected.expected
Normal file
1
lib/js/test262-slice/async/await_rejected.expected
Normal file
@@ -0,0 +1 @@
|
||||
caught:no
|
||||
9
lib/js/test262-slice/async/await_rejected.js
Normal file
9
lib/js/test262-slice/async/await_rejected.js
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
bad
|
||||
9
lib/js/test262-slice/async/await_throws_error_object.js
Normal file
9
lib/js/test262-slice/async/await_throws_error_object.js
Normal 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
|
||||
1
lib/js/test262-slice/closures/adder.expected
Normal file
1
lib/js/test262-slice/closures/adder.expected
Normal file
@@ -0,0 +1 @@
|
||||
44
|
||||
1
lib/js/test262-slice/closures/adder.js
Normal file
1
lib/js/test262-slice/closures/adder.js
Normal file
@@ -0,0 +1 @@
|
||||
function mk(n) { return x => x + n; } let add7 = mk(7); add7(10) + add7(20)
|
||||
1
lib/js/test262-slice/closures/counter.expected
Normal file
1
lib/js/test262-slice/closures/counter.expected
Normal file
@@ -0,0 +1 @@
|
||||
4
|
||||
1
lib/js/test262-slice/closures/counter.js
Normal file
1
lib/js/test262-slice/closures/counter.js
Normal file
@@ -0,0 +1 @@
|
||||
function mkc() { let n = 0; return () => { n = n + 1; return n; }; } let c = mkc(); c(); c(); c(); c()
|
||||
1
lib/js/test262-slice/closures/multi_closure.expected
Normal file
1
lib/js/test262-slice/closures/multi_closure.expected
Normal file
@@ -0,0 +1 @@
|
||||
42
|
||||
1
lib/js/test262-slice/closures/multi_closure.js
Normal file
1
lib/js/test262-slice/closures/multi_closure.js
Normal 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()
|
||||
1
lib/js/test262-slice/closures/nested_scope.expected
Normal file
1
lib/js/test262-slice/closures/nested_scope.expected
Normal file
@@ -0,0 +1 @@
|
||||
3
|
||||
1
lib/js/test262-slice/closures/nested_scope.js
Normal file
1
lib/js/test262-slice/closures/nested_scope.js
Normal file
@@ -0,0 +1 @@
|
||||
function outer() { let a = 1; function inner() { let b = 2; function deepest() { return a + b; } return deepest(); } return inner(); } outer()
|
||||
1
lib/js/test262-slice/closures/sum_sq.expected
Normal file
1
lib/js/test262-slice/closures/sum_sq.expected
Normal file
@@ -0,0 +1 @@
|
||||
55
|
||||
1
lib/js/test262-slice/closures/sum_sq.js
Normal file
1
lib/js/test262-slice/closures/sum_sq.js
Normal 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])
|
||||
1
lib/js/test262-slice/coercion/implicit_str_add.expected
Normal file
1
lib/js/test262-slice/coercion/implicit_str_add.expected
Normal file
@@ -0,0 +1 @@
|
||||
"n=42"
|
||||
1
lib/js/test262-slice/coercion/implicit_str_add.js
Normal file
1
lib/js/test262-slice/coercion/implicit_str_add.js
Normal file
@@ -0,0 +1 @@
|
||||
"n=" + 42
|
||||
1
lib/js/test262-slice/coercion/loose_str_num.expected
Normal file
1
lib/js/test262-slice/coercion/loose_str_num.expected
Normal file
@@ -0,0 +1 @@
|
||||
true
|
||||
1
lib/js/test262-slice/coercion/loose_str_num.js
Normal file
1
lib/js/test262-slice/coercion/loose_str_num.js
Normal file
@@ -0,0 +1 @@
|
||||
"5" == 5
|
||||
1
lib/js/test262-slice/coercion/typeof_bool.expected
Normal file
1
lib/js/test262-slice/coercion/typeof_bool.expected
Normal file
@@ -0,0 +1 @@
|
||||
"boolean"
|
||||
1
lib/js/test262-slice/coercion/typeof_bool.js
Normal file
1
lib/js/test262-slice/coercion/typeof_bool.js
Normal file
@@ -0,0 +1 @@
|
||||
typeof true
|
||||
1
lib/js/test262-slice/coercion/typeof_fn.expected
Normal file
1
lib/js/test262-slice/coercion/typeof_fn.expected
Normal file
@@ -0,0 +1 @@
|
||||
"function"
|
||||
1
lib/js/test262-slice/coercion/typeof_fn.js
Normal file
1
lib/js/test262-slice/coercion/typeof_fn.js
Normal file
@@ -0,0 +1 @@
|
||||
typeof (x => x)
|
||||
1
lib/js/test262-slice/coercion/typeof_null.expected
Normal file
1
lib/js/test262-slice/coercion/typeof_null.expected
Normal file
@@ -0,0 +1 @@
|
||||
"object"
|
||||
1
lib/js/test262-slice/coercion/typeof_null.js
Normal file
1
lib/js/test262-slice/coercion/typeof_null.js
Normal file
@@ -0,0 +1 @@
|
||||
typeof null
|
||||
1
lib/js/test262-slice/coercion/typeof_num.expected
Normal file
1
lib/js/test262-slice/coercion/typeof_num.expected
Normal file
@@ -0,0 +1 @@
|
||||
"number"
|
||||
1
lib/js/test262-slice/coercion/typeof_num.js
Normal file
1
lib/js/test262-slice/coercion/typeof_num.js
Normal file
@@ -0,0 +1 @@
|
||||
typeof 42
|
||||
1
lib/js/test262-slice/coercion/typeof_str.expected
Normal file
1
lib/js/test262-slice/coercion/typeof_str.expected
Normal file
@@ -0,0 +1 @@
|
||||
"string"
|
||||
1
lib/js/test262-slice/coercion/typeof_str.js
Normal file
1
lib/js/test262-slice/coercion/typeof_str.js
Normal file
@@ -0,0 +1 @@
|
||||
typeof "x"
|
||||
1
lib/js/test262-slice/coercion/typeof_undef.expected
Normal file
1
lib/js/test262-slice/coercion/typeof_undef.expected
Normal file
@@ -0,0 +1 @@
|
||||
"undefined"
|
||||
1
lib/js/test262-slice/coercion/typeof_undef.js
Normal file
1
lib/js/test262-slice/coercion/typeof_undef.js
Normal file
@@ -0,0 +1 @@
|
||||
typeof undefined
|
||||
1
lib/js/test262-slice/collections/array_empty.expected
Normal file
1
lib/js/test262-slice/collections/array_empty.expected
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
lib/js/test262-slice/collections/array_empty.js
Normal file
1
lib/js/test262-slice/collections/array_empty.js
Normal file
@@ -0,0 +1 @@
|
||||
[].length
|
||||
1
lib/js/test262-slice/collections/array_index.expected
Normal file
1
lib/js/test262-slice/collections/array_index.expected
Normal file
@@ -0,0 +1 @@
|
||||
20
|
||||
1
lib/js/test262-slice/collections/array_index.js
Normal file
1
lib/js/test262-slice/collections/array_index.js
Normal file
@@ -0,0 +1 @@
|
||||
[10,20,30][1]
|
||||
1
lib/js/test262-slice/collections/array_length.expected
Normal file
1
lib/js/test262-slice/collections/array_length.expected
Normal file
@@ -0,0 +1 @@
|
||||
3
|
||||
1
lib/js/test262-slice/collections/array_length.js
Normal file
1
lib/js/test262-slice/collections/array_length.js
Normal file
@@ -0,0 +1 @@
|
||||
[1,2,3].length
|
||||
1
lib/js/test262-slice/collections/array_nested.expected
Normal file
1
lib/js/test262-slice/collections/array_nested.expected
Normal file
@@ -0,0 +1 @@
|
||||
3
|
||||
1
lib/js/test262-slice/collections/array_nested.js
Normal file
1
lib/js/test262-slice/collections/array_nested.js
Normal 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
Reference in New Issue
Block a user