From 9e568ad886046263454dc18f505f240e70906452 Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 23 Apr 2026 19:42:16 +0000 Subject: [PATCH] js-on-sx: baseline commit (278/280 unit, 148/148 slice, runner stub) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/js/.gitignore | 1 + lib/js/conformance.sh | 130 ++ lib/js/lexer.sx | 519 ++++++ lib/js/parser.sx | 1097 +++++++++++ lib/js/runtime.sx | 1596 +++++++++++++++++ lib/js/test.sh | 1156 ++++++++++++ lib/js/test262-runner.py | 711 ++++++++ lib/js/test262-scoreboard.json | 35 + lib/js/test262-scoreboard.md | 21 + lib/js/test262-slice/README.md | 31 + lib/js/test262-slice/arithmetic/add.expected | 1 + lib/js/test262-slice/arithmetic/add.js | 1 + .../arithmetic/big_expr.expected | 1 + lib/js/test262-slice/arithmetic/big_expr.js | 1 + .../test262-slice/arithmetic/bitnot.expected | 1 + lib/js/test262-slice/arithmetic/bitnot.js | 1 + .../test262-slice/arithmetic/chained.expected | 1 + lib/js/test262-slice/arithmetic/chained.js | 1 + lib/js/test262-slice/arithmetic/div.expected | 1 + lib/js/test262-slice/arithmetic/div.js | 1 + .../arithmetic/mixed_concat.expected | 1 + .../test262-slice/arithmetic/mixed_concat.js | 1 + lib/js/test262-slice/arithmetic/mod.expected | 1 + lib/js/test262-slice/arithmetic/mod.js | 1 + lib/js/test262-slice/arithmetic/neg.expected | 1 + lib/js/test262-slice/arithmetic/neg.js | 1 + .../arithmetic/paren_precedence.expected | 1 + .../arithmetic/paren_precedence.js | 1 + lib/js/test262-slice/arithmetic/pos.expected | 1 + lib/js/test262-slice/arithmetic/pos.js | 1 + lib/js/test262-slice/arithmetic/pow.expected | 1 + lib/js/test262-slice/arithmetic/pow.js | 1 + .../arithmetic/pow_right_assoc.expected | 1 + .../arithmetic/pow_right_assoc.js | 1 + .../arithmetic/precedence.expected | 1 + lib/js/test262-slice/arithmetic/precedence.js | 1 + .../arithmetic/string_concat.expected | 1 + .../test262-slice/arithmetic/string_concat.js | 1 + lib/js/test262-slice/arithmetic/sub.expected | 1 + lib/js/test262-slice/arithmetic/sub.js | 1 + .../test262-slice/async/async_arrow.expected | 1 + lib/js/test262-slice/async/async_arrow.js | 5 + .../async/async_arrow_multiparam.expected | 1 + .../async/async_arrow_multiparam.js | 5 + .../async/async_fn_basic.expected | 1 + lib/js/test262-slice/async/async_fn_basic.js | 5 + .../async/async_fn_throws.expected | 1 + lib/js/test262-slice/async/async_fn_throws.js | 5 + .../async/async_nested_calls.expected | 1 + .../test262-slice/async/async_nested_calls.js | 7 + .../async/async_returns_promise.expected | 1 + .../async/async_returns_promise.js | 5 + .../test262-slice/async/await_basic.expected | 1 + lib/js/test262-slice/async/await_basic.js | 9 + .../test262-slice/async/await_chain.expected | 1 + lib/js/test262-slice/async/await_chain.js | 11 + .../async/await_in_loop.expected | 1 + lib/js/test262-slice/async/await_in_loop.js | 12 + .../async/await_nonpromise.expected | 1 + .../test262-slice/async/await_nonpromise.js | 8 + .../async/await_promise_all.expected | 1 + .../test262-slice/async/await_promise_all.js | 8 + .../async/await_rejected.expected | 1 + lib/js/test262-slice/async/await_rejected.js | 9 + .../async/await_throws_error_object.expected | 1 + .../async/await_throws_error_object.js | 9 + lib/js/test262-slice/closures/adder.expected | 1 + lib/js/test262-slice/closures/adder.js | 1 + .../test262-slice/closures/counter.expected | 1 + lib/js/test262-slice/closures/counter.js | 1 + .../closures/multi_closure.expected | 1 + .../test262-slice/closures/multi_closure.js | 1 + .../closures/nested_scope.expected | 1 + lib/js/test262-slice/closures/nested_scope.js | 1 + lib/js/test262-slice/closures/sum_sq.expected | 1 + lib/js/test262-slice/closures/sum_sq.js | 1 + .../coercion/implicit_str_add.expected | 1 + .../coercion/implicit_str_add.js | 1 + .../coercion/loose_str_num.expected | 1 + .../test262-slice/coercion/loose_str_num.js | 1 + .../coercion/typeof_bool.expected | 1 + lib/js/test262-slice/coercion/typeof_bool.js | 1 + .../test262-slice/coercion/typeof_fn.expected | 1 + lib/js/test262-slice/coercion/typeof_fn.js | 1 + .../coercion/typeof_null.expected | 1 + lib/js/test262-slice/coercion/typeof_null.js | 1 + .../coercion/typeof_num.expected | 1 + lib/js/test262-slice/coercion/typeof_num.js | 1 + .../coercion/typeof_str.expected | 1 + lib/js/test262-slice/coercion/typeof_str.js | 1 + .../coercion/typeof_undef.expected | 1 + lib/js/test262-slice/coercion/typeof_undef.js | 1 + .../collections/array_empty.expected | 1 + .../test262-slice/collections/array_empty.js | 1 + .../collections/array_index.expected | 1 + .../test262-slice/collections/array_index.js | 1 + .../collections/array_length.expected | 1 + .../test262-slice/collections/array_length.js | 1 + .../collections/array_nested.expected | 1 + .../test262-slice/collections/array_nested.js | 1 + .../collections/array_plus_length.expected | 1 + .../collections/array_plus_length.js | 1 + .../collections/object_chain.expected | 1 + .../test262-slice/collections/object_chain.js | 1 + .../collections/object_prop.expected | 1 + .../test262-slice/collections/object_prop.js | 1 + .../collections/string_index.expected | 1 + .../test262-slice/collections/string_index.js | 1 + .../collections/string_length.expected | 1 + .../collections/string_length.js | 1 + lib/js/test262-slice/compare/ge_eq.expected | 1 + lib/js/test262-slice/compare/ge_eq.js | 1 + lib/js/test262-slice/compare/gt.expected | 1 + lib/js/test262-slice/compare/gt.js | 1 + lib/js/test262-slice/compare/le_eq.expected | 1 + lib/js/test262-slice/compare/le_eq.js | 1 + .../compare/loose_eq_num_str.expected | 1 + .../test262-slice/compare/loose_eq_num_str.js | 1 + .../compare/loose_null_undef.expected | 1 + .../test262-slice/compare/loose_null_undef.js | 1 + .../compare/loose_zero_false.expected | 1 + .../test262-slice/compare/loose_zero_false.js | 1 + lib/js/test262-slice/compare/lt.expected | 1 + lib/js/test262-slice/compare/lt.js | 1 + lib/js/test262-slice/compare/str_lt.expected | 1 + lib/js/test262-slice/compare/str_lt.js | 1 + .../compare/strict_eq_cross_type.expected | 1 + .../compare/strict_eq_cross_type.js | 1 + .../compare/strict_eq_num.expected | 1 + lib/js/test262-slice/compare/strict_eq_num.js | 1 + .../compare/strict_eq_num_false.expected | 1 + .../compare/strict_eq_num_false.js | 1 + .../test262-slice/compare/strict_neq.expected | 1 + lib/js/test262-slice/compare/strict_neq.js | 1 + .../control/ternary_false.expected | 1 + lib/js/test262-slice/control/ternary_false.js | 1 + .../control/ternary_nested.expected | 1 + .../test262-slice/control/ternary_nested.js | 1 + .../control/ternary_true.expected | 1 + lib/js/test262-slice/control/ternary_true.js | 1 + .../errors/error_message.expected | 1 + lib/js/test262-slice/errors/error_message.js | 3 + .../errors/throw_catch_string.expected | 1 + .../errors/throw_catch_string.js | 3 + .../errors/throw_in_function.expected | 1 + .../test262-slice/errors/throw_in_function.js | 7 + .../test262-slice/errors/try_finally.expected | 1 + lib/js/test262-slice/errors/try_finally.js | 3 + .../errors/typeerror_name.expected | 1 + lib/js/test262-slice/errors/typeerror_name.js | 3 + .../functions/arrow_add.expected | 1 + lib/js/test262-slice/functions/arrow_add.js | 1 + .../functions/arrow_block.expected | 1 + lib/js/test262-slice/functions/arrow_block.js | 1 + .../functions/arrow_curry.expected | 1 + lib/js/test262-slice/functions/arrow_curry.js | 1 + .../functions/arrow_identity.expected | 1 + .../test262-slice/functions/arrow_identity.js | 1 + .../functions/arrow_multi_arg.expected | 1 + .../functions/arrow_multi_arg.js | 1 + .../functions/arrow_zero.expected | 1 + lib/js/test262-slice/functions/arrow_zero.js | 1 + .../functions/closure_ref.expected | 1 + lib/js/test262-slice/functions/closure_ref.js | 1 + .../functions/default_param.expected | 1 + .../test262-slice/functions/default_param.js | 1 + .../functions/func_decl.expected | 1 + lib/js/test262-slice/functions/func_decl.js | 1 + .../functions/higher_order.expected | 1 + .../test262-slice/functions/higher_order.js | 1 + lib/js/test262-slice/functions/iife.expected | 1 + lib/js/test262-slice/functions/iife.js | 1 + .../test262-slice/functions/math_abs.expected | 1 + lib/js/test262-slice/functions/math_abs.js | 1 + .../functions/math_ceil.expected | 1 + lib/js/test262-slice/functions/math_ceil.js | 1 + .../functions/math_floor.expected | 1 + lib/js/test262-slice/functions/math_floor.js | 1 + .../test262-slice/functions/math_max.expected | 1 + lib/js/test262-slice/functions/math_max.js | 1 + .../test262-slice/functions/math_min.expected | 1 + lib/js/test262-slice/functions/math_min.js | 1 + .../functions/math_pi_gt_3.expected | 1 + .../test262-slice/functions/math_pi_gt_3.js | 1 + .../functions/recursion_fact.expected | 1 + .../test262-slice/functions/recursion_fact.js | 1 + .../functions/recursion_fib.expected | 1 + .../test262-slice/functions/recursion_fib.js | 1 + .../functions/rest_param.expected | 1 + lib/js/test262-slice/functions/rest_param.js | 1 + .../logical/and_short_circuit.expected | 1 + .../logical/and_short_circuit.js | 1 + .../test262-slice/logical/and_truthy.expected | 1 + lib/js/test262-slice/logical/and_truthy.js | 1 + lib/js/test262-slice/logical/chain.expected | 1 + lib/js/test262-slice/logical/chain.js | 1 + .../test262-slice/logical/not_true.expected | 1 + lib/js/test262-slice/logical/not_true.js | 1 + .../test262-slice/logical/not_zero.expected | 1 + lib/js/test262-slice/logical/not_zero.js | 1 + .../logical/nullish_null.expected | 1 + lib/js/test262-slice/logical/nullish_null.js | 1 + .../logical/nullish_undef.expected | 1 + lib/js/test262-slice/logical/nullish_undef.js | 1 + .../logical/nullish_zero.expected | 1 + lib/js/test262-slice/logical/nullish_zero.js | 1 + .../test262-slice/logical/or_falsy.expected | 1 + lib/js/test262-slice/logical/or_falsy.js | 1 + .../test262-slice/logical/or_truthy.expected | 1 + lib/js/test262-slice/logical/or_truthy.js | 1 + lib/js/test262-slice/logical/or_zero.expected | 1 + lib/js/test262-slice/logical/or_zero.js | 1 + lib/js/test262-slice/loops/do_while.expected | 1 + lib/js/test262-slice/loops/do_while.js | 1 + lib/js/test262-slice/loops/for_basic.expected | 1 + lib/js/test262-slice/loops/for_basic.js | 1 + lib/js/test262-slice/loops/for_break.expected | 1 + lib/js/test262-slice/loops/for_break.js | 1 + .../test262-slice/loops/for_continue.expected | 1 + lib/js/test262-slice/loops/for_continue.js | 1 + .../test262-slice/loops/nested_for.expected | 1 + lib/js/test262-slice/loops/nested_for.js | 1 + .../test262-slice/loops/while_basic.expected | 1 + lib/js/test262-slice/loops/while_basic.js | 1 + .../loops/while_break_infinite.expected | 1 + .../loops/while_break_infinite.js | 1 + .../objects/array_filter_reduce.expected | 1 + .../objects/array_filter_reduce.js | 1 + .../test262-slice/objects/array_map.expected | 1 + lib/js/test262-slice/objects/array_map.js | 1 + .../objects/array_method_chain.expected | 1 + .../objects/array_method_chain.js | 1 + .../objects/array_mutate.expected | 1 + lib/js/test262-slice/objects/array_mutate.js | 3 + .../objects/array_push_length.expected | 1 + .../objects/array_push_length.js | 5 + .../objects/arrow_lexical_this.expected | 1 + .../objects/arrow_lexical_this.js | 2 + .../objects/class_basic.expected | 1 + lib/js/test262-slice/objects/class_basic.js | 6 + .../objects/class_extend_chain.expected | 1 + .../objects/class_extend_chain.js | 5 + .../objects/class_inherit.expected | 1 + lib/js/test262-slice/objects/class_inherit.js | 5 + .../objects/counter_closure.expected | 1 + .../test262-slice/objects/counter_closure.js | 10 + .../objects/in_operator.expected | 1 + lib/js/test262-slice/objects/in_operator.js | 2 + .../test262-slice/objects/instanceof.expected | 1 + lib/js/test262-slice/objects/instanceof.js | 4 + .../objects/method_this.expected | 1 + lib/js/test262-slice/objects/method_this.js | 2 + .../objects/new_constructor.expected | 1 + .../test262-slice/objects/new_constructor.js | 3 + .../objects/object_mutate.expected | 1 + lib/js/test262-slice/objects/object_mutate.js | 3 + .../objects/prototype_chain.expected | 1 + .../test262-slice/objects/prototype_chain.js | 4 + .../objects/string_method.expected | 1 + lib/js/test262-slice/objects/string_method.js | 1 + .../objects/string_slice.expected | 1 + lib/js/test262-slice/objects/string_slice.js | 1 + .../promises/executor_throws.expected | 1 + .../test262-slice/promises/executor_throws.js | 4 + .../promises/finally_passthrough.expected | 1 + .../promises/finally_passthrough.js | 5 + .../promises/microtask_ordering.expected | 1 + .../promises/microtask_ordering.js | 5 + .../promises/new_promise_reject.expected | 1 + .../promises/new_promise_reject.js | 4 + .../promises/new_promise_resolve.expected | 1 + .../promises/new_promise_resolve.js | 4 + .../promises/promise_all.expected | 1 + lib/js/test262-slice/promises/promise_all.js | 5 + .../promises/promise_all_empty.expected | 1 + .../promises/promise_all_empty.js | 4 + .../promises/promise_all_nonpromise.expected | 1 + .../promises/promise_all_nonpromise.js | 4 + .../promises/promise_all_reject.expected | 1 + .../promises/promise_all_reject.js | 5 + .../promises/promise_race.expected | 1 + lib/js/test262-slice/promises/promise_race.js | 5 + .../promise_resolve_already_promise.expected | 1 + .../promise_resolve_already_promise.js | 3 + .../promises/reject_catch.expected | 1 + lib/js/test262-slice/promises/reject_catch.js | 4 + .../promises/resolve_adopts.expected | 1 + .../test262-slice/promises/resolve_adopts.js | 5 + .../promises/resolve_then.expected | 1 + lib/js/test262-slice/promises/resolve_then.js | 4 + .../promises/then_chain.expected | 1 + lib/js/test262-slice/promises/then_chain.js | 4 + .../promises/then_throw_catch.expected | 1 + .../promises/then_throw_catch.js | 6 + .../statements/block_scope.expected | 1 + .../test262-slice/statements/block_scope.js | 1 + .../statements/const_multi.expected | 1 + .../test262-slice/statements/const_multi.js | 1 + .../statements/if_else_false.expected | 1 + .../test262-slice/statements/if_else_false.js | 1 + .../statements/if_else_true.expected | 1 + .../test262-slice/statements/if_else_true.js | 1 + .../statements/let_init.expected | 1 + lib/js/test262-slice/statements/let_init.js | 1 + .../statements/var_decl.expected | 1 + lib/js/test262-slice/statements/var_decl.js | 1 + lib/js/transpile.sx | 915 ++++++++++ plans/agent-briefings/loop.md | 88 + plans/js-on-sx.md | 200 +++ plans/restore-loop.sh | 65 + 310 files changed, 7056 insertions(+) create mode 100644 lib/js/.gitignore create mode 100755 lib/js/conformance.sh create mode 100644 lib/js/lexer.sx create mode 100644 lib/js/parser.sx create mode 100644 lib/js/runtime.sx create mode 100755 lib/js/test.sh create mode 100644 lib/js/test262-runner.py create mode 100644 lib/js/test262-scoreboard.json create mode 100644 lib/js/test262-scoreboard.md create mode 100644 lib/js/test262-slice/README.md create mode 100644 lib/js/test262-slice/arithmetic/add.expected create mode 100644 lib/js/test262-slice/arithmetic/add.js create mode 100644 lib/js/test262-slice/arithmetic/big_expr.expected create mode 100644 lib/js/test262-slice/arithmetic/big_expr.js create mode 100644 lib/js/test262-slice/arithmetic/bitnot.expected create mode 100644 lib/js/test262-slice/arithmetic/bitnot.js create mode 100644 lib/js/test262-slice/arithmetic/chained.expected create mode 100644 lib/js/test262-slice/arithmetic/chained.js create mode 100644 lib/js/test262-slice/arithmetic/div.expected create mode 100644 lib/js/test262-slice/arithmetic/div.js create mode 100644 lib/js/test262-slice/arithmetic/mixed_concat.expected create mode 100644 lib/js/test262-slice/arithmetic/mixed_concat.js create mode 100644 lib/js/test262-slice/arithmetic/mod.expected create mode 100644 lib/js/test262-slice/arithmetic/mod.js create mode 100644 lib/js/test262-slice/arithmetic/neg.expected create mode 100644 lib/js/test262-slice/arithmetic/neg.js create mode 100644 lib/js/test262-slice/arithmetic/paren_precedence.expected create mode 100644 lib/js/test262-slice/arithmetic/paren_precedence.js create mode 100644 lib/js/test262-slice/arithmetic/pos.expected create mode 100644 lib/js/test262-slice/arithmetic/pos.js create mode 100644 lib/js/test262-slice/arithmetic/pow.expected create mode 100644 lib/js/test262-slice/arithmetic/pow.js create mode 100644 lib/js/test262-slice/arithmetic/pow_right_assoc.expected create mode 100644 lib/js/test262-slice/arithmetic/pow_right_assoc.js create mode 100644 lib/js/test262-slice/arithmetic/precedence.expected create mode 100644 lib/js/test262-slice/arithmetic/precedence.js create mode 100644 lib/js/test262-slice/arithmetic/string_concat.expected create mode 100644 lib/js/test262-slice/arithmetic/string_concat.js create mode 100644 lib/js/test262-slice/arithmetic/sub.expected create mode 100644 lib/js/test262-slice/arithmetic/sub.js create mode 100644 lib/js/test262-slice/async/async_arrow.expected create mode 100644 lib/js/test262-slice/async/async_arrow.js create mode 100644 lib/js/test262-slice/async/async_arrow_multiparam.expected create mode 100644 lib/js/test262-slice/async/async_arrow_multiparam.js create mode 100644 lib/js/test262-slice/async/async_fn_basic.expected create mode 100644 lib/js/test262-slice/async/async_fn_basic.js create mode 100644 lib/js/test262-slice/async/async_fn_throws.expected create mode 100644 lib/js/test262-slice/async/async_fn_throws.js create mode 100644 lib/js/test262-slice/async/async_nested_calls.expected create mode 100644 lib/js/test262-slice/async/async_nested_calls.js create mode 100644 lib/js/test262-slice/async/async_returns_promise.expected create mode 100644 lib/js/test262-slice/async/async_returns_promise.js create mode 100644 lib/js/test262-slice/async/await_basic.expected create mode 100644 lib/js/test262-slice/async/await_basic.js create mode 100644 lib/js/test262-slice/async/await_chain.expected create mode 100644 lib/js/test262-slice/async/await_chain.js create mode 100644 lib/js/test262-slice/async/await_in_loop.expected create mode 100644 lib/js/test262-slice/async/await_in_loop.js create mode 100644 lib/js/test262-slice/async/await_nonpromise.expected create mode 100644 lib/js/test262-slice/async/await_nonpromise.js create mode 100644 lib/js/test262-slice/async/await_promise_all.expected create mode 100644 lib/js/test262-slice/async/await_promise_all.js create mode 100644 lib/js/test262-slice/async/await_rejected.expected create mode 100644 lib/js/test262-slice/async/await_rejected.js create mode 100644 lib/js/test262-slice/async/await_throws_error_object.expected create mode 100644 lib/js/test262-slice/async/await_throws_error_object.js create mode 100644 lib/js/test262-slice/closures/adder.expected create mode 100644 lib/js/test262-slice/closures/adder.js create mode 100644 lib/js/test262-slice/closures/counter.expected create mode 100644 lib/js/test262-slice/closures/counter.js create mode 100644 lib/js/test262-slice/closures/multi_closure.expected create mode 100644 lib/js/test262-slice/closures/multi_closure.js create mode 100644 lib/js/test262-slice/closures/nested_scope.expected create mode 100644 lib/js/test262-slice/closures/nested_scope.js create mode 100644 lib/js/test262-slice/closures/sum_sq.expected create mode 100644 lib/js/test262-slice/closures/sum_sq.js create mode 100644 lib/js/test262-slice/coercion/implicit_str_add.expected create mode 100644 lib/js/test262-slice/coercion/implicit_str_add.js create mode 100644 lib/js/test262-slice/coercion/loose_str_num.expected create mode 100644 lib/js/test262-slice/coercion/loose_str_num.js create mode 100644 lib/js/test262-slice/coercion/typeof_bool.expected create mode 100644 lib/js/test262-slice/coercion/typeof_bool.js create mode 100644 lib/js/test262-slice/coercion/typeof_fn.expected create mode 100644 lib/js/test262-slice/coercion/typeof_fn.js create mode 100644 lib/js/test262-slice/coercion/typeof_null.expected create mode 100644 lib/js/test262-slice/coercion/typeof_null.js create mode 100644 lib/js/test262-slice/coercion/typeof_num.expected create mode 100644 lib/js/test262-slice/coercion/typeof_num.js create mode 100644 lib/js/test262-slice/coercion/typeof_str.expected create mode 100644 lib/js/test262-slice/coercion/typeof_str.js create mode 100644 lib/js/test262-slice/coercion/typeof_undef.expected create mode 100644 lib/js/test262-slice/coercion/typeof_undef.js create mode 100644 lib/js/test262-slice/collections/array_empty.expected create mode 100644 lib/js/test262-slice/collections/array_empty.js create mode 100644 lib/js/test262-slice/collections/array_index.expected create mode 100644 lib/js/test262-slice/collections/array_index.js create mode 100644 lib/js/test262-slice/collections/array_length.expected create mode 100644 lib/js/test262-slice/collections/array_length.js create mode 100644 lib/js/test262-slice/collections/array_nested.expected create mode 100644 lib/js/test262-slice/collections/array_nested.js create mode 100644 lib/js/test262-slice/collections/array_plus_length.expected create mode 100644 lib/js/test262-slice/collections/array_plus_length.js create mode 100644 lib/js/test262-slice/collections/object_chain.expected create mode 100644 lib/js/test262-slice/collections/object_chain.js create mode 100644 lib/js/test262-slice/collections/object_prop.expected create mode 100644 lib/js/test262-slice/collections/object_prop.js create mode 100644 lib/js/test262-slice/collections/string_index.expected create mode 100644 lib/js/test262-slice/collections/string_index.js create mode 100644 lib/js/test262-slice/collections/string_length.expected create mode 100644 lib/js/test262-slice/collections/string_length.js create mode 100644 lib/js/test262-slice/compare/ge_eq.expected create mode 100644 lib/js/test262-slice/compare/ge_eq.js create mode 100644 lib/js/test262-slice/compare/gt.expected create mode 100644 lib/js/test262-slice/compare/gt.js create mode 100644 lib/js/test262-slice/compare/le_eq.expected create mode 100644 lib/js/test262-slice/compare/le_eq.js create mode 100644 lib/js/test262-slice/compare/loose_eq_num_str.expected create mode 100644 lib/js/test262-slice/compare/loose_eq_num_str.js create mode 100644 lib/js/test262-slice/compare/loose_null_undef.expected create mode 100644 lib/js/test262-slice/compare/loose_null_undef.js create mode 100644 lib/js/test262-slice/compare/loose_zero_false.expected create mode 100644 lib/js/test262-slice/compare/loose_zero_false.js create mode 100644 lib/js/test262-slice/compare/lt.expected create mode 100644 lib/js/test262-slice/compare/lt.js create mode 100644 lib/js/test262-slice/compare/str_lt.expected create mode 100644 lib/js/test262-slice/compare/str_lt.js create mode 100644 lib/js/test262-slice/compare/strict_eq_cross_type.expected create mode 100644 lib/js/test262-slice/compare/strict_eq_cross_type.js create mode 100644 lib/js/test262-slice/compare/strict_eq_num.expected create mode 100644 lib/js/test262-slice/compare/strict_eq_num.js create mode 100644 lib/js/test262-slice/compare/strict_eq_num_false.expected create mode 100644 lib/js/test262-slice/compare/strict_eq_num_false.js create mode 100644 lib/js/test262-slice/compare/strict_neq.expected create mode 100644 lib/js/test262-slice/compare/strict_neq.js create mode 100644 lib/js/test262-slice/control/ternary_false.expected create mode 100644 lib/js/test262-slice/control/ternary_false.js create mode 100644 lib/js/test262-slice/control/ternary_nested.expected create mode 100644 lib/js/test262-slice/control/ternary_nested.js create mode 100644 lib/js/test262-slice/control/ternary_true.expected create mode 100644 lib/js/test262-slice/control/ternary_true.js create mode 100644 lib/js/test262-slice/errors/error_message.expected create mode 100644 lib/js/test262-slice/errors/error_message.js create mode 100644 lib/js/test262-slice/errors/throw_catch_string.expected create mode 100644 lib/js/test262-slice/errors/throw_catch_string.js create mode 100644 lib/js/test262-slice/errors/throw_in_function.expected create mode 100644 lib/js/test262-slice/errors/throw_in_function.js create mode 100644 lib/js/test262-slice/errors/try_finally.expected create mode 100644 lib/js/test262-slice/errors/try_finally.js create mode 100644 lib/js/test262-slice/errors/typeerror_name.expected create mode 100644 lib/js/test262-slice/errors/typeerror_name.js create mode 100644 lib/js/test262-slice/functions/arrow_add.expected create mode 100644 lib/js/test262-slice/functions/arrow_add.js create mode 100644 lib/js/test262-slice/functions/arrow_block.expected create mode 100644 lib/js/test262-slice/functions/arrow_block.js create mode 100644 lib/js/test262-slice/functions/arrow_curry.expected create mode 100644 lib/js/test262-slice/functions/arrow_curry.js create mode 100644 lib/js/test262-slice/functions/arrow_identity.expected create mode 100644 lib/js/test262-slice/functions/arrow_identity.js create mode 100644 lib/js/test262-slice/functions/arrow_multi_arg.expected create mode 100644 lib/js/test262-slice/functions/arrow_multi_arg.js create mode 100644 lib/js/test262-slice/functions/arrow_zero.expected create mode 100644 lib/js/test262-slice/functions/arrow_zero.js create mode 100644 lib/js/test262-slice/functions/closure_ref.expected create mode 100644 lib/js/test262-slice/functions/closure_ref.js create mode 100644 lib/js/test262-slice/functions/default_param.expected create mode 100644 lib/js/test262-slice/functions/default_param.js create mode 100644 lib/js/test262-slice/functions/func_decl.expected create mode 100644 lib/js/test262-slice/functions/func_decl.js create mode 100644 lib/js/test262-slice/functions/higher_order.expected create mode 100644 lib/js/test262-slice/functions/higher_order.js create mode 100644 lib/js/test262-slice/functions/iife.expected create mode 100644 lib/js/test262-slice/functions/iife.js create mode 100644 lib/js/test262-slice/functions/math_abs.expected create mode 100644 lib/js/test262-slice/functions/math_abs.js create mode 100644 lib/js/test262-slice/functions/math_ceil.expected create mode 100644 lib/js/test262-slice/functions/math_ceil.js create mode 100644 lib/js/test262-slice/functions/math_floor.expected create mode 100644 lib/js/test262-slice/functions/math_floor.js create mode 100644 lib/js/test262-slice/functions/math_max.expected create mode 100644 lib/js/test262-slice/functions/math_max.js create mode 100644 lib/js/test262-slice/functions/math_min.expected create mode 100644 lib/js/test262-slice/functions/math_min.js create mode 100644 lib/js/test262-slice/functions/math_pi_gt_3.expected create mode 100644 lib/js/test262-slice/functions/math_pi_gt_3.js create mode 100644 lib/js/test262-slice/functions/recursion_fact.expected create mode 100644 lib/js/test262-slice/functions/recursion_fact.js create mode 100644 lib/js/test262-slice/functions/recursion_fib.expected create mode 100644 lib/js/test262-slice/functions/recursion_fib.js create mode 100644 lib/js/test262-slice/functions/rest_param.expected create mode 100644 lib/js/test262-slice/functions/rest_param.js create mode 100644 lib/js/test262-slice/logical/and_short_circuit.expected create mode 100644 lib/js/test262-slice/logical/and_short_circuit.js create mode 100644 lib/js/test262-slice/logical/and_truthy.expected create mode 100644 lib/js/test262-slice/logical/and_truthy.js create mode 100644 lib/js/test262-slice/logical/chain.expected create mode 100644 lib/js/test262-slice/logical/chain.js create mode 100644 lib/js/test262-slice/logical/not_true.expected create mode 100644 lib/js/test262-slice/logical/not_true.js create mode 100644 lib/js/test262-slice/logical/not_zero.expected create mode 100644 lib/js/test262-slice/logical/not_zero.js create mode 100644 lib/js/test262-slice/logical/nullish_null.expected create mode 100644 lib/js/test262-slice/logical/nullish_null.js create mode 100644 lib/js/test262-slice/logical/nullish_undef.expected create mode 100644 lib/js/test262-slice/logical/nullish_undef.js create mode 100644 lib/js/test262-slice/logical/nullish_zero.expected create mode 100644 lib/js/test262-slice/logical/nullish_zero.js create mode 100644 lib/js/test262-slice/logical/or_falsy.expected create mode 100644 lib/js/test262-slice/logical/or_falsy.js create mode 100644 lib/js/test262-slice/logical/or_truthy.expected create mode 100644 lib/js/test262-slice/logical/or_truthy.js create mode 100644 lib/js/test262-slice/logical/or_zero.expected create mode 100644 lib/js/test262-slice/logical/or_zero.js create mode 100644 lib/js/test262-slice/loops/do_while.expected create mode 100644 lib/js/test262-slice/loops/do_while.js create mode 100644 lib/js/test262-slice/loops/for_basic.expected create mode 100644 lib/js/test262-slice/loops/for_basic.js create mode 100644 lib/js/test262-slice/loops/for_break.expected create mode 100644 lib/js/test262-slice/loops/for_break.js create mode 100644 lib/js/test262-slice/loops/for_continue.expected create mode 100644 lib/js/test262-slice/loops/for_continue.js create mode 100644 lib/js/test262-slice/loops/nested_for.expected create mode 100644 lib/js/test262-slice/loops/nested_for.js create mode 100644 lib/js/test262-slice/loops/while_basic.expected create mode 100644 lib/js/test262-slice/loops/while_basic.js create mode 100644 lib/js/test262-slice/loops/while_break_infinite.expected create mode 100644 lib/js/test262-slice/loops/while_break_infinite.js create mode 100644 lib/js/test262-slice/objects/array_filter_reduce.expected create mode 100644 lib/js/test262-slice/objects/array_filter_reduce.js create mode 100644 lib/js/test262-slice/objects/array_map.expected create mode 100644 lib/js/test262-slice/objects/array_map.js create mode 100644 lib/js/test262-slice/objects/array_method_chain.expected create mode 100644 lib/js/test262-slice/objects/array_method_chain.js create mode 100644 lib/js/test262-slice/objects/array_mutate.expected create mode 100644 lib/js/test262-slice/objects/array_mutate.js create mode 100644 lib/js/test262-slice/objects/array_push_length.expected create mode 100644 lib/js/test262-slice/objects/array_push_length.js create mode 100644 lib/js/test262-slice/objects/arrow_lexical_this.expected create mode 100644 lib/js/test262-slice/objects/arrow_lexical_this.js create mode 100644 lib/js/test262-slice/objects/class_basic.expected create mode 100644 lib/js/test262-slice/objects/class_basic.js create mode 100644 lib/js/test262-slice/objects/class_extend_chain.expected create mode 100644 lib/js/test262-slice/objects/class_extend_chain.js create mode 100644 lib/js/test262-slice/objects/class_inherit.expected create mode 100644 lib/js/test262-slice/objects/class_inherit.js create mode 100644 lib/js/test262-slice/objects/counter_closure.expected create mode 100644 lib/js/test262-slice/objects/counter_closure.js create mode 100644 lib/js/test262-slice/objects/in_operator.expected create mode 100644 lib/js/test262-slice/objects/in_operator.js create mode 100644 lib/js/test262-slice/objects/instanceof.expected create mode 100644 lib/js/test262-slice/objects/instanceof.js create mode 100644 lib/js/test262-slice/objects/method_this.expected create mode 100644 lib/js/test262-slice/objects/method_this.js create mode 100644 lib/js/test262-slice/objects/new_constructor.expected create mode 100644 lib/js/test262-slice/objects/new_constructor.js create mode 100644 lib/js/test262-slice/objects/object_mutate.expected create mode 100644 lib/js/test262-slice/objects/object_mutate.js create mode 100644 lib/js/test262-slice/objects/prototype_chain.expected create mode 100644 lib/js/test262-slice/objects/prototype_chain.js create mode 100644 lib/js/test262-slice/objects/string_method.expected create mode 100644 lib/js/test262-slice/objects/string_method.js create mode 100644 lib/js/test262-slice/objects/string_slice.expected create mode 100644 lib/js/test262-slice/objects/string_slice.js create mode 100644 lib/js/test262-slice/promises/executor_throws.expected create mode 100644 lib/js/test262-slice/promises/executor_throws.js create mode 100644 lib/js/test262-slice/promises/finally_passthrough.expected create mode 100644 lib/js/test262-slice/promises/finally_passthrough.js create mode 100644 lib/js/test262-slice/promises/microtask_ordering.expected create mode 100644 lib/js/test262-slice/promises/microtask_ordering.js create mode 100644 lib/js/test262-slice/promises/new_promise_reject.expected create mode 100644 lib/js/test262-slice/promises/new_promise_reject.js create mode 100644 lib/js/test262-slice/promises/new_promise_resolve.expected create mode 100644 lib/js/test262-slice/promises/new_promise_resolve.js create mode 100644 lib/js/test262-slice/promises/promise_all.expected create mode 100644 lib/js/test262-slice/promises/promise_all.js create mode 100644 lib/js/test262-slice/promises/promise_all_empty.expected create mode 100644 lib/js/test262-slice/promises/promise_all_empty.js create mode 100644 lib/js/test262-slice/promises/promise_all_nonpromise.expected create mode 100644 lib/js/test262-slice/promises/promise_all_nonpromise.js create mode 100644 lib/js/test262-slice/promises/promise_all_reject.expected create mode 100644 lib/js/test262-slice/promises/promise_all_reject.js create mode 100644 lib/js/test262-slice/promises/promise_race.expected create mode 100644 lib/js/test262-slice/promises/promise_race.js create mode 100644 lib/js/test262-slice/promises/promise_resolve_already_promise.expected create mode 100644 lib/js/test262-slice/promises/promise_resolve_already_promise.js create mode 100644 lib/js/test262-slice/promises/reject_catch.expected create mode 100644 lib/js/test262-slice/promises/reject_catch.js create mode 100644 lib/js/test262-slice/promises/resolve_adopts.expected create mode 100644 lib/js/test262-slice/promises/resolve_adopts.js create mode 100644 lib/js/test262-slice/promises/resolve_then.expected create mode 100644 lib/js/test262-slice/promises/resolve_then.js create mode 100644 lib/js/test262-slice/promises/then_chain.expected create mode 100644 lib/js/test262-slice/promises/then_chain.js create mode 100644 lib/js/test262-slice/promises/then_throw_catch.expected create mode 100644 lib/js/test262-slice/promises/then_throw_catch.js create mode 100644 lib/js/test262-slice/statements/block_scope.expected create mode 100644 lib/js/test262-slice/statements/block_scope.js create mode 100644 lib/js/test262-slice/statements/const_multi.expected create mode 100644 lib/js/test262-slice/statements/const_multi.js create mode 100644 lib/js/test262-slice/statements/if_else_false.expected create mode 100644 lib/js/test262-slice/statements/if_else_false.js create mode 100644 lib/js/test262-slice/statements/if_else_true.expected create mode 100644 lib/js/test262-slice/statements/if_else_true.js create mode 100644 lib/js/test262-slice/statements/let_init.expected create mode 100644 lib/js/test262-slice/statements/let_init.js create mode 100644 lib/js/test262-slice/statements/var_decl.expected create mode 100644 lib/js/test262-slice/statements/var_decl.js create mode 100644 lib/js/transpile.sx create mode 100644 plans/agent-briefings/loop.md create mode 100644 plans/js-on-sx.md create mode 100755 plans/restore-loop.sh diff --git a/lib/js/.gitignore b/lib/js/.gitignore new file mode 100644 index 00000000..cb52cc17 --- /dev/null +++ b/lib/js/.gitignore @@ -0,0 +1 @@ +test262-upstream/ diff --git a/lib/js/conformance.sh b/lib/js/conformance.sh new file mode 100755 index 00000000..c6f91502 --- /dev/null +++ b/lib/js/conformance.sh @@ -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="" + + 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 diff --git a/lib/js/lexer.sx b/lib/js/lexer.sx new file mode 100644 index 00000000..cabe9957 --- /dev/null +++ b/lib/js/lexer.sx @@ -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))) diff --git a/lib/js/parser.sx b/lib/js/parser.sx new file mode 100644 index 00000000..9c93c269 --- /dev/null +++ b/lib/js/parser.sx @@ -0,0 +1,1097 @@ +;; lib/js/parser.sx — tokens → JS AST (Pratt-style) +;; +;; Top-level parsing functions take a parser state dict +;; {:tokens tokens :idx 0} +;; and mutate :idx via set-key!. We use a boxed state so we can share it +;; across mutually-recursive parse fns without deep nesting. + +;; ── Operator precedence table ──────────────────────────────────── +(define + js-op-prec + (fn + (op) + (cond + ((= op "||") 4) + ((= op "??") 4) + ((= op "&&") 5) + ((= op "|") 6) + ((= op "^") 7) + ((= op "&") 8) + ((= op "==") 9) + ((= op "!=") 9) + ((= op "===") 9) + ((= op "!==") 9) + ((= op "<") 10) + ((= op ">") 10) + ((= op "<=") 10) + ((= op ">=") 10) + ((= op "<<") 11) + ((= op ">>") 11) + ((= op ">>>") 11) + ((= op "+") 12) + ((= op "-") 12) + ((= op "*") 13) + ((= op "/") 13) + ((= op "%") 13) + ((= op "instanceof") 10) + ((= op "in") 10) + ((= op "**") 14) + (else -1)))) + +(define js-op-right-assoc? (fn (op) (= op "**"))) + +(define + js-assign-op? + (fn + (op) + (or + (= op "=") + (= op "+=") + (= op "-=") + (= op "*=") + (= op "/=") + (= op "%=") + (= op "**=") + (= op "<<=") + (= op ">>=") + (= op ">>>=") + (= op "&=") + (= op "|=") + (= op "^=") + (= op "&&=") + (= op "||=") + (= op "??=")))) + +;; ── State helpers ──────────────────────────────────────────────── +(define + jp-peek + (fn + (st) + (let + ((i (get st :idx)) (tokens (get st :tokens))) + (if (< i (len tokens)) (nth tokens i) {:pos 0 :value nil :type "eof"})))) + +(define + jp-peek-at + (fn + (st off) + (let + ((i (+ (get st :idx) off)) (tokens (get st :tokens))) + (if (< i (len tokens)) (nth tokens i) {:pos 0 :value nil :type "eof"})))) + +(define jp-advance! (fn (st) (dict-set! st :idx (+ (get st :idx) 1)))) + +(define + jp-at? + (fn + (st type value) + (let + ((t (jp-peek st))) + (and + (= (get t :type) type) + (or (= value nil) (= (get t :value) value)))))) + +(define + jp-expect! + (fn + (st type value) + (let + ((t (jp-peek st))) + (if + (jp-at? st type value) + (do (jp-advance! st) t) + (error + (str + "Expected " + type + " '" + (if (= value nil) "" value) + "' got " + (get t :type) + " '" + (get t :value) + "'")))))) + +;; ── Primary ────────────────────────────────────────────────────── +(define + jp-parse-new-expr + (fn + (st) + (let + ((callee (jp-parse-new-callee st))) + (if + (jp-at? st "punct" "(") + (do + (jp-advance! st) + (let + ((args (list))) + (do + (jp-call-args-loop st args) + (jp-expect! st "punct" ")") + (list (quote js-new) callee args)))) + (list (quote js-new) callee (list)))))) + +;; ── Paren expression / arrow function ─────────────────────────── +(define + jp-parse-new-callee + (fn + (st) + (let + ((first (jp-parse-new-primary st))) + (jp-parse-new-member-chain st first)))) + +(define + jp-parse-new-primary + (fn + (st) + (let + ((t (jp-peek st))) + (cond + ((= (get t :type) "ident") + (do (jp-advance! st) (list (quote js-ident) (get t :value)))) + ((and (= (get t :type) "keyword") (= (get t :value) "this")) + (do (jp-advance! st) (list (quote js-ident) "this"))) + ((and (= (get t :type) "keyword") (= (get t :value) "new")) + (do (jp-advance! st) (jp-parse-new-expr st))) + ((and (= (get t :type) "punct") (= (get t :value) "(")) + (jp-parse-paren-or-arrow st)) + (else + (error + (str + "Unexpected token after new: " + (get t :type) + " '" + (get t :value) + "'"))))))) + +;; Helper: collect comma-separated idents into `params`. Sets +;; (:arrow-candidate true/false) on st to signal whether it still looks +;; like a potential arrow-fn param list. +(define + jp-parse-new-member-chain + (fn + (st obj) + (let + ((t (jp-peek st))) + (cond + ((and (= (get t :type) "punct") (= (get t :value) ".")) + (do + (jp-advance! st) + (let + ((name (get (jp-peek st) :value))) + (do + (jp-advance! st) + (jp-parse-new-member-chain + st + (list (quote js-member) obj name)))))) + ((and (= (get t :type) "punct") (= (get t :value) "[")) + (do + (jp-advance! st) + (let + ((idx (jp-parse-assignment st))) + (do + (jp-expect! st "punct" "]") + (jp-parse-new-member-chain + st + (list (quote js-index) obj idx)))))) + (else obj))))) + +(define + jp-parse-async-tail + (fn + (st) + (let + ((t (jp-peek st))) + (cond + ((and (= (get t :type) "keyword") (= (get t :value) "function")) + (do + (jp-advance! st) + (let + ((nm (if (= (get (jp-peek st) :type) "ident") (let ((n (get (jp-peek st) :value))) (do (jp-advance! st) n)) nil))) + (let + ((params (jp-parse-param-list st))) + (let + ((body (jp-parse-block st))) + (list (quote js-funcexpr-async) nm params body)))))) + ((= (get t :type) "ident") + (do + (jp-advance! st) + (jp-expect! st "op" "=>") + (list + (quote js-arrow-async) + (list (get t :value)) + (jp-parse-arrow-body st)))) + ((= (get t :value) "(") (jp-parse-async-paren-arrow st)) + (else + (error + (str "Unexpected token after `async`: '" (get t :value) "'"))))))) + +(define + jp-parse-async-paren-arrow + (fn + (st) + (do + (jp-advance! st) + (if + (jp-at? st "punct" ")") + (do + (jp-advance! st) + (jp-expect! st "op" "=>") + (list (quote js-arrow-async) (list) (jp-parse-arrow-body st))) + (let + ((params (list))) + (jp-parse-async-paren-arrow-loop st params) + (jp-expect! st "punct" ")") + (jp-expect! st "op" "=>") + (list (quote js-arrow-async) params (jp-parse-arrow-body st))))))) + +(define + jp-parse-async-paren-arrow-loop + (fn + (st params) + (let + ((t (jp-peek st))) + (cond + ((= (get t :type) "ident") + (do + (jp-advance! st) + (append! params (get t :value)) + (if + (jp-at? st "punct" ",") + (do + (jp-advance! st) + (jp-parse-async-paren-arrow-loop st params)) + nil))) + (else + (error + (str + "Expected ident in async arrow params, got: '" + (get t :value) + "'"))))))) + +;; ── Array literal ─────────────────────────────────────────────── +(define + jp-build-template-ast + (fn (parts) (cons (quote js-tpl) (list (jp-map-template-parts parts))))) + +(define + jp-map-template-parts + (fn + (parts) + (if + (empty? parts) + (list) + (cons + (jp-template-part (first parts)) + (jp-map-template-parts (rest parts)))))) + +;; ── Object literal ────────────────────────────────────────────── +(define + jp-template-part + (fn + (p) + (let + ((kind (nth p 0)) (text (nth p 1))) + (if (= kind "str") (list (quote js-str) text) (js-parse-expr text))))) + +(define + jp-parse-primary + (fn + (st) + (let + ((t (jp-peek st))) + (cond + ((= (get t :type) "number") + (do (jp-advance! st) (list (quote js-num) (get t :value)))) + ((= (get t :type) "string") + (do (jp-advance! st) (list (quote js-str) (get t :value)))) + ((= (get t :type) "template") + (do + (jp-advance! st) + (let + ((val (get t :value))) + (if + (list? val) + (jp-build-template-ast val) + (list (quote js-str) val))))) + ((and (= (get t :type) "keyword") (= (get t :value) "true")) + (do (jp-advance! st) (list (quote js-bool) true))) + ((and (= (get t :type) "keyword") (= (get t :value) "false")) + (do (jp-advance! st) (list (quote js-bool) false))) + ((and (= (get t :type) "keyword") (= (get t :value) "null")) + (do (jp-advance! st) (list (quote js-null)))) + ((and (= (get t :type) "keyword") (= (get t :value) "undefined")) + (do (jp-advance! st) (list (quote js-undef)))) + ((and (= (get t :type) "keyword") (= (get t :value) "new")) + (do (jp-advance! st) (jp-parse-new-expr st))) + ((and (= (get t :type) "keyword") (= (get t :value) "this")) + (do (jp-advance! st) (list (quote js-ident) "this"))) + ((and (= (get t :type) "op") (or (= (get t :value) "-") (= (get t :value) "+") (= (get t :value) "!") (= (get t :value) "~"))) + (do + (jp-advance! st) + (list (quote js-unop) (get t :value) (jp-parse-unary st)))) + ((and (= (get t :type) "keyword") (or (= (get t :value) "typeof") (= (get t :value) "void") (= (get t :value) "delete"))) + (do + (jp-advance! st) + (list (quote js-unop) (get t :value) (jp-parse-unary st)))) + ((and (= (get t :type) "punct") (= (get t :value) "(")) + (jp-parse-paren-or-arrow st)) + ((and (= (get t :type) "punct") (= (get t :value) "[")) + (jp-parse-array st)) + ((and (= (get t :type) "punct") (= (get t :value) "{")) + (jp-parse-object st)) + ((and (= (get t :type) "keyword") (= (get t :value) "await")) + (do (jp-advance! st) (list (quote js-await) (jp-parse-unary st)))) + ((and (= (get t :type) "keyword") (= (get t :value) "async")) + (do (jp-advance! st) (jp-parse-async-tail st))) + ((and (= (get t :type) "keyword") (= (get t :value) "function")) + (do + (jp-advance! st) + (let + ((nm (if (= (get (jp-peek st) :type) "ident") (let ((n (get (jp-peek st) :value))) (do (jp-advance! st) n)) nil))) + (let + ((params (jp-parse-param-list st))) + (let + ((body (jp-parse-block st))) + (list (quote js-funcexpr) nm params body)))))) + ((= (get t :type) "ident") + (do + (jp-advance! st) + (if + (jp-at? st "op" "=>") + (do + (jp-advance! st) + (list + (quote js-arrow) + (list (get t :value)) + (jp-parse-arrow-body st))) + (list (quote js-ident) (get t :value))))) + (else + (error + (str "Unexpected token: " (get t :type) " '" (get t :value) "'"))))))) + +(define + jp-parse-paren-or-arrow + (fn + (st) + (let + ((saved (get st :idx))) + (do + (jp-advance! st) + (if + (jp-at? st "punct" ")") + (do + (jp-advance! st) + (jp-expect! st "op" "=>") + (list (quote js-arrow) (list) (jp-parse-arrow-body st))) + (jp-try-arrow-or-paren st saved)))))) + +;; ── Postfix chain: call, member, index ────────────────────────── +(define + jp-try-arrow-or-paren + (fn + (st saved) + (let + ((params (list)) (is-params true)) + (do + (jp-collect-params st params) + (if + (and (get-state-flag st) (jp-at? st "punct" ")")) + (if + (jp-looks-like-arrow? st) + (do + (jp-advance! st) + (jp-advance! st) + (list (quote js-arrow) params (jp-parse-arrow-body st))) + (do + (dict-set! st :idx saved) + (jp-advance! st) + (let + ((e (jp-parse-assignment st))) + (jp-expect! st "punct" ")") + e))) + (do + (dict-set! st :idx saved) + (jp-advance! st) + (let + ((e (jp-parse-assignment st))) + (jp-expect! st "punct" ")") + e))))))) + +(define + jp-collect-params + (fn + (st params) + (do + (dict-set! st :arrow-candidate true) + (jp-collect-params-loop st params)))) + +;; ── Unary ─────────────────────────────────────────────────────── +(define + jp-collect-params-loop + (fn + (st params) + (cond + ((= (get (jp-peek st) :type) "ident") + (do + (append! params (get (jp-peek st) :value)) + (jp-advance! st) + (cond + ((jp-at? st "punct" ",") + (do (jp-advance! st) (jp-collect-params-loop st params))) + ((jp-at? st "punct" ")") nil) + (else (dict-set! st :arrow-candidate false))))) + (else (dict-set! st :arrow-candidate false))))) + +;; ── Binary (precedence climbing) ──────────────────────────────── +(define get-state-flag (fn (st) (get st :arrow-candidate))) + +(define + jp-looks-like-arrow? + (fn + (st) + (let + ((after (jp-peek-at st 1))) + (and (= (get after :type) "op") (= (get after :value) "=>"))))) + +;; ── Conditional (ternary) ─────────────────────────────────────── +(define + jp-parse-array + (fn + (st) + (do + (jp-advance! st) + (let + ((elems (list))) + (jp-array-loop st elems) + (jp-expect! st "punct" "]") + (list (quote js-array) elems))))) + +;; ── Assignment (right-associative) ────────────────────────────── +(define + jp-array-loop + (fn + (st elems) + (cond + ((jp-at? st "punct" "]") nil) + (else + (do + (append! elems (jp-parse-assignment st)) + (cond + ((jp-at? st "punct" ",") + (do (jp-advance! st) (jp-array-loop st elems))) + (else nil))))))) + +;; ── Entry point ───────────────────────────────────────────────── +(define + jp-parse-object + (fn + (st) + (do + (jp-advance! st) + (let + ((kvs (list))) + (jp-object-loop st kvs) + (jp-expect! st "punct" "}") + (list (quote js-object) kvs))))) + +(define + jp-object-loop + (fn + (st kvs) + (cond + ((jp-at? st "punct" "}") nil) + (else + (do + (jp-parse-object-entry st kvs) + (cond + ((jp-at? st "punct" ",") + (do (jp-advance! st) (jp-object-loop st kvs))) + (else nil))))))) + +(define + jp-parse-object-entry + (fn + (st kvs) + (let + ((t (jp-peek st))) + (cond + ((= (get t :type) "ident") + (do + (jp-advance! st) + (let + ((key (get t :value))) + (cond + ((jp-at? st "punct" ":") + (do (jp-advance! st) (append! kvs {:value (jp-parse-assignment st) :key key}))) + (else (append! kvs {:value (list (quote js-ident) key) :key key})))))) + ((= (get t :type) "string") + (do + (jp-advance! st) + (jp-expect! st "punct" ":") + (append! kvs {:value (jp-parse-assignment st) :key (get t :value)}))) + ((= (get t :type) "number") + (do + (jp-advance! st) + (jp-expect! st "punct" ":") + (append! kvs {:value (jp-parse-assignment st) :key (get t :value)}))) + ((= (get t :type) "keyword") + (do + (jp-advance! st) + (jp-expect! st "punct" ":") + (append! kvs {:value (jp-parse-assignment st) :key (get t :value)}))) + (else (error (str "Unexpected in object: " (get t :type)))))))) + +(define + jp-parse-postfix + (fn + (st left) + (cond + ((jp-at? st "punct" ".") + (do + (jp-advance! st) + (let + ((t (jp-peek st))) + (if + (or (= (get t :type) "ident") (= (get t :type) "keyword")) + (do + (jp-advance! st) + (jp-parse-postfix + st + (list (quote js-member) left (get t :value)))) + (error "expected ident after ."))))) + ((jp-at? st "punct" "[") + (do + (jp-advance! st) + (let + ((k (jp-parse-assignment st))) + (jp-expect! st "punct" "]") + (jp-parse-postfix st (list (quote js-index) left k))))) + ((jp-at? st "punct" "(") + (do + (jp-advance! st) + (let + ((args (list))) + (jp-call-args-loop st args) + (jp-expect! st "punct" ")") + (jp-parse-postfix st (list (quote js-call) left args))))) + (else left)))) + +(define + jp-call-args-loop + (fn + (st args) + (cond + ((jp-at? st "punct" ")") nil) + (else + (do + (append! args (jp-parse-assignment st)) + (cond + ((jp-at? st "punct" ",") + (do (jp-advance! st) (jp-call-args-loop st args))) + (else nil))))))) + +(define + jp-parse-unary + (fn (st) (jp-parse-postfix st (jp-parse-primary st)))) + +(define + jp-parse-binary + (fn + (st min-prec) + (let ((left (jp-parse-unary st))) (jp-binary-loop st min-prec left)))) + +(define + jp-binary-loop + (fn + (st min-prec left) + (let + ((t (jp-peek st))) + (cond + ((not (or (= (get t :type) "op") (and (= (get t :type) "keyword") (or (= (get t :value) "instanceof") (= (get t :value) "in"))))) + left) + (else + (let + ((op (get t :value)) (prec (js-op-prec (get t :value)))) + (cond + ((< prec 0) left) + ((< prec min-prec) left) + (else + (do + (jp-advance! st) + (let + ((next-prec (if (js-op-right-assoc? op) prec (+ prec 1)))) + (let + ((right (jp-parse-binary st next-prec))) + (jp-binary-loop + st + min-prec + (list (quote js-binop) op left right))))))))))))) + +(define + jp-parse-conditional + (fn + (st) + (let + ((c (jp-parse-binary st 0))) + (cond + ((jp-at? st "op" "?") + (do + (jp-advance! st) + (let + ((t (jp-parse-assignment st))) + (jp-expect! st "punct" ":") + (let + ((e (jp-parse-assignment st))) + (list (quote js-cond) c t e))))) + (else c))))) + +(define + jp-parse-assignment + (fn + (st) + (let + ((left (jp-parse-conditional st))) + (let + ((t (jp-peek st))) + (cond + ((and (= (get t :type) "op") (js-assign-op? (get t :value))) + (do + (jp-advance! st) + (list + (quote js-assign) + (get t :value) + left + (jp-parse-assignment st)))) + (else left)))))) + +(define + jp-parse-param-list + (fn + (st) + (let + ((params (list))) + (do + (jp-expect! st "punct" "(") + (if + (jp-at? st "punct" ")") + (do (jp-advance! st) params) + (do + (jp-parse-param-list-loop st params) + (jp-expect! st "punct" ")") + params)))))) + +(define + jp-parse-param-list-loop + (fn + (st params) + (cond + ((jp-at? st "punct" "...") + (do + (jp-advance! st) + (let + ((nm (get (jp-peek st) :value))) + (do + (jp-advance! st) + (append! params (list (quote js-rest) nm)))))) + ((= (get (jp-peek st) :type) "ident") + (do + (let + ((nm (get (jp-peek st) :value))) + (do + (jp-advance! st) + (if + (jp-at? st "op" "=") + (do + (jp-advance! st) + (let + ((dv (jp-parse-assignment st))) + (append! params (list (quote js-param) nm dv)))) + (append! params nm)))) + (if + (jp-at? st "punct" ",") + (do (jp-advance! st) (jp-parse-param-list-loop st params)) + nil))) + (else + (error + (str + "Expected parameter, got " + (get (jp-peek st) :type) + " '" + (get (jp-peek st) :value) + "'")))))) + +(define + jp-parse-block + (fn + (st) + (do + (jp-expect! st "punct" "{") + (let + ((stmts (list))) + (do + (jp-parse-block-loop st stmts) + (jp-expect! st "punct" "}") + (list (quote js-block) stmts)))))) + +(define + jp-parse-block-loop + (fn + (st stmts) + (if + (or (jp-at? st "punct" "}") (jp-at? st "eof" nil)) + nil + (do (append! stmts (jp-parse-stmt st)) (jp-parse-block-loop st stmts))))) + +(define + jp-eat-semi + (fn (st) (if (jp-at? st "punct" ";") (do (jp-advance! st) nil) nil))) + +(define + jp-parse-vardecl + (fn + (st) + (let + ((nm (get (jp-peek st) :value))) + (do + (if + (= (get (jp-peek st) :type) "ident") + (jp-advance! st) + (error + (str "Expected ident in var decl, got " (get (jp-peek st) :type)))) + (if + (jp-at? st "op" "=") + (do + (jp-advance! st) + (list (quote js-vardecl) nm (jp-parse-assignment st))) + (list (quote js-vardecl) nm (list (quote js-undef)))))))) + +(define + jp-parse-var-stmt + (fn + (st kind) + (do + (jp-advance! st) + (let + ((decls (list))) + (do + (append! decls (jp-parse-vardecl st)) + (jp-parse-var-stmt-loop st decls) + (jp-eat-semi st) + (list (quote js-var) kind decls)))))) + +(define + jp-parse-var-stmt-loop + (fn + (st decls) + (if + (jp-at? st "punct" ",") + (do + (jp-advance! st) + (append! decls (jp-parse-vardecl st)) + (jp-parse-var-stmt-loop st decls)) + nil))) + +(define + jp-parse-if-stmt + (fn + (st) + (do + (jp-advance! st) + (jp-expect! st "punct" "(") + (let + ((c (jp-parse-assignment st))) + (do + (jp-expect! st "punct" ")") + (let + ((t (jp-parse-stmt st))) + (if + (jp-at? st "keyword" "else") + (do + (jp-advance! st) + (list (quote js-if) c t (jp-parse-stmt st))) + (list (quote js-if) c t nil)))))))) + +(define + jp-parse-while-stmt + (fn + (st) + (do + (jp-advance! st) + (jp-expect! st "punct" "(") + (let + ((c (jp-parse-assignment st))) + (do + (jp-expect! st "punct" ")") + (let ((body (jp-parse-stmt st))) (list (quote js-while) c body))))))) + +(define + jp-parse-do-while-stmt + (fn + (st) + (do + (jp-advance! st) + (let + ((body (jp-parse-stmt st))) + (do + (if + (jp-at? st "keyword" "while") + (jp-advance! st) + (error "Expected 'while' after do-block")) + (jp-expect! st "punct" "(") + (let + ((c (jp-parse-assignment st))) + (do + (jp-expect! st "punct" ")") + (jp-eat-semi st) + (list (quote js-do-while) body c)))))))) + +(define + jp-parse-for-stmt + (fn + (st) + (do + (jp-advance! st) + (jp-expect! st "punct" "(") + (let + ((init (jp-parse-for-init st))) + (let + ((cond-ast (if (jp-at? st "punct" ";") nil (jp-parse-assignment st)))) + (do + (jp-expect! st "punct" ";") + (let + ((step (if (jp-at? st "punct" ")") nil (jp-parse-assignment st)))) + (do + (jp-expect! st "punct" ")") + (let + ((body (jp-parse-stmt st))) + (list (quote js-for) init cond-ast step body)))))))))) + +(define + jp-parse-for-init + (fn + (st) + (cond + ((jp-at? st "punct" ";") (do (jp-advance! st) nil)) + ((jp-at? st "keyword" "var") (jp-parse-var-stmt st "var")) + ((jp-at? st "keyword" "let") (jp-parse-var-stmt st "let")) + ((jp-at? st "keyword" "const") (jp-parse-var-stmt st "const")) + (else + (let + ((e (jp-parse-assignment st))) + (do (jp-expect! st "punct" ";") (list (quote js-exprstmt) e))))))) + +(define + jp-parse-return-stmt + (fn + (st) + (do + (jp-advance! st) + (if + (or + (jp-at? st "punct" ";") + (jp-at? st "punct" "}") + (jp-at? st "eof" nil)) + (do (jp-eat-semi st) (list (quote js-return) nil)) + (let + ((e (jp-parse-assignment st))) + (do (jp-eat-semi st) (list (quote js-return) e))))))) + +(define + jp-parse-function-decl + (fn + (st) + (do + (jp-advance! st) + (let + ((nm (get (jp-peek st) :value))) + (do + (if + (= (get (jp-peek st) :type) "ident") + (jp-advance! st) + (error "Expected function name")) + (let + ((params (jp-parse-param-list st))) + (let + ((body (jp-parse-block st))) + (list (quote js-funcdecl) nm params body)))))))) + +(define + jp-parse-async-function-decl + (fn + (st) + (do + (jp-advance! st) + (let + ((nm (get (jp-peek st) :value))) + (do + (if + (= (get (jp-peek st) :type) "ident") + (jp-advance! st) + (error "Expected function name")) + (let + ((params (jp-parse-param-list st))) + (let + ((body (jp-parse-block st))) + (list (quote js-funcdecl-async) nm params body)))))))) + +(define + jp-parse-class-decl + (fn + (st) + (do + (jp-advance! st) + (let + ((name (get (jp-peek st) :value))) + (do + (jp-advance! st) + (let + ((parent (if (jp-at? st "keyword" "extends") (do (jp-advance! st) (let ((p-name (get (jp-peek st) :value))) (do (jp-advance! st) p-name))) nil))) + (do + (jp-expect! st "punct" "{") + (let + ((methods (jp-parse-class-body st (list)))) + (do + (jp-expect! st "punct" "}") + (list (quote js-class) name parent methods)))))))))) + +(define + jp-parse-class-body + (fn + (st acc) + (cond + ((jp-at? st "punct" "}") acc) + ((jp-at? st "punct" ";") + (do (jp-advance! st) (jp-parse-class-body st acc))) + (else + (do + (append! acc (jp-parse-class-method st)) + (jp-parse-class-body st acc)))))) + +(define + jp-parse-class-method + (fn + (st) + (let + ((static? (if (jp-at? st "keyword" "static") (do (jp-advance! st) true) false))) + (let + ((name (get (jp-peek st) :value))) + (do + (jp-advance! st) + (let + ((params (jp-parse-param-list st))) + (let + ((body (jp-parse-block st))) + (list + (quote js-method) + (if static? "static" "instance") + name + params + body)))))))) + +(define + jp-parse-throw-stmt + (fn + (st) + (do + (jp-advance! st) + (let + ((e (jp-parse-assignment st))) + (do (jp-eat-semi st) (list (quote js-throw) e)))))) + +(define + jp-parse-try-stmt + (fn + (st) + (do + (jp-advance! st) + (let + ((body (jp-parse-block st))) + (let + ((catch-part (if (jp-at? st "keyword" "catch") (do (jp-advance! st) (let ((has-param (jp-at? st "punct" "("))) (if has-param (do (jp-advance! st) (let ((pname (get (jp-peek st) :value))) (do (jp-advance! st) (jp-expect! st "punct" ")") (let ((cbody (jp-parse-block st))) (list pname cbody))))) (let ((cbody (jp-parse-block st))) (list nil cbody))))) nil))) + (let + ((finally-part (if (jp-at? st "keyword" "finally") (do (jp-advance! st) (jp-parse-block st)) nil))) + (list (quote js-try) body catch-part finally-part))))))) + +(define + jp-parse-stmt + (fn + (st) + (cond + ((jp-at? st "punct" "{") (jp-parse-block st)) + ((jp-at? st "punct" ";") + (do (jp-advance! st) (list (quote js-empty)))) + ((jp-at? st "keyword" "var") (jp-parse-var-stmt st "var")) + ((jp-at? st "keyword" "let") (jp-parse-var-stmt st "let")) + ((jp-at? st "keyword" "const") (jp-parse-var-stmt st "const")) + ((jp-at? st "keyword" "if") (jp-parse-if-stmt st)) + ((jp-at? st "keyword" "while") (jp-parse-while-stmt st)) + ((jp-at? st "keyword" "do") (jp-parse-do-while-stmt st)) + ((jp-at? st "keyword" "for") (jp-parse-for-stmt st)) + ((jp-at? st "keyword" "return") (jp-parse-return-stmt st)) + ((jp-at? st "keyword" "break") + (do (jp-advance! st) (jp-eat-semi st) (list (quote js-break)))) + ((jp-at? st "keyword" "continue") + (do (jp-advance! st) (jp-eat-semi st) (list (quote js-continue)))) + ((jp-at? st "keyword" "class") (jp-parse-class-decl st)) + ((jp-at? st "keyword" "throw") (jp-parse-throw-stmt st)) + ((jp-at? st "keyword" "try") (jp-parse-try-stmt st)) + ((and (jp-at? st "keyword" "async") (= (get (jp-peek-at st 1) :type) "keyword") (= (get (jp-peek-at st 1) :value) "function")) + (do (jp-advance! st) (jp-parse-async-function-decl st))) + ((jp-at? st "keyword" "function") (jp-parse-function-decl st)) + (else + (let + ((e (jp-parse-assignment st))) + (do (jp-eat-semi st) (list (quote js-exprstmt) e))))))) + +(define + jp-parse-program + (fn + (st) + (let + ((stmts (list))) + (do (jp-parse-program-loop st stmts) (list (quote js-program) stmts))))) + +(define + jp-parse-program-loop + (fn + (st stmts) + (if + (jp-at? st "eof" nil) + nil + (do + (append! stmts (jp-parse-stmt st)) + (jp-parse-program-loop st stmts))))) + +(define + jp-parse-arrow-body + (fn + (st) + (if + (jp-at? st "punct" "{") + (jp-parse-block st) + (jp-parse-assignment st)))) + +(define + js-parse + (fn + (tokens) + (if + (or + (= (len tokens) 0) + (and (= (len tokens) 1) (= (get (nth tokens 0) :type) "eof"))) + (list (quote js-program) (list)) + (let ((st {:idx 0 :tokens tokens :arrow-candidate true})) (jp-parse-program st))))) + +(define + js-parse-expr + (fn + (src) + (let + ((tokens (js-tokenize src))) + (if + (or + (= (len tokens) 0) + (and (= (len tokens) 1) (= (get (nth tokens 0) :type) "eof"))) + (list) + (let ((st {:idx 0 :tokens tokens :arrow-candidate true})) (jp-parse-assignment st)))))) diff --git a/lib/js/runtime.sx b/lib/js/runtime.sx new file mode 100644 index 00000000..6887e117 --- /dev/null +++ b/lib/js/runtime.sx @@ -0,0 +1,1596 @@ +;; lib/js/runtime.sx — JS semantics shims +;; +;; Coercions, abstract equality, arithmetic with JS rules, and the +;; global object live here. Transpiled code (from lib/js/transpile.sx) +;; compiles to calls into these helpers. +;; +;; Phase 4 status: core coercions, arithmetic, comparison, equality, +;; property access, `console.log`, Math shim wired up. Enough to make +;; the expression-level test262 slice green. + +;; ── JS value sentinels ──────────────────────────────────────────── + +;; JS `undefined` — we represent it as a distinct keyword so it +;; survives round-trips through the evaluator without colliding with +;; SX `nil` (which maps to JS `null`). +(define js-undefined :js-undefined) + +(define js-undefined? (fn (v) (= v :js-undefined))) + +;; ── Type predicates ─────────────────────────────────────────────── + +(define __js_this_cell__ (dict)) + +;; ── Boolean coercion (ToBoolean) ────────────────────────────────── + +(define + js-this + (fn + () + (if + (dict-has? __js_this_cell__ "this") + (get __js_this_cell__ "this") + :js-undefined))) + +;; ── Numeric coercion (ToNumber) ─────────────────────────────────── + +(define js-this-set! (fn (v) (dict-set! __js_this_cell__ "this" v))) + +;; Parse a JS-style string to a number. For the slice we just delegate +;; to SX's number parser via `str->num`/`parse-number`. Empty string → 0 +;; per JS (technically ToNumber("") === 0). +(define + js-call-with-this + (fn + (recv fn-val args) + (let + ((saved (js-this))) + (begin + (js-this-set! recv) + (let + ((result (js-apply-fn fn-val args))) + (begin (js-this-set! saved) result)))))) + +;; Safe number-parser. Tries to call an SX primitive that can parse +;; strings to numbers; on failure returns 0 (stand-in for NaN so the +;; slice doesn't crash). +(define + js-apply-fn + (fn + (fn-val args) + (cond + ((= (len args) 0) (fn-val)) + ((= (len args) 1) (fn-val (nth args 0))) + ((= (len args) 2) (fn-val (nth args 0) (nth args 1))) + ((= (len args) 3) (fn-val (nth args 0) (nth args 1) (nth args 2))) + ((= (len args) 4) + (fn-val (nth args 0) (nth args 1) (nth args 2) (nth args 3))) + ((= (len args) 5) + (fn-val + (nth args 0) + (nth args 1) + (nth args 2) + (nth args 3) + (nth args 4))) + ((= (len args) 6) + (fn-val + (nth args 0) + (nth args 1) + (nth args 2) + (nth args 3) + (nth args 4) + (nth args 5))) + (else (apply fn-val args))))) + +;; Minimal string->number for the slice. Handles integers, negatives, +;; and simple decimals. Returns 0 on malformed input. +(define + js-invoke-method + (fn + (recv key args) + (cond + ((and (js-promise? recv) (js-promise-builtin-method? key)) + (js-invoke-promise-method recv key args)) + (else + (let + ((m (js-get-prop recv key))) + (cond + ((js-undefined? m) + (error + (str "TypeError: " (js-to-string key) " is not a function"))) + (else (js-call-with-this recv m args)))))))) + +(define js-upper-case (fn (s) (js-case-loop s 0 "" true))) + +(define js-lower-case (fn (s) (js-case-loop s 0 "" false))) + +(define + js-case-loop + (fn + (s i acc to-upper?) + (cond + ((>= i (len s)) acc) + (else + (let + ((c (char-at s i))) + (let + ((cc (char-code c))) + (let + ((cv (cond ((and to-upper? (>= cc 97) (<= cc 122)) (js-code-to-char (- cc 32))) ((and (not to-upper?) (>= cc 65) (<= cc 90)) (js-code-to-char (+ cc 32))) (else c)))) + (js-case-loop s (+ i 1) (str acc cv) to-upper?)))))))) + +(define + js-code-to-char + (fn + (code) + (cond + ((= code 65) "A") + ((= code 66) "B") + ((= code 67) "C") + ((= code 68) "D") + ((= code 69) "E") + ((= code 70) "F") + ((= code 71) "G") + ((= code 72) "H") + ((= code 73) "I") + ((= code 74) "J") + ((= code 75) "K") + ((= code 76) "L") + ((= code 77) "M") + ((= code 78) "N") + ((= code 79) "O") + ((= code 80) "P") + ((= code 81) "Q") + ((= code 82) "R") + ((= code 83) "S") + ((= code 84) "T") + ((= code 85) "U") + ((= code 86) "V") + ((= code 87) "W") + ((= code 88) "X") + ((= code 89) "Y") + ((= code 90) "Z") + ((= code 97) "a") + ((= code 98) "b") + ((= code 99) "c") + ((= code 100) "d") + ((= code 101) "e") + ((= code 102) "f") + ((= code 103) "g") + ((= code 104) "h") + ((= code 105) "i") + ((= code 106) "j") + ((= code 107) "k") + ((= code 108) "l") + ((= code 109) "m") + ((= code 110) "n") + ((= code 111) "o") + ((= code 112) "p") + ((= code 113) "q") + ((= code 114) "r") + ((= code 115) "s") + ((= code 116) "t") + ((= code 117) "u") + ((= code 118) "v") + ((= code 119) "w") + ((= code 120) "x") + ((= code 121) "y") + ((= code 122) "z") + (else "")))) + +(define + js-invoke-method-dyn + (fn (recv key args) (js-invoke-method recv key args))) + +(define + js-call-plain + (fn + (fn-val args) + (cond + ((js-undefined? fn-val) + (error "TypeError: undefined is not a function")) + (else (js-call-with-this :js-undefined fn-val args))))) + +;; parse a decimal number from a trimmed non-empty string. +;; s — source +;; i — cursor +;; acc — integer part so far (or total for decimals) +;; sign — 1 or -1 +;; frac? — are we past the decimal point +;; fdiv — divisor used to scale fraction digits (only if frac?) +(define + js-new-call + (fn + (ctor args) + (let + ((obj (dict))) + (begin + (dict-set! obj "__proto__" (js-get-ctor-proto ctor)) + (let + ((ret (js-call-with-this obj ctor args))) + (if + (and (not (js-undefined? ret)) (= (type-of ret) "dict")) + ret + obj)))))) + +(define + js-instanceof + (fn + (obj ctor) + (cond + ((not (= (type-of obj) "dict")) false) + ((not (js-function? ctor)) + (error "TypeError: Right-hand side of instanceof is not callable")) + (else + (let + ((proto (js-get-ctor-proto ctor))) + (js-instanceof-walk obj proto)))))) + +(define + js-instanceof-walk + (fn + (obj proto) + (cond + ((not (= (type-of obj) "dict")) false) + ((not (dict-has? obj "__proto__")) false) + (else + (let + ((p (get obj "__proto__"))) + (cond + ((= p proto) true) + ((not (= (type-of p) "dict")) false) + (else (js-instanceof-walk p proto)))))))) + +;; ── String coercion (ToString) ──────────────────────────────────── + +(define + js-in + (fn + (key obj) + (cond + ((not (= (type-of obj) "dict")) false) + (else (js-in-walk obj (js-to-string key)))))) + +(define + js-in-walk + (fn + (obj skey) + (cond + ((not (= (type-of obj) "dict")) false) + ((dict-has? obj skey) true) + ((dict-has? obj "__proto__") (js-in-walk (get obj "__proto__") skey)) + (else false)))) + +;; ── Arithmetic (JS rules) ───────────────────────────────────────── + +;; JS `+`: if either operand is a string → string concat, else numeric. +(define + Error + (fn + (&rest args) + (let + ((this (js-this))) + (begin + (if + (= (type-of this) "dict") + (do + (dict-set! + this + "message" + (if (= (len args) 0) "" (js-to-string (nth args 0)))) + (dict-set! this "name" "Error")) + nil) + this)))) + +(define + TypeError + (fn + (&rest args) + (let + ((this (js-this))) + (begin + (if + (= (type-of this) "dict") + (do + (dict-set! + this + "message" + (if (= (len args) 0) "" (js-to-string (nth args 0)))) + (dict-set! this "name" "TypeError")) + nil) + this)))) + +(define + RangeError + (fn + (&rest args) + (let + ((this (js-this))) + (begin + (if + (= (type-of this) "dict") + (do + (dict-set! + this + "message" + (if (= (len args) 0) "" (js-to-string (nth args 0)))) + (dict-set! this "name" "RangeError")) + nil) + this)))) + +(define + SyntaxError + (fn + (&rest args) + (let + ((this (js-this))) + (begin + (if + (= (type-of this) "dict") + (do + (dict-set! + this + "message" + (if (= (len args) 0) "" (js-to-string (nth args 0)))) + (dict-set! this "name" "SyntaxError")) + nil) + this)))) + +(define + ReferenceError + (fn + (&rest args) + (let + ((this (js-this))) + (begin + (if + (= (type-of this) "dict") + (do + (dict-set! + this + "message" + (if (= (len args) 0) "" (js-to-string (nth args 0)))) + (dict-set! this "name" "ReferenceError")) + nil) + this)))) + +(define + js-function? + (fn + (v) + (let + ((t (type-of v))) + (or (= t "lambda") (= t "function") (= t "component"))))) + +(define __js_proto_table__ (dict)) + +(define __js_next_id__ (dict)) + +;; Bitwise + logical-not +(dict-set! __js_next_id__ "n" 0) + +(define + js-get-ctor-proto + (fn + (ctor) + (let + ((id (js-ctor-id ctor))) + (cond + ((dict-has? __js_proto_table__ id) (get __js_proto_table__ id)) + (else + (let ((p (dict))) (begin (dict-set! __js_proto_table__ id p) p))))))) + +;; ── Equality ────────────────────────────────────────────────────── + +;; Strict equality (===): no coercion; js-undefined matches js-undefined. +(define + js-reset-ctor-proto! + (fn + (ctor) + (let + ((id (js-ctor-id ctor)) (p (dict))) + (begin (dict-set! __js_proto_table__ id p) p)))) + +(define + js-set-ctor-proto! + (fn + (ctor proto) + (let ((id (js-ctor-id ctor))) (dict-set! __js_proto_table__ id proto)))) + +;; Abstract equality (==): type coercion rules. +;; Simplified: number↔string coerce both to number; null == undefined; +;; everything else falls back to strict equality. +(define + js-ctor-id + (fn + (ctor) + (cond + ((and (= (type-of ctor) "dict") (dict-has? ctor "__ctor_id__")) + (get ctor "__ctor_id__")) + (else (inspect ctor))))) + +(define + js-typeof + (fn + (v) + (cond + ((js-undefined? v) "undefined") + ((= v nil) "object") + ((= (type-of v) "boolean") "boolean") + ((= (type-of v) "number") "number") + ((= (type-of v) "string") "string") + ((= (type-of v) "lambda") "function") + ((= (type-of v) "native-fn") "function") + (else "object")))) + +;; ── Relational comparisons ──────────────────────────────────────── + +;; Abstract relational comparison from ES5. +;; Numbers compare numerically; two strings compare lexicographically; +;; mixed types coerce both to numbers. +(define + js-to-boolean + (fn + (v) + (cond + ((js-undefined? v) false) + ((= v nil) false) + ((= v false) false) + ((= v 0) false) + ((= v "") false) + (else true)))) + +(define + js-to-number + (fn + (v) + (cond + ((js-undefined? v) 0) + ((= v nil) 0) + ((= v true) 1) + ((= v false) 0) + ((= (type-of v) "number") v) + ((= (type-of v) "string") (js-string-to-number v)) + (else 0)))) + +(define + js-string-to-number + (fn (s) (cond ((= s "") 0) (else (js-parse-num-safe s))))) + +(define js-parse-num-safe (fn (s) (cond (else (js-num-from-string s))))) + +(define + js-num-from-string + (fn + (s) + (let + ((trimmed (js-trim s))) + (cond + ((= trimmed "") 0) + (else (js-parse-decimal trimmed 0 0 1 false 0)))))) + +(define js-trim (fn (s) (js-trim-left (js-trim-right s)))) + +(define + js-trim-left + (fn (s) (let ((n (len s))) (js-trim-left-at s n 0)))) + +;; ── Property access ─────────────────────────────────────────────── + +;; obj[key] or obj.key in JS. Handles: +;; • dicts keyed by string +;; • lists indexed by number (incl. .length) +;; • strings indexed by number (incl. .length) +;; Returns js-undefined if the key is absent. +(define + js-trim-left-at + (fn + (s n i) + (cond + ((>= i n) "") + ((js-is-space? (char-at s i)) (js-trim-left-at s n (+ i 1))) + (else (substr s i n))))) + +(define + js-trim-right + (fn (s) (let ((n (len s))) (js-trim-right-at s n)))) + +(define + js-trim-right-at + (fn + (s n) + (cond + ((<= n 0) "") + ((js-is-space? (char-at s (- n 1))) (js-trim-right-at s (- n 1))) + (else (substr s 0 n))))) + +;; Setter — mutates the dict. Returns the new value (JS assignment yields rhs). +(define + js-is-space? + (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r")))) + +;; ── Short-circuit logical ops ───────────────────────────────────── + +;; `a && b` in JS: if a is truthy return b else return a. The thunk +;; form defers evaluation of b — the transpiler passes (fn () b). +(define + js-parse-decimal + (fn + (s i acc sign frac? fdiv) + (let + ((n (len s))) + (cond + ((>= i n) (* sign (if frac? (/ acc fdiv) acc))) + ((and (= i 0) (= (char-at s 0) "-")) + (js-parse-decimal s 1 0 -1 false 0)) + ((and (= i 0) (= (char-at s 0) "+")) + (js-parse-decimal s 1 0 1 false 0)) + ((= (char-at s i) ".") + (js-parse-decimal s (+ i 1) acc sign true 1)) + ((js-is-digit? (char-at s i)) + (if + frac? + (js-parse-decimal + s + (+ i 1) + (+ (* acc 10) (js-digit-val (char-at s i))) + sign + true + (* fdiv 10)) + (js-parse-decimal + s + (+ i 1) + (+ (* acc 10) (js-digit-val (char-at s i))) + sign + false + 0))) + (else (* sign (if frac? (/ acc fdiv) acc))))))) + +(define + js-is-digit? + (fn + (c) + (and + (or + (= c "0") + (= c "1") + (= c "2") + (= c "3") + (= c "4") + (= c "5") + (= c "6") + (= c "7") + (= c "8") + (= c "9"))))) + +;; ── console.log ─────────────────────────────────────────────────── + +;; Trivial bridge. `log-info` is available on OCaml; fall back to print. +(define + js-digit-val + (fn + (c) + (cond + ((= c "0") 0) + ((= c "1") 1) + ((= c "2") 2) + ((= c "3") 3) + ((= c "4") 4) + ((= c "5") 5) + ((= c "6") 6) + ((= c "7") 7) + ((= c "8") 8) + ((= c "9") 9) + (else 0)))) + +(define + js-to-string + (fn + (v) + (cond + ((js-undefined? v) "undefined") + ((= v nil) "null") + ((= v true) "true") + ((= v false) "false") + ((= (type-of v) "string") v) + ((= (type-of v) "number") (js-number-to-string v)) + (else (str v))))) + +;; ── Math object ─────────────────────────────────────────────────── + +(define + js-template-concat + (fn (&rest parts) (js-template-concat-loop parts 0 ""))) +(define + js-template-concat-loop + (fn + (parts i acc) + (if + (>= i (len parts)) + acc + (js-template-concat-loop + parts + (+ i 1) + (str acc (js-to-string (nth parts i))))))) +(define js-number-to-string (fn (n) (str n))) +(define + js-add + (fn + (a b) + (cond + ((or (= (type-of a) "string") (= (type-of b) "string")) + (str (js-to-string a) (js-to-string b))) + (else (+ (js-to-number a) (js-to-number b)))))) +(define js-sub (fn (a b) (- (js-to-number a) (js-to-number b)))) +(define js-mul (fn (a b) (* (js-to-number a) (js-to-number b)))) +(define js-div (fn (a b) (/ (js-to-number a) (js-to-number b)))) +(define js-mod (fn (a b) (mod (js-to-number a) (js-to-number b)))) +(define js-pow (fn (a b) (pow (js-to-number a) (js-to-number b)))) ; deterministic placeholder for tests + +(define js-neg (fn (a) (- 0 (js-to-number a)))) + +;; The global object — lookup table for JS names that aren't in the +;; SX env. Transpiled idents look up locally first; globals here are a +;; fallback, but most slice programs reference `console`, `Math`, +;; `undefined` as plain symbols, which we bind as defines above. +(define js-pos (fn (a) (js-to-number a))) + +(define js-not (fn (a) (not (js-to-boolean a)))) + +(define js-bitnot (fn (a) (- 0 (+ (js-num-to-int (js-to-number a)) 1)))) + +(define + js-strict-eq + (fn + (a b) + (cond + ((and (js-undefined? a) (js-undefined? b)) true) + ((or (js-undefined? a) (js-undefined? b)) false) + ((not (= (type-of a) (type-of b))) false) + (else (= a b))))) + +(define js-strict-neq (fn (a b) (not (js-strict-eq a b)))) + +(define + js-loose-eq + (fn + (a b) + (cond + ((js-strict-eq a b) true) + ((and (= a nil) (js-undefined? b)) true) + ((and (js-undefined? a) (= b nil)) true) + ((and (= (type-of a) "number") (= (type-of b) "string")) + (= a (js-to-number b))) + ((and (= (type-of a) "string") (= (type-of b) "number")) + (= (js-to-number a) b)) + ((= (type-of a) "boolean") (js-loose-eq (js-to-number a) b)) + ((= (type-of b) "boolean") (js-loose-eq a (js-to-number b))) + (else false)))) + +(define js-loose-neq (fn (a b) (not (js-loose-eq a b)))) + +(define + js-lt + (fn + (a b) + (cond + ((and (= (type-of a) "string") (= (type-of b) "string")) + (js-str-lt a b)) + (else (< (js-to-number a) (js-to-number b)))))) + +(define js-gt (fn (a b) (js-lt b a))) + +(define js-le (fn (a b) (not (js-lt b a)))) + +(define js-ge (fn (a b) (not (js-lt a b)))) + +(define js-str-lt (fn (a b) (js-str-lt-at a b 0 (len a) (len b)))) + +(define + js-str-lt-at + (fn + (a b i la lb) + (cond + ((and (>= i la) (>= i lb)) false) + ((>= i la) true) + ((>= i lb) false) + ((< (char-code-at a i) (char-code-at b i)) true) + ((> (char-code-at a i) (char-code-at b i)) false) + (else (js-str-lt-at a b (+ i 1) la lb))))) + +(define char-code-at (fn (s i) (char-code (char-at s i)))) + +(define + js-array-method + (fn + (arr name) + (cond + ((= name "push") + (fn + (&rest args) + (begin (for-each (fn (x) (append! arr x)) args) (len arr)))) + ((= name "pop") + (fn + () + (if + (= (len arr) 0) + js-undefined + (let + ((v (nth arr (- (len arr) 1)))) + (begin (pop-last! arr) v))))) + ((= name "shift") + (fn + () + (if + (= (len arr) 0) + js-undefined + (let ((v (nth arr 0))) (begin (pop-first! arr) v))))) + ((= name "slice") + (fn + (&rest args) + (let + ((start (if (= (len args) 0) 0 (js-num-to-int (nth args 0)))) + (stop + (if + (< (len args) 2) + (len arr) + (js-num-to-int (nth args 1))))) + (js-list-slice arr start stop)))) + ((= name "indexOf") + (fn + (&rest args) + (if (= (len args) 0) -1 (js-list-index-of arr (nth args 0) 0)))) + ((= name "join") + (fn + (&rest args) + (let + ((sep (if (= (len args) 0) "," (js-to-string (nth args 0))))) + (js-list-join arr sep)))) + ((= name "concat") (fn (&rest args) (js-list-concat arr args))) + ((= name "map") (fn (f) (js-list-map-loop f arr 0 (list)))) + ((= name "filter") (fn (f) (js-list-filter-loop f arr 0 (list)))) + ((= name "forEach") + (fn (f) (begin (js-list-foreach-loop f arr 0) js-undefined))) + ((= name "reduce") + (fn + (&rest args) + (cond + ((= (len args) 1) + (if + (= (len arr) 0) + (error "Reduce of empty array with no initial value") + (js-list-reduce-loop (nth args 0) (nth arr 0) arr 1))) + (else (js-list-reduce-loop (nth args 0) (nth args 1) arr 0))))) + (else js-undefined)))) + +(define pop-last! (fn (lst) nil)) + +(define pop-first! (fn (lst) nil)) + +(define + js-list-slice + (fn + (arr start stop) + (let + ((n (len arr))) + (let + ((s (if (< start 0) (max 0 (+ n start)) (min start n))) + (e (if (< stop 0) (max 0 (+ n stop)) (min stop n)))) + (js-list-slice-loop arr s e (list)))))) + +(define + js-list-slice-loop + (fn + (arr i e acc) + (cond + ((>= i e) acc) + (else + (do + (append! acc (nth arr i)) + (js-list-slice-loop arr (+ i 1) e acc)))))) + +(define + js-list-index-of + (fn + (arr v i) + (cond + ((>= i (len arr)) -1) + ((js-strict-eq (nth arr i) v) i) + (else (js-list-index-of arr v (+ i 1)))))) + +(define + js-list-join + (fn + (arr sep) + (cond + ((= (len arr) 0) "") + (else + (js-list-join-loop arr sep 1 (js-to-string-for-join (nth arr 0))))))) + +(define + js-to-string-for-join + (fn + (v) + (cond ((js-undefined? v) "") ((= v nil) "") (else (js-to-string v))))) + +(define + js-list-join-loop + (fn + (arr sep i acc) + (cond + ((>= i (len arr)) acc) + (else + (js-list-join-loop + arr + sep + (+ i 1) + (str acc sep (js-to-string-for-join (nth arr i)))))))) + +(define + js-list-concat + (fn + (arr tail) + (let + ((result (list))) + (begin + (for-each (fn (x) (append! result x)) arr) + (for-each + (fn + (other) + (cond + ((= (type-of other) "list") + (for-each (fn (x) (append! result x)) other)) + (else (append! result other)))) + tail) + result)))) + +(define + js-list-map-loop + (fn + (f arr i acc) + (cond + ((>= i (len arr)) acc) + (else + (do + (append! acc (f (nth arr i))) + (js-list-map-loop f arr (+ i 1) acc)))))) + +(define + js-list-filter-loop + (fn + (f arr i acc) + (cond + ((>= i (len arr)) acc) + (else + (do + (let + ((v (nth arr i))) + (if (js-to-boolean (f v)) (append! acc v) nil)) + (js-list-filter-loop f arr (+ i 1) acc)))))) + +(define + js-list-foreach-loop + (fn + (f arr i) + (cond + ((>= i (len arr)) nil) + (else (do (f (nth arr i)) (js-list-foreach-loop f arr (+ i 1))))))) + +(define + js-list-reduce-loop + (fn + (f acc arr i) + (cond + ((>= i (len arr)) acc) + (else (js-list-reduce-loop f (f acc (nth arr i)) arr (+ i 1)))))) + +(define + js-string-method + (fn + (s name) + (cond + ((= name "charAt") + (fn + (i) + (let + ((idx (js-num-to-int i))) + (if (and (>= idx 0) (< idx (len s))) (char-at s idx) "")))) + ((= name "charCodeAt") + (fn + (i) + (let + ((idx (js-num-to-int i))) + (if + (and (>= idx 0) (< idx (len s))) + (char-code (char-at s idx)) + 0)))) + ((= name "indexOf") + (fn (needle) (js-string-index-of s (js-to-string needle) 0))) + ((= name "slice") + (fn + (&rest args) + (let + ((start (if (= (len args) 0) 0 (js-num-to-int (nth args 0)))) + (stop + (if (< (len args) 2) (len s) (js-num-to-int (nth args 1))))) + (js-string-slice s start stop)))) + ((= name "substring") + (fn + (&rest args) + (let + ((start (if (= (len args) 0) 0 (max 0 (js-num-to-int (nth args 0))))) + (stop + (if + (< (len args) 2) + (len s) + (max 0 (js-num-to-int (nth args 1)))))) + (let + ((lo (min start stop)) (hi (max start stop))) + (js-string-slice s lo (min hi (len s))))))) + ((= name "toUpperCase") (fn () (js-upper-case s))) + ((= name "toLowerCase") (fn () (js-lower-case s))) + ((= name "split") (fn (sep) (js-string-split s (js-to-string sep)))) + ((= name "concat") + (fn (&rest args) (js-string-concat-loop s args 0))) + (else js-undefined)))) + +(define + js-string-slice + (fn + (s start stop) + (let + ((n (len s))) + (let + ((lo (if (< start 0) (max 0 (+ n start)) (min start n))) + (hi (if (< stop 0) (max 0 (+ n stop)) (min stop n)))) + (if (>= lo hi) "" (js-string-slice-loop s lo hi "")))))) + +(define + js-string-slice-loop + (fn + (s i e acc) + (cond + ((>= i e) acc) + (else (js-string-slice-loop s (+ i 1) e (str acc (char-at s i))))))) + +(define + js-string-index-of + (fn + (s needle i) + (cond + ((> (+ i (len needle)) (len s)) -1) + ((js-string-matches? s needle i 0) i) + (else (js-string-index-of s needle (+ i 1)))))) + +(define + js-string-matches? + (fn + (s needle si ni) + (cond + ((>= ni (len needle)) true) + ((not (= (char-at s (+ si ni)) (char-at needle ni))) false) + (else (js-string-matches? s needle si (+ ni 1)))))) + +(define + js-string-split + (fn + (s sep) + (cond + ((= sep "") (js-string-split-chars s 0 (list))) + (else (js-string-split-loop s sep 0 0 (list)))))) + +(define + js-string-split-chars + (fn + (s i acc) + (cond + ((>= i (len s)) acc) + (else + (do + (append! acc (char-at s i)) + (js-string-split-chars s (+ i 1) acc)))))) + +(define + js-string-split-loop + (fn + (s sep start i acc) + (cond + ((> (+ i (len sep)) (len s)) + (do (append! acc (js-string-slice s start (len s))) acc)) + ((js-string-matches? s sep i 0) + (do + (append! acc (js-string-slice s start i)) + (js-string-split-loop s sep (+ i (len sep)) (+ i (len sep)) acc))) + (else (js-string-split-loop s sep start (+ i 1) acc))))) + +(define + js-string-concat-loop + (fn + (acc args i) + (cond + ((>= i (len args)) acc) + (else + (js-string-concat-loop + (str acc (js-to-string (nth args i))) + args + (+ i 1)))))) + +(begin + (define + js-get-prop + (fn + (obj key) + (cond + ((= obj nil) js-undefined) + ((js-undefined? obj) js-undefined) + ((= (type-of obj) "list") + (cond + ((= key "length") (len obj)) + ((= (type-of key) "number") + (if + (and (>= key 0) (< key (len obj))) + (nth obj (js-num-to-int key)) + js-undefined)) + ((= key "push") (js-array-method obj "push")) + ((= key "pop") (js-array-method obj "pop")) + ((= key "shift") (js-array-method obj "shift")) + ((= key "slice") (js-array-method obj "slice")) + ((= key "indexOf") (js-array-method obj "indexOf")) + ((= key "join") (js-array-method obj "join")) + ((= key "concat") (js-array-method obj "concat")) + ((= key "map") (js-array-method obj "map")) + ((= key "filter") (js-array-method obj "filter")) + ((= key "forEach") (js-array-method obj "forEach")) + ((= key "reduce") (js-array-method obj "reduce")) + (else js-undefined))) + ((= (type-of obj) "string") + (cond + ((= key "length") (len obj)) + ((= (type-of key) "number") + (if + (and (>= key 0) (< key (len obj))) + (char-at obj (js-num-to-int key)) + js-undefined)) + ((= key "charAt") (js-string-method obj "charAt")) + ((= key "charCodeAt") (js-string-method obj "charCodeAt")) + ((= key "indexOf") (js-string-method obj "indexOf")) + ((= key "slice") (js-string-method obj "slice")) + ((= key "substring") (js-string-method obj "substring")) + ((= key "toUpperCase") (js-string-method obj "toUpperCase")) + ((= key "toLowerCase") (js-string-method obj "toLowerCase")) + ((= key "split") (js-string-method obj "split")) + ((= key "concat") (js-string-method obj "concat")) + (else js-undefined))) + ((= (type-of obj) "dict") + (js-dict-get-walk obj (js-to-string key))) + ((and (= obj Promise) (dict-has? __js_promise_statics__ (js-to-string key))) + (get __js_promise_statics__ (js-to-string key))) + ((and (js-function? obj) (= (js-to-string key) "prototype")) + (js-get-ctor-proto obj)) + (else js-undefined)))) + (define + js-dict-get-walk + (fn + (obj skey) + (cond + ((= obj nil) js-undefined) + ((js-undefined? obj) js-undefined) + ((not (= (type-of obj) "dict")) js-undefined) + ((dict-has? obj skey) (get obj skey)) + ((dict-has? obj "__proto__") + (js-dict-get-walk (get obj "__proto__") skey)) + (else js-undefined))))) + +(define + js-num-to-int + (fn (n) (if (>= n 0) (floor n) (- 0 (floor (- 0 n)))))) + +(define dict-has? (fn (d k) (contains? (keys d) k))) + +(begin + (define + js-set-prop + (fn + (obj key val) + (cond + ((js-undefined? obj) (error "js-set-prop: cannot set on undefined")) + ((= (type-of obj) "dict") + (do (dict-set! obj (js-to-string key) val) val)) + ((= (type-of obj) "list") (do (js-list-set! obj key val) val)) + (else val)))) + (define + js-list-set! + (fn + (lst key val) + (cond + ((= (type-of key) "number") + (let + ((i (js-num-to-int key)) (n (len lst))) + (cond + ((< i 0) nil) + ((< i n) (set-nth! lst i val)) + ((= i n) (append! lst val)) + (else (do (js-pad-list! lst n i) (append! lst val)))))) + ((= key "length") nil) + (else nil)))) + (define + js-pad-list! + (fn + (lst from target) + (cond + ((>= from target) nil) + (else + (do + (append! lst js-undefined) + (js-pad-list! lst (+ from 1) target))))))) + +(define js-and (fn (a b-thunk) (if (js-to-boolean a) (b-thunk) a))) + +(define js-or (fn (a b-thunk) (if (js-to-boolean a) a (b-thunk)))) + +(define + js-console-log + (fn (&rest args) (for-each (fn (a) (log-info (js-to-string a))) args))) + +(define console {:log js-console-log}) + +(define js-math-abs (fn (x) (abs (js-to-number x)))) + +(define js-math-floor (fn (x) (floor (js-to-number x)))) + +(define js-math-ceil (fn (x) (ceil (js-to-number x)))) + +(define js-math-round (fn (x) (floor (+ (js-to-number x) 0.5)))) + +(define + js-math-max + (fn + (&rest args) + (cond + ((empty? args) (- 0 (/ 1 0))) + (else (js-math-max-loop (first args) (rest args)))))) + +(define + js-math-max-loop + (fn + (acc xs) + (cond + ((empty? xs) acc) + (else + (let + ((h (js-to-number (first xs)))) + (js-math-max-loop (if (> h acc) h acc) (rest xs))))))) + +(define + js-math-min + (fn + (&rest args) + (cond + ((empty? args) (/ 1 0)) + (else (js-math-min-loop (first args) (rest args)))))) + +(define + js-math-min-loop + (fn + (acc xs) + (cond + ((empty? xs) acc) + (else + (let + ((h (js-to-number (first xs)))) + (js-math-min-loop (if (< h acc) h acc) (rest xs))))))) + +(define js-math-random (fn () 0)) + +(define Math {:random js-math-random :floor js-math-floor :PI 3.14159 :round js-math-round :abs js-math-abs :ceil js-math-ceil :max js-math-max :min js-math-min :E 2.71828}) + +(define __js_microtask_queue__ (dict)) + +(dict-set! __js_microtask_queue__ "q" (list)) + +(define + js-mt-push! + (fn + (thunk) + (dict-set! + __js_microtask_queue__ + "q" + (append (get __js_microtask_queue__ "q") (list thunk))))) + +(define + js-mt-pop! + (fn + () + (let + ((q (get __js_microtask_queue__ "q"))) + (if + (empty? q) + nil + (let + ((h (first q))) + (dict-set! __js_microtask_queue__ "q" (rest q)) + h))))) + +(define js-mt-empty? (fn () (empty? (get __js_microtask_queue__ "q")))) + +(define + js-drain-microtasks! + (fn + () + (cond + ((js-mt-empty?) :js-undefined) + (else (let ((t (js-mt-pop!))) (t) (js-drain-microtasks!)))))) + +(define + js-promise? + (fn + (v) + (and + (= (type-of v) "dict") + (dict-has? v "__js_promise__") + (= (get v "__js_promise__") true)))) + +(define + js-make-promise + (fn + () + (let + ((p (dict))) + (dict-set! p "__js_promise__" true) + (dict-set! p "state" "pending") + (dict-set! p "value" :js-undefined) + (dict-set! p "callbacks" (list)) + p))) + +(define + js-promise-resolve! + (fn + (p value) + (cond + ((not (= (get p "state") "pending")) :js-undefined) + ((js-promise? value) + (js-promise-then-internal! + value + (fn (v) (js-promise-resolve! p v)) + (fn (r) (js-promise-reject! p r)))) + (else + (begin + (dict-set! p "state" "fulfilled") + (dict-set! p "value" value) + (js-promise-flush-callbacks! p)))))) + +(define + js-promise-reject! + (fn + (p reason) + (cond + ((not (= (get p "state") "pending")) :js-undefined) + (else + (begin + (dict-set! p "state" "rejected") + (dict-set! p "value" reason) + (js-promise-flush-callbacks! p)))))) + +(define + js-promise-flush-callbacks! + (fn + (p) + (let + ((cbs (get p "callbacks"))) + (dict-set! p "callbacks" (list)) + (for-each + (fn (cb) (js-mt-push! (fn () (js-promise-run-callback! p cb)))) + cbs)))) + +(define + js-promise-run-callback! + (fn + (p cb) + (let + ((on-fulfilled (nth cb 0)) + (on-rejected (nth cb 1)) + (result-promise (nth cb 2)) + (state (get p "state")) + (value (get p "value"))) + (cond + ((= state "fulfilled") + (if + (js-function? on-fulfilled) + (js-promise-run-handler! result-promise on-fulfilled value) + (js-promise-resolve! result-promise value))) + ((= state "rejected") + (if + (js-function? on-rejected) + (js-promise-run-handler! result-promise on-rejected value) + (js-promise-reject! result-promise value))) + (else :js-undefined))))) + +(define + js-promise-run-handler! + (fn + (result-promise handler arg) + (let + ((outcome (js-promise-try-call handler arg))) + (cond + ((get outcome "threw") + (js-promise-reject! result-promise (get outcome "error"))) + (else (js-promise-resolve! result-promise (get outcome "value"))))))) + +(define + js-call-arity-tolerant + (fn + (handler arg) + (cond + ((= (type-of handler) "lambda") + (let + ((params (lambda-params handler))) + (cond + ((empty? params) (handler)) + ((= (first params) "&rest") (handler arg)) + (else (handler arg))))) + (else (handler arg))))) + +(define + js-promise-try-call + (fn + (handler arg) + (let + ((out (dict))) + (dict-set! out "threw" false) + (dict-set! out "value" :js-undefined) + (dict-set! out "error" :js-undefined) + (guard + (e + (else + (begin + (dict-set! out "threw" true) + (dict-set! out "error" e) + out))) + (dict-set! out "value" (js-call-arity-tolerant handler arg)) + out)))) + +(define + js-promise-then-internal! + (fn + (p on-fulfilled on-rejected) + (let + ((new-p (js-make-promise)) (cb (list on-fulfilled on-rejected))) + (let + ((cb3 (append cb (list new-p)))) + (cond + ((= (get p "state") "pending") + (dict-set! + p + "callbacks" + (append (get p "callbacks") (list cb3)))) + (else (js-mt-push! (fn () (js-promise-run-callback! p cb3)))))) + new-p))) + +(define + js-promise-then! + (fn + (p args) + (let + ((on-f (if (>= (len args) 1) (nth args 0) :js-undefined)) + (on-r (if (>= (len args) 2) (nth args 1) :js-undefined))) + (js-promise-then-internal! p on-f on-r)))) + +(define + js-promise-catch! + (fn + (p args) + (let + ((on-r (if (>= (len args) 1) (nth args 0) :js-undefined))) + (js-promise-then-internal! p :js-undefined on-r)))) + +(define + js-promise-finally! + (fn + (p args) + (let + ((on-fin (if (>= (len args) 1) (nth args 0) :js-undefined))) + (let + ((pass-val (fn (v) (begin (when (js-function? on-fin) (on-fin)) v))) + (pass-err + (fn + (r) + (begin + (when (js-function? on-fin) (on-fin)) + (let + ((throw-p (js-make-promise))) + (js-promise-reject! throw-p r) + throw-p))))) + (js-promise-then-internal! p pass-val pass-err))))) + +(define + js-invoke-promise-method + (fn + (p name args) + (cond + ((= name "then") (js-promise-then! p args)) + ((= name "catch") (js-promise-catch! p args)) + ((= name "finally") (js-promise-finally! p args)) + (else (error (str "TypeError: Promise." name " is not a function")))))) + +(define + js-promise-builtin-method? + (fn (name) (or (= name "then") (= name "catch") (= name "finally")))) + +(define + Promise + (fn + (&rest args) + (let + ((executor (if (empty? args) :js-undefined (first args))) + (p (js-make-promise))) + (let + ((resolve-fn (fn (&rest a) (let ((v (if (empty? a) :js-undefined (first a)))) (js-promise-resolve! p v) :js-undefined))) + (reject-fn + (fn + (&rest a) + (let + ((r (if (empty? a) :js-undefined (first a)))) + (js-promise-reject! p r) + :js-undefined)))) + (cond + ((js-function? executor) + (guard + (e (else (js-promise-reject! p e))) + (executor resolve-fn reject-fn))) + (else :js-undefined)) + p)))) + +(define + js-promise-resolve-static + (fn + (&rest args) + (let + ((v (if (empty? args) :js-undefined (first args)))) + (cond + ((js-promise? v) v) + (else (let ((p (js-make-promise))) (js-promise-resolve! p v) p)))))) + +(define + js-promise-reject-static + (fn + (&rest args) + (let + ((r (if (empty? args) :js-undefined (first args))) + (p (js-make-promise))) + (js-promise-reject! p r) + p))) + +(define + js-make-list-of-length + (fn (n fill) (js-make-list-loop (list) n fill))) + +(define + js-make-list-loop + (fn + (acc n fill) + (cond + ((<= n 0) acc) + (else + (begin (append! acc fill) (js-make-list-loop acc (- n 1) fill)))))) + +(define + js-promise-all-loop! + (fn + (result-p items state idx) + (cond + ((>= idx (len items)) :js-undefined) + (else + (let + ((item (nth items idx)) (i idx)) + (let + ((child (if (js-promise? item) item (js-promise-resolve-static item)))) + (js-promise-then-internal! + child + (fn + (v) + (let + ((results (get state "results"))) + (set-nth! results i v) + (dict-set! state "remaining" (- (get state "remaining") 1)) + (cond + ((= (get state "remaining") 0) + (js-promise-resolve! result-p results)) + (else :js-undefined)))) + (fn (r) (js-promise-reject! result-p r)))) + (js-promise-all-loop! result-p items state (+ idx 1))))))) + +(define + js-promise-all-static + (fn + (&rest args) + (let + ((items (if (empty? args) (list) (first args))) + (p (js-make-promise))) + (cond + ((= (len items) 0) (begin (js-promise-resolve! p (list)) p)) + (else + (let + ((n (len items)) (state (dict))) + (dict-set! state "remaining" n) + (dict-set! + state + "results" + (js-make-list-of-length n :js-undefined)) + (js-promise-all-loop! p items state 0) + p)))))) + +(define + js-promise-race-static + (fn + (&rest args) + (let + ((items (if (empty? args) (list) (first args))) + (p (js-make-promise))) + (for-each + (fn + (item) + (let + ((child (if (js-promise? item) item (js-promise-resolve-static item)))) + (js-promise-then-internal! + child + (fn (v) (js-promise-resolve! p v)) + (fn (r) (js-promise-reject! p r))))) + items) + p))) + +(define __js_promise_statics__ (dict)) + +(dict-set! __js_promise_statics__ "resolve" js-promise-resolve-static) + +(dict-set! __js_promise_statics__ "reject" js-promise-reject-static) + +(dict-set! __js_promise_statics__ "all" js-promise-all-static) + +(dict-set! __js_promise_statics__ "race" js-promise-race-static) + +(define + js-async-wrap + (fn + (thunk) + (let + ((p (js-make-promise))) + (guard + (e (else (js-promise-reject! p e))) + (let + ((v (thunk))) + (cond + ((js-promise? v) + (js-promise-then-internal! + v + (fn (x) (js-promise-resolve! p x)) + (fn (r) (js-promise-reject! p r)))) + (else (js-promise-resolve! p v))))) + p))) + +(define + js-await-value + (fn + (v) + (cond + ((not (js-promise? v)) v) + (else + (begin + (js-drain-microtasks!) + (let + ((state (get v "state"))) + (cond + ((= state "fulfilled") (get v "value")) + ((= state "rejected") (raise (get v "value"))) + (else + (begin + (js-drain-microtasks!) + (let + ((state2 (get v "state"))) + (cond + ((= state2 "fulfilled") (get v "value")) + ((= state2 "rejected") (raise (get v "value"))) + (else (error "await on pending Promise (no scheduler)"))))))))))))) + +(define __drain (fn () (js-drain-microtasks!) :js-undefined)) + +(define js-global {:console console :Math Math :NaN 0 :Infinity (/ 1 0) :undefined js-undefined}) diff --git a/lib/js/test.sh b/lib/js/test.sh new file mode 100755 index 00000000..ee0a8c34 --- /dev/null +++ b/lib/js/test.sh @@ -0,0 +1,1156 @@ +#!/usr/bin/env bash +# Fast JS-on-SX test runner — epoch protocol direct to sx_server.exe. +# Mirrors lib/hyperscript/test.sh. +# +# Usage: +# bash lib/js/test.sh # run all tests +# bash lib/js/test.sh -v # verbose + +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:-}" +PASS=0 +FAIL=0 +ERRORS="" +TMPFILE=$(mktemp) +trap "rm -f $TMPFILE" EXIT + +cat > "$TMPFILE" << 'EPOCHS' +(epoch 1) +(load "lib/r7rs.sx") +(epoch 2) +(load "lib/js/lexer.sx") +(epoch 3) +(load "lib/js/parser.sx") +(epoch 4) +(load "lib/js/transpile.sx") +(epoch 5) +(load "lib/js/runtime.sx") + +;; ── Phase 0: stubs still behave ───────────────────────────────── +(epoch 10) +(eval "(len (js-tokenize \"\"))") +(epoch 11) +(eval "(js-parse (list))") +(epoch 12) +(eval "(js-transpile (list))") +(epoch 13) +(eval "(js-to-boolean 0)") +(epoch 14) +(eval "(js-to-boolean 1)") +(epoch 15) +(eval "(js-to-boolean \"\")") +(epoch 16) +(eval "(js-to-boolean \"x\")") + +;; ── Phase 1: lexer ────────────────────────────────────────────── +;; Empty input → just EOF +(epoch 100) +(eval "(len (js-tokenize \"\"))") +(epoch 101) +(eval "(get (nth (js-tokenize \"\") 0) :type)") + +;; Whitespace only +(epoch 102) +(eval "(len (js-tokenize \" \\n\\t \"))") + +;; Single integer +(epoch 110) +(eval "(get (nth (js-tokenize \"42\") 0) :type)") +(epoch 111) +(eval "(get (nth (js-tokenize \"42\") 0) :value)") + +;; Float +(epoch 112) +(eval "(get (nth (js-tokenize \"3.14\") 0) :value)") + +;; Exponent +(epoch 113) +(eval "(get (nth (js-tokenize \"1e3\") 0) :value)") + +;; Hex +(epoch 114) +(eval "(get (nth (js-tokenize \"0xff\") 0) :value)") + +;; Leading-dot number +(epoch 115) +(eval "(get (nth (js-tokenize \".5\") 0) :value)") + +;; Strings +(epoch 120) +(eval "(get (nth (js-tokenize \"\\\"hi\\\"\") 0) :value)") +(epoch 121) +(eval "(get (nth (js-tokenize \"'ab'\") 0) :value)") +(epoch 122) +(eval "(get (nth (js-tokenize \"\\\"a\\\\nb\\\"\") 0) :value)") + +;; Identifiers vs keywords +(epoch 130) +(eval "(get (nth (js-tokenize \"foo\") 0) :type)") +(epoch 131) +(eval "(get (nth (js-tokenize \"if\") 0) :type)") +(epoch 132) +(eval "(get (nth (js-tokenize \"_x1\") 0) :value)") +(epoch 133) +(eval "(get (nth (js-tokenize \"$y\") 0) :value)") + +;; Operators — multi-char longest match +(epoch 140) +(eval "(get (nth (js-tokenize \"===\") 0) :value)") +(epoch 141) +(eval "(get (nth (js-tokenize \"!==\") 0) :value)") +(epoch 142) +(eval "(get (nth (js-tokenize \"<=\") 0) :value)") +(epoch 143) +(eval "(get (nth (js-tokenize \"&&\") 0) :value)") +(epoch 144) +(eval "(get (nth (js-tokenize \"=>\") 0) :value)") +(epoch 145) +(eval "(get (nth (js-tokenize \"...\") 0) :value)") + +;; Punctuation +(epoch 150) +(eval "(get (nth (js-tokenize \"(\") 0) :type)") +(epoch 151) +(eval "(get (nth (js-tokenize \";\") 0) :value)") + +;; Comments are stripped +(epoch 160) +(eval "(len (js-tokenize \"// comment\\n\"))") +(epoch 161) +(eval "(len (js-tokenize \"/* block */\"))") +(epoch 162) +(eval "(get (nth (js-tokenize \"1 // c\\n2\") 0) :value)") +(epoch 163) +(eval "(get (nth (js-tokenize \"1 // c\\n2\") 1) :value)") + +;; Compound expression — count tokens for `a + b * 3` +(epoch 170) +(eval "(len (js-tokenize \"a + b * 3\"))") +(epoch 171) +(eval "(get (nth (js-tokenize \"a + b * 3\") 1) :value)") +(epoch 172) +(eval "(get (nth (js-tokenize \"a + b * 3\") 3) :value)") + +;; ── Phase 2: parser ───────────────────────────────────────────── +;; Literals → AST nodes +(epoch 200) +(eval "(js-parse-expr \"42\")") +(epoch 201) +(eval "(js-parse-expr \"3.14\")") +(epoch 202) +(eval "(js-parse-expr \"\\\"hi\\\"\")") +(epoch 203) +(eval "(js-parse-expr \"true\")") +(epoch 204) +(eval "(js-parse-expr \"false\")") +(epoch 205) +(eval "(js-parse-expr \"null\")") +(epoch 206) +(eval "(js-parse-expr \"undefined\")") +(epoch 207) +(eval "(js-parse-expr \"this\")") +(epoch 208) +(eval "(js-parse-expr \"foo\")") + +;; Binary operators with precedence +(epoch 210) +(eval "(js-parse-expr \"1+2\")") +(epoch 211) +(eval "(js-parse-expr \"a + b * c\")") +(epoch 212) +(eval "(js-parse-expr \"a * b + c\")") +(epoch 213) +(eval "(js-parse-expr \"a === b\")") +(epoch 214) +(eval "(js-parse-expr \"a && b || c\")") +(epoch 215) +(eval "(js-parse-expr \"a ** b ** c\")") +(epoch 216) +(eval "(js-parse-expr \"(a + b) * c\")") + +;; Unary operators +(epoch 220) +(eval "(js-parse-expr \"-x\")") +(epoch 221) +(eval "(js-parse-expr \"!x\")") +(epoch 222) +(eval "(js-parse-expr \"~x\")") +(epoch 223) +(eval "(js-parse-expr \"typeof x\")") +(epoch 224) +(eval "(js-parse-expr \"void x\")") +(epoch 225) +(eval "(js-parse-expr \"+x\")") + +;; Member access and index +(epoch 230) +(eval "(js-parse-expr \"a.b\")") +(epoch 231) +(eval "(js-parse-expr \"a.b.c\")") +(epoch 232) +(eval "(js-parse-expr \"a[0]\")") +(epoch 233) +(eval "(js-parse-expr \"a[b+1]\")") + +;; Function calls +(epoch 240) +(eval "(js-parse-expr \"f()\")") +(epoch 241) +(eval "(js-parse-expr \"f(x)\")") +(epoch 242) +(eval "(js-parse-expr \"f(a, b, c)\")") +(epoch 243) +(eval "(js-parse-expr \"a.b(c)\")") +(epoch 244) +(eval "(js-parse-expr \"a.b(c)[d]\")") + +;; Array literals +(epoch 250) +(eval "(js-parse-expr \"[]\")") +(epoch 251) +(eval "(js-parse-expr \"[1, 2, 3]\")") +(epoch 252) +(eval "(js-parse-expr \"[[1,2],[3,4]]\")") + +;; Object literals +(epoch 260) +(eval "(js-parse-expr \"{}\")") +(epoch 261) +(eval "(js-parse-expr \"{a: 1}\")") +(epoch 262) +(eval "(js-parse-expr \"{a: 1, b: 2}\")") +(epoch 263) +(eval "(js-parse-expr \"{\\\"k\\\": 1}\")") + +;; Conditional +(epoch 270) +(eval "(js-parse-expr \"a ? b : c\")") +(epoch 271) +(eval "(js-parse-expr \"a ? b ? c : d : e\")") + +;; Arrow functions +(epoch 280) +(eval "(js-parse-expr \"x => x + 1\")") +(epoch 281) +(eval "(js-parse-expr \"() => 42\")") +(epoch 282) +(eval "(js-parse-expr \"(a, b) => a + b\")") +(epoch 283) +(eval "(js-parse-expr \"x => y => x + y\")") + +;; Assignment +(epoch 290) +(eval "(js-parse-expr \"a = b\")") +(epoch 291) +(eval "(js-parse-expr \"a = b = c\")") +(epoch 292) +(eval "(js-parse-expr \"a += 1\")") + +;; ── Phase 3: transpile + end-to-end js-eval ──────────────────── +;; Literals — numbers/strings/bools/null/undefined round-trip. +(epoch 300) +(eval "(js-eval \"42\")") +(epoch 301) +(eval "(js-eval \"3.14\")") +(epoch 302) +(eval "(js-eval \"\\\"hi\\\"\")") +(epoch 303) +(eval "(js-eval \"true\")") +(epoch 304) +(eval "(js-eval \"false\")") +(epoch 305) +(eval "(js-eval \"null\")") +(epoch 306) +(eval "(js-eval \"undefined\")") + +;; Arithmetic +(epoch 310) +(eval "(js-eval \"1 + 2\")") +(epoch 311) +(eval "(js-eval \"1 + 2 * 3\")") +(epoch 312) +(eval "(js-eval \"(1 + 2) * 3\")") +(epoch 313) +(eval "(js-eval \"10 - 4\")") +(epoch 314) +(eval "(js-eval \"10 / 4\")") +(epoch 315) +(eval "(js-eval \"10 % 3\")") +(epoch 316) +(eval "(js-eval \"2 ** 10\")") +(epoch 317) +(eval "(js-eval \"-5\")") +(epoch 318) +(eval "(js-eval \"+5\")") +(epoch 319) +(eval "(js-eval \"~5\")") + +;; String concat via + +(epoch 320) +(eval "(js-eval \"\\\"a\\\" + \\\"b\\\"\")") +(epoch 321) +(eval "(js-eval \"1 + \\\"2\\\"\")") +(epoch 322) +(eval "(js-eval \"\\\"n=\\\" + 42\")") + +;; Comparisons +(epoch 330) +(eval "(js-eval \"1 === 1\")") +(epoch 331) +(eval "(js-eval \"1 === 2\")") +(epoch 332) +(eval "(js-eval \"1 === \\\"1\\\"\")") +(epoch 333) +(eval "(js-eval \"1 !== 1\")") +(epoch 334) +(eval "(js-eval \"1 < 2\")") +(epoch 335) +(eval "(js-eval \"2 <= 2\")") +(epoch 336) +(eval "(js-eval \"3 > 1\")") +(epoch 337) +(eval "(js-eval \"3 >= 3\")") +(epoch 338) +(eval "(js-eval \"\\\"a\\\" < \\\"b\\\"\")") + +;; Abstract equality +(epoch 340) +(eval "(js-eval \"1 == \\\"1\\\"\")") +(epoch 341) +(eval "(js-eval \"null == undefined\")") +(epoch 342) +(eval "(js-eval \"0 == false\")") + +;; Logical — short-circuit, value-returning +(epoch 350) +(eval "(js-eval \"true && false\")") +(epoch 351) +(eval "(js-eval \"1 && 2\")") +(epoch 352) +(eval "(js-eval \"0 && 2\")") +(epoch 353) +(eval "(js-eval \"false || 5\")") +(epoch 354) +(eval "(js-eval \"0 || \\\"x\\\"\")") +(epoch 355) +(eval "(js-eval \"null ?? 7\")") +(epoch 356) +(eval "(js-eval \"0 ?? 7\")") +(epoch 357) +(eval "(js-eval \"!true\")") +(epoch 358) +(eval "(js-eval \"!0\")") + +;; Conditional +(epoch 360) +(eval "(js-eval \"true ? 10 : 20\")") +(epoch 361) +(eval "(js-eval \"0 ? 10 : 20\")") +(epoch 362) +(eval "(js-eval \"1 < 2 ? 'yes' : 'no'\")") + +;; Arrays +(epoch 370) +(eval "(js-eval \"[1,2,3]\")") +(epoch 371) +(eval "(js-eval \"[1,2,3].length\")") +(epoch 372) +(eval "(js-eval \"[1,2,3][1]\")") +(epoch 373) +(eval "(js-eval \"[[1,2],[3,4]][1][0]\")") +(epoch 374) +(eval "(js-eval \"[]\")") + +;; Objects +(epoch 380) +(eval "(js-eval \"({a:1, b:2}).a\")") +(epoch 381) +(eval "(js-eval \"({a:1, b:2}).b\")") +(epoch 382) +(eval "(js-eval \"({a:{b:{c:42}}}).a.b.c\")") +(epoch 383) +(eval "(js-eval \"({})\")") + +;; Arrow functions and calls +(epoch 390) +(eval "(js-eval \"(x => x + 1)(41)\")") +(epoch 391) +(eval "(js-eval \"((a,b) => a + b)(3,4)\")") +(epoch 392) +(eval "(js-eval \"(x => y => x + y)(3)(4)\")") +(epoch 393) +(eval "(js-eval \"(() => 42)()\")") + +;; Member access chains + function calls +(epoch 400) +(eval "(js-eval \"Math.abs(-5)\")") +(epoch 401) +(eval "(js-eval \"Math.floor(3.9)\")") +(epoch 402) +(eval "(js-eval \"Math.ceil(3.1)\")") +(epoch 403) +(eval "(js-eval \"Math.max(1,5,3)\")") +(epoch 404) +(eval "(js-eval \"Math.min(1,5,3)\")") +(epoch 405) +(eval "(js-eval \"Math.PI > 3\")") + +;; typeof +(epoch 410) +(eval "(js-eval \"typeof 42\")") +(epoch 411) +(eval "(js-eval \"typeof 'x'\")") +(epoch 412) +(eval "(js-eval \"typeof true\")") +(epoch 413) +(eval "(js-eval \"typeof undefined\")") +(epoch 414) +(eval "(js-eval \"typeof null\")") +(epoch 415) +(eval "(js-eval \"typeof (x => x)\")") + +;; ── Phase 6: statements ───────────────────────────────────────── +;; Parser-level — program shape and statements +(epoch 500) +(eval "(js-parse (js-tokenize \"let x = 1;\"))") +(epoch 501) +(eval "(js-parse (js-tokenize \"const x = 1, y = 2;\"))") +(epoch 502) +(eval "(js-parse (js-tokenize \"var x;\"))") +(epoch 503) +(eval "(js-parse (js-tokenize \"if (x) y else z\"))") +(epoch 504) +(eval "(js-parse (js-tokenize \"while (x) y\"))") +(epoch 505) +(eval "(js-parse (js-tokenize \"do y while (x);\"))") +(epoch 506) +(eval "(js-parse (js-tokenize \"for (let i = 0; i < 5; i = i + 1) x\"))") +(epoch 507) +(eval "(js-parse (js-tokenize \"{ a; b; c; }\"))") +(epoch 508) +(eval "(js-parse (js-tokenize \"return;\"))") +(epoch 509) +(eval "(js-parse (js-tokenize \"break;\"))") +(epoch 510) +(eval "(js-parse (js-tokenize \"continue;\"))") +(epoch 511) +(eval "(js-parse (js-tokenize \"function f(x) { return x; }\"))") +(epoch 512) +(eval "(js-parse (js-tokenize \";;;;\"))") + +;; Runtime — statements evaluate +(epoch 520) +(eval "(js-eval \"let x = 1; let y = 2; x + y\")") +(epoch 521) +(eval "(js-eval \"let x = 10; if (x > 5) 1 else 2\")") +(epoch 522) +(eval "(js-eval \"let x = 0; while (x < 5) { x = x + 1; } x\")") +(epoch 523) +(eval "(js-eval \"let x = 0; do { x = x + 1; } while (x < 3); x\")") +(epoch 524) +(eval "(js-eval \"let n = 0; for (let i = 0; i < 10; i = i + 1) n = n + i; n\")") +(epoch 525) +(eval "(js-eval \"let x = 0; for (let i = 0; i < 10; i = i + 1) { if (i === 5) break; x = i; } x\")") +(epoch 526) +(eval "(js-eval \"let s = 0; for (let i = 0; i < 10; i = i + 1) { if (i % 2 === 0) continue; s = s + i; } s\")") +(epoch 527) +(eval "(js-eval \"{ let x = 1; let y = 2; x + y }\")") +(epoch 528) +(eval "(js-eval \"let a; a\")") +(epoch 529) +(eval "(js-eval \"let a = 5, b = 10; a * b\")") +(epoch 530) +(eval "(js-eval \"let i = 0; while (true) { i = i + 1; if (i >= 3) break; } i\")") + +;; ── Phase 7: functions and scoping ────────────────────────────── +(epoch 600) +(eval "(js-eval \"function add(a, b) { return a + b; } add(3, 4)\")") +(epoch 601) +(eval "(js-eval \"function fact(n) { if (n <= 1) return 1; return n * fact(n - 1); } fact(6)\")") +(epoch 602) +(eval "(js-eval \"function fib(n) { if (n < 2) return n; return fib(n-1) + fib(n-2); } fib(10)\")") +(epoch 603) +(eval "(js-eval \"function outer() { function inner() { return 42; } return inner(); } outer()\")") +(epoch 604) +(eval "(js-eval \"function make(n) { return function() { return n; }; } make(7)()\")") +(epoch 605) +(eval "(js-eval \"function counter() { let n = 0; return () => { n = n + 1; return n; }; } let c = counter(); c(); c(); c()\")") +(epoch 606) +(eval "(js-eval \"function f(x = 5) { return x; } f()\")") +(epoch 607) +(eval "(js-eval \"function f(x = 5) { return x; } f(10)\")") +(epoch 608) +(eval "(js-eval \"function f(x, y = 100) { return x + y; } f(1)\")") +(epoch 609) +(eval "(js-eval \"function sum(...nums) { let t = 0; for (let i = 0; i < nums.length; i = i + 1) t = t + nums[i]; return t; } sum(1,2,3,4,5)\")") +(epoch 610) +(eval "(js-eval \"function f() { return; } f()\")") +(epoch 611) +(eval "(js-eval \"function twice(f, x) { return f(f(x)); } twice(n => n + 1, 10)\")") +(epoch 612) +(eval "(js-eval \"function early(n) { for (let i = 0; i < 100; i = i + 1) { if (i === n) return i * 2; } return -1; } early(7)\")") +(epoch 613) +(eval "(js-eval \"var x = 1; x + 1\")") +(epoch 614) +(eval "(js-eval \"function f() { let y = 10; return y; } f()\")") +(epoch 615) +(eval "(js-eval \"let f = x => { let y = x + 1; return y * 2; }; f(3)\")") +(epoch 616) +(eval "(js-eval \"(function(x) { return x * 2; })(21)\")") + +;; ── Phase 8: objects, prototypes, `this`, `new`, classes ───────── +;; Array mutation +(epoch 700) +(eval "(js-eval \"var a = [1,2,3]; a[0] = 99; a[0]\")") +(epoch 701) +(eval "(js-eval \"var a = []; a.push(1); a.push(2); a.push(3); a.length\")") +(epoch 702) +(eval "(js-eval \"var a = [10,20,30]; a[1] = 99; a[1]\")") +(epoch 703) +(eval "(js-eval \"var a = [1,2,3]; a.indexOf(2)\")") +(epoch 704) +(eval "(js-eval \"var a = [1,2,3]; a.join('-')\")") + +;; Object property set/get +(epoch 710) +(eval "(js-eval \"var o = { x: 1 }; o.y = 2; o.x + o.y\")") +(epoch 711) +(eval "(js-eval \"var o = {}; o['k'] = 42; o.k\")") + +;; Method calls — `this` +(epoch 720) +(eval "(js-eval \"var o = { x: 5, getX: function() { return this.x; } }; o.getX()\")") +(epoch 721) +(eval "(js-eval \"var o = { x: 5, scale: function(n) { return this.x * n; } }; o.scale(4)\")") +(epoch 722) +(eval "(js-eval \"var o = { nums: [1,2,3], first: function() { return this.nums[0]; } }; o.first()\")") + +;; Arrow fn — lexical `this` +(epoch 730) +(eval "(js-eval \"var o = { x: 5, get: function() { var f = () => this.x; return f(); } }; o.get()\")") + +;; `new` + constructor +(epoch 740) +(eval "(js-eval \"function Foo() { this.x = 42; } var f = new Foo(); f.x\")") +(epoch 741) +(eval "(js-eval \"function Pt(x,y) { this.x = x; this.y = y; } var p = new Pt(3,4); p.x + p.y\")") + +;; Prototype chain +(epoch 750) +(eval "(js-eval \"function Pt(x,y) { this.x = x; this.y = y; } Pt.prototype.sum = function() { return this.x + this.y; }; var p = new Pt(3,4); p.sum()\")") +(epoch 751) +(eval "(js-eval \"function A() {} A.prototype.foo = 99; var a = new A(); a.foo\")") + +;; instanceof +(epoch 760) +(eval "(js-eval \"function A() {} var a = new A(); a instanceof A\")") +(epoch 761) +(eval "(js-eval \"function A() {} function B() {} var a = new A(); a instanceof B\")") + +;; `in` operator +(epoch 770) +(eval "(js-eval \"var o = { x: 1 }; 'x' in o\")") +(epoch 771) +(eval "(js-eval \"var o = { x: 1 }; 'y' in o\")") + +;; ES6 classes +(epoch 780) +(eval "(js-eval \"class Pt { constructor(x,y) { this.x = x; this.y = y; } sum() { return this.x + this.y; } } var p = new Pt(3,4); p.sum()\")") +(epoch 781) +(eval "(js-eval \"class A { hello() { return 'A'; } } class B extends A { } var b = new B(); b.hello()\")") +(epoch 782) +(eval "(js-eval \"class A { hello() { return 'A'; } } class B extends A { hello() { return 'B'; } } var b = new B(); b.hello()\")") +(epoch 783) +(eval "(js-eval \"class Pt { constructor(x,y) { this.x = x; this.y = y; } } var p = new Pt(1,2); p instanceof Pt\")") + +;; Throw / try / catch / finally +(epoch 790) +(eval "(js-eval \"try { throw 'boom' } catch(e) { e }\")") +(epoch 791) +(eval "(js-eval \"try { throw { msg: 'nope' } } catch(e) { e.msg }\")") +(epoch 792) +(eval "(js-eval \"var x = 0; try { x = 1; throw 'e'; } catch(e) { x = 2; } finally { x = 3; } x\")") +(epoch 793) +(eval "(js-eval \"try { throw new Error('oops') } catch(e) { e.message }\")") +(epoch 794) +(eval "(js-eval \"try { throw new TypeError('bad') } catch(e) { e.name + ':' + e.message }\")") + +;; ── Phase 9 parser: async/await AST shape ──────────────────────── +(epoch 795) +(eval "(js-parse-expr \"await f()\")") +(epoch 796) +(eval "(js-parse-expr \"async x => x + 1\")") +(epoch 797) +(eval "(js-parse-expr \"async (a, b) => a + b\")") +(epoch 798) +(eval "(js-parse-expr \"async () => 42\")") +(epoch 799) +(eval "(js-parse-expr \"async function(a) { return a; }\")") + +;; ── Phase 9: Promises & async/await ────────────────────────────── +;; Tests use __drain() to force microtask queue to run before +;; reading the result variable. +;; +;; Promise construction, resolve/reject, .then / .catch / .finally +(epoch 800) +(eval "(js-eval \"var p = Promise.resolve(42); p.state\")") +(epoch 801) +(eval "(js-eval \"var p = Promise.reject('err'); p.state\")") +(epoch 802) +(eval "(js-eval \"var r = null; Promise.resolve(7).then(x => { r = x + 1; }); __drain(); r\")") +(epoch 803) +(eval "(js-eval \"var r = null; Promise.reject('boom').catch(e => { r = 'caught:' + e; }); __drain(); r\")") +(epoch 804) +(eval "(js-eval \"var r = null; var hit = 0; Promise.resolve(5).finally(() => { hit = 1; }).then(v => { r = v; }); __drain(); '' + hit + ':' + r\")") + +;; .then chaining +(epoch 810) +(eval "(js-eval \"var r = null; Promise.resolve(1).then(x => x + 1).then(x => x * 10).then(x => { r = x; }); __drain(); r\")") +(epoch 811) +(eval "(js-eval \"var r = null; Promise.resolve(1).then(x => { throw 'oops'; }).catch(e => { r = e; }); __drain(); r\")") + +;; new Promise(executor) +(epoch 820) +(eval "(js-eval \"var r = null; new Promise((res, rej) => res(123)).then(v => { r = v; }); __drain(); r\")") +(epoch 821) +(eval "(js-eval \"var r = null; new Promise((res, rej) => rej('nope')).catch(e => { r = e; }); __drain(); r\")") +(epoch 822) +(eval "(js-eval \"var r = null; new Promise((res, rej) => { throw 'executor-threw'; }).catch(e => { r = e; }); __drain(); r\")") + +;; Promise.all and Promise.race +(epoch 830) +(eval "(js-eval \"var r = null; Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then(vs => { r = vs[0] + vs[1] + vs[2]; }); __drain(); r\")") +(epoch 831) +(eval "(js-eval \"var r = null; Promise.all([1, 2, 3]).then(vs => { r = vs.length; }); __drain(); r\")") +(epoch 832) +(eval "(js-eval \"var r = null; Promise.all([Promise.resolve(1), Promise.reject('x')]).catch(e => { r = e; }); __drain(); r\")") +(epoch 833) +(eval "(js-eval \"var r = null; Promise.race([Promise.resolve('first'), Promise.resolve('second')]).then(v => { r = v; }); __drain(); r\")") + +;; async/await basics +(epoch 840) +(eval "(js-eval \"async function f() { return 1; } var r = null; f().then(v => { r = v; }); __drain(); r\")") +(epoch 841) +(eval "(js-eval \"async function f() { return 2 + 3; } var r = null; f().then(v => { r = v; }); __drain(); r\")") +(epoch 842) +(eval "(js-eval \"async function a() { return 10; } async function b() { var x = await a(); return x + 1; } var r = null; b().then(v => { r = v; }); __drain(); r\")") +(epoch 843) +(eval "(js-eval \"async function throws() { throw 'x'; } var r = null; throws().catch(e => { r = e; }); __drain(); r\")") +(epoch 844) +(eval "(js-eval \"async function div(a,b) { if (b === 0) throw 'divZero'; return a / b; } var r = null; div(10,2).then(v => { r = v; }); __drain(); r\")") +(epoch 845) +(eval "(js-eval \"async function div(a,b) { if (b === 0) throw 'divZero'; return a / b; } var r = null; div(10,0).catch(e => { r = e; }); __drain(); r\")") + +;; async arrow functions +(epoch 850) +(eval "(js-eval \"var f = async x => x * 2; var r = null; f(21).then(v => { r = v; }); __drain(); r\")") +(epoch 851) +(eval "(js-eval \"var f = async (a,b) => a - b; var r = null; f(10,3).then(v => { r = v; }); __drain(); r\")") +(epoch 852) +(eval "(js-eval \"var f = async () => 99; var r = null; f().then(v => { r = v; }); __drain(); r\")") + +;; await chains multi-step +(epoch 860) +(eval "(js-eval \"async function a() { return 10; } async function b() { var x = await a(); var y = await a(); return x + y; } var r = null; b().then(v => { r = v; }); __drain(); r\")") +(epoch 861) +(eval "(js-eval \"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\")") + +;; typeof a Promise → object +(epoch 870) +(eval "(js-eval \"typeof Promise.resolve(1)\")") + +;; ── Phase 11: template strings ──────────────────────────────────── +;; Lexer — plain template (no interpolation) still returns a string +(epoch 900) +(eval "(get (nth (js-tokenize \"`hello`\") 0) :type)") +(epoch 901) +(eval "(get (nth (js-tokenize \"`hello`\") 0) :value)") +;; Lexer — interpolated template yields a list payload +(epoch 902) +(eval "(list? (get (nth (js-tokenize \"`hi ${x}!`\") 0) :value))") +(epoch 903) +(eval "(len (get (nth (js-tokenize \"`hi ${x}!`\") 0) :value))") + +;; Parser — plain template becomes (js-str ...) +(epoch 910) +(eval "(js-parse-expr \"`hi`\")") +;; Parser — interpolated template becomes (js-tpl (parts...)) +(epoch 911) +(eval "(js-parse-expr \"`a${1}b`\")") +(epoch 912) +(eval "(js-parse-expr \"`${x}`\")") + +;; js-eval — no interpolation +(epoch 920) +(eval "(js-eval \"`hello`\")") +(epoch 921) +(eval "(js-eval \"``\")") +;; Single interpolation +(epoch 922) +(eval "(js-eval \"`hi ${42}!`\")") +;; Multiple interpolations + string coercion +(epoch 923) +(eval "(js-eval \"var a=2,b=3; `${a}+${b}=${a+b}`\")") +;; Identifier coercion +(epoch 924) +(eval "(js-eval \"var x = 5; `num: ${x}`\")") +;; Boolean and null/undefined coercion +(epoch 925) +(eval "(js-eval \"`b=${true},n=${null},u=${undefined}`\")") +;; Member access in expr +(epoch 926) +(eval "(js-eval \"var o={x:1,y:2}; `${o.x+o.y}`\")") +;; Array indexing and .length +(epoch 927) +(eval "(js-eval \"var xs=[1,2,3]; `first=${xs[0]}, len=${xs.length}`\")") +;; Nested ternary inside ${} +(epoch 928) +(eval "(js-eval \"var n=5; `n is ${n>0?'pos':'neg'}`\")") +;; Nested template inside interpolation +(epoch 929) +(eval "(js-eval \"`outer ${`inner ${1+2}`} end`\")") +;; Call inside interpolation +(epoch 930) +(eval "(js-eval \"var f = (x)=>x*2; `f(3)=${f(3)}`\")") +;; Template as arrow-fn body +(epoch 931) +(eval "(js-eval \"var g = (x) => `val=${x}`; g(42)\")") +;; Escape sequences: \n +(epoch 932) +(eval "(js-eval \"`a\\nb`.length\")") +;; Escaped dollar — literal ${} (no interpolation) +(epoch 933) +(eval "(js-eval \"`\\${not-expr}`\")") +;; Escaped backtick inside template +(epoch 934) +(eval "(js-eval \"`has\\`tick`\")") +;; Interpolation adjacency (no space) +(epoch 935) +(eval "(js-eval \"`${1}${2}${3}`\")") +;; Interpolation only (no plain text) +(epoch 936) +(eval "(js-eval \"`${42}`\")") +;; Object in interpolation yields dict string (our js-to-string policy) +(epoch 937) +(eval "(js-eval \"var xs=[1,2]; `len is ${xs.length + 10}`\")") +;; Empty interpolations between text +(epoch 938) +(eval "(js-eval \"`[${''}-${''}]`\")") + +EPOCHS + +OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) + +check() { + local epoch="$1" desc="$2" expected="$3" + local actual + actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1) + if [ -z "$actual" ]; then + actual=$(echo "$OUTPUT" | grep "^(ok $epoch " || true) + fi + if [ -z "$actual" ]; then + actual=$(echo "$OUTPUT" | grep "^(error $epoch " || true) + fi + if [ -z "$actual" ]; then + actual="" + fi + + if echo "$actual" | grep -qF -- "$expected"; then + PASS=$((PASS + 1)) + [ "$VERBOSE" = "-v" ] && echo " ✓ $desc" + else + FAIL=$((FAIL + 1)) + ERRORS+=" ✗ $desc (epoch $epoch) + expected: $expected + actual: $actual +" + fi +} + +# ── Smoke: stubs load and return something sensible ──────────────── +check 10 "js-tokenize empty tokens length" '1' +check 11 "js-parse empty" '()' +check 12 "js-transpile passes" '()' +check 13 "to-boolean 0 false" 'false' +check 14 "to-boolean 1 true" 'true' +check 15 "to-boolean \"\" false" 'false' +check 16 "to-boolean \"x\" true" 'true' + +# ── Phase 1: lexer ──────────────────────────────────────────────── +check 100 "empty → 1 EOF token" '1' +check 101 "empty[0] is EOF" '"eof"' +check 102 "whitespace only" '1' + +check 110 "'42' is number" '"number"' +check 111 "'42' value" '42' +check 112 "'3.14' value" '3.14' +check 113 "'1e3' value" '1000' +check 114 "'0xff' value" '255' +check 115 "'.5' value" '0.5' + +check 120 '"hi" string value' '"hi"' +check 121 "'ab' string value" '"ab"' +check 122 'escaped \n string' '"a\nb"' + +check 130 "'foo' is ident" '"ident"' +check 131 "'if' is keyword" '"keyword"' +check 132 "'_x1' value" '"_x1"' +check 133 "'\$y' value" '"$y"' + +check 140 "'===' full match" '"==="' +check 141 "'!==' full match" '"!=="' +check 142 "'<=' full match" '"<="' +check 143 "'&&' full match" '"&&"' +check 144 "'=>' full match" '"=>"' +check 145 "'...' spread" '"..."' + +check 150 "'(' is punct" '"punct"' +check 151 "';' value" '";"' + +check 160 "line comment → EOF" '1' +check 161 "block comment → EOF" '1' +check 162 "comment splits 1|2 first" '1' +check 163 "comment splits 1|2 second" '2' + +check 170 "'a + b * 3' → 6 tokens incl EOF" '6' +check 171 "'a + b * 3' tok1 is +" '"+"' +check 172 "'a + b * 3' tok3 is *" '"*"' + +# ── Phase 2: parser ─────────────────────────────────────────────── +# Literals +check 200 "num literal" '(js-num 42)' +check 201 "float literal" '(js-num 3.14)' +check 202 "string literal" '(js-str "hi")' +check 203 "true literal" '(js-bool true)' +check 204 "false literal" '(js-bool false)' +check 205 "null literal" '(js-null)' +check 206 "undefined literal" '(js-undef)' +check 207 "this literal" '(js-ident "this")' +check 208 "ident literal" '(js-ident "foo")' + +# Binary operators with precedence +check 210 "1+2" '(js-binop "+" (js-num 1) (js-num 2))' +check 211 "a + b * c" '(js-binop "+" (js-ident "a") (js-binop "*" (js-ident "b") (js-ident "c")))' +check 212 "a * b + c" '(js-binop "+" (js-binop "*" (js-ident "a") (js-ident "b")) (js-ident "c"))' +check 213 "a === b" '(js-binop "===" (js-ident "a") (js-ident "b"))' +check 214 "a && b || c" '(js-binop "||" (js-binop "&&" (js-ident "a") (js-ident "b")) (js-ident "c"))' +check 215 "a ** b ** c (right-assoc)" '(js-binop "**" (js-ident "a") (js-binop "**" (js-ident "b") (js-ident "c")))' +check 216 "(a + b) * c" '(js-binop "*" (js-binop "+" (js-ident "a") (js-ident "b")) (js-ident "c"))' + +# Unary operators +check 220 "-x" '(js-unop "-" (js-ident "x"))' +check 221 "!x" '(js-unop "!" (js-ident "x"))' +check 222 "~x" '(js-unop "~" (js-ident "x"))' +check 223 "typeof x" '(js-unop "typeof" (js-ident "x"))' +check 224 "void x" '(js-unop "void" (js-ident "x"))' +check 225 "+x" '(js-unop "+" (js-ident "x"))' + +# Member / index +check 230 "a.b" '(js-member (js-ident "a") "b")' +check 231 "a.b.c" '(js-member (js-member (js-ident "a") "b") "c")' +check 232 "a[0]" '(js-index (js-ident "a") (js-num 0))' +check 233 "a[b+1]" '(js-index (js-ident "a") (js-binop "+" (js-ident "b") (js-num 1)))' + +# Function calls +check 240 "f()" '(js-call (js-ident "f") ())' +check 241 "f(x)" '(js-call (js-ident "f") ((js-ident "x")))' +check 242 "f(a,b,c)" '(js-call (js-ident "f") ((js-ident "a") (js-ident "b") (js-ident "c")))' +check 243 "a.b(c)" '(js-call (js-member (js-ident "a") "b") ((js-ident "c")))' +check 244 "a.b(c)[d]" '(js-index (js-call (js-member (js-ident "a") "b") ((js-ident "c"))) (js-ident "d"))' + +# Array literals +check 250 "[]" '(js-array ())' +check 251 "[1,2,3]" '(js-array ((js-num 1) (js-num 2) (js-num 3)))' +check 252 "[[1,2],[3,4]]" '(js-array ((js-array ((js-num 1) (js-num 2))) (js-array ((js-num 3) (js-num 4)))))' + +# Object literals +check 260 "{}" '(js-object ())' +check 261 "{a:1}" '(js-object ({:value (js-num 1) :key "a"}))' +check 262 "{a:1,b:2}" '(js-object ({:value (js-num 1) :key "a"} {:value (js-num 2) :key "b"}))' +check 263 '{"k":1}' '(js-object ({:value (js-num 1) :key "k"}))' + +# Conditional +check 270 "a?b:c" '(js-cond (js-ident "a") (js-ident "b") (js-ident "c"))' +check 271 "nested ternary" '(js-cond (js-ident "a") (js-cond (js-ident "b") (js-ident "c") (js-ident "d")) (js-ident "e"))' + +# Arrow functions +check 280 "x => x+1" '(js-arrow ("x") (js-binop "+" (js-ident "x") (js-num 1)))' +check 281 "() => 42" '(js-arrow () (js-num 42))' +check 282 "(a,b) => a+b" '(js-arrow ("a" "b") (js-binop "+" (js-ident "a") (js-ident "b")))' +check 283 "x => y => x+y" '(js-arrow ("x") (js-arrow ("y") (js-binop "+" (js-ident "x") (js-ident "y"))))' + +# Assignment +check 290 "a = b" '(js-assign "=" (js-ident "a") (js-ident "b"))' +check 291 "a = b = c (right-assoc)" '(js-assign "=" (js-ident "a") (js-assign "=" (js-ident "b") (js-ident "c")))' +check 292 "a += 1" '(js-assign "+=" (js-ident "a") (js-num 1))' + +# ── Phase 3: transpile + end-to-end js-eval ────────────────────── +# Literals +check 300 "js-eval 42" '42' +check 301 "js-eval 3.14" '3.14' +check 302 "js-eval \"hi\"" '"hi"' +check 303 "js-eval true" 'true' +check 304 "js-eval false" 'false' +check 305 "js-eval null" 'nil' +check 306 "js-eval undefined" '"js-undefined"' + +# Arithmetic +check 310 "js-eval 1+2" '3' +check 311 "js-eval 1+2*3" '7' +check 312 "js-eval (1+2)*3" '9' +check 313 "js-eval 10-4" '6' +check 314 "js-eval 10/4" '2.5' +check 315 "js-eval 10%3" '1' +check 316 "js-eval 2**10" '1024' +check 317 "js-eval -5" '-5' +check 318 "js-eval +5" '5' +check 319 "js-eval ~5" '-6' + +# String concat +check 320 "js-eval \"a\"+\"b\"" '"ab"' +check 321 "js-eval 1+\"2\"" '"12"' +check 322 "js-eval \"n=\"+42" '"n=42"' + +# Comparisons +check 330 "js-eval 1===1" 'true' +check 331 "js-eval 1===2" 'false' +check 332 "js-eval 1===\"1\"" 'false' +check 333 "js-eval 1!==1" 'false' +check 334 "js-eval 1<2" 'true' +check 335 "js-eval 2<=2" 'true' +check 336 "js-eval 3>1" 'true' +check 337 "js-eval 3>=3" 'true' +check 338 "js-eval 'a'<'b'" 'true' + +# Loose equality +check 340 "js-eval 1==\"1\"" 'true' +check 341 "js-eval null==undef" 'true' +check 342 "js-eval 0==false" 'true' + +# Logical +check 350 "js-eval true&&false" 'false' +check 351 "js-eval 1&&2" '2' +check 352 "js-eval 0&&2" '0' +check 353 "js-eval false||5" '5' +check 354 "js-eval 0||\"x\"" '"x"' +check 355 "js-eval null??7" '7' +check 356 "js-eval 0??7" '0' +check 357 "js-eval !true" 'false' +check 358 "js-eval !0" 'true' + +# Conditional +check 360 "js-eval true?10:20" '10' +check 361 "js-eval 0?10:20" '20' +check 362 "js-eval ternary" '"yes"' + +# Arrays +check 370 "js-eval [1,2,3]" '(1 2 3)' +check 371 "js-eval arr.length" '3' +check 372 "js-eval arr[1]" '2' +check 373 "js-eval nested arr" '3' +check 374 "js-eval []" '()' + +# Objects +check 380 "js-eval obj.a" '1' +check 381 "js-eval obj.b" '2' +check 382 "js-eval deep obj" '42' +check 383 "js-eval {}" '{}' + +# Arrow functions +check 390 "js-eval x=>x+1" '42' +check 391 "js-eval (a,b)=>a+b" '7' +check 392 "js-eval curried" '7' +check 393 "js-eval ()=>42" '42' + +# Member calls +check 400 "Math.abs(-5)" '5' +check 401 "Math.floor(3.9)" '3' +check 402 "Math.ceil(3.1)" '4' +check 403 "Math.max" '5' +check 404 "Math.min" '1' +check 405 "Math.PI>3" 'true' + +# typeof +check 410 "typeof 42" '"number"' +check 411 "typeof 'x'" '"string"' +check 412 "typeof true" '"boolean"' +check 413 "typeof undefined" '"undefined"' +check 414 "typeof null" '"object"' +check 415 "typeof arrow" '"function"' + +# ── Phase 6: statements (parser) ────────────────────────────────── +check 500 "let decl" '(js-program ((js-var "let" ((js-vardecl "x" (js-num 1))))))' +check 501 "const multi-decl" '(js-program ((js-var "const" ((js-vardecl "x" (js-num 1)) (js-vardecl "y" (js-num 2))))))' +check 502 "var no init" '(js-program ((js-var "var" ((js-vardecl "x" (js-undef))))))' +check 503 "if-else" '(js-program ((js-if (js-ident "x") (js-exprstmt (js-ident "y")) (js-exprstmt (js-ident "z")))))' +check 504 "while" '(js-program ((js-while (js-ident "x") (js-exprstmt (js-ident "y")))))' +check 505 "do-while" '(js-program ((js-do-while (js-exprstmt (js-ident "y")) (js-ident "x"))))' +check 506 "for" 'js-for' +check 507 "block" 'js-block' +check 508 "return;" '(js-program ((js-return nil)))' +check 509 "break;" '(js-program ((js-break)))' +check 510 "continue;" '(js-program ((js-continue)))' +check 511 "function decl" 'js-funcdecl' +check 512 "empty stmts skipped" 'js-empty' + +# ── Phase 6: statement evaluation ───────────────────────────────── +check 520 "multi-decl sum" '3' +check 521 "if expr" '1' +check 522 "while incr" '5' +check 523 "do-while incr" '3' +check 524 "for sum 0..9" '45' +check 525 "break" '4' +check 526 "continue" '25' +check 527 "block scope" '3' +check 528 "let a uninit" '"js-undefined"' +check 529 "let multi-decl *" '50' +check 530 "break from while(true)" '3' + +# ── Phase 7: functions ──────────────────────────────────────────── +check 600 "function add" '7' +check 601 "fact(6)" '720' +check 602 "fib(10)" '55' +check 603 "nested funcs" '42' +check 604 "closure" '7' +check 605 "counter closure" '3' +check 606 "default param missing" '5' +check 607 "default param passed" '10' +check 608 "default second arg" '101' +check 609 "rest params" '15' +check 610 "bare return" '"js-undefined"' +check 611 "higher-order" '12' +check 612 "early return from loop" '14' +check 613 "var decl" '2' +check 614 "inner let" '10' +check 615 "arrow block body" '8' +check 616 "iife function expr" '42' + +# ── Phase 8: objects, prototypes, `this`, `new`, classes ────────── +check 700 "array[i] = v; array[i]" '99' +check 701 "array.push length" '3' +check 702 "array middle mutation" '99' +check 703 "array.indexOf" '1' +check 704 "array.join" '"1-2-3"' + +check 710 "obj.y = v; obj.x + obj.y" '3' +check 711 "obj[k] = v; obj.k" '42' + +check 720 "method this.x" '5' +check 721 "method this.x * n" '20' +check 722 "method this.nums[0]" '1' + +check 730 "arrow lexical this" '5' + +check 740 "new Foo() this.x" '42' +check 741 "new Pt(x,y) this" '7' + +check 750 "proto.sum with this" '7' +check 751 "proto prop lookup" '99' + +check 760 "a instanceof A" 'true' +check 761 "a instanceof B" 'false' + +check 770 "'x' in o true" 'true' +check 771 "'y' in o false" 'false' + +check 780 "class Pt sum()" '7' +check 781 "class inherit method" '"A"' +check 782 "class override method" '"B"' +check 783 "class instanceof" 'true' + +check 790 "try/catch string" '"boom"' +check 791 "try/catch object" '"nope"' +check 792 "finally runs" '3' +check 793 "new Error caught" '"oops"' +check 794 "new TypeError caught" '"TypeError:bad"' + +# ── Phase 9 parser: async/await AST ─────────────────────────────── +check 795 "await parse" '(js-await (js-call (js-ident "f") ()))' +check 796 "async single arrow" '(js-arrow-async ("x") (js-binop "+" (js-ident "x") (js-num 1)))' +check 797 "async multi-arrow" '(js-arrow-async ("a" "b") (js-binop "+" (js-ident "a") (js-ident "b")))' +check 798 "async empty arrow" '(js-arrow-async () (js-num 42))' +check 799 "async funcexpr" 'js-funcexpr-async' + +# ── Phase 9: Promises & async/await ─────────────────────────────── +check 800 "Promise.resolve state" '"fulfilled"' +check 801 "Promise.reject state" '"rejected"' +check 802 "then basic" '8' +check 803 "catch basic" '"caught:boom"' +check 804 "finally pass-through" '"1:5"' + +check 810 ".then chain" '20' +check 811 ".then throw → catch" '"oops"' + +check 820 "new Promise resolve" '123' +check 821 "new Promise reject" '"nope"' +check 822 "executor throw → reject" '"executor-threw"' + +check 830 "Promise.all" '6' +check 831 "Promise.all with non-Promise values" '3' +check 832 "Promise.all reject" '"x"' +check 833 "Promise.race first" '"first"' + +check 840 "async fn basic" '1' +check 841 "async fn computed" '5' +check 842 "await fn" '11' +check 843 "async throw" '"x"' +check 844 "async + catch ok" '5' +check 845 "async + catch reject" '"divZero"' + +check 850 "async arrow single" '42' +check 851 "async arrow pair" '7' +check 852 "async arrow empty" '99' + +check 860 "await x2" '20' +check 861 "await Promise.all" '6' + +check 870 "typeof Promise" '"object"' + +# ── Phase 11: template strings ──────────────────────────────────── +check 900 "tpl plain: type" '"template"' +check 901 "tpl plain: value" '"hello"' +check 902 "tpl interp: value is list" 'true' +check 903 "tpl interp: 2 parts" '2' + +check 910 "parse plain" '(js-str "hi")' +check 911 'parse interp a${1}b' '(js-tpl ((js-str "a") (js-num 1) (js-str "b")))' +check 912 'parse ${x} only' '(js-tpl ((js-ident "x")))' + +check 920 "plain" '"hello"' +check 921 "empty template" '""' +check 922 'single ${42}' '"hi 42!"' +check 923 "multi interp" '"2+3=5"' +check 924 "ident interp" '"num: 5"' +check 925 "bool/null/undef coerce" '"b=true,n=null,u=undefined"' +check 926 "member access interp" '"3"' +check 927 "array index+length" '"first=1, len=3"' +check 928 "nested ternary" '"n is pos"' +check 929 "nested template" '"outer inner 3 end"' +check 930 'call in ${}' '"f(3)=6"' +check 931 "arrow returns tpl" '"val=42"' +check 932 "escape \\n length" '3' +check 933 "escaped dollar" '${not-expr}' +check 934 "escaped backtick" '"has`tick"' +check 935 "adjacent interps" '"123"' +check 936 'bare ${42}' '"42"' +check 937 "expr in interp" '"len is 12"' +check 938 "empty interps" '"[-]"' + +TOTAL=$((PASS + FAIL)) +if [ $FAIL -eq 0 ]; then + echo "✓ $PASS/$TOTAL JS-on-SX tests passed" +else + echo "✗ $PASS/$TOTAL passed, $FAIL failed:" + echo "" + echo "$ERRORS" +fi + +[ $FAIL -eq 0 ] diff --git a/lib/js/test262-runner.py b/lib/js/test262-runner.py new file mode 100644 index 00000000..de6ec956 --- /dev/null +++ b/lib/js/test262-runner.py @@ -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:])) diff --git a/lib/js/test262-scoreboard.json b/lib/js/test262-scoreboard.json new file mode 100644 index 00000000..39d2c6d5 --- /dev/null +++ b/lib/js/test262-scoreboard.json @@ -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 +} \ No newline at end of file diff --git a/lib/js/test262-scoreboard.md b/lib/js/test262-scoreboard.md new file mode 100644 index 00000000..bd1f6728 --- /dev/null +++ b/lib/js/test262-scoreboard.md @@ -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) diff --git a/lib/js/test262-slice/README.md b/lib/js/test262-slice/README.md new file mode 100644 index 00000000..a75141ae --- /dev/null +++ b/lib/js/test262-slice/README.md @@ -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. diff --git a/lib/js/test262-slice/arithmetic/add.expected b/lib/js/test262-slice/arithmetic/add.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/arithmetic/add.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/arithmetic/add.js b/lib/js/test262-slice/arithmetic/add.js new file mode 100644 index 00000000..e0ef5840 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/add.js @@ -0,0 +1 @@ +1 + 2 diff --git a/lib/js/test262-slice/arithmetic/big_expr.expected b/lib/js/test262-slice/arithmetic/big_expr.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/big_expr.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/arithmetic/big_expr.js b/lib/js/test262-slice/arithmetic/big_expr.js new file mode 100644 index 00000000..a5297651 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/big_expr.js @@ -0,0 +1 @@ +1 + 2 * 3 - 4 / 2 diff --git a/lib/js/test262-slice/arithmetic/bitnot.expected b/lib/js/test262-slice/arithmetic/bitnot.expected new file mode 100644 index 00000000..3cfb5efd --- /dev/null +++ b/lib/js/test262-slice/arithmetic/bitnot.expected @@ -0,0 +1 @@ +-6 diff --git a/lib/js/test262-slice/arithmetic/bitnot.js b/lib/js/test262-slice/arithmetic/bitnot.js new file mode 100644 index 00000000..072132fe --- /dev/null +++ b/lib/js/test262-slice/arithmetic/bitnot.js @@ -0,0 +1 @@ +~5 diff --git a/lib/js/test262-slice/arithmetic/chained.expected b/lib/js/test262-slice/arithmetic/chained.expected new file mode 100644 index 00000000..f599e28b --- /dev/null +++ b/lib/js/test262-slice/arithmetic/chained.expected @@ -0,0 +1 @@ +10 diff --git a/lib/js/test262-slice/arithmetic/chained.js b/lib/js/test262-slice/arithmetic/chained.js new file mode 100644 index 00000000..411c616f --- /dev/null +++ b/lib/js/test262-slice/arithmetic/chained.js @@ -0,0 +1 @@ +1 + 2 + 3 + 4 diff --git a/lib/js/test262-slice/arithmetic/div.expected b/lib/js/test262-slice/arithmetic/div.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/arithmetic/div.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/arithmetic/div.js b/lib/js/test262-slice/arithmetic/div.js new file mode 100644 index 00000000..db285b14 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/div.js @@ -0,0 +1 @@ +12 / 4 diff --git a/lib/js/test262-slice/arithmetic/mixed_concat.expected b/lib/js/test262-slice/arithmetic/mixed_concat.expected new file mode 100644 index 00000000..24a06cb3 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/mixed_concat.expected @@ -0,0 +1 @@ +"12" diff --git a/lib/js/test262-slice/arithmetic/mixed_concat.js b/lib/js/test262-slice/arithmetic/mixed_concat.js new file mode 100644 index 00000000..8fcf76e2 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/mixed_concat.js @@ -0,0 +1 @@ +1 + "2" diff --git a/lib/js/test262-slice/arithmetic/mod.expected b/lib/js/test262-slice/arithmetic/mod.expected new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/lib/js/test262-slice/arithmetic/mod.expected @@ -0,0 +1 @@ +1 diff --git a/lib/js/test262-slice/arithmetic/mod.js b/lib/js/test262-slice/arithmetic/mod.js new file mode 100644 index 00000000..5ab6467f --- /dev/null +++ b/lib/js/test262-slice/arithmetic/mod.js @@ -0,0 +1 @@ +10 % 3 diff --git a/lib/js/test262-slice/arithmetic/neg.expected b/lib/js/test262-slice/arithmetic/neg.expected new file mode 100644 index 00000000..d83d2bc1 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/neg.expected @@ -0,0 +1 @@ +-5 diff --git a/lib/js/test262-slice/arithmetic/neg.js b/lib/js/test262-slice/arithmetic/neg.js new file mode 100644 index 00000000..d83d2bc1 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/neg.js @@ -0,0 +1 @@ +-5 diff --git a/lib/js/test262-slice/arithmetic/paren_precedence.expected b/lib/js/test262-slice/arithmetic/paren_precedence.expected new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/paren_precedence.expected @@ -0,0 +1 @@ +9 diff --git a/lib/js/test262-slice/arithmetic/paren_precedence.js b/lib/js/test262-slice/arithmetic/paren_precedence.js new file mode 100644 index 00000000..5dfe5729 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/paren_precedence.js @@ -0,0 +1 @@ +(1 + 2) * 3 diff --git a/lib/js/test262-slice/arithmetic/pos.expected b/lib/js/test262-slice/arithmetic/pos.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/pos.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/arithmetic/pos.js b/lib/js/test262-slice/arithmetic/pos.js new file mode 100644 index 00000000..b2497e2f --- /dev/null +++ b/lib/js/test262-slice/arithmetic/pos.js @@ -0,0 +1 @@ ++5 diff --git a/lib/js/test262-slice/arithmetic/pow.expected b/lib/js/test262-slice/arithmetic/pow.expected new file mode 100644 index 00000000..d7b1c440 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/pow.expected @@ -0,0 +1 @@ +1024 diff --git a/lib/js/test262-slice/arithmetic/pow.js b/lib/js/test262-slice/arithmetic/pow.js new file mode 100644 index 00000000..52dac862 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/pow.js @@ -0,0 +1 @@ +2 ** 10 diff --git a/lib/js/test262-slice/arithmetic/pow_right_assoc.expected b/lib/js/test262-slice/arithmetic/pow_right_assoc.expected new file mode 100644 index 00000000..4d0e90cb --- /dev/null +++ b/lib/js/test262-slice/arithmetic/pow_right_assoc.expected @@ -0,0 +1 @@ +512 diff --git a/lib/js/test262-slice/arithmetic/pow_right_assoc.js b/lib/js/test262-slice/arithmetic/pow_right_assoc.js new file mode 100644 index 00000000..665aeada --- /dev/null +++ b/lib/js/test262-slice/arithmetic/pow_right_assoc.js @@ -0,0 +1 @@ +2 ** 3 ** 2 diff --git a/lib/js/test262-slice/arithmetic/precedence.expected b/lib/js/test262-slice/arithmetic/precedence.expected new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/lib/js/test262-slice/arithmetic/precedence.expected @@ -0,0 +1 @@ +7 diff --git a/lib/js/test262-slice/arithmetic/precedence.js b/lib/js/test262-slice/arithmetic/precedence.js new file mode 100644 index 00000000..cba549f0 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/precedence.js @@ -0,0 +1 @@ +1 + 2 * 3 diff --git a/lib/js/test262-slice/arithmetic/string_concat.expected b/lib/js/test262-slice/arithmetic/string_concat.expected new file mode 100644 index 00000000..cb5537d5 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/string_concat.expected @@ -0,0 +1 @@ +"ab" diff --git a/lib/js/test262-slice/arithmetic/string_concat.js b/lib/js/test262-slice/arithmetic/string_concat.js new file mode 100644 index 00000000..2a0e65a2 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/string_concat.js @@ -0,0 +1 @@ +"a" + "b" diff --git a/lib/js/test262-slice/arithmetic/sub.expected b/lib/js/test262-slice/arithmetic/sub.expected new file mode 100644 index 00000000..1e8b3149 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/sub.expected @@ -0,0 +1 @@ +6 diff --git a/lib/js/test262-slice/arithmetic/sub.js b/lib/js/test262-slice/arithmetic/sub.js new file mode 100644 index 00000000..27a9e302 --- /dev/null +++ b/lib/js/test262-slice/arithmetic/sub.js @@ -0,0 +1 @@ +10 - 4 diff --git a/lib/js/test262-slice/async/async_arrow.expected b/lib/js/test262-slice/async/async_arrow.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/async/async_arrow.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/async/async_arrow.js b/lib/js/test262-slice/async/async_arrow.js new file mode 100644 index 00000000..b0b172ed --- /dev/null +++ b/lib/js/test262-slice/async/async_arrow.js @@ -0,0 +1,5 @@ +var double = async x => x * 2; +var r = null; +double(21).then(v => { r = v; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/async/async_arrow_multiparam.expected b/lib/js/test262-slice/async/async_arrow_multiparam.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/async/async_arrow_multiparam.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/async/async_arrow_multiparam.js b/lib/js/test262-slice/async/async_arrow_multiparam.js new file mode 100644 index 00000000..eb1549fc --- /dev/null +++ b/lib/js/test262-slice/async/async_arrow_multiparam.js @@ -0,0 +1,5 @@ +var add = async (a, b) => a + b; +var r = null; +add(10, 32).then(v => { r = v; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/async/async_fn_basic.expected b/lib/js/test262-slice/async/async_fn_basic.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/async/async_fn_basic.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/async/async_fn_basic.js b/lib/js/test262-slice/async/async_fn_basic.js new file mode 100644 index 00000000..e8ea42f1 --- /dev/null +++ b/lib/js/test262-slice/async/async_fn_basic.js @@ -0,0 +1,5 @@ +async function f() { return 42; } +var r = null; +f().then(v => { r = v; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/async/async_fn_throws.expected b/lib/js/test262-slice/async/async_fn_throws.expected new file mode 100644 index 00000000..1634764a --- /dev/null +++ b/lib/js/test262-slice/async/async_fn_throws.expected @@ -0,0 +1 @@ +nope diff --git a/lib/js/test262-slice/async/async_fn_throws.js b/lib/js/test262-slice/async/async_fn_throws.js new file mode 100644 index 00000000..f5d3c542 --- /dev/null +++ b/lib/js/test262-slice/async/async_fn_throws.js @@ -0,0 +1,5 @@ +async function f() { throw "nope"; } +var r = null; +f().catch(e => { r = e; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/async/async_nested_calls.expected b/lib/js/test262-slice/async/async_nested_calls.expected new file mode 100644 index 00000000..bb95160c --- /dev/null +++ b/lib/js/test262-slice/async/async_nested_calls.expected @@ -0,0 +1 @@ +33 diff --git a/lib/js/test262-slice/async/async_nested_calls.js b/lib/js/test262-slice/async/async_nested_calls.js new file mode 100644 index 00000000..48f4358b --- /dev/null +++ b/lib/js/test262-slice/async/async_nested_calls.js @@ -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 \ No newline at end of file diff --git a/lib/js/test262-slice/async/async_returns_promise.expected b/lib/js/test262-slice/async/async_returns_promise.expected new file mode 100644 index 00000000..29d6383b --- /dev/null +++ b/lib/js/test262-slice/async/async_returns_promise.expected @@ -0,0 +1 @@ +100 diff --git a/lib/js/test262-slice/async/async_returns_promise.js b/lib/js/test262-slice/async/async_returns_promise.js new file mode 100644 index 00000000..c716cc8a --- /dev/null +++ b/lib/js/test262-slice/async/async_returns_promise.js @@ -0,0 +1,5 @@ +async function wrap(x) { return Promise.resolve(x); } +var r = null; +wrap(100).then(v => { r = v; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/async/await_basic.expected b/lib/js/test262-slice/async/await_basic.expected new file mode 100644 index 00000000..2bbd69c2 --- /dev/null +++ b/lib/js/test262-slice/async/await_basic.expected @@ -0,0 +1 @@ +70 diff --git a/lib/js/test262-slice/async/await_basic.js b/lib/js/test262-slice/async/await_basic.js new file mode 100644 index 00000000..fd2d0ea7 --- /dev/null +++ b/lib/js/test262-slice/async/await_basic.js @@ -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 \ No newline at end of file diff --git a/lib/js/test262-slice/async/await_chain.expected b/lib/js/test262-slice/async/await_chain.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/async/await_chain.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/async/await_chain.js b/lib/js/test262-slice/async/await_chain.js new file mode 100644 index 00000000..39937d25 --- /dev/null +++ b/lib/js/test262-slice/async/await_chain.js @@ -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 \ No newline at end of file diff --git a/lib/js/test262-slice/async/await_in_loop.expected b/lib/js/test262-slice/async/await_in_loop.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/async/await_in_loop.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/async/await_in_loop.js b/lib/js/test262-slice/async/await_in_loop.js new file mode 100644 index 00000000..93c9a7c7 --- /dev/null +++ b/lib/js/test262-slice/async/await_in_loop.js @@ -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 \ No newline at end of file diff --git a/lib/js/test262-slice/async/await_nonpromise.expected b/lib/js/test262-slice/async/await_nonpromise.expected new file mode 100644 index 00000000..920a1396 --- /dev/null +++ b/lib/js/test262-slice/async/await_nonpromise.expected @@ -0,0 +1 @@ +43 diff --git a/lib/js/test262-slice/async/await_nonpromise.js b/lib/js/test262-slice/async/await_nonpromise.js new file mode 100644 index 00000000..f79c5e73 --- /dev/null +++ b/lib/js/test262-slice/async/await_nonpromise.js @@ -0,0 +1,8 @@ +async function main() { + var x = await 42; + return x + 1; +} +var r = null; +main().then(v => { r = v; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/async/await_promise_all.expected b/lib/js/test262-slice/async/await_promise_all.expected new file mode 100644 index 00000000..1e8b3149 --- /dev/null +++ b/lib/js/test262-slice/async/await_promise_all.expected @@ -0,0 +1 @@ +6 diff --git a/lib/js/test262-slice/async/await_promise_all.js b/lib/js/test262-slice/async/await_promise_all.js new file mode 100644 index 00000000..cb6ea4ee --- /dev/null +++ b/lib/js/test262-slice/async/await_promise_all.js @@ -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 \ No newline at end of file diff --git a/lib/js/test262-slice/async/await_rejected.expected b/lib/js/test262-slice/async/await_rejected.expected new file mode 100644 index 00000000..ffa15488 --- /dev/null +++ b/lib/js/test262-slice/async/await_rejected.expected @@ -0,0 +1 @@ +caught:no diff --git a/lib/js/test262-slice/async/await_rejected.js b/lib/js/test262-slice/async/await_rejected.js new file mode 100644 index 00000000..4951ac74 --- /dev/null +++ b/lib/js/test262-slice/async/await_rejected.js @@ -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 \ No newline at end of file diff --git a/lib/js/test262-slice/async/await_throws_error_object.expected b/lib/js/test262-slice/async/await_throws_error_object.expected new file mode 100644 index 00000000..67be85f1 --- /dev/null +++ b/lib/js/test262-slice/async/await_throws_error_object.expected @@ -0,0 +1 @@ +bad diff --git a/lib/js/test262-slice/async/await_throws_error_object.js b/lib/js/test262-slice/async/await_throws_error_object.js new file mode 100644 index 00000000..66c42f48 --- /dev/null +++ b/lib/js/test262-slice/async/await_throws_error_object.js @@ -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 \ No newline at end of file diff --git a/lib/js/test262-slice/closures/adder.expected b/lib/js/test262-slice/closures/adder.expected new file mode 100644 index 00000000..c739b42c --- /dev/null +++ b/lib/js/test262-slice/closures/adder.expected @@ -0,0 +1 @@ +44 diff --git a/lib/js/test262-slice/closures/adder.js b/lib/js/test262-slice/closures/adder.js new file mode 100644 index 00000000..f1d9f02b --- /dev/null +++ b/lib/js/test262-slice/closures/adder.js @@ -0,0 +1 @@ +function mk(n) { return x => x + n; } let add7 = mk(7); add7(10) + add7(20) diff --git a/lib/js/test262-slice/closures/counter.expected b/lib/js/test262-slice/closures/counter.expected new file mode 100644 index 00000000..b8626c4c --- /dev/null +++ b/lib/js/test262-slice/closures/counter.expected @@ -0,0 +1 @@ +4 diff --git a/lib/js/test262-slice/closures/counter.js b/lib/js/test262-slice/closures/counter.js new file mode 100644 index 00000000..67b0fd4c --- /dev/null +++ b/lib/js/test262-slice/closures/counter.js @@ -0,0 +1 @@ +function mkc() { let n = 0; return () => { n = n + 1; return n; }; } let c = mkc(); c(); c(); c(); c() diff --git a/lib/js/test262-slice/closures/multi_closure.expected b/lib/js/test262-slice/closures/multi_closure.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/closures/multi_closure.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/closures/multi_closure.js b/lib/js/test262-slice/closures/multi_closure.js new file mode 100644 index 00000000..7130ce16 --- /dev/null +++ b/lib/js/test262-slice/closures/multi_closure.js @@ -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() diff --git a/lib/js/test262-slice/closures/nested_scope.expected b/lib/js/test262-slice/closures/nested_scope.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/closures/nested_scope.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/closures/nested_scope.js b/lib/js/test262-slice/closures/nested_scope.js new file mode 100644 index 00000000..2261a63a --- /dev/null +++ b/lib/js/test262-slice/closures/nested_scope.js @@ -0,0 +1 @@ +function outer() { let a = 1; function inner() { let b = 2; function deepest() { return a + b; } return deepest(); } return inner(); } outer() diff --git a/lib/js/test262-slice/closures/sum_sq.expected b/lib/js/test262-slice/closures/sum_sq.expected new file mode 100644 index 00000000..c3f407c0 --- /dev/null +++ b/lib/js/test262-slice/closures/sum_sq.expected @@ -0,0 +1 @@ +55 diff --git a/lib/js/test262-slice/closures/sum_sq.js b/lib/js/test262-slice/closures/sum_sq.js new file mode 100644 index 00000000..5737b503 --- /dev/null +++ b/lib/js/test262-slice/closures/sum_sq.js @@ -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]) diff --git a/lib/js/test262-slice/coercion/implicit_str_add.expected b/lib/js/test262-slice/coercion/implicit_str_add.expected new file mode 100644 index 00000000..8f24d62a --- /dev/null +++ b/lib/js/test262-slice/coercion/implicit_str_add.expected @@ -0,0 +1 @@ +"n=42" diff --git a/lib/js/test262-slice/coercion/implicit_str_add.js b/lib/js/test262-slice/coercion/implicit_str_add.js new file mode 100644 index 00000000..3bea4754 --- /dev/null +++ b/lib/js/test262-slice/coercion/implicit_str_add.js @@ -0,0 +1 @@ +"n=" + 42 diff --git a/lib/js/test262-slice/coercion/loose_str_num.expected b/lib/js/test262-slice/coercion/loose_str_num.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/coercion/loose_str_num.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/coercion/loose_str_num.js b/lib/js/test262-slice/coercion/loose_str_num.js new file mode 100644 index 00000000..06720eea --- /dev/null +++ b/lib/js/test262-slice/coercion/loose_str_num.js @@ -0,0 +1 @@ +"5" == 5 diff --git a/lib/js/test262-slice/coercion/typeof_bool.expected b/lib/js/test262-slice/coercion/typeof_bool.expected new file mode 100644 index 00000000..012160df --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_bool.expected @@ -0,0 +1 @@ +"boolean" diff --git a/lib/js/test262-slice/coercion/typeof_bool.js b/lib/js/test262-slice/coercion/typeof_bool.js new file mode 100644 index 00000000..2206d2a1 --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_bool.js @@ -0,0 +1 @@ +typeof true diff --git a/lib/js/test262-slice/coercion/typeof_fn.expected b/lib/js/test262-slice/coercion/typeof_fn.expected new file mode 100644 index 00000000..f45c029c --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_fn.expected @@ -0,0 +1 @@ +"function" diff --git a/lib/js/test262-slice/coercion/typeof_fn.js b/lib/js/test262-slice/coercion/typeof_fn.js new file mode 100644 index 00000000..f0fe6d27 --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_fn.js @@ -0,0 +1 @@ +typeof (x => x) diff --git a/lib/js/test262-slice/coercion/typeof_null.expected b/lib/js/test262-slice/coercion/typeof_null.expected new file mode 100644 index 00000000..b5ffc7c7 --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_null.expected @@ -0,0 +1 @@ +"object" diff --git a/lib/js/test262-slice/coercion/typeof_null.js b/lib/js/test262-slice/coercion/typeof_null.js new file mode 100644 index 00000000..82b40656 --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_null.js @@ -0,0 +1 @@ +typeof null diff --git a/lib/js/test262-slice/coercion/typeof_num.expected b/lib/js/test262-slice/coercion/typeof_num.expected new file mode 100644 index 00000000..9a6f32ae --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_num.expected @@ -0,0 +1 @@ +"number" diff --git a/lib/js/test262-slice/coercion/typeof_num.js b/lib/js/test262-slice/coercion/typeof_num.js new file mode 100644 index 00000000..283ab63b --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_num.js @@ -0,0 +1 @@ +typeof 42 diff --git a/lib/js/test262-slice/coercion/typeof_str.expected b/lib/js/test262-slice/coercion/typeof_str.expected new file mode 100644 index 00000000..ace2d72d --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_str.expected @@ -0,0 +1 @@ +"string" diff --git a/lib/js/test262-slice/coercion/typeof_str.js b/lib/js/test262-slice/coercion/typeof_str.js new file mode 100644 index 00000000..982514a9 --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_str.js @@ -0,0 +1 @@ +typeof "x" diff --git a/lib/js/test262-slice/coercion/typeof_undef.expected b/lib/js/test262-slice/coercion/typeof_undef.expected new file mode 100644 index 00000000..63218430 --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_undef.expected @@ -0,0 +1 @@ +"undefined" diff --git a/lib/js/test262-slice/coercion/typeof_undef.js b/lib/js/test262-slice/coercion/typeof_undef.js new file mode 100644 index 00000000..639f727f --- /dev/null +++ b/lib/js/test262-slice/coercion/typeof_undef.js @@ -0,0 +1 @@ +typeof undefined diff --git a/lib/js/test262-slice/collections/array_empty.expected b/lib/js/test262-slice/collections/array_empty.expected new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/lib/js/test262-slice/collections/array_empty.expected @@ -0,0 +1 @@ +0 diff --git a/lib/js/test262-slice/collections/array_empty.js b/lib/js/test262-slice/collections/array_empty.js new file mode 100644 index 00000000..624aca75 --- /dev/null +++ b/lib/js/test262-slice/collections/array_empty.js @@ -0,0 +1 @@ +[].length diff --git a/lib/js/test262-slice/collections/array_index.expected b/lib/js/test262-slice/collections/array_index.expected new file mode 100644 index 00000000..209e3ef4 --- /dev/null +++ b/lib/js/test262-slice/collections/array_index.expected @@ -0,0 +1 @@ +20 diff --git a/lib/js/test262-slice/collections/array_index.js b/lib/js/test262-slice/collections/array_index.js new file mode 100644 index 00000000..c09924e1 --- /dev/null +++ b/lib/js/test262-slice/collections/array_index.js @@ -0,0 +1 @@ +[10,20,30][1] diff --git a/lib/js/test262-slice/collections/array_length.expected b/lib/js/test262-slice/collections/array_length.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/collections/array_length.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/collections/array_length.js b/lib/js/test262-slice/collections/array_length.js new file mode 100644 index 00000000..8122bdb6 --- /dev/null +++ b/lib/js/test262-slice/collections/array_length.js @@ -0,0 +1 @@ +[1,2,3].length diff --git a/lib/js/test262-slice/collections/array_nested.expected b/lib/js/test262-slice/collections/array_nested.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/collections/array_nested.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/collections/array_nested.js b/lib/js/test262-slice/collections/array_nested.js new file mode 100644 index 00000000..d0bb959d --- /dev/null +++ b/lib/js/test262-slice/collections/array_nested.js @@ -0,0 +1 @@ +[[1,2],[3,4]][1][0] diff --git a/lib/js/test262-slice/collections/array_plus_length.expected b/lib/js/test262-slice/collections/array_plus_length.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/collections/array_plus_length.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/collections/array_plus_length.js b/lib/js/test262-slice/collections/array_plus_length.js new file mode 100644 index 00000000..90d0f1af --- /dev/null +++ b/lib/js/test262-slice/collections/array_plus_length.js @@ -0,0 +1 @@ +[1,2,3].length + [4,5].length diff --git a/lib/js/test262-slice/collections/object_chain.expected b/lib/js/test262-slice/collections/object_chain.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/collections/object_chain.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/collections/object_chain.js b/lib/js/test262-slice/collections/object_chain.js new file mode 100644 index 00000000..f633e61d --- /dev/null +++ b/lib/js/test262-slice/collections/object_chain.js @@ -0,0 +1 @@ +({a:{b:{c:42}}}).a.b.c diff --git a/lib/js/test262-slice/collections/object_prop.expected b/lib/js/test262-slice/collections/object_prop.expected new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/lib/js/test262-slice/collections/object_prop.expected @@ -0,0 +1 @@ +1 diff --git a/lib/js/test262-slice/collections/object_prop.js b/lib/js/test262-slice/collections/object_prop.js new file mode 100644 index 00000000..c3f85711 --- /dev/null +++ b/lib/js/test262-slice/collections/object_prop.js @@ -0,0 +1 @@ +({x:1,y:2}).x diff --git a/lib/js/test262-slice/collections/string_index.expected b/lib/js/test262-slice/collections/string_index.expected new file mode 100644 index 00000000..3cc43c70 --- /dev/null +++ b/lib/js/test262-slice/collections/string_index.expected @@ -0,0 +1 @@ +"e" diff --git a/lib/js/test262-slice/collections/string_index.js b/lib/js/test262-slice/collections/string_index.js new file mode 100644 index 00000000..6bc3b720 --- /dev/null +++ b/lib/js/test262-slice/collections/string_index.js @@ -0,0 +1 @@ +"hello"[1] diff --git a/lib/js/test262-slice/collections/string_length.expected b/lib/js/test262-slice/collections/string_length.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/collections/string_length.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/collections/string_length.js b/lib/js/test262-slice/collections/string_length.js new file mode 100644 index 00000000..6c904254 --- /dev/null +++ b/lib/js/test262-slice/collections/string_length.js @@ -0,0 +1 @@ +"hello".length diff --git a/lib/js/test262-slice/compare/ge_eq.expected b/lib/js/test262-slice/compare/ge_eq.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/ge_eq.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/ge_eq.js b/lib/js/test262-slice/compare/ge_eq.js new file mode 100644 index 00000000..0bd168cc --- /dev/null +++ b/lib/js/test262-slice/compare/ge_eq.js @@ -0,0 +1 @@ +3 >= 3 diff --git a/lib/js/test262-slice/compare/gt.expected b/lib/js/test262-slice/compare/gt.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/gt.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/gt.js b/lib/js/test262-slice/compare/gt.js new file mode 100644 index 00000000..954f1368 --- /dev/null +++ b/lib/js/test262-slice/compare/gt.js @@ -0,0 +1 @@ +3 > 1 diff --git a/lib/js/test262-slice/compare/le_eq.expected b/lib/js/test262-slice/compare/le_eq.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/le_eq.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/le_eq.js b/lib/js/test262-slice/compare/le_eq.js new file mode 100644 index 00000000..f3bc7f5d --- /dev/null +++ b/lib/js/test262-slice/compare/le_eq.js @@ -0,0 +1 @@ +2 <= 2 diff --git a/lib/js/test262-slice/compare/loose_eq_num_str.expected b/lib/js/test262-slice/compare/loose_eq_num_str.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/loose_eq_num_str.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/loose_eq_num_str.js b/lib/js/test262-slice/compare/loose_eq_num_str.js new file mode 100644 index 00000000..1a69b506 --- /dev/null +++ b/lib/js/test262-slice/compare/loose_eq_num_str.js @@ -0,0 +1 @@ +1 == "1" diff --git a/lib/js/test262-slice/compare/loose_null_undef.expected b/lib/js/test262-slice/compare/loose_null_undef.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/loose_null_undef.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/loose_null_undef.js b/lib/js/test262-slice/compare/loose_null_undef.js new file mode 100644 index 00000000..3515962b --- /dev/null +++ b/lib/js/test262-slice/compare/loose_null_undef.js @@ -0,0 +1 @@ +null == undefined diff --git a/lib/js/test262-slice/compare/loose_zero_false.expected b/lib/js/test262-slice/compare/loose_zero_false.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/loose_zero_false.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/loose_zero_false.js b/lib/js/test262-slice/compare/loose_zero_false.js new file mode 100644 index 00000000..2ed0065a --- /dev/null +++ b/lib/js/test262-slice/compare/loose_zero_false.js @@ -0,0 +1 @@ +0 == false diff --git a/lib/js/test262-slice/compare/lt.expected b/lib/js/test262-slice/compare/lt.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/lt.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/lt.js b/lib/js/test262-slice/compare/lt.js new file mode 100644 index 00000000..17e69c79 --- /dev/null +++ b/lib/js/test262-slice/compare/lt.js @@ -0,0 +1 @@ +1 < 2 diff --git a/lib/js/test262-slice/compare/str_lt.expected b/lib/js/test262-slice/compare/str_lt.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/str_lt.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/str_lt.js b/lib/js/test262-slice/compare/str_lt.js new file mode 100644 index 00000000..598c800e --- /dev/null +++ b/lib/js/test262-slice/compare/str_lt.js @@ -0,0 +1 @@ +"a" < "b" diff --git a/lib/js/test262-slice/compare/strict_eq_cross_type.expected b/lib/js/test262-slice/compare/strict_eq_cross_type.expected new file mode 100644 index 00000000..c508d536 --- /dev/null +++ b/lib/js/test262-slice/compare/strict_eq_cross_type.expected @@ -0,0 +1 @@ +false diff --git a/lib/js/test262-slice/compare/strict_eq_cross_type.js b/lib/js/test262-slice/compare/strict_eq_cross_type.js new file mode 100644 index 00000000..65825db8 --- /dev/null +++ b/lib/js/test262-slice/compare/strict_eq_cross_type.js @@ -0,0 +1 @@ +1 === "1" diff --git a/lib/js/test262-slice/compare/strict_eq_num.expected b/lib/js/test262-slice/compare/strict_eq_num.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/strict_eq_num.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/strict_eq_num.js b/lib/js/test262-slice/compare/strict_eq_num.js new file mode 100644 index 00000000..db7acdb2 --- /dev/null +++ b/lib/js/test262-slice/compare/strict_eq_num.js @@ -0,0 +1 @@ +1 === 1 diff --git a/lib/js/test262-slice/compare/strict_eq_num_false.expected b/lib/js/test262-slice/compare/strict_eq_num_false.expected new file mode 100644 index 00000000..c508d536 --- /dev/null +++ b/lib/js/test262-slice/compare/strict_eq_num_false.expected @@ -0,0 +1 @@ +false diff --git a/lib/js/test262-slice/compare/strict_eq_num_false.js b/lib/js/test262-slice/compare/strict_eq_num_false.js new file mode 100644 index 00000000..6a2fb465 --- /dev/null +++ b/lib/js/test262-slice/compare/strict_eq_num_false.js @@ -0,0 +1 @@ +1 === 2 diff --git a/lib/js/test262-slice/compare/strict_neq.expected b/lib/js/test262-slice/compare/strict_neq.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/compare/strict_neq.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/compare/strict_neq.js b/lib/js/test262-slice/compare/strict_neq.js new file mode 100644 index 00000000..511b2c84 --- /dev/null +++ b/lib/js/test262-slice/compare/strict_neq.js @@ -0,0 +1 @@ +1 !== 2 diff --git a/lib/js/test262-slice/control/ternary_false.expected b/lib/js/test262-slice/control/ternary_false.expected new file mode 100644 index 00000000..209e3ef4 --- /dev/null +++ b/lib/js/test262-slice/control/ternary_false.expected @@ -0,0 +1 @@ +20 diff --git a/lib/js/test262-slice/control/ternary_false.js b/lib/js/test262-slice/control/ternary_false.js new file mode 100644 index 00000000..517e5288 --- /dev/null +++ b/lib/js/test262-slice/control/ternary_false.js @@ -0,0 +1 @@ +0 ? 10 : 20 diff --git a/lib/js/test262-slice/control/ternary_nested.expected b/lib/js/test262-slice/control/ternary_nested.expected new file mode 100644 index 00000000..231f150c --- /dev/null +++ b/lib/js/test262-slice/control/ternary_nested.expected @@ -0,0 +1 @@ +"a" diff --git a/lib/js/test262-slice/control/ternary_nested.js b/lib/js/test262-slice/control/ternary_nested.js new file mode 100644 index 00000000..4f7038e5 --- /dev/null +++ b/lib/js/test262-slice/control/ternary_nested.js @@ -0,0 +1 @@ +1 < 2 ? 2 < 3 ? "a" : "b" : "c" diff --git a/lib/js/test262-slice/control/ternary_true.expected b/lib/js/test262-slice/control/ternary_true.expected new file mode 100644 index 00000000..59555ef5 --- /dev/null +++ b/lib/js/test262-slice/control/ternary_true.expected @@ -0,0 +1 @@ +"yes" diff --git a/lib/js/test262-slice/control/ternary_true.js b/lib/js/test262-slice/control/ternary_true.js new file mode 100644 index 00000000..334d1274 --- /dev/null +++ b/lib/js/test262-slice/control/ternary_true.js @@ -0,0 +1 @@ +1 < 2 ? "yes" : "no" diff --git a/lib/js/test262-slice/errors/error_message.expected b/lib/js/test262-slice/errors/error_message.expected new file mode 100644 index 00000000..75891bc6 --- /dev/null +++ b/lib/js/test262-slice/errors/error_message.expected @@ -0,0 +1 @@ +oops diff --git a/lib/js/test262-slice/errors/error_message.js b/lib/js/test262-slice/errors/error_message.js new file mode 100644 index 00000000..9f9d136b --- /dev/null +++ b/lib/js/test262-slice/errors/error_message.js @@ -0,0 +1,3 @@ +var m; +try { throw new Error("oops"); } catch(e) { m = e.message; } +m \ No newline at end of file diff --git a/lib/js/test262-slice/errors/throw_catch_string.expected b/lib/js/test262-slice/errors/throw_catch_string.expected new file mode 100644 index 00000000..9e2ba7ec --- /dev/null +++ b/lib/js/test262-slice/errors/throw_catch_string.expected @@ -0,0 +1 @@ +boom diff --git a/lib/js/test262-slice/errors/throw_catch_string.js b/lib/js/test262-slice/errors/throw_catch_string.js new file mode 100644 index 00000000..135dd332 --- /dev/null +++ b/lib/js/test262-slice/errors/throw_catch_string.js @@ -0,0 +1,3 @@ +var msg; +try { throw "boom"; } catch(e) { msg = e; } +msg \ No newline at end of file diff --git a/lib/js/test262-slice/errors/throw_in_function.expected b/lib/js/test262-slice/errors/throw_in_function.expected new file mode 100644 index 00000000..c85f8c3d --- /dev/null +++ b/lib/js/test262-slice/errors/throw_in_function.expected @@ -0,0 +1 @@ +div by zero diff --git a/lib/js/test262-slice/errors/throw_in_function.js b/lib/js/test262-slice/errors/throw_in_function.js new file mode 100644 index 00000000..e5edce45 --- /dev/null +++ b/lib/js/test262-slice/errors/throw_in_function.js @@ -0,0 +1,7 @@ +function divide(a, b) { + if (b === 0) throw new Error("div by zero"); + return a / b; +} +var result; +try { result = divide(10, 0); } catch(e) { result = e.message; } +result \ No newline at end of file diff --git a/lib/js/test262-slice/errors/try_finally.expected b/lib/js/test262-slice/errors/try_finally.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/errors/try_finally.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/errors/try_finally.js b/lib/js/test262-slice/errors/try_finally.js new file mode 100644 index 00000000..d52365f5 --- /dev/null +++ b/lib/js/test262-slice/errors/try_finally.js @@ -0,0 +1,3 @@ +var x = 0; +try { x = 1; throw "e"; } catch(e) { x = 2; } finally { x = 3; } +x \ No newline at end of file diff --git a/lib/js/test262-slice/errors/typeerror_name.expected b/lib/js/test262-slice/errors/typeerror_name.expected new file mode 100644 index 00000000..6002b71c --- /dev/null +++ b/lib/js/test262-slice/errors/typeerror_name.expected @@ -0,0 +1 @@ +TypeError diff --git a/lib/js/test262-slice/errors/typeerror_name.js b/lib/js/test262-slice/errors/typeerror_name.js new file mode 100644 index 00000000..5bd34e5c --- /dev/null +++ b/lib/js/test262-slice/errors/typeerror_name.js @@ -0,0 +1,3 @@ +var n; +try { throw new TypeError("bad type"); } catch(e) { n = e.name; } +n \ No newline at end of file diff --git a/lib/js/test262-slice/functions/arrow_add.expected b/lib/js/test262-slice/functions/arrow_add.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_add.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/functions/arrow_add.js b/lib/js/test262-slice/functions/arrow_add.js new file mode 100644 index 00000000..d0a4d80c --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_add.js @@ -0,0 +1 @@ +(x => x + 1)(41) diff --git a/lib/js/test262-slice/functions/arrow_block.expected b/lib/js/test262-slice/functions/arrow_block.expected new file mode 100644 index 00000000..2bd5a0a9 --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_block.expected @@ -0,0 +1 @@ +22 diff --git a/lib/js/test262-slice/functions/arrow_block.js b/lib/js/test262-slice/functions/arrow_block.js new file mode 100644 index 00000000..6277c42d --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_block.js @@ -0,0 +1 @@ +let f = x => { let y = x + 1; return y * 2; }; f(10) diff --git a/lib/js/test262-slice/functions/arrow_curry.expected b/lib/js/test262-slice/functions/arrow_curry.expected new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_curry.expected @@ -0,0 +1 @@ +7 diff --git a/lib/js/test262-slice/functions/arrow_curry.js b/lib/js/test262-slice/functions/arrow_curry.js new file mode 100644 index 00000000..dd19763c --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_curry.js @@ -0,0 +1 @@ +(x => y => x + y)(3)(4) diff --git a/lib/js/test262-slice/functions/arrow_identity.expected b/lib/js/test262-slice/functions/arrow_identity.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_identity.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/functions/arrow_identity.js b/lib/js/test262-slice/functions/arrow_identity.js new file mode 100644 index 00000000..31b21c9e --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_identity.js @@ -0,0 +1 @@ +(x => x)(42) diff --git a/lib/js/test262-slice/functions/arrow_multi_arg.expected b/lib/js/test262-slice/functions/arrow_multi_arg.expected new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_multi_arg.expected @@ -0,0 +1 @@ +7 diff --git a/lib/js/test262-slice/functions/arrow_multi_arg.js b/lib/js/test262-slice/functions/arrow_multi_arg.js new file mode 100644 index 00000000..fa50a864 --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_multi_arg.js @@ -0,0 +1 @@ +((a,b) => a + b)(3,4) diff --git a/lib/js/test262-slice/functions/arrow_zero.expected b/lib/js/test262-slice/functions/arrow_zero.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_zero.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/functions/arrow_zero.js b/lib/js/test262-slice/functions/arrow_zero.js new file mode 100644 index 00000000..d84c01b1 --- /dev/null +++ b/lib/js/test262-slice/functions/arrow_zero.js @@ -0,0 +1 @@ +(() => 42)() diff --git a/lib/js/test262-slice/functions/closure_ref.expected b/lib/js/test262-slice/functions/closure_ref.expected new file mode 100644 index 00000000..aabe6ec3 --- /dev/null +++ b/lib/js/test262-slice/functions/closure_ref.expected @@ -0,0 +1 @@ +21 diff --git a/lib/js/test262-slice/functions/closure_ref.js b/lib/js/test262-slice/functions/closure_ref.js new file mode 100644 index 00000000..41404439 --- /dev/null +++ b/lib/js/test262-slice/functions/closure_ref.js @@ -0,0 +1 @@ +function compose(f, g) { return x => f(g(x)); } let addOne = x => x + 1; let double = x => x * 2; compose(addOne, double)(10) diff --git a/lib/js/test262-slice/functions/default_param.expected b/lib/js/test262-slice/functions/default_param.expected new file mode 100644 index 00000000..7273c0fa --- /dev/null +++ b/lib/js/test262-slice/functions/default_param.expected @@ -0,0 +1 @@ +25 diff --git a/lib/js/test262-slice/functions/default_param.js b/lib/js/test262-slice/functions/default_param.js new file mode 100644 index 00000000..1b79c911 --- /dev/null +++ b/lib/js/test262-slice/functions/default_param.js @@ -0,0 +1 @@ +function f(x = 10, y = 20) { return x + y; } f(5) diff --git a/lib/js/test262-slice/functions/func_decl.expected b/lib/js/test262-slice/functions/func_decl.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/functions/func_decl.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/functions/func_decl.js b/lib/js/test262-slice/functions/func_decl.js new file mode 100644 index 00000000..7b8ed158 --- /dev/null +++ b/lib/js/test262-slice/functions/func_decl.js @@ -0,0 +1 @@ +function add(a, b) { return a + b; } add(17, 25) diff --git a/lib/js/test262-slice/functions/higher_order.expected b/lib/js/test262-slice/functions/higher_order.expected new file mode 100644 index 00000000..d88e3136 --- /dev/null +++ b/lib/js/test262-slice/functions/higher_order.expected @@ -0,0 +1 @@ +81 diff --git a/lib/js/test262-slice/functions/higher_order.js b/lib/js/test262-slice/functions/higher_order.js new file mode 100644 index 00000000..1fb66179 --- /dev/null +++ b/lib/js/test262-slice/functions/higher_order.js @@ -0,0 +1 @@ +function twice(f, x) { return f(f(x)); } twice(n => n * n, 3) diff --git a/lib/js/test262-slice/functions/iife.expected b/lib/js/test262-slice/functions/iife.expected new file mode 100644 index 00000000..d88e3136 --- /dev/null +++ b/lib/js/test262-slice/functions/iife.expected @@ -0,0 +1 @@ +81 diff --git a/lib/js/test262-slice/functions/iife.js b/lib/js/test262-slice/functions/iife.js new file mode 100644 index 00000000..b0f51400 --- /dev/null +++ b/lib/js/test262-slice/functions/iife.js @@ -0,0 +1 @@ +(function(x) { return x * x; })(9) diff --git a/lib/js/test262-slice/functions/math_abs.expected b/lib/js/test262-slice/functions/math_abs.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/functions/math_abs.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/functions/math_abs.js b/lib/js/test262-slice/functions/math_abs.js new file mode 100644 index 00000000..0600cff5 --- /dev/null +++ b/lib/js/test262-slice/functions/math_abs.js @@ -0,0 +1 @@ +Math.abs(-5) diff --git a/lib/js/test262-slice/functions/math_ceil.expected b/lib/js/test262-slice/functions/math_ceil.expected new file mode 100644 index 00000000..b8626c4c --- /dev/null +++ b/lib/js/test262-slice/functions/math_ceil.expected @@ -0,0 +1 @@ +4 diff --git a/lib/js/test262-slice/functions/math_ceil.js b/lib/js/test262-slice/functions/math_ceil.js new file mode 100644 index 00000000..d72a0589 --- /dev/null +++ b/lib/js/test262-slice/functions/math_ceil.js @@ -0,0 +1 @@ +Math.ceil(3.1) diff --git a/lib/js/test262-slice/functions/math_floor.expected b/lib/js/test262-slice/functions/math_floor.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/functions/math_floor.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/functions/math_floor.js b/lib/js/test262-slice/functions/math_floor.js new file mode 100644 index 00000000..cb86ee32 --- /dev/null +++ b/lib/js/test262-slice/functions/math_floor.js @@ -0,0 +1 @@ +Math.floor(3.9) diff --git a/lib/js/test262-slice/functions/math_max.expected b/lib/js/test262-slice/functions/math_max.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/functions/math_max.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/functions/math_max.js b/lib/js/test262-slice/functions/math_max.js new file mode 100644 index 00000000..46f6c6be --- /dev/null +++ b/lib/js/test262-slice/functions/math_max.js @@ -0,0 +1 @@ +Math.max(1,5,3) diff --git a/lib/js/test262-slice/functions/math_min.expected b/lib/js/test262-slice/functions/math_min.expected new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/lib/js/test262-slice/functions/math_min.expected @@ -0,0 +1 @@ +1 diff --git a/lib/js/test262-slice/functions/math_min.js b/lib/js/test262-slice/functions/math_min.js new file mode 100644 index 00000000..fa7005a1 --- /dev/null +++ b/lib/js/test262-slice/functions/math_min.js @@ -0,0 +1 @@ +Math.min(1,5,3) diff --git a/lib/js/test262-slice/functions/math_pi_gt_3.expected b/lib/js/test262-slice/functions/math_pi_gt_3.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/functions/math_pi_gt_3.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/functions/math_pi_gt_3.js b/lib/js/test262-slice/functions/math_pi_gt_3.js new file mode 100644 index 00000000..d144ef78 --- /dev/null +++ b/lib/js/test262-slice/functions/math_pi_gt_3.js @@ -0,0 +1 @@ +Math.PI > 3 diff --git a/lib/js/test262-slice/functions/recursion_fact.expected b/lib/js/test262-slice/functions/recursion_fact.expected new file mode 100644 index 00000000..a1708fb1 --- /dev/null +++ b/lib/js/test262-slice/functions/recursion_fact.expected @@ -0,0 +1 @@ +720 diff --git a/lib/js/test262-slice/functions/recursion_fact.js b/lib/js/test262-slice/functions/recursion_fact.js new file mode 100644 index 00000000..e1e2238f --- /dev/null +++ b/lib/js/test262-slice/functions/recursion_fact.js @@ -0,0 +1 @@ +function fact(n) { if (n <= 1) return 1; return n * fact(n - 1); } fact(6) diff --git a/lib/js/test262-slice/functions/recursion_fib.expected b/lib/js/test262-slice/functions/recursion_fib.expected new file mode 100644 index 00000000..a29644e5 --- /dev/null +++ b/lib/js/test262-slice/functions/recursion_fib.expected @@ -0,0 +1 @@ +144 diff --git a/lib/js/test262-slice/functions/recursion_fib.js b/lib/js/test262-slice/functions/recursion_fib.js new file mode 100644 index 00000000..e15798fb --- /dev/null +++ b/lib/js/test262-slice/functions/recursion_fib.js @@ -0,0 +1 @@ +function fib(n) { if (n < 2) return n; return fib(n-1) + fib(n-2); } fib(12) diff --git a/lib/js/test262-slice/functions/rest_param.expected b/lib/js/test262-slice/functions/rest_param.expected new file mode 100644 index 00000000..aabe6ec3 --- /dev/null +++ b/lib/js/test262-slice/functions/rest_param.expected @@ -0,0 +1 @@ +21 diff --git a/lib/js/test262-slice/functions/rest_param.js b/lib/js/test262-slice/functions/rest_param.js new file mode 100644 index 00000000..1c858289 --- /dev/null +++ b/lib/js/test262-slice/functions/rest_param.js @@ -0,0 +1 @@ +function sum(...xs) { let t = 0; for (let i = 0; i < xs.length; i = i + 1) t = t + xs[i]; return t; } sum(1,2,3,4,5,6) diff --git a/lib/js/test262-slice/logical/and_short_circuit.expected b/lib/js/test262-slice/logical/and_short_circuit.expected new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/lib/js/test262-slice/logical/and_short_circuit.expected @@ -0,0 +1 @@ +0 diff --git a/lib/js/test262-slice/logical/and_short_circuit.js b/lib/js/test262-slice/logical/and_short_circuit.js new file mode 100644 index 00000000..5573577f --- /dev/null +++ b/lib/js/test262-slice/logical/and_short_circuit.js @@ -0,0 +1 @@ +0 && 5 diff --git a/lib/js/test262-slice/logical/and_truthy.expected b/lib/js/test262-slice/logical/and_truthy.expected new file mode 100644 index 00000000..0cfbf088 --- /dev/null +++ b/lib/js/test262-slice/logical/and_truthy.expected @@ -0,0 +1 @@ +2 diff --git a/lib/js/test262-slice/logical/and_truthy.js b/lib/js/test262-slice/logical/and_truthy.js new file mode 100644 index 00000000..d28fefdc --- /dev/null +++ b/lib/js/test262-slice/logical/and_truthy.js @@ -0,0 +1 @@ +1 && 2 diff --git a/lib/js/test262-slice/logical/chain.expected b/lib/js/test262-slice/logical/chain.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/logical/chain.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/logical/chain.js b/lib/js/test262-slice/logical/chain.js new file mode 100644 index 00000000..dd8e0355 --- /dev/null +++ b/lib/js/test262-slice/logical/chain.js @@ -0,0 +1 @@ +true && false || true diff --git a/lib/js/test262-slice/logical/not_true.expected b/lib/js/test262-slice/logical/not_true.expected new file mode 100644 index 00000000..c508d536 --- /dev/null +++ b/lib/js/test262-slice/logical/not_true.expected @@ -0,0 +1 @@ +false diff --git a/lib/js/test262-slice/logical/not_true.js b/lib/js/test262-slice/logical/not_true.js new file mode 100644 index 00000000..b66ac984 --- /dev/null +++ b/lib/js/test262-slice/logical/not_true.js @@ -0,0 +1 @@ +!true diff --git a/lib/js/test262-slice/logical/not_zero.expected b/lib/js/test262-slice/logical/not_zero.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/logical/not_zero.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/logical/not_zero.js b/lib/js/test262-slice/logical/not_zero.js new file mode 100644 index 00000000..a5762b82 --- /dev/null +++ b/lib/js/test262-slice/logical/not_zero.js @@ -0,0 +1 @@ +!0 diff --git a/lib/js/test262-slice/logical/nullish_null.expected b/lib/js/test262-slice/logical/nullish_null.expected new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/lib/js/test262-slice/logical/nullish_null.expected @@ -0,0 +1 @@ +7 diff --git a/lib/js/test262-slice/logical/nullish_null.js b/lib/js/test262-slice/logical/nullish_null.js new file mode 100644 index 00000000..80c9b923 --- /dev/null +++ b/lib/js/test262-slice/logical/nullish_null.js @@ -0,0 +1 @@ +null ?? 7 diff --git a/lib/js/test262-slice/logical/nullish_undef.expected b/lib/js/test262-slice/logical/nullish_undef.expected new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/lib/js/test262-slice/logical/nullish_undef.expected @@ -0,0 +1 @@ +7 diff --git a/lib/js/test262-slice/logical/nullish_undef.js b/lib/js/test262-slice/logical/nullish_undef.js new file mode 100644 index 00000000..c089cc87 --- /dev/null +++ b/lib/js/test262-slice/logical/nullish_undef.js @@ -0,0 +1 @@ +undefined ?? 7 diff --git a/lib/js/test262-slice/logical/nullish_zero.expected b/lib/js/test262-slice/logical/nullish_zero.expected new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/lib/js/test262-slice/logical/nullish_zero.expected @@ -0,0 +1 @@ +0 diff --git a/lib/js/test262-slice/logical/nullish_zero.js b/lib/js/test262-slice/logical/nullish_zero.js new file mode 100644 index 00000000..7ed9a205 --- /dev/null +++ b/lib/js/test262-slice/logical/nullish_zero.js @@ -0,0 +1 @@ +0 ?? 7 diff --git a/lib/js/test262-slice/logical/or_falsy.expected b/lib/js/test262-slice/logical/or_falsy.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/logical/or_falsy.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/logical/or_falsy.js b/lib/js/test262-slice/logical/or_falsy.js new file mode 100644 index 00000000..dba22333 --- /dev/null +++ b/lib/js/test262-slice/logical/or_falsy.js @@ -0,0 +1 @@ +false || 5 diff --git a/lib/js/test262-slice/logical/or_truthy.expected b/lib/js/test262-slice/logical/or_truthy.expected new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/lib/js/test262-slice/logical/or_truthy.expected @@ -0,0 +1 @@ +1 diff --git a/lib/js/test262-slice/logical/or_truthy.js b/lib/js/test262-slice/logical/or_truthy.js new file mode 100644 index 00000000..78e3a7c9 --- /dev/null +++ b/lib/js/test262-slice/logical/or_truthy.js @@ -0,0 +1 @@ +1 || 2 diff --git a/lib/js/test262-slice/logical/or_zero.expected b/lib/js/test262-slice/logical/or_zero.expected new file mode 100644 index 00000000..92232f69 --- /dev/null +++ b/lib/js/test262-slice/logical/or_zero.expected @@ -0,0 +1 @@ +"x" diff --git a/lib/js/test262-slice/logical/or_zero.js b/lib/js/test262-slice/logical/or_zero.js new file mode 100644 index 00000000..0bff7a11 --- /dev/null +++ b/lib/js/test262-slice/logical/or_zero.js @@ -0,0 +1 @@ +0 || "x" diff --git a/lib/js/test262-slice/loops/do_while.expected b/lib/js/test262-slice/loops/do_while.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/loops/do_while.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/loops/do_while.js b/lib/js/test262-slice/loops/do_while.js new file mode 100644 index 00000000..f7a677a4 --- /dev/null +++ b/lib/js/test262-slice/loops/do_while.js @@ -0,0 +1 @@ +let i = 0; do { i = i + 1; } while (i < 3); i diff --git a/lib/js/test262-slice/loops/for_basic.expected b/lib/js/test262-slice/loops/for_basic.expected new file mode 100644 index 00000000..c3f407c0 --- /dev/null +++ b/lib/js/test262-slice/loops/for_basic.expected @@ -0,0 +1 @@ +55 diff --git a/lib/js/test262-slice/loops/for_basic.js b/lib/js/test262-slice/loops/for_basic.js new file mode 100644 index 00000000..f74d4b5a --- /dev/null +++ b/lib/js/test262-slice/loops/for_basic.js @@ -0,0 +1 @@ +let s = 0; for (let i = 1; i <= 10; i = i + 1) s = s + i; s diff --git a/lib/js/test262-slice/loops/for_break.expected b/lib/js/test262-slice/loops/for_break.expected new file mode 100644 index 00000000..1e8b3149 --- /dev/null +++ b/lib/js/test262-slice/loops/for_break.expected @@ -0,0 +1 @@ +6 diff --git a/lib/js/test262-slice/loops/for_break.js b/lib/js/test262-slice/loops/for_break.js new file mode 100644 index 00000000..1489653b --- /dev/null +++ b/lib/js/test262-slice/loops/for_break.js @@ -0,0 +1 @@ +let x = 0; for (let i = 0; i < 100; i = i + 1) { if (i === 7) break; x = i; } x diff --git a/lib/js/test262-slice/loops/for_continue.expected b/lib/js/test262-slice/loops/for_continue.expected new file mode 100644 index 00000000..7273c0fa --- /dev/null +++ b/lib/js/test262-slice/loops/for_continue.expected @@ -0,0 +1 @@ +25 diff --git a/lib/js/test262-slice/loops/for_continue.js b/lib/js/test262-slice/loops/for_continue.js new file mode 100644 index 00000000..c71e5852 --- /dev/null +++ b/lib/js/test262-slice/loops/for_continue.js @@ -0,0 +1 @@ +let s = 0; for (let i = 0; i < 10; i = i + 1) { if (i % 2 === 0) continue; s = s + i; } s diff --git a/lib/js/test262-slice/loops/nested_for.expected b/lib/js/test262-slice/loops/nested_for.expected new file mode 100644 index 00000000..48082f72 --- /dev/null +++ b/lib/js/test262-slice/loops/nested_for.expected @@ -0,0 +1 @@ +12 diff --git a/lib/js/test262-slice/loops/nested_for.js b/lib/js/test262-slice/loops/nested_for.js new file mode 100644 index 00000000..8f327c91 --- /dev/null +++ b/lib/js/test262-slice/loops/nested_for.js @@ -0,0 +1 @@ +let n = 0; for (let i = 0; i < 3; i = i + 1) for (let j = 0; j < 4; j = j + 1) n = n + 1; n diff --git a/lib/js/test262-slice/loops/while_basic.expected b/lib/js/test262-slice/loops/while_basic.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/loops/while_basic.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/loops/while_basic.js b/lib/js/test262-slice/loops/while_basic.js new file mode 100644 index 00000000..f86fc765 --- /dev/null +++ b/lib/js/test262-slice/loops/while_basic.js @@ -0,0 +1 @@ +let x = 0; while (x < 5) { x = x + 1; } x diff --git a/lib/js/test262-slice/loops/while_break_infinite.expected b/lib/js/test262-slice/loops/while_break_infinite.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/loops/while_break_infinite.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/loops/while_break_infinite.js b/lib/js/test262-slice/loops/while_break_infinite.js new file mode 100644 index 00000000..bb8301a8 --- /dev/null +++ b/lib/js/test262-slice/loops/while_break_infinite.js @@ -0,0 +1 @@ +let i = 0; while (true) { i = i + 1; if (i >= 5) break; } i diff --git a/lib/js/test262-slice/objects/array_filter_reduce.expected b/lib/js/test262-slice/objects/array_filter_reduce.expected new file mode 100644 index 00000000..48082f72 --- /dev/null +++ b/lib/js/test262-slice/objects/array_filter_reduce.expected @@ -0,0 +1 @@ +12 diff --git a/lib/js/test262-slice/objects/array_filter_reduce.js b/lib/js/test262-slice/objects/array_filter_reduce.js new file mode 100644 index 00000000..2a310695 --- /dev/null +++ b/lib/js/test262-slice/objects/array_filter_reduce.js @@ -0,0 +1 @@ +[1, 2, 3, 4, 5].filter(x => x > 2).reduce((a, b) => a + b, 0) \ No newline at end of file diff --git a/lib/js/test262-slice/objects/array_map.expected b/lib/js/test262-slice/objects/array_map.expected new file mode 100644 index 00000000..c8f53251 --- /dev/null +++ b/lib/js/test262-slice/objects/array_map.expected @@ -0,0 +1 @@ +2,4,6 diff --git a/lib/js/test262-slice/objects/array_map.js b/lib/js/test262-slice/objects/array_map.js new file mode 100644 index 00000000..de55a3b0 --- /dev/null +++ b/lib/js/test262-slice/objects/array_map.js @@ -0,0 +1 @@ +[1, 2, 3].map(x => x * 2).join(",") \ No newline at end of file diff --git a/lib/js/test262-slice/objects/array_method_chain.expected b/lib/js/test262-slice/objects/array_method_chain.expected new file mode 100644 index 00000000..2cd1cfa2 --- /dev/null +++ b/lib/js/test262-slice/objects/array_method_chain.expected @@ -0,0 +1 @@ +170 diff --git a/lib/js/test262-slice/objects/array_method_chain.js b/lib/js/test262-slice/objects/array_method_chain.js new file mode 100644 index 00000000..ee1898d6 --- /dev/null +++ b/lib/js/test262-slice/objects/array_method_chain.js @@ -0,0 +1 @@ +[5, 2, 8, 1, 4].filter(x => x > 2).map(x => x * 10).reduce((a, b) => a + b, 0) \ No newline at end of file diff --git a/lib/js/test262-slice/objects/array_mutate.expected b/lib/js/test262-slice/objects/array_mutate.expected new file mode 100644 index 00000000..3ad5abd0 --- /dev/null +++ b/lib/js/test262-slice/objects/array_mutate.expected @@ -0,0 +1 @@ +99 diff --git a/lib/js/test262-slice/objects/array_mutate.js b/lib/js/test262-slice/objects/array_mutate.js new file mode 100644 index 00000000..fe4fb544 --- /dev/null +++ b/lib/js/test262-slice/objects/array_mutate.js @@ -0,0 +1,3 @@ +var a = [1, 2, 3]; +a[0] = 99; +a[0] \ No newline at end of file diff --git a/lib/js/test262-slice/objects/array_push_length.expected b/lib/js/test262-slice/objects/array_push_length.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/objects/array_push_length.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/objects/array_push_length.js b/lib/js/test262-slice/objects/array_push_length.js new file mode 100644 index 00000000..014ddd9d --- /dev/null +++ b/lib/js/test262-slice/objects/array_push_length.js @@ -0,0 +1,5 @@ +var a = []; +a.push(1); +a.push(2); +a.push(3); +a.length \ No newline at end of file diff --git a/lib/js/test262-slice/objects/arrow_lexical_this.expected b/lib/js/test262-slice/objects/arrow_lexical_this.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/objects/arrow_lexical_this.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/objects/arrow_lexical_this.js b/lib/js/test262-slice/objects/arrow_lexical_this.js new file mode 100644 index 00000000..682a23c5 --- /dev/null +++ b/lib/js/test262-slice/objects/arrow_lexical_this.js @@ -0,0 +1,2 @@ +var o = { x: 5, get: function() { var f = () => this.x; return f(); } }; +o.get() \ No newline at end of file diff --git a/lib/js/test262-slice/objects/class_basic.expected b/lib/js/test262-slice/objects/class_basic.expected new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/lib/js/test262-slice/objects/class_basic.expected @@ -0,0 +1 @@ +7 diff --git a/lib/js/test262-slice/objects/class_basic.js b/lib/js/test262-slice/objects/class_basic.js new file mode 100644 index 00000000..ebe57a1b --- /dev/null +++ b/lib/js/test262-slice/objects/class_basic.js @@ -0,0 +1,6 @@ +class Point { + constructor(x, y) { this.x = x; this.y = y; } + sum() { return this.x + this.y; } +} +var p = new Point(3, 4); +p.sum() \ No newline at end of file diff --git a/lib/js/test262-slice/objects/class_extend_chain.expected b/lib/js/test262-slice/objects/class_extend_chain.expected new file mode 100644 index 00000000..8baef1b4 --- /dev/null +++ b/lib/js/test262-slice/objects/class_extend_chain.expected @@ -0,0 +1 @@ +abc diff --git a/lib/js/test262-slice/objects/class_extend_chain.js b/lib/js/test262-slice/objects/class_extend_chain.js new file mode 100644 index 00000000..a1952d62 --- /dev/null +++ b/lib/js/test262-slice/objects/class_extend_chain.js @@ -0,0 +1,5 @@ +class A { a() { return "a"; } } +class B extends A { b() { return "b"; } } +class C extends B { c() { return this.a() + this.b() + "c"; } } +var c = new C(); +c.c() \ No newline at end of file diff --git a/lib/js/test262-slice/objects/class_inherit.expected b/lib/js/test262-slice/objects/class_inherit.expected new file mode 100644 index 00000000..cfbc74af --- /dev/null +++ b/lib/js/test262-slice/objects/class_inherit.expected @@ -0,0 +1 @@ +woof diff --git a/lib/js/test262-slice/objects/class_inherit.js b/lib/js/test262-slice/objects/class_inherit.js new file mode 100644 index 00000000..5a87fdb3 --- /dev/null +++ b/lib/js/test262-slice/objects/class_inherit.js @@ -0,0 +1,5 @@ +class Animal { speak() { return "generic"; } } +class Dog extends Animal { speak() { return "woof"; } } +class Puppy extends Dog {} +var p = new Puppy(); +p.speak() \ No newline at end of file diff --git a/lib/js/test262-slice/objects/counter_closure.expected b/lib/js/test262-slice/objects/counter_closure.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/objects/counter_closure.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/objects/counter_closure.js b/lib/js/test262-slice/objects/counter_closure.js new file mode 100644 index 00000000..2d5039c0 --- /dev/null +++ b/lib/js/test262-slice/objects/counter_closure.js @@ -0,0 +1,10 @@ +function makeCounter() { + var n = 0; + return { + inc: function() { n = n + 1; return n; }, + val: function() { return n; } + }; +} +var c = makeCounter(); +c.inc(); c.inc(); c.inc(); +c.val() \ No newline at end of file diff --git a/lib/js/test262-slice/objects/in_operator.expected b/lib/js/test262-slice/objects/in_operator.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/objects/in_operator.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/objects/in_operator.js b/lib/js/test262-slice/objects/in_operator.js new file mode 100644 index 00000000..63ef6d0e --- /dev/null +++ b/lib/js/test262-slice/objects/in_operator.js @@ -0,0 +1,2 @@ +var o = { x: 1, y: 2 }; +('x' in o) && !('z' in o) \ No newline at end of file diff --git a/lib/js/test262-slice/objects/instanceof.expected b/lib/js/test262-slice/objects/instanceof.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/objects/instanceof.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/objects/instanceof.js b/lib/js/test262-slice/objects/instanceof.js new file mode 100644 index 00000000..cef3eb06 --- /dev/null +++ b/lib/js/test262-slice/objects/instanceof.js @@ -0,0 +1,4 @@ +class A {} +class B extends A {} +var b = new B(); +(b instanceof B) && (b instanceof A) \ No newline at end of file diff --git a/lib/js/test262-slice/objects/method_this.expected b/lib/js/test262-slice/objects/method_this.expected new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/lib/js/test262-slice/objects/method_this.expected @@ -0,0 +1 @@ +5 diff --git a/lib/js/test262-slice/objects/method_this.js b/lib/js/test262-slice/objects/method_this.js new file mode 100644 index 00000000..ba832a52 --- /dev/null +++ b/lib/js/test262-slice/objects/method_this.js @@ -0,0 +1,2 @@ +var o = { x: 5, getX: function() { return this.x; } }; +o.getX() \ No newline at end of file diff --git a/lib/js/test262-slice/objects/new_constructor.expected b/lib/js/test262-slice/objects/new_constructor.expected new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/lib/js/test262-slice/objects/new_constructor.expected @@ -0,0 +1 @@ +7 diff --git a/lib/js/test262-slice/objects/new_constructor.js b/lib/js/test262-slice/objects/new_constructor.js new file mode 100644 index 00000000..d0d27fc1 --- /dev/null +++ b/lib/js/test262-slice/objects/new_constructor.js @@ -0,0 +1,3 @@ +function Point(x, y) { this.x = x; this.y = y; } +var p = new Point(3, 4); +p.x + p.y \ No newline at end of file diff --git a/lib/js/test262-slice/objects/object_mutate.expected b/lib/js/test262-slice/objects/object_mutate.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/objects/object_mutate.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/objects/object_mutate.js b/lib/js/test262-slice/objects/object_mutate.js new file mode 100644 index 00000000..23535887 --- /dev/null +++ b/lib/js/test262-slice/objects/object_mutate.js @@ -0,0 +1,3 @@ +var o = { x: 1 }; +o.y = 2; +o.x + o.y \ No newline at end of file diff --git a/lib/js/test262-slice/objects/prototype_chain.expected b/lib/js/test262-slice/objects/prototype_chain.expected new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/lib/js/test262-slice/objects/prototype_chain.expected @@ -0,0 +1 @@ +7 diff --git a/lib/js/test262-slice/objects/prototype_chain.js b/lib/js/test262-slice/objects/prototype_chain.js new file mode 100644 index 00000000..7c78606d --- /dev/null +++ b/lib/js/test262-slice/objects/prototype_chain.js @@ -0,0 +1,4 @@ +function Point(x, y) { this.x = x; this.y = y; } +Point.prototype.sum = function() { return this.x + this.y; }; +var p = new Point(3, 4); +p.sum() \ No newline at end of file diff --git a/lib/js/test262-slice/objects/string_method.expected b/lib/js/test262-slice/objects/string_method.expected new file mode 100644 index 00000000..2cdf7a99 --- /dev/null +++ b/lib/js/test262-slice/objects/string_method.expected @@ -0,0 +1 @@ +HELLO-WORLD diff --git a/lib/js/test262-slice/objects/string_method.js b/lib/js/test262-slice/objects/string_method.js new file mode 100644 index 00000000..affc8b58 --- /dev/null +++ b/lib/js/test262-slice/objects/string_method.js @@ -0,0 +1 @@ +"Hello World".toUpperCase().split(" ").join("-") \ No newline at end of file diff --git a/lib/js/test262-slice/objects/string_slice.expected b/lib/js/test262-slice/objects/string_slice.expected new file mode 100644 index 00000000..d8ef65a5 --- /dev/null +++ b/lib/js/test262-slice/objects/string_slice.expected @@ -0,0 +1 @@ +cde diff --git a/lib/js/test262-slice/objects/string_slice.js b/lib/js/test262-slice/objects/string_slice.js new file mode 100644 index 00000000..15a89c9f --- /dev/null +++ b/lib/js/test262-slice/objects/string_slice.js @@ -0,0 +1 @@ +"abcdefg".slice(2, 5) \ No newline at end of file diff --git a/lib/js/test262-slice/promises/executor_throws.expected b/lib/js/test262-slice/promises/executor_throws.expected new file mode 100644 index 00000000..f6d2e49a --- /dev/null +++ b/lib/js/test262-slice/promises/executor_throws.expected @@ -0,0 +1 @@ +bang diff --git a/lib/js/test262-slice/promises/executor_throws.js b/lib/js/test262-slice/promises/executor_throws.js new file mode 100644 index 00000000..65f167d0 --- /dev/null +++ b/lib/js/test262-slice/promises/executor_throws.js @@ -0,0 +1,4 @@ +var r = null; +new Promise((res, rej) => { throw "bang"; }).catch(e => { r = e; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/finally_passthrough.expected b/lib/js/test262-slice/promises/finally_passthrough.expected new file mode 100644 index 00000000..0acbea55 --- /dev/null +++ b/lib/js/test262-slice/promises/finally_passthrough.expected @@ -0,0 +1 @@ +1:5 diff --git a/lib/js/test262-slice/promises/finally_passthrough.js b/lib/js/test262-slice/promises/finally_passthrough.js new file mode 100644 index 00000000..23bba88d --- /dev/null +++ b/lib/js/test262-slice/promises/finally_passthrough.js @@ -0,0 +1,5 @@ +var r = null; +var hit = 0; +Promise.resolve(5).finally(() => { hit = 1; }).then(v => { r = v; }); +__drain(); +"" + hit + ":" + r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/microtask_ordering.expected b/lib/js/test262-slice/promises/microtask_ordering.expected new file mode 100644 index 00000000..09ebd8f6 --- /dev/null +++ b/lib/js/test262-slice/promises/microtask_ordering.expected @@ -0,0 +1 @@ +A,B,C,D diff --git a/lib/js/test262-slice/promises/microtask_ordering.js b/lib/js/test262-slice/promises/microtask_ordering.js new file mode 100644 index 00000000..960652b3 --- /dev/null +++ b/lib/js/test262-slice/promises/microtask_ordering.js @@ -0,0 +1,5 @@ +var log = []; +Promise.resolve(0).then(() => { log.push("A"); }).then(() => { log.push("C"); }); +Promise.resolve(0).then(() => { log.push("B"); }).then(() => { log.push("D"); }); +__drain(); +log.join(",") \ No newline at end of file diff --git a/lib/js/test262-slice/promises/new_promise_reject.expected b/lib/js/test262-slice/promises/new_promise_reject.expected new file mode 100644 index 00000000..75891bc6 --- /dev/null +++ b/lib/js/test262-slice/promises/new_promise_reject.expected @@ -0,0 +1 @@ +oops diff --git a/lib/js/test262-slice/promises/new_promise_reject.js b/lib/js/test262-slice/promises/new_promise_reject.js new file mode 100644 index 00000000..7531fded --- /dev/null +++ b/lib/js/test262-slice/promises/new_promise_reject.js @@ -0,0 +1,4 @@ +var r = null; +new Promise((resolve, reject) => { reject("oops"); }).catch(e => { r = e; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/new_promise_resolve.expected b/lib/js/test262-slice/promises/new_promise_resolve.expected new file mode 100644 index 00000000..d81cc071 --- /dev/null +++ b/lib/js/test262-slice/promises/new_promise_resolve.expected @@ -0,0 +1 @@ +42 diff --git a/lib/js/test262-slice/promises/new_promise_resolve.js b/lib/js/test262-slice/promises/new_promise_resolve.js new file mode 100644 index 00000000..1154d1b6 --- /dev/null +++ b/lib/js/test262-slice/promises/new_promise_resolve.js @@ -0,0 +1,4 @@ +var r = null; +new Promise((resolve, reject) => { resolve(42); }).then(v => { r = v; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/promise_all.expected b/lib/js/test262-slice/promises/promise_all.expected new file mode 100644 index 00000000..1e8b3149 --- /dev/null +++ b/lib/js/test262-slice/promises/promise_all.expected @@ -0,0 +1 @@ +6 diff --git a/lib/js/test262-slice/promises/promise_all.js b/lib/js/test262-slice/promises/promise_all.js new file mode 100644 index 00000000..a5ca72e1 --- /dev/null +++ b/lib/js/test262-slice/promises/promise_all.js @@ -0,0 +1,5 @@ +var r = null; +Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]) + .then(vs => { r = vs[0] + vs[1] + vs[2]; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/promise_all_empty.expected b/lib/js/test262-slice/promises/promise_all_empty.expected new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/lib/js/test262-slice/promises/promise_all_empty.expected @@ -0,0 +1 @@ +0 diff --git a/lib/js/test262-slice/promises/promise_all_empty.js b/lib/js/test262-slice/promises/promise_all_empty.js new file mode 100644 index 00000000..fa50a2bf --- /dev/null +++ b/lib/js/test262-slice/promises/promise_all_empty.js @@ -0,0 +1,4 @@ +var r = null; +Promise.all([]).then(vs => { r = vs.length; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/promise_all_nonpromise.expected b/lib/js/test262-slice/promises/promise_all_nonpromise.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/promises/promise_all_nonpromise.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/promises/promise_all_nonpromise.js b/lib/js/test262-slice/promises/promise_all_nonpromise.js new file mode 100644 index 00000000..81622d73 --- /dev/null +++ b/lib/js/test262-slice/promises/promise_all_nonpromise.js @@ -0,0 +1,4 @@ +var r = null; +Promise.all([1, 2, 3]).then(vs => { r = vs.length; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/promise_all_reject.expected b/lib/js/test262-slice/promises/promise_all_reject.expected new file mode 100644 index 00000000..587be6b4 --- /dev/null +++ b/lib/js/test262-slice/promises/promise_all_reject.expected @@ -0,0 +1 @@ +x diff --git a/lib/js/test262-slice/promises/promise_all_reject.js b/lib/js/test262-slice/promises/promise_all_reject.js new file mode 100644 index 00000000..e3a7266c --- /dev/null +++ b/lib/js/test262-slice/promises/promise_all_reject.js @@ -0,0 +1,5 @@ +var r = null; +Promise.all([Promise.resolve(1), Promise.reject("x"), Promise.resolve(3)]) + .catch(e => { r = e; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/promise_race.expected b/lib/js/test262-slice/promises/promise_race.expected new file mode 100644 index 00000000..9c59e24b --- /dev/null +++ b/lib/js/test262-slice/promises/promise_race.expected @@ -0,0 +1 @@ +first diff --git a/lib/js/test262-slice/promises/promise_race.js b/lib/js/test262-slice/promises/promise_race.js new file mode 100644 index 00000000..8f9b7afb --- /dev/null +++ b/lib/js/test262-slice/promises/promise_race.js @@ -0,0 +1,5 @@ +var r = null; +Promise.race([Promise.resolve("first"), Promise.resolve("second")]) + .then(v => { r = v; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/promise_resolve_already_promise.expected b/lib/js/test262-slice/promises/promise_resolve_already_promise.expected new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/lib/js/test262-slice/promises/promise_resolve_already_promise.expected @@ -0,0 +1 @@ +true diff --git a/lib/js/test262-slice/promises/promise_resolve_already_promise.js b/lib/js/test262-slice/promises/promise_resolve_already_promise.js new file mode 100644 index 00000000..dad8fa1b --- /dev/null +++ b/lib/js/test262-slice/promises/promise_resolve_already_promise.js @@ -0,0 +1,3 @@ +var p1 = Promise.resolve(5); +var p2 = Promise.resolve(p1); +p1 === p2 \ No newline at end of file diff --git a/lib/js/test262-slice/promises/reject_catch.expected b/lib/js/test262-slice/promises/reject_catch.expected new file mode 100644 index 00000000..67be85f1 --- /dev/null +++ b/lib/js/test262-slice/promises/reject_catch.expected @@ -0,0 +1 @@ +bad diff --git a/lib/js/test262-slice/promises/reject_catch.js b/lib/js/test262-slice/promises/reject_catch.js new file mode 100644 index 00000000..f771861d --- /dev/null +++ b/lib/js/test262-slice/promises/reject_catch.js @@ -0,0 +1,4 @@ +var msg = null; +Promise.reject("bad").catch(e => { msg = e; }); +__drain(); +msg \ No newline at end of file diff --git a/lib/js/test262-slice/promises/resolve_adopts.expected b/lib/js/test262-slice/promises/resolve_adopts.expected new file mode 100644 index 00000000..987e7ca9 --- /dev/null +++ b/lib/js/test262-slice/promises/resolve_adopts.expected @@ -0,0 +1 @@ +77 diff --git a/lib/js/test262-slice/promises/resolve_adopts.js b/lib/js/test262-slice/promises/resolve_adopts.js new file mode 100644 index 00000000..362bac99 --- /dev/null +++ b/lib/js/test262-slice/promises/resolve_adopts.js @@ -0,0 +1,5 @@ +var r = null; +var inner = Promise.resolve(77); +new Promise((res, rej) => { res(inner); }).then(v => { r = v; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/resolve_then.expected b/lib/js/test262-slice/promises/resolve_then.expected new file mode 100644 index 00000000..45a4fb75 --- /dev/null +++ b/lib/js/test262-slice/promises/resolve_then.expected @@ -0,0 +1 @@ +8 diff --git a/lib/js/test262-slice/promises/resolve_then.js b/lib/js/test262-slice/promises/resolve_then.js new file mode 100644 index 00000000..6a746e75 --- /dev/null +++ b/lib/js/test262-slice/promises/resolve_then.js @@ -0,0 +1,4 @@ +var r = null; +Promise.resolve(7).then(x => { r = x + 1; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/then_chain.expected b/lib/js/test262-slice/promises/then_chain.expected new file mode 100644 index 00000000..209e3ef4 --- /dev/null +++ b/lib/js/test262-slice/promises/then_chain.expected @@ -0,0 +1 @@ +20 diff --git a/lib/js/test262-slice/promises/then_chain.js b/lib/js/test262-slice/promises/then_chain.js new file mode 100644 index 00000000..4f06a001 --- /dev/null +++ b/lib/js/test262-slice/promises/then_chain.js @@ -0,0 +1,4 @@ +var r = null; +Promise.resolve(1).then(x => x + 1).then(x => x * 10).then(x => { r = x; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/promises/then_throw_catch.expected b/lib/js/test262-slice/promises/then_throw_catch.expected new file mode 100644 index 00000000..9e2ba7ec --- /dev/null +++ b/lib/js/test262-slice/promises/then_throw_catch.expected @@ -0,0 +1 @@ +boom diff --git a/lib/js/test262-slice/promises/then_throw_catch.js b/lib/js/test262-slice/promises/then_throw_catch.js new file mode 100644 index 00000000..825564c7 --- /dev/null +++ b/lib/js/test262-slice/promises/then_throw_catch.js @@ -0,0 +1,6 @@ +var r = null; +Promise.resolve(1) + .then(x => { throw "boom"; }) + .catch(e => { r = e; }); +__drain(); +r \ No newline at end of file diff --git a/lib/js/test262-slice/statements/block_scope.expected b/lib/js/test262-slice/statements/block_scope.expected new file mode 100644 index 00000000..bb95160c --- /dev/null +++ b/lib/js/test262-slice/statements/block_scope.expected @@ -0,0 +1 @@ +33 diff --git a/lib/js/test262-slice/statements/block_scope.js b/lib/js/test262-slice/statements/block_scope.js new file mode 100644 index 00000000..49d14433 --- /dev/null +++ b/lib/js/test262-slice/statements/block_scope.js @@ -0,0 +1 @@ +{ let a = 11; let b = 22; a + b } diff --git a/lib/js/test262-slice/statements/const_multi.expected b/lib/js/test262-slice/statements/const_multi.expected new file mode 100644 index 00000000..7273c0fa --- /dev/null +++ b/lib/js/test262-slice/statements/const_multi.expected @@ -0,0 +1 @@ +25 diff --git a/lib/js/test262-slice/statements/const_multi.js b/lib/js/test262-slice/statements/const_multi.js new file mode 100644 index 00000000..d6fa8880 --- /dev/null +++ b/lib/js/test262-slice/statements/const_multi.js @@ -0,0 +1 @@ +const a = 3, b = 4; a * a + b * b diff --git a/lib/js/test262-slice/statements/if_else_false.expected b/lib/js/test262-slice/statements/if_else_false.expected new file mode 100644 index 00000000..0cfbf088 --- /dev/null +++ b/lib/js/test262-slice/statements/if_else_false.expected @@ -0,0 +1 @@ +2 diff --git a/lib/js/test262-slice/statements/if_else_false.js b/lib/js/test262-slice/statements/if_else_false.js new file mode 100644 index 00000000..e6bf7515 --- /dev/null +++ b/lib/js/test262-slice/statements/if_else_false.js @@ -0,0 +1 @@ +let x = 2; if (x > 5) 1 else 2 diff --git a/lib/js/test262-slice/statements/if_else_true.expected b/lib/js/test262-slice/statements/if_else_true.expected new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/lib/js/test262-slice/statements/if_else_true.expected @@ -0,0 +1 @@ +1 diff --git a/lib/js/test262-slice/statements/if_else_true.js b/lib/js/test262-slice/statements/if_else_true.js new file mode 100644 index 00000000..ef1a951e --- /dev/null +++ b/lib/js/test262-slice/statements/if_else_true.js @@ -0,0 +1 @@ +let x = 10; if (x > 5) 1 else 2 diff --git a/lib/js/test262-slice/statements/let_init.expected b/lib/js/test262-slice/statements/let_init.expected new file mode 100644 index 00000000..f599e28b --- /dev/null +++ b/lib/js/test262-slice/statements/let_init.expected @@ -0,0 +1 @@ +10 diff --git a/lib/js/test262-slice/statements/let_init.js b/lib/js/test262-slice/statements/let_init.js new file mode 100644 index 00000000..e6eaf620 --- /dev/null +++ b/lib/js/test262-slice/statements/let_init.js @@ -0,0 +1 @@ +let x = 7; x + 3 diff --git a/lib/js/test262-slice/statements/var_decl.expected b/lib/js/test262-slice/statements/var_decl.expected new file mode 100644 index 00000000..00750edc --- /dev/null +++ b/lib/js/test262-slice/statements/var_decl.expected @@ -0,0 +1 @@ +3 diff --git a/lib/js/test262-slice/statements/var_decl.js b/lib/js/test262-slice/statements/var_decl.js new file mode 100644 index 00000000..ffcfd66e --- /dev/null +++ b/lib/js/test262-slice/statements/var_decl.js @@ -0,0 +1 @@ +var x = 1; x + x + x diff --git a/lib/js/transpile.sx b/lib/js/transpile.sx new file mode 100644 index 00000000..673381aa --- /dev/null +++ b/lib/js/transpile.sx @@ -0,0 +1,915 @@ +;; lib/js/transpile.sx — JS AST → SX AST +;; +;; Produces SX trees the existing CEK/VM can evaluate directly. +;; Reuses lib/js/runtime.sx shims for JS-specific semantics +;; (coercions, prototype lookup, abstract equality, etc.). +;; +;; Input AST node shapes (from lib/js/parser.sx): +;; (js-num n) (js-str s) (js-bool b) +;; (js-null) (js-undef) +;; (js-ident "name") +;; (js-unop op expr) +;; (js-binop op l r) +;; (js-member obj "key") (js-index obj expr) +;; (js-call fn (args...)) +;; (js-array (elts...)) (js-object ({:key :value}...)) +;; (js-cond c t f) +;; (js-arrow (params...) body) +;; (js-assign op target rhs) +;; +;; Output is plain SX the evaluator can run, built with `list`/`cons`/ +;; `make-symbol`. The entry point `js-eval-ast` calls `eval-expr` on +;; the transpiled tree. + +;; ── tiny helpers ────────────────────────────────────────────────── + +(define js-sym (fn (name) (make-symbol name))) +(define + js-tag? + (fn + (ast tag) + (and + (= (type-of ast) "list") + (not (empty? ast)) + (= (type-of (first ast)) "symbol") + (= (symbol-name (first ast)) tag)))) + +(define js-ast-tag (fn (ast) (symbol-name (first ast)))) + +;; ── main dispatcher ─────────────────────────────────────────────── + +(define + js-transpile + (fn + (ast) + (cond + ((= (type-of ast) "number") ast) + ((= (type-of ast) "string") ast) + ((= (type-of ast) "boolean") ast) + ((= ast nil) ast) + ((= (type-of ast) "list") + (cond + ((empty? ast) (list)) + ((js-tag? ast "js-num") (nth ast 1)) + ((js-tag? ast "js-str") (nth ast 1)) + ((js-tag? ast "js-bool") (nth ast 1)) + ((js-tag? ast "js-null") nil) + ((js-tag? ast "js-undef") (list (js-sym "quote") :js-undefined)) + ((js-tag? ast "js-ident") (js-transpile-ident (nth ast 1))) + ((js-tag? ast "js-unop") + (js-transpile-unop (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-binop") + (js-transpile-binop (nth ast 1) (nth ast 2) (nth ast 3))) + ((js-tag? ast "js-member") + (js-transpile-member (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-index") + (js-transpile-index (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-call") + (js-transpile-call (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-array") (js-transpile-array (nth ast 1))) + ((js-tag? ast "js-object") (js-transpile-object (nth ast 1))) + ((js-tag? ast "js-cond") + (js-transpile-cond (nth ast 1) (nth ast 2) (nth ast 3))) + ((js-tag? ast "js-arrow") + (js-transpile-arrow (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-program") (js-transpile-stmts (nth ast 1))) + ((js-tag? ast "js-block") (js-transpile-stmts (nth ast 1))) + ((js-tag? ast "js-exprstmt") (js-transpile (nth ast 1))) + ((js-tag? ast "js-empty") nil) + ((js-tag? ast "js-var") + (js-transpile-var (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-if") + (js-transpile-if-stmt (nth ast 1) (nth ast 2) (nth ast 3))) + ((js-tag? ast "js-while") + (js-transpile-while (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-do-while") + (js-transpile-do-while (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-for") + (js-transpile-for + (nth ast 1) + (nth ast 2) + (nth ast 3) + (nth ast 4))) + ((js-tag? ast "js-return") (js-transpile-return (nth ast 1))) + ((js-tag? ast "js-break") (js-transpile-break)) + ((js-tag? ast "js-continue") (js-transpile-continue)) + ((js-tag? ast "js-funcdecl") + (js-transpile-funcdecl (nth ast 1) (nth ast 2) (nth ast 3))) + ((js-tag? ast "js-funcexpr") + (js-transpile-funcexpr (nth ast 1) (nth ast 2) (nth ast 3))) + ((js-tag? ast "js-assign") + (js-transpile-assign (nth ast 1) (nth ast 2) (nth ast 3))) + ((js-tag? ast "js-new") + (js-transpile-new (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-class") + (js-transpile-class (nth ast 1) (nth ast 2) (nth ast 3))) + ((js-tag? ast "js-throw") (js-transpile-throw (nth ast 1))) + ((js-tag? ast "js-try") + (js-transpile-try (nth ast 1) (nth ast 2) (nth ast 3))) + ((js-tag? ast "js-await") + (list (js-sym "js-await-value") (js-transpile (nth ast 1)))) + ((js-tag? ast "js-funcdecl-async") + (js-transpile-funcdecl-async + (nth ast 1) + (nth ast 2) + (nth ast 3))) + ((js-tag? ast "js-funcexpr-async") + (js-transpile-funcexpr-async + (nth ast 1) + (nth ast 2) + (nth ast 3))) + ((js-tag? ast "js-arrow-async") + (js-transpile-arrow-async (nth ast 1) (nth ast 2))) + ((js-tag? ast "js-tpl") (js-transpile-tpl (nth ast 1))) + (else + (error (str "js-transpile: unknown AST tag: " (js-ast-tag ast)))))) + (else + (error (str "js-transpile: unexpected value type: " (type-of ast))))))) + +;; ── Identifier lookup ───────────────────────────────────────────── + +;; `undefined` in JS is really a global binding. If the parser emits +;; (js-undef) we handle that above. A bare `undefined` ident also maps +;; to the same sentinel. +(define + js-transpile-ident + (fn + (name) + (cond + ((= name "undefined") (list (js-sym "quote") :js-undefined)) + (else (js-sym name))))) + +;; ── Unary ops ───────────────────────────────────────────────────── + +(define + js-transpile-unop + (fn + (op arg) + (let + ((a (js-transpile arg))) + (cond + ((= op "-") (list (js-sym "js-neg") a)) + ((= op "+") (list (js-sym "js-pos") a)) + ((= op "!") (list (js-sym "js-not") a)) + ((= op "~") (list (js-sym "js-bitnot") a)) + ((= op "typeof") (list (js-sym "js-typeof") a)) + ((= op "void") (list (js-sym "quote") :js-undefined)) + (else (error (str "js-transpile-unop: unsupported op: " op))))))) + +;; ── Binary ops ──────────────────────────────────────────────────── + +(define + js-transpile-binop + (fn + (op l r) + (cond + ((= op "+") + (list (js-sym "js-add") (js-transpile l) (js-transpile r))) + ((= op "-") + (list (js-sym "js-sub") (js-transpile l) (js-transpile r))) + ((= op "*") + (list (js-sym "js-mul") (js-transpile l) (js-transpile r))) + ((= op "/") + (list (js-sym "js-div") (js-transpile l) (js-transpile r))) + ((= op "%") + (list (js-sym "js-mod") (js-transpile l) (js-transpile r))) + ((= op "**") + (list (js-sym "js-pow") (js-transpile l) (js-transpile r))) + ((= op "===") + (list (js-sym "js-strict-eq") (js-transpile l) (js-transpile r))) + ((= op "!==") + (list (js-sym "js-strict-neq") (js-transpile l) (js-transpile r))) + ((= op "==") + (list (js-sym "js-loose-eq") (js-transpile l) (js-transpile r))) + ((= op "!=") + (list (js-sym "js-loose-neq") (js-transpile l) (js-transpile r))) + ((= op "<") + (list (js-sym "js-lt") (js-transpile l) (js-transpile r))) + ((= op ">") + (list (js-sym "js-gt") (js-transpile l) (js-transpile r))) + ((= op "<=") + (list (js-sym "js-le") (js-transpile l) (js-transpile r))) + ((= op ">=") + (list (js-sym "js-ge") (js-transpile l) (js-transpile r))) + ((= op "&&") + (list + (js-sym "js-and") + (js-transpile l) + (list (js-sym "fn") (list) (js-transpile r)))) + ((= op "||") + (list + (js-sym "js-or") + (js-transpile l) + (list (js-sym "fn") (list) (js-transpile r)))) + ((= op "instanceof") + (list (js-sym "js-instanceof") (js-transpile l) (js-transpile r))) + ((= op "in") + (list (js-sym "js-in") (js-transpile l) (js-transpile r))) + ((= op "??") + (list + (js-sym "let") + (list (list (js-sym "_a") (js-transpile l))) + (list + (js-sym "if") + (list + (js-sym "or") + (list (js-sym "=") (js-sym "_a") nil) + (list (js-sym "js-undefined?") (js-sym "_a"))) + (js-transpile r) + (js-sym "_a")))) + (else (error (str "js-transpile-binop: unsupported op: " op)))))) + +;; ── Member / index ──────────────────────────────────────────────── + +(define + js-transpile-member + (fn (obj key) (list (js-sym "js-get-prop") (js-transpile obj) key))) + +(define + js-transpile-index + (fn + (obj idx) + (list (js-sym "js-get-prop") (js-transpile obj) (js-transpile idx)))) + +;; ── Call ────────────────────────────────────────────────────────── + +;; JS `f(a, b, c)` → `(f a b c)` after transpile. Works for both +;; identifier calls and computed callee (arrow fn, member access). +(define + js-transpile-call + (fn + (callee args) + (cond + ((and (list? callee) (js-tag? callee "js-member")) + (let + ((recv (js-transpile (nth callee 1))) (key (nth callee 2))) + (list + (js-sym "js-invoke-method") + recv + key + (cons (js-sym "list") (map js-transpile args))))) + ((and (list? callee) (js-tag? callee "js-index")) + (let + ((recv (js-transpile (nth callee 1))) + (key (js-transpile (nth callee 2)))) + (list + (js-sym "js-invoke-method-dyn") + recv + key + (cons (js-sym "list") (map js-transpile args))))) + (else + (list + (js-sym "js-call-plain") + (js-transpile callee) + (cons (js-sym "list") (map js-transpile args))))))) + +;; ── Array literal ───────────────────────────────────────────────── + +(define + js-transpile-new + (fn + (callee args) + (list + (js-sym "js-new-call") + (js-transpile callee) + (cons (js-sym "list") (map js-transpile args))))) + +;; ── Object literal ──────────────────────────────────────────────── + +;; Build a dict by `(dict)` + `dict-set!` inside a `let` that yields +;; the dict as its final expression. This keeps keys in JS insertion +;; order and allows computed values. +(define + js-transpile-array + (fn (elts) (cons (js-sym "list") (map js-transpile elts)))) + +;; ── Conditional ─────────────────────────────────────────────────── + +(define + js-transpile-object + (fn + (entries) + (list + (js-sym "let") + (list (list (js-sym "_obj") (list (js-sym "dict")))) + (cons + (js-sym "begin") + (append + (map + (fn + (entry) + (list + (js-sym "dict-set!") + (js-sym "_obj") + (get entry :key) + (js-transpile (get entry :value)))) + entries) + (list (js-sym "_obj"))))))) + +;; ── Arrow function ──────────────────────────────────────────────── + +(define + js-transpile-cond + (fn + (c t f) + (list + (js-sym "if") + (list (js-sym "js-to-boolean") (js-transpile c)) + (js-transpile t) + (js-transpile f)))) + +;; ── Assignment ──────────────────────────────────────────────────── + +;; `a = b` on an ident → (set! a b). +;; `a += b` on an ident → (set! a (js-add a b)). +;; `obj.k = v` / `obj[k] = v` → (js-set-prop obj "k" v). +(define + js-transpile-arrow + (fn + (params body) + (let + ((param-syms (js-build-param-list params)) + (inits (js-param-init-forms params)) + (body-tr + (if + (and (list? body) (js-tag? body "js-block")) + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__return__")) + (cons + (js-sym "begin") + (append + inits + (append + (js-collect-funcdecls (nth body 1)) + (js-transpile-stmt-list (nth body 1))))))) + (if + (empty? inits) + (js-transpile body) + (cons + (js-sym "begin") + (append inits (list (js-transpile body)))))))) + (list (js-sym "fn") param-syms body-tr)))) + +(define + js-transpile-tpl + (fn + (parts) + (cond + ((empty? parts) (list (quote quote) "")) + ((= (len parts) 1) + (list (js-sym "js-to-string") (js-transpile (first parts)))) + (else + (cons (js-sym "js-template-concat") (js-transpile-tpl-parts parts)))))) + +(define + js-transpile-tpl-parts + (fn + (parts) + (if + (empty? parts) + (list) + (cons + (js-transpile (first parts)) + (js-transpile-tpl-parts (rest parts)))))) + +;; ── End-to-end entry points ─────────────────────────────────────── + +;; Transpile + eval a single JS expression string. +(define + js-transpile-assign + (fn + (op target rhs) + (cond + ((js-tag? target "js-ident") + (let + ((name (nth target 1))) + (let + ((sxname (js-sym name))) + (cond + ((= op "=") (list (js-sym "set!") sxname (js-transpile rhs))) + (else + (list + (js-sym "set!") + sxname + (js-compound-update op sxname (js-transpile rhs)))))))) + ((js-tag? target "js-member") + (list + (js-sym "js-set-prop") + (js-transpile (nth target 1)) + (nth target 2) + (js-compound-update-or-plain + op + (js-transpile target) + (js-transpile rhs)))) + ((js-tag? target "js-index") + (list + (js-sym "js-set-prop") + (js-transpile (nth target 1)) + (js-transpile (nth target 2)) + (js-compound-update-or-plain + op + (js-transpile target) + (js-transpile rhs)))) + (else (error "js-transpile-assign: unsupported target"))))) + +;; Transpile a JS expression string to SX source text (for inspection +;; in tests). Useful for asserting the exact emitted tree. +(define + js-compound-update + (fn + (op lhs-expr rhs-expr) + (cond + ((= op "+=") (list (js-sym "js-add") lhs-expr rhs-expr)) + ((= op "-=") (list (js-sym "js-sub") lhs-expr rhs-expr)) + ((= op "*=") (list (js-sym "js-mul") lhs-expr rhs-expr)) + ((= op "/=") (list (js-sym "js-div") lhs-expr rhs-expr)) + ((= op "%=") (list (js-sym "js-mod") lhs-expr rhs-expr)) + ((= op "**=") (list (js-sym "js-pow") lhs-expr rhs-expr)) + (else (error (str "js-compound-update: unsupported op: " op)))))) + +(define + js-compound-update-or-plain + (fn + (op lhs-expr rhs-expr) + (cond + ((= op "=") rhs-expr) + (else (js-compound-update op lhs-expr rhs-expr))))) + +(define + js-param-sym + (fn + (p) + (cond + ((string? p) (js-sym p)) + ((and (list? p) (js-tag? p "js-param")) (js-sym (nth p 1))) + ((and (list? p) (js-tag? p "js-rest")) (js-sym (nth p 1))) + (else (js-sym p))))) + +(define + js-build-param-list + (fn + (params) + (cond + ((empty? params) (list)) + ((and (list? (first params)) (js-tag? (first params) "js-rest")) + (list (js-sym "&rest") (js-sym (nth (first params) 1)))) + (else + (cons + (js-param-sym (first params)) + (js-build-param-list (rest params))))))) + +(define + js-param-init-forms + (fn + (params) + (cond + ((empty? params) (list)) + ((and (list? (first params)) (js-tag? (first params) "js-param")) + (let + ((nm (js-sym (nth (first params) 1))) + (dv (js-transpile (nth (first params) 2)))) + (cons + (list + (js-sym "set!") + nm + (list + (js-sym "if") + (list + (js-sym "or") + (list (js-sym "=") nm nil) + (list + (js-sym "=") + nm + (list (js-sym "quote") :js-undefined))) + dv + nm)) + (js-param-init-forms (rest params))))) + (else (js-param-init-forms (rest params)))))) + +(define + js-transpile-stmts + (fn + (stmts) + (let + ((hoisted (js-collect-funcdecls stmts))) + (let + ((rest-stmts (js-transpile-stmt-list stmts))) + (cons (js-sym "begin") (append hoisted rest-stmts)))))) + +(define + js-collect-funcdecls + (fn + (stmts) + (cond + ((empty? stmts) (list)) + ((and (list? (first stmts)) (js-tag? (first stmts) "js-funcdecl")) + (cons + (js-transpile-funcdecl + (nth (first stmts) 1) + (nth (first stmts) 2) + (nth (first stmts) 3)) + (js-collect-funcdecls (rest stmts)))) + (else (js-collect-funcdecls (rest stmts)))))) + +(define + js-transpile-stmt-list + (fn + (stmts) + (cond + ((empty? stmts) (list)) + ((and (list? (first stmts)) (js-tag? (first stmts) "js-funcdecl")) + (cons nil (js-transpile-stmt-list (rest stmts)))) + (else + (cons + (js-transpile (first stmts)) + (js-transpile-stmt-list (rest stmts))))))) + +(define + js-transpile-var + (fn (kind decls) (cons (js-sym "begin") (js-vardecl-forms decls)))) + +(define + js-vardecl-forms + (fn + (decls) + (cond + ((empty? decls) (list)) + (else + (let + ((d (first decls))) + (cons + (list + (js-sym "define") + (js-sym (nth d 1)) + (js-transpile (nth d 2))) + (js-vardecl-forms (rest decls)))))))) + +(define + js-transpile-if-stmt + (fn + (c t e) + (let + ((c-tr (list (js-sym "js-to-boolean") (js-transpile c))) + (t-tr (if (= t nil) nil (js-transpile t)))) + (if + (= e nil) + (list (js-sym "if") c-tr t-tr nil) + (list (js-sym "if") c-tr t-tr (js-transpile e)))))) + +(define + js-transpile-while + (fn + (c body) + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__break__")) + (list + (js-sym "letrec") + (list + (list + (js-sym "__loop__") + (list + (js-sym "fn") + (list) + (list + (js-sym "if") + (list (js-sym "js-to-boolean") (js-transpile c)) + (list + (js-sym "begin") + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__continue__")) + (js-transpile body))) + (list (js-sym "__loop__"))) + nil)))) + (list (js-sym "__loop__"))))))) + +(define + js-transpile-do-while + (fn + (body c) + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__break__")) + (list + (js-sym "letrec") + (list + (list + (js-sym "__loop__") + (list + (js-sym "fn") + (list) + (list + (js-sym "begin") + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__continue__")) + (js-transpile body))) + (list + (js-sym "if") + (list (js-sym "js-to-boolean") (js-transpile c)) + (list (js-sym "__loop__")) + nil))))) + (list (js-sym "__loop__"))))))) + +(define + js-transpile-for + (fn + (init cond-ast step body) + (let + ((init-form (if (= init nil) nil (js-transpile init))) + (cond-form + (if + (= cond-ast nil) + (list (js-sym "quote") true) + (list (js-sym "js-to-boolean") (js-transpile cond-ast)))) + (step-form (if (= step nil) nil (js-transpile step))) + (body-tr (js-transpile body))) + (list + (js-sym "begin") + (if (= init-form nil) nil init-form) + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__break__")) + (list + (js-sym "letrec") + (list + (list + (js-sym "__loop__") + (list + (js-sym "fn") + (list) + (list + (js-sym "if") + cond-form + (list + (js-sym "begin") + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__continue__")) + body-tr)) + (if (= step-form nil) nil step-form) + (list (js-sym "__loop__"))) + nil)))) + (list (js-sym "__loop__"))))))))) + +(define + js-transpile-return + (fn + (e) + (list + (js-sym "__return__") + (if (= e nil) (list (js-sym "quote") :js-undefined) (js-transpile e))))) + +(define js-transpile-break (fn () (list (js-sym "__break__") nil))) + +(define js-transpile-continue (fn () (list (js-sym "__continue__") nil))) + +(define + js-transpile-funcdecl + (fn + (name params body) + (list + (js-sym "define") + (js-sym name) + (js-transpile-funcexpr name params body)))) + +(define + js-transpile-class + (fn + (name parent methods) + (let + ((ctor-method (js-find-ctor methods)) + (instance-methods (js-filter-methods methods "instance")) + (name-sym (js-sym name))) + (let + ((ctor-body (if (= ctor-method nil) (if parent (js-default-ctor-with-super parent) (js-default-ctor-noop)) (js-transpile-funcexpr name (nth ctor-method 3) (nth ctor-method 4))))) + (cons + (js-sym "begin") + (append + (list (list (js-sym "define") name-sym ctor-body)) + (list (list (js-sym "js-reset-ctor-proto!") name-sym)) + (if + parent + (list + (list + (js-sym "dict-set!") + (list (js-sym "js-get-ctor-proto") name-sym) + "__proto__" + (list (js-sym "js-get-ctor-proto") (js-sym parent)))) + (list)) + (map + (fn + (m) + (let + ((mname (nth m 2)) + (mparams (nth m 3)) + (mbody (nth m 4))) + (list + (js-sym "dict-set!") + (list (js-sym "js-get-ctor-proto") name-sym) + mname + (js-transpile-funcexpr mname mparams mbody)))) + instance-methods) + (list + (list + (js-sym "dict-set!") + (list (js-sym "js-get-ctor-proto") name-sym) + "constructor" + name-sym)))))))) + +(define + js-find-ctor + (fn + (methods) + (cond + ((empty? methods) nil) + ((and (js-tag? (first methods) "js-method") (= (nth (first methods) 1) "instance") (= (nth (first methods) 2) "constructor")) + (first methods)) + (else (js-find-ctor (rest methods)))))) + +(define + js-filter-methods + (fn + (methods kind) + (cond + ((empty? methods) (list)) + ((and (js-tag? (first methods) "js-method") (= (nth (first methods) 1) kind) (not (= (nth (first methods) 2) "constructor"))) + (cons (first methods) (js-filter-methods (rest methods) kind))) + (else (js-filter-methods (rest methods) kind))))) + +(define + js-default-ctor-noop + (fn + () + (list + (js-sym "fn") + (list (js-sym "&rest") (js-sym "__args__")) + (list + (js-sym "let") + (list (list (js-sym "this") (list (js-sym "js-this")))) + :js-undefined)))) + +(define + js-default-ctor-with-super + (fn + (parent) + (list + (js-sym "fn") + (list (js-sym "&rest") (js-sym "__args__")) + (list + (js-sym "let") + (list (list (js-sym "this") (list (js-sym "js-this")))) + (list + (js-sym "js-call-with-this") + (js-sym "this") + (js-sym parent) + (js-sym "__args__")))))) + +(define + js-transpile-throw + (fn (e) (list (js-sym "raise") (js-transpile e)))) + +(define + js-transpile-try + (fn + (body catch-part finally-part) + (let + ((body-tr (js-transpile body))) + (let + ((with-catch (cond ((= catch-part nil) body-tr) (else (let ((pname (nth catch-part 0)) (cbody (nth catch-part 1))) (list (js-sym "guard") (list (if (= pname nil) (js-sym "__exc__") (js-sym pname)) (list (js-sym "else") (js-transpile cbody))) body-tr)))))) + (cond + ((= finally-part nil) with-catch) + (else + (list + (js-sym "let") + (list (list (js-sym "__try_result__") with-catch)) + (js-transpile finally-part) + (js-sym "__try_result__")))))))) + +(define + js-transpile-funcexpr + (fn + (name params body) + (let + ((param-syms (js-build-param-list params)) + (inits (js-param-init-forms params)) + (body-forms + (if + (and (list? body) (js-tag? body "js-block")) + (let + ((hoisted (js-collect-funcdecls (nth body 1)))) + (append hoisted (js-transpile-stmt-list (nth body 1)))) + (list (js-transpile body))))) + (list + (js-sym "fn") + param-syms + (list + (js-sym "let") + (list (list (js-sym "this") (list (js-sym "js-this")))) + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__return__")) + (cons (js-sym "begin") (append inits body-forms))))))))) + +(define + js-transpile-funcexpr-async + (fn + (name params body) + (let + ((param-syms (js-build-param-list params)) + (inits (js-param-init-forms params)) + (body-forms + (if + (and (list? body) (js-tag? body "js-block")) + (let + ((hoisted (js-collect-funcdecls (nth body 1)))) + (append hoisted (js-transpile-stmt-list (nth body 1)))) + (list (js-transpile body))))) + (list + (js-sym "fn") + param-syms + (list + (js-sym "let") + (list (list (js-sym "this") (list (js-sym "js-this")))) + (list + (js-sym "js-async-wrap") + (list + (js-sym "fn") + (list) + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__return__")) + (cons (js-sym "begin") (append inits body-forms))))))))))) + +(define + js-transpile-funcdecl-async + (fn + (name params body) + (list + (js-sym "define") + (js-sym name) + (js-transpile-funcexpr-async name params body)))) + +(define + js-transpile-arrow-async + (fn + (params body) + (let + ((param-syms (js-build-param-list params)) + (inits (js-param-init-forms params)) + (body-tr + (if + (and (list? body) (js-tag? body "js-block")) + (list + (js-sym "call/cc") + (list + (js-sym "fn") + (list (js-sym "__return__")) + (cons + (js-sym "begin") + (append + inits + (append + (js-collect-funcdecls (nth body 1)) + (js-transpile-stmt-list (nth body 1))))))) + (if + (empty? inits) + (js-transpile body) + (cons + (js-sym "begin") + (append inits (list (js-transpile body)))))))) + (list + (js-sym "fn") + param-syms + (list (js-sym "js-async-wrap") (list (js-sym "fn") (list) body-tr)))))) + +(define + js-eval + (fn + (src) + (let + ((result (eval-expr (js-transpile (js-parse (js-tokenize src)))))) + (js-drain-microtasks!) + result))) + +(define js-compile-expr (fn (src) (js-transpile (js-parse-expr src)))) diff --git a/plans/agent-briefings/loop.md b/plans/agent-briefings/loop.md new file mode 100644 index 00000000..4d067a81 --- /dev/null +++ b/plans/agent-briefings/loop.md @@ -0,0 +1,88 @@ +# js-on-sx loop agent (single agent, queue-driven) + +Role: iterates `plans/js-on-sx.md` forever. Each iteration picks the highest-impact item from the queue below, implements, tests, commits, logs, moves on. Scoreboard-driven: the real test262 pass rate is the north star. + +``` +description: js-on-sx queue loop +subagent_type: general-purpose +run_in_background: true +``` + +## Prompt + +You are the sole background agent working `/root/rose-ash/plans/js-on-sx.md`. A previous three-agent setup was consolidated — there is only you now. You work a prioritized queue, forever, one item per commit. + +## Current state (restart baseline — verify before iterating) + +- Branch: `architecture`. HEAD: `14b6586e` (HS-related, not js-on-sx). +- `lib/js/` is **untracked** — nothing is committed yet. First commit should stage everything current on disk. +- `lib/js/test262-upstream/` is a clone of tc39/test262 pinned at `d5e73fc8d2c663554fb72e2380a8c2bc1a318a33`. **Gitignore it** (`lib/js/.gitignore` → `test262-upstream/`). Do not commit the 50k test files. +- `lib/js/test262-runner.py` exists but is buggy — current scoreboard is `0/8 (7 timeouts, 1 fail)`. The runner needs real work: harness script loading, batching, per-test timeout tuning, strict-mode skipping. +- `lib/js/test262-scoreboard.{json,md}` exist as placeholders. Regenerate after fixing the runner. +- `bash lib/js/test.sh` → **254/254** pass (timeout 180s already applied). +- `bash lib/js/conformance.sh` → **148/148** pass. + +## The queue + +Work in this order. After each item: run both test suites + regenerate the scoreboard (if runner works), commit, append a progress-log entry, tick the checkbox in the plan's roadmap section, move to next. + +1. **Baseline commit.** Stage the whole `lib/js/` tree + `plans/` directory as-is and commit. Message: `js-on-sx: baseline commit (254/254 unit, 148/148 slice, runner stub)`. After this, no more giant commits — one feature per commit. + +2. **Fix the test262 runner** (`lib/js/test262-runner.py`). Goal: get a real pass-rate number against at least a few thousand tests. Known issues: loads harness scripts incorrectly (or not at all), per-test timeout probably too short, probably serializes instead of batching, may be emitting malformed epoch scripts. Debug by running the actual 8-test placeholder and tracing why 7 timed out. Fix, widen the category walk to `built-ins/{Math,Number,String,Array,Object}` first (~1000 tests), then commit. Re-run — expect somewhere in 5-30% pass rate. + +3. **Full scoreboard run.** Widen to the whole `test/` tree. Record wall-time, pass rate, top-10 worst categories, top-10 failure modes. Save to `test262-scoreboard.{json,md}`. Commit. This is the dial that drives everything after. + +4. **Regex lexer+parser+runtime stub.** Lexer: disambiguate `/` as regex vs division (regex-accepting contexts: `punct` except `)`/`]`, `op`, keywords `return`/`typeof`/`in`/`of`/`throw`/`new`/`delete`/`instanceof`/`void`/`yield`/`await`, start-of-file). Emit `{:type "regex" :value {:pattern :flags}}`. Parser: `(js-regex pat flags)` AST. Transpile: `(js-regex-new pat flags)`. Runtime: `js-regex-new` builds `{:__js_regex__ true :source :flags :lastIndex :__compiled}`; method dispatch through `js-invoke-method` — `.test`, `.exec`, `.source`, `.flags`, booleans for flags. Route `String.prototype.{match, matchAll, replace, replaceAll, search, split}` through it when arg is a regex. **Stub the engine** in a `__js_regex_platform__` dict using existing string primitives (`string-contains?`, `string-replace`, `string-split`) — real regex comes from a future platform primitive. Expose `js-regex-platform-override!` so the primitive can be swapped in later. Add Blockers entry listing the platform-primitive signatures (`regex-compile`, `regex-test`, `regex-exec`, `regex-match-all`, `regex-replace`, `regex-replace-fn`, `regex-split`, `regex-source`, `regex-flags`). + +5. **Scoreboard-driven from here.** Each iteration: re-read `lib/js/test262-scoreboard.md`, pick the worst-passing category where the failure mode is something you can fix in :str }`. `:utf16` is canonical. Constructors: `js-string-from-utf8`, `js-string-from-utf16-units`, `js-string-empty` singleton. Observers: `js-string-length` (code-unit count, what `.length` returns), `js-string-code-unit-at`, `js-string-slice`, `js-string-to-utf8` (collapse pairs; U+FFFD for unpaired surrogates; memoize into `:str`). Every JS "string" entry point lifts raw SX strings via `js-as-js-string`. `js-eval` result: convert JsString → SX string for terminal output. Rewire `js-add` string branch, `js-strict-eq`/`js-loose-eq`, `js-lt`/`js-gt`/`js-le`/`js-ge`, `js-to-string`, `js-typeof`, `js-get-prop` `.length` and indexing, and every `String.prototype` method. Non-goals: `.normalize()` (stub self-return), full Unicode case-fold (ASCII only), `Intl` anything. + +## Ground rules + +- **Scope:** only `lib/js/**` and `plans/js-on-sx.md`. Do NOT touch `spec/`, `shared/`, `lib/hyperscript/`. Shared-file issues go under the plan's "Blockers" section. +- **SX files:** `sx-tree` MCP tools ONLY. `sx_summarise` / `sx_read_subtree` / `sx_find_all` / `sx_get_context` before edits. `sx_replace_node` / `sx_insert_child` / `sx_insert_near` / `sx_replace_by_pattern` / `sx_rename_symbol` for edits. `sx_validate` after. `sx_write_file` for new files. Never `Edit`/`Read`/`Write` on `.sx`. +- **Shell, Python, Markdown, JSON:** edit normally. +- **Branch:** `architecture`. Commit locally. Never push. Never touch `main`. +- **Commit granularity:** one feature per commit. Short, factual commit messages. Commit even if a partial fix — don't hoard changes. +- **Tests:** `bash lib/js/test.sh` (254/254 baseline) and `bash lib/js/conformance.sh` (148/148 baseline). Never regress. If a feature requires larger refactor, split into multiple commits each green. +- **Plan file:** append one paragraph per iteration to "Progress log". Tick `[x]` boxes. Don't rewrite history. + +## Gotchas already learned + +- SX `do` is R7RS iteration — **use `begin`** for multi-expr sequences. +- `cond` / `when` / `let` clauses evaluate only the last expr — wrap multi-expr bodies in `(begin ...)`. +- `guard` handler clauses same rule: `(guard (e (else (begin ...))))`. +- `type-of` on user fn returns `"lambda"`, not `"function"`. Use `js-function?`. +- **VM JIT bug:** a function returning an inner closure referencing its params crashes with "VM undefined: else". Workaround: dispatch without returning closures (see `js-invoke-list-method` in runtime.sx). +- `&rest args` is SX varargs. `make-symbol` builds identifier symbols. +- Keywords render as quoted strings through the `eval` epoch command. +- Shell heredoc `||` gets eaten by bash — escape or use `case`. +- `...` lexes as `punct`, not `op`. + +## Style + +- No comments in `.sx` unless non-obvious. +- No new planning docs — update the plan inline. +- One feature per iteration. Commit. Log. Next. +- If blocked for two iterations on the same issue, add to Blockers and move on. + +Go. Start with (1) baseline commit, then (2) fix the runner. Keep iterating indefinitely until stopped. diff --git a/plans/js-on-sx.md b/plans/js-on-sx.md new file mode 100644 index 00000000..227fcfad --- /dev/null +++ b/plans/js-on-sx.md @@ -0,0 +1,200 @@ +# JS-on-SX: cherry-picked test262 conformance + +Transpile a restricted JS subset to SX AST, run on the existing CEK/VM. Goal is runtime-hardening via conformance tests, plus a second hyperscript substrate (hyperscript.js running on SX). + +## Ground rules for the loop + +- **Scope:** only touch `lib/js/**` and `plans/js-on-sx.md`. Do **not** edit `spec/evaluator.sx`, `spec/primitives.sx`, `shared/sx/**`, or `lib/hyperscript/**` — the user is working on those elsewhere. +- **Shared-file issues** go under "Blockers" below with a minimal repro; do not fix them from this loop. +- **SX files:** use `sx-tree` MCP tools only (never `Edit`/`Read`/`Write` on `.sx` files). Use `sx_write_file` for new files, path-based or pattern-based edits for changes. +- **Architecture:** JS source → JS AST → SX AST → existing CEK. No standalone JS evaluator — reuse everything. +- **Tests:** mirror `lib/hyperscript/test.sh` pattern. Start with a tiny in-repo smoke suite; graduate to a cherry-picked test262 slice once the lexer+expression-parser cycle is green. + +## The loop + +A single long-running background agent works `plans/js-on-sx.md` forever, one feature per commit. It runs a prioritized queue driven by the real test262 scoreboard. The briefing lives at `plans/agent-briefings/loop.md`; the recovery helper is `plans/restore-loop.sh`. + +The queue (condensed — full version in the briefing): + +1. Baseline commit (stage what's on disk now). +2. Fix `lib/js/test262-runner.py` so it produces a real scoreboard. +3. Full scoreboard run across the whole `test/` tree. +4. Regex lexer/parser/runtime stub + Blockers entry listing platform primitives needed. +5. Scoreboard-driven: pick the worst-passing category each iteration; fix; re-score. +6. When the scoreboard plateaus, tackle deferred items (ASI, CEK-suspend await). + +### Crash recovery + +The agent process dies with the machine. **Work survives if it was committed** — that's why every iteration ends in a commit. After a crash: + +```bash +bash plans/restore-loop.sh # show state: tests, commits, scoreboard, regex hook +bash plans/restore-loop.sh --print # also cat the briefing for easy copy-paste +``` + +Then respawn the agent by pasting `plans/agent-briefings/loop.md` into Claude Code via the `Agent` tool with `run_in_background=true`. The agent re-reads the plan, picks up wherever the queue has got to, and carries on. + +## Architecture sketch + +``` +JS source text + │ + ▼ +lib/js/lexer.sx — tokens: {:type :value :pos} + │ + ▼ +lib/js/parser.sx — JS AST as SX trees, e.g. (js-binop "+" left right) + │ + ▼ +lib/js/transpile.sx — JS AST → SX AST (reusable by CEK) + │ e.g. (js-binop "+" l r) → (+ (transpile l) (transpile r)) + ▼ +existing CEK / VM — evaluation, async/IO, exceptions already handled +``` + +Runtime shims in `lib/js/runtime.sx`: `js-global`, `js-console-log`, `js-typeof`, coercion helpers (`to-number`, `to-string`, `to-boolean`, abstract-equality), eventually the prototype chain. + +## Roadmap + +Each item: implement → tests → update progress. Mark `[x]` when tests green. + +### Phase 1 — Lexer +- [x] Numeric literals (int, float, hex, exponent) +- [x] String literals (double, single, escape sequences, template strings later) +- [x] Identifiers + reserved words +- [x] Punctuation: `( ) { } [ ] , ; : . ...` +- [x] Operators: `+ - * / % ** = == === != !== < > <= >= && || ! ?? ?: & | ^ ~ << >> >>> += -= ...` +- [x] Comments (`//`, `/* */`) +- [ ] Automatic Semicolon Insertion (defer — initially require semicolons) + +### Phase 2 — Expression parser (Pratt-style) +- [x] Literals → AST nodes +- [x] Binary operators with precedence +- [x] Unary operators (`- + ! ~ typeof void`) +- [x] Member access (`.`, `[]`) +- [x] Function calls +- [x] Array literals +- [x] Object literals (string/ident keys, shorthand later) +- [x] Conditional `a ? b : c` +- [x] Arrow functions (expression body only) + +### Phase 3 — Transpile to SX AST +- [x] Numeric/string/bool/null literals → SX literals +- [x] Arithmetic: `+ - * / % **` with numeric coercion (`js-add` does string-concat dispatch) +- [x] Comparisons: `=== !== == != < > <= >=` +- [x] Logical: `&& ||` (short-circuit, value-returning via thunk trick); `??` nullish coalesce +- [x] Unary: `- + ! ~ typeof void` +- [x] Member access → `js-get-prop` (handles dicts, lists `.length`/index, strings) +- [x] Array literal → `(list ...)` +- [x] Object literal → `(dict)` + `dict-set!` sequence +- [x] Function call → SX call (callee can be ident, member, arrow) +- [x] Arrow fn → `(fn ...)` (params become SX symbols; closures inherited) +- [x] Assignment (`=` and compound `+= -= ...`) on ident/member/index targets +- [x] `js-eval` end-to-end: source → tokens → AST → SX → `eval-expr` + +### Phase 4 — Runtime shims (`lib/js/runtime.sx`) +- [x] `js-typeof`, `js-to-number`, `js-to-string`, `js-to-boolean` +- [x] Abstract equality (`js-loose-eq`) vs strict (`js-strict-eq`) +- [x] Arithmetic (`js-add` `js-sub` `js-mul` `js-div` `js-mod` `js-pow` `js-neg` `js-pos`) +- [x] Logical (`js-and` `js-or` via thunks for lazy rhs) and `js-not` / `js-bitnot` +- [x] Relational (`js-lt` `js-gt` `js-le` `js-ge`) incl. lexicographic strings +- [x] `js-get-prop` / `js-set-prop` (dict/list/string; `.length`; numeric index) +- [x] `console.log` → `log-info` bridge (`console` dict wired) +- [x] `Math` object shim (`abs` `floor` `ceil` `round` `max` `min` `random` `PI` `E`) +- [x] `js-undefined` sentinel (keyword) distinct from `nil` (JS `null`) +- [x] Number parser `js-num-from-string` (handles int/float/±sign, no NaN yet) + +### Phase 5 — Conformance harness +- [x] Cherry-picked slice vendored at `lib/js/test262-slice/` (69 fixtures across 7 categories) +- [x] Harness `lib/js/conformance.sh` — batch-load kernel, one epoch per fixture, substring-compare +- [x] Initial target: ≥50% pass. **Actual: 69/69 (100%).** + +### Phase 6 — Statements +- [x] `var`/`let`/`const` declarations (all behave as `define` — block scope via SX lexical semantics) +- [x] `if`/`else` +- [x] `while`, `do..while` +- [x] `for (init; cond; step)` +- [x] `return`, `break`, `continue` (via `call/cc` continuation bindings `__return__` / `__break__` / `__continue__`) +- [x] Block scoping (via `begin` — lexical scope inherited, no TDZ) + +### Phase 7 — Functions & scoping +- [x] `function` declarations (with call/cc-wrapped bodies for `return`) +- [x] Function expressions (named + anonymous) +- [x] Hoisting — function decls hoisted to enclosing scope (scan body first, emit defines ahead of statements) +- [x] Closures — work via SX `fn` env capture +- [x] Rest params (`...rest` → `&rest`) +- [x] Default parameters (desugar to `if (param === undefined) param = default`) +- [ ] `var` hoisting (deferred — treated as `let` for now) +- [ ] `let`/`const` TDZ (deferred) + +### Phase 8 — Objects, prototypes, `this` +- [x] Property descriptors (simplified — plain-dict `__proto__` chain, `js-set-prop` mutates) +- [x] Prototype chain lookup (`js-dict-get-walk` walks `__proto__`) +- [x] `this` binding rules: method call, function call (undefined), arrow (lexical) +- [x] `new` + constructor semantics (fresh dict, proto linked, ctor called with `this`) +- [x] ES6 classes (sugar over prototypes, incl. `extends`) +- [x] Array mutation: `a[i] = v`, `a.push(...)` — via `set-nth!` / `append!` +- [x] Array builtins: push, pop, shift, slice, indexOf, join, concat, map, filter, forEach, reduce +- [x] String builtins: charAt, charCodeAt, indexOf, slice, substring, toUpperCase, toLowerCase, split, concat +- [x] `instanceof` and `in` operators + +### Phase 9 — Async & Promises +- [x] Promise constructor + `.then` / `.catch` / `.finally` +- [x] `Promise.resolve` / `Promise.reject` / `Promise.all` / `Promise.race` +- [x] `async` functions (decl, expr, arrow) return Promises +- [x] `await` — synchronous-ish: drains microtasks, unwraps settled Promise +- [x] Microtask queue with FIFO drain (`__drain()` exposed to JS) +- [ ] True CEK suspension on `await` for pending Promises (deferred — needs cek-step-loop plumbing) + +### Phase 10 — Error handling +- [x] `throw` statement → `(raise v)` +- [x] `try`/`catch`/`finally` (desugars to `guard` + optional finally wrapper) +- [x] Error hierarchy (`Error`, `TypeError`, `RangeError`, `SyntaxError`, `ReferenceError` as constructor shims) + +### Phase 11 — Stretch / deferred +- ASI, regex literals, generators, iterators, destructuring, template strings with `${}`, tagged templates, Symbol, Proxy, typed arrays, ESM modules. + +## Progress log + +Append-only record of completed iterations. Loop writes one line per iteration: date, what was done, test count delta. + +- 2026-04-23 — scaffold landed: lib/js/{lexer,parser,transpile,runtime}.sx stubs + test.sh. 7/7 smoke tests pass (js-tokenize/js-parse/js-transpile stubs + js-to-boolean coercion cases). +- 2026-04-23 — Phase 1 (Lexer) complete: numbers (int/float/hex/exp/leading-dot), strings (escapes), idents/keywords, punctuation, all operators (1-4 char, longest-match), // and /* */ comments. 38/38 tests pass. Gotchas found: `peek` and `emit!` are primitives (shadowed to `js-peek`, `js-emit!`); `cond` clauses take ONE body only, multi-expr needs `(do ...)` wrapper. +- 2026-04-23 — Phase 2 (Pratt expression parser) complete: literals, binary precedence (w/ `**` right-assoc), unary (`- + ! ~ typeof void`), member access (`.`/`[]`), call chains, array/object literals (ident+string+number keys), ternary, arrow fns (zero/one/many params; curried), assignment (right-assoc incl. compound `+=` etc.). AST node shapes all match the `js-*` names already wired. 47 new tests, 85/85 total. Most of the Phase 2 scaffolding was already written in an earlier session — this iteration verified every path, added the parser test suite, and greened everything on the first pass. No new gotchas beyond Phase 1. +- 2026-04-23 — Phase 3 (Transpile to SX AST) complete: dispatch on AST head using `js-tag?`/`symbol-name` inspection, emit SX trees built from `list`/`cons`/`make-symbol`. All binops, unaries, member/index, call (arbitrary callee), array (`list`), object (`let` + `dict` + `dict-set!`), ternary (`if` around `js-to-boolean`), arrow (`fn` with `make-symbol` params), assignment (ident → `set!`; member/index → `js-set-prop`; compound → combines). Short-circuit `&&`/`||` built via thunk passed to `js-and`/`js-or` — this preserves JS value-returning semantics and avoids re-evaluating lhs. `??` uses `let`+`if`. `js-eval src` pipelines `js-parse-expr` → `js-transpile` → `eval-expr`. 69 new assertions for end-to-end eval; 154/154 main suite. +- 2026-04-23 — Phase 4 (Runtime shims) complete: coercions (ToBoolean/ToNumber/ToString) including a tiny recursive string→number parser (handles ±int/float, no NaN yet); arithmetic with JS `+` dispatch (string-concat when either operand is string, else ToNumber); `js-strict-eq`/`js-loose-eq` with null↔undefined and boolean-coercion rules; relational with lexicographic string path via `char-code`; `js-get-prop`/`js-set-prop` covering dict/list/string with numeric index and `.length`; `Math` object, `console.log`, `js-undefined` sentinel. Several SX gotchas hit and noted below. +- 2026-04-23 — Phase 5 (Conformance harness) complete: 69 hand-picked fixtures under `lib/js/test262-slice/` covering arithmetic, comparison, loose+strict equality, logical, nullish, conditional, arrays, objects, strings, arrows, Math, typeof. Runner `lib/js/conformance.sh` builds one batch script (single kernel boot), one epoch per fixture, substring-matches the sibling `.expected` file. Scoring: **69/69 (100%)** — well past the 50% target. Real test262 integration deferred to later phase (needs network + Python harness; deferred to Phase 5.5 per plan). +- 2026-04-23 — Phases 6 + 7 (Statements + Functions) complete. Parser now accepts top-level programs — `js-parse` returns `(js-program (stmts...))`, with new node types `js-var / js-vardecl / js-block / js-if / js-while / js-do-while / js-for / js-return / js-break / js-continue / js-exprstmt / js-empty / js-funcdecl / js-funcexpr / js-param / js-rest`. Arrow bodies now accept `{...}` block form. Transpile dispatch extended with 14 new clauses. Loops built via `letrec` recursion; `break` / `continue` / `return` via lexical `call/cc` bindings (`__break__` / `__continue__` / `__return__`). Function declarations hoisted to the enclosing scope before other statements run (two-pass: `js-collect-funcdecls` scans, `js-transpile-stmt-list` replaces the hoisted entry with `nil`). Default params desugar to `(if (or (= x nil) (= x :js-undefined)) default x)` updates; rest params emit `&rest name`. Unit tests: **195/195** (154 → +41 for statements, functions, closures, loops). Conformance: **96/96** (69 → +27 new fixtures across `statements/`, `loops/`, `functions/`, `closures/`). Gotchas: (1) SX `do` is R7RS iteration, not sequence — must use `begin`. A `(do (x) ...)` where `x` is a list → "first: expected list, got N" because the do-form tries to parse its iteration bindings. (2) SX passes unsupplied fn params as `nil`, not an undefined sentinel — default-param init must test for both `nil` and `:js-undefined`. (3) `jp-collect-params` (used by arrow heads) doesn't understand rest/defaults; new `jp-parse-param-list` used for function declarations. Arrow rest/defaults deferred. (4) `...` lexes as `punct`, not `op`. + +- 2026-04-23 — **Phase 9 (Async & Promises) complete.** New AST tags: `js-await`, `js-funcdecl-async`, `js-funcexpr-async`, `js-arrow-async`. Parser extended: `async` keyword consumed, dispatches by the next token (function/ident/paren). Primary parser grows a pre-function `async` case and a new `await` unary. Statement parser adds a two-token lookahead for `async function` decls. Runtime adds: microtask queue (`__js_microtask_queue__` dict cell + push/pop/empty/drain), `js-promise?` predicate, full `{:__js_promise__ true :state :value :callbacks}` object, `js-promise-resolve!`/`reject!`/`flush-callbacks!`, callback dispatch (`run-callback!` / `run-handler!` / `try-call` using `guard`), `.then` via `js-promise-then-internal!`, `.catch`/`.finally` derivative calls. `js-invoke-method` now routes Promise methods through `js-invoke-promise-method` (same single-dispatch no-closure pattern as Phase 8 list/string builtins). `Promise` constructor runs executor synchronously inside a guard so throws reject the Promise. Statics `resolve`/`reject`/`all`/`race` live in `__js_promise_statics__` dict; `js-get-prop` special-cases identity-equality against the `Promise` function. `js-async-wrap` wraps a thunk → Promise (fulfilled on return, rejected on throw, adopts returned Promises). `js-await-value` drains microtasks then unwraps a settled Promise or raises its reason; pending Promise = error (no scheduler — see Blockers). `js-eval` drains microtasks at end. `__drain()` exposed to JS so tests can force-run pending callbacks synchronously before reading a mutable result. Arity-tolerant call path `js-call-arity-tolerant` adapts 1-arg handler invocations to handlers declared with `()` (zero params) via `lambda-params` introspection. Unit tests: **254/254** (+31 parser + runtime). Conformance: **148/148** (+29: `test262-slice/promises/*` × 16, `test262-slice/async/*` × 13). Microtask ordering is FIFO (append on settle, drain one-at-a-time); spec-ish but not obsessive about thenable-adoption iteration count. Gotchas: (1) **`cond` needs `begin` for multi-body clauses** — same rule as Phase 1, bit me hard because the original draft had `(cond ((state) (side-effect) (side-effect2)))` which silently discarded the first expression as "predicate" and tried to call it as a function. (2) **`guard` with multi-body handler clauses** — same fix, `(guard (e (else (begin …))))`. (3) **`(= (type-of fn) "function")` is FALSE** — `type-of` returns `"lambda"` for user-defined fns; use `js-function?` which accepts lambda/function/component. (4) **Forward refs in SX work** because `define` is late-bound in the global env. (5) **Microtask semantics vs top-level last-expression** — `js-eval` evaluates all stmts THEN drains; if the last stmt reads `r` assigned in a `.then`, you'll see `nil` unless you insert `__drain()` between the setup and the read. (6) **`Promise.resolve(p)` returns p for existing Promises** — identity preserved via `(js-promise? v) → v` short-circuit. (7) **Strict arity in SX lambdas vs tolerant JS** — `() => side-effect()` in JS accepts extra args silently; SX `(fn () ...)` errors. Callback invocations go through `js-call-arity-tolerant` which introspects `lambda-params` and calls with no args if the handler has zero params. + +- 2026-04-23 — Phases 8 + 10 (Objects + Errors) complete in a single session. **Object model:** regular JS `function` bodies wrap with `(let ((this (js-this))) ...)` — a dynamic `this` via a global cell `__js_this_cell__`. Method calls `obj.m(args)` route through `js-invoke-method` which saves/restores the cell around the call, so `this` works without an explicit first-arg calling convention. Arrow functions don't wrap — they inherit the enclosing lexical `this`. **`new`:** creates a fresh dict with `__proto__` linked to the constructor's prototype dict, calls the constructor with `this` bound, returns the ctor's dict return (if any) else the new object. **Prototype chain:** lives in a side table `__js_proto_table__` keyed by `inspect(ctor)`. `ctor.prototype` access and assignment both go through this table. `js-dict-get-walk` walks the `__proto__` chain on dict property lookup. **Classes:** desugar to `(define Name ctor)` + `(js-reset-ctor-proto! Name)` (critical for redefinition) + `(dict-set! (js-get-ctor-proto Name) mname mfn)` for each method. `extends` chains by setting `(js-get-ctor-proto Child).__proto__ = (js-get-ctor-proto Parent)`. Default ctor with `extends` calls parent with same args. **Arrays:** `js-set-prop` on lists dispatches to `js-list-set!` which does in-bounds `set-nth!` or `append!` past end (pads with `js-undefined`). No shrinking (primitive gap — `pop-last!` is a no-op). **Array + String builtins** are routed through `js-invoke-method` directly via `js-invoke-list-method` / `js-invoke-string-method` to AVOID a VM JIT bug: returning a closure from a JIT-compiled function (which happened when `js-array-method` returned an inner `fn`) crashed with "VM undefined: else". Dispatching without closures works. **Throw/try/catch/finally:** `throw v` → `(raise v)`; try/catch → `(guard (e (else cbody)) body)`; finally wraps via `(let ((r try-tr)) finally-tr r)`. **Error hierarchy:** `Error`/`TypeError`/`RangeError`/`SyntaxError`/`ReferenceError` are constructor shims that set `this.message` + `this.name` on the new object. **`instanceof` + `in`:** parser precedence table extended to accept both as keywords at prec 10; binary-loop predicate extended to allow keyword-type tokens for these two. Unit tests: **223/223** (+28). Conformance: **119/119** (+23 new fixtures across `objects/` and `errors/`). Gotchas: (1) **Ctor-id collision on redefine** — `inspect` of a lambda is keyed by (name + arity), so redefining `class B` found the OLD proto-table entry. Fix: class decl always calls `js-reset-ctor-proto!`. (2) **VM closure bug** — functions returning inner closures from JIT-compiled bodies break: `(fn (arr) (fn (f) ...use arr...))` compiles to a VM closure for the outer that can't produce a working inner. Workaround: route all builtin method dispatch through a single (non-closure-returning) helper. (3) **`jp-parse-param-list` eats its own `(`** — don't prefix with `jp-expect! st "punct" "("`, the parser handles both. Class method parser hit this. + +## Phase 3-5 gotchas + +Worth remembering for later phases: + +- **Varargs in SX** use `&rest args`, not bare `args`. `(fn args …)` errors with "Expected list, got symbol" — use `(fn (&rest args) …)`. +- **`make-symbol`** is the way to build an SX identifier-symbol at runtime for later `eval-expr`. Use it to turn JS idents into SX variable references. +- **`eval-expr`** is the primitive you want to evaluate an SX list you've just constructed. It's already in the kernel primitives. +- **Keywords print as quoted strings** via the epoch `eval` command — `:foo` comes back as `"foo"`. Affected the `js-undefined` test expectation. +- **`char-code` (not `code-char`)** for char→codepoint. No `bit-not` primitive — implement `~x` as `-(int(x)+1)`. +- **Epoch `eval` string must double-escape** to survive two nested SX string literals (`(eval "(js-eval \"…\")")`). The conformance harness uses a small Python helper to do that reliably. +- **Shell heredocs and `||`** — writing fixtures with `||` in a here-doc fed to a `while read` loop gets interpreted as a pipe mid-parse. Use `case|a\|\|b|…` style or hand-write the `||` cases. + +## Blockers / shared-file issues + +Anything that would require a change outside `lib/js/` goes here with a minimal repro. Don't fix from the loop. + +- **Pending-Promise await** — our `js-await-value` drains microtasks and unwraps *settled* Promises; it cannot truly suspend a JS fiber and resume later. Every Promise that settles eventually through the synchronous `resolve`/`reject` + microtask path works. A Promise that never settles without external input (e.g. a real `setTimeout` waiting on the event loop) would hit the `"await on pending Promise (no scheduler)"` error. Proper async suspension would need the JS eval path to run under `cek-step-loop` (not `eval-expr` → `cek-run`) and treat `await pending-Promise` as a `perform` that registers a resume thunk on the Promise's callback list. Non-trivial plumbing; out of scope for this phase. Consider it a Phase 9.5 item. + +## First-iteration checklist (scaffolding) — DONE + +- [x] `lib/js/lexer.sx` — stub `js-tokenize` +- [x] `lib/js/parser.sx` — stub `js-parse` +- [x] `lib/js/transpile.sx` — stub `js-transpile` +- [x] `lib/js/runtime.sx` — stub `js-global`, `js-to-boolean` +- [x] `lib/js/test.sh` — epoch-protocol runner mirroring `lib/hyperscript/test.sh` +- [x] Smoke suite green (7/7) + +Next iteration: Phase 1 — lexer. Start with numeric literals + identifiers + whitespace skipping; extend test.sh with tokenization assertions. diff --git a/plans/restore-loop.sh b/plans/restore-loop.sh new file mode 100755 index 00000000..eb87b187 --- /dev/null +++ b/plans/restore-loop.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# restore-loop.sh — print recovery state for the js-on-sx loop. +# +# Claude Code can't be driven from shell, so this script shows you where things +# stand and points at the briefing file. To respawn the agent, feed +# plans/agent-briefings/loop.md into Claude via the Agent tool with +# run_in_background=true. +# +# Usage: +# bash plans/restore-loop.sh # status snapshot +# bash plans/restore-loop.sh --print # also cat the briefing +# +set -uo pipefail + +cd "$(dirname "$0")/.." + +echo "=== js-on-sx loop state ===" +echo +echo "Branch: $(git rev-parse --abbrev-ref HEAD)" +echo "HEAD: $(git log -1 --oneline)" +echo +echo "Recent commits on lib/js/ and plans/:" +git log -10 --oneline -- lib/js/ plans/js-on-sx.md 2>/dev/null || echo " (none yet)" +echo +echo "=== Test baseline ===" +if [ -x hosts/ocaml/_build/default/bin/sx_server.exe ]; then + echo -n "Unit tests: "; bash lib/js/test.sh 2>&1 | tail -1 || true + echo -n "Conformance: "; bash lib/js/conformance.sh 2>&1 | tail -1 || true +else + echo "sx_server.exe not built — run ./scripts/sx-build-all.sh first." +fi +echo +echo "=== test262 scoreboard ===" +if [ -f lib/js/test262-scoreboard.json ]; then + echo " ✓ lib/js/test262-scoreboard.json exists" + python3 -c "import json;d=json.load(open('lib/js/test262-scoreboard.json'));t=d.get('totals', d.get('overall', {}));print(f\" totals: {t}\")" 2>/dev/null || true +else + echo " ✗ lib/js/test262-scoreboard.json NOT found" +fi +echo +echo "=== regex platform hook ===" +if grep -q js-regex-platform-override lib/js/runtime.sx 2>/dev/null; then + echo " ✓ js-regex-platform-override! wired in runtime.sx" +else + echo " ✗ js-regex-platform-override! not yet wired" +fi +echo +echo "=== Briefing ===" +for f in plans/agent-briefings/*.md; do + [ -f "$f" ] && echo " $f" +done +echo +echo "To respawn: paste plans/agent-briefings/loop.md into Claude via the Agent tool" +echo " with run_in_background=true. The agent reads the plan's progress" +echo " log and picks up wherever the queue left off." + +if [ "${1:-}" = "--print" ]; then + echo + echo "=== Briefing contents ===" + for f in plans/agent-briefings/*.md; do + echo + echo "---- $f ----" + cat "$f" + done +fi