diff --git a/examples/spec/async_iterators.js b/examples/spec/async_iterators.js index b79f758..10be8dc 100644 --- a/examples/spec/async_iterators.js +++ b/examples/spec/async_iterators.js @@ -1,4 +1,5 @@ import { test, testDeep, summary } from './helpers.js'; +import { inspect } from 'node:util'; console.log('Async Iterator Tests\n'); @@ -17,6 +18,7 @@ test('async generator function has prototype property', protoShapeAsyncGen.hasOw test('async generator function constructor name', AsyncGeneratorFunction.name, 'AsyncGeneratorFunction'); test('async generator function prototype chain', Object.getPrototypeOf(protoShapeAsyncGen), AsyncGeneratorFunction.prototype); test('async generator function prototype tag', AsyncGeneratorFunction.prototype[Symbol.toStringTag], 'AsyncGeneratorFunction'); +test('async generator function inspect tag', inspect(protoShapeAsyncGen), '[AsyncGeneratorFunction: protoShapeAsyncGen]'); test('async generator function prototype prototype', AsyncGeneratorFunction.prototype.prototype, sharedAsyncGeneratorProto); test( 'new async generator function throws', diff --git a/examples/spec/completion.js b/examples/spec/completion.js new file mode 100644 index 0000000..7bf2699 --- /dev/null +++ b/examples/spec/completion.js @@ -0,0 +1,47 @@ +import { test, testDeep, testThrows, summary } from './helpers.js'; + +console.log('Completion Value Tests\n'); + +test('empty blocks produce undefined', eval('{}{}{};'), undefined); +test('label inside block completion', eval("{foo: 'bar'}{};"), 'bar'); +test('block before labelled completion', eval("{}{foo: 'bar'};"), 'bar'); +test('last non-empty block completion', eval("{a: 'b'}{c: 'd'}{};"), 'd'); + +test('nested label passes expression completion', eval('a: b: c: 1;'), 1); +test('nested label passes comma expression completion', eval('a: b: c: (1, 2, 3);'), 3); + +test('statement-position braces parse as block label', eval('{ a: 1 }'), 1); +test('multiple labels in block preserve last completion', eval('{ a: 1; b: 2 }'), 2); +testDeep('parenthesized braces parse as object literal', eval('({ a: 1, b: 2 })'), { a: 1, b: 2 }); +testThrows('comma cannot separate labelled statements', () => eval('{ a: 1, b: 2 }')); + +test('declaration empty completion preserves previous value', eval("'a'; var x = 5;"), 'a'); +test('if true carries body completion', eval("if (true) 'a';"), 'a'); +test('if false has empty completion', eval("if (false) 'a';"), undefined); +test('if false overwrites previous expression with undefined', eval("'a'; if (false) 'b';"), undefined); +test('if true empty body overwrites previous expression with undefined', eval("'a'; if (true) ;"), undefined); +test('for loop completion is last body value', eval('for (let i=0;i<3;i++) i;'), 2); +test('while false overwrites previous expression with undefined', eval("'a'; while (false) 'b';"), undefined); +test('for false overwrites previous expression with undefined', eval("'a'; for (;false;) 'b';"), undefined); +test('for empty body overwrites previous expression with undefined', eval("'a'; for (let i=0;i<1;i++);"), undefined); +test('do while empty body overwrites previous expression with undefined', eval("'a'; do ; while(false);"), undefined); +test('with empty body overwrites previous expression with undefined', eval("'a'; with ({}) ;"), undefined); + +test('empty finally preserves try completion', eval("try { 'a' } finally { }"), 'a'); +test('non-empty finally overrides try completion', eval("try { 'a' } finally { 'b' }"), 'b'); +test('empty try finally overwrites previous expression with undefined', eval("'a'; try { } finally { }"), undefined); + +test('break label preserves previous non-empty completion', eval("outer: { 'before'; break outer; 'after'; }"), 'before'); +test('nested block completion flows outward', eval("outer: { { 'inner'; } ; }"), 'inner'); +test('empty block after expression preserves value', eval("'a'; { var x; }"), 'a'); + +test('switch fallthrough preserves last non-empty case value', eval("switch (1) { case 0: 'zero'; case 1: 'one'; case 2: ; }"), 'one'); +test('switch with no matching case is empty', eval("switch (9) { case 0: 'zero'; }"), undefined); +test('switch with no matching case overwrites previous expression with undefined', eval("'a'; switch (9) { case 0: 'zero'; }"), undefined); +test('switch default fallthrough can be overwritten', eval("switch (2) { case 1: 'one'; default: 'default'; case 2: 'two'; }"), 'two'); + +test('function block body ignores completion value', eval("(function(){ 'block'; })()"), undefined); +test('arrow block body ignores completion value', eval("(()=>{ 'block'; })()"), undefined); +test('arrow expression body returns expression value', eval("(()=> 'expr')()"), 'expr'); + +summary(); diff --git a/examples/spec/generators.js b/examples/spec/generators.js index 225ca05..28218d7 100644 --- a/examples/spec/generators.js +++ b/examples/spec/generators.js @@ -1,4 +1,5 @@ import { test, testDeep, summary } from './helpers.js'; +import { inspect } from 'node:util'; console.log('Generator Tests\n'); @@ -262,6 +263,12 @@ const protoShapeGen = protoShapeFn(); const ownGeneratorProto = Object.getPrototypeOf(protoShapeGen); const sharedGeneratorProto = Object.getPrototypeOf(ownGeneratorProto); const iteratorProto = Object.getPrototypeOf(sharedGeneratorProto); +const GeneratorFunction = protoShapeFn.constructor; +test('generator function constructor name', GeneratorFunction.name, 'GeneratorFunction'); +test('generator function prototype chain', Object.getPrototypeOf(protoShapeFn), GeneratorFunction.prototype); +test('generator function prototype tag', GeneratorFunction.prototype[Symbol.toStringTag], 'GeneratorFunction'); +test('generator function toStringTag', Object.prototype.toString.call(protoShapeFn), '[object GeneratorFunction]'); +test('generator function inspect tag', inspect(protoShapeFn), '[GeneratorFunction: protoShapeFn]'); test('generator instance uses function prototype', ownGeneratorProto, protoShapeFn.prototype); test('generator shared prototype has next', sharedGeneratorProto.hasOwnProperty('next'), true); test('generator Symbol.iterator inherited from iterator prototype', iteratorProto.hasOwnProperty(Symbol.iterator), true); diff --git a/include/silver/compiler.h b/include/silver/compiler.h index 94646e2..595b8ab 100644 --- a/include/silver/compiler.h +++ b/include/silver/compiler.h @@ -119,6 +119,7 @@ typedef struct sv_compiler { bool is_tla; int try_depth; int with_depth; + int completion_local; int strict_args_local; int new_target_local; int super_local; diff --git a/src/modules/io.c b/src/modules/io.c index a220391..8654941 100644 --- a/src/modules/io.c +++ b/src/modules/io.c @@ -210,12 +210,23 @@ rbrace: lbrack: switch (p[1]) { - case 'A': if (memcmp(p + 2, "syncFunction", 7) == 0) { EMIT_UNTIL(']', JSON_FUNC) } break; + case 'A': + if ( + memcmp(p + 2, "syncFunction", 7) == 0 || + memcmp(p + 2, "syncGeneratorFunction", 21) == 0 + ) { EMIT_UNTIL(']', JSON_FUNC) } + break; case 'b': if (memcmp(p + 2, "yte", 3) == 0 || memcmp(p + 2, "uffer]", 6) == 0) { EMIT_UNTIL(']', JSON_STRING) } break; case 'F': if (memcmp(p + 2, "unction", 7) == 0) { EMIT_UNTIL(']', JSON_FUNC) } break; case 'n': if (memcmp(p + 2, "ative code", 10) == 0) { EMIT_UNTIL(']', JSON_FUNC) } break; case 'C': if (memcmp(p + 2, "ircular", 7) == 0) { EMIT_UNTIL(']', JSON_REF) } break; - case 'G': if (memcmp(p + 2, "etter/Setter]", 13) == 0 || memcmp(p + 2, "etter]", 6) == 0) { EMIT_UNTIL(']', JSON_FUNC) } break; + case 'G': + if ( + memcmp(p + 2, "etter/Setter]", 13) == 0 || + memcmp(p + 2, "etter]", 6) == 0 || + memcmp(p + 2, "eneratorFunction", 16) == 0 + ) { EMIT_UNTIL(']', JSON_FUNC) } + break; case 'S': if (memcmp(p + 2, "etter]", 6) == 0) { EMIT_UNTIL(']', JSON_FUNC) } break; case 'O': if (memcmp(p + 2, "bject: null prototype]", 22) == 0) { EMIT_UNTIL(']', JSON_TAG) } break; case 'M': if (memcmp(p + 2, "odule]", 6) == 0) { EMIT_UNTIL(']', JSON_TAG) } break; diff --git a/src/modules/symbol.c b/src/modules/symbol.c index dea8f8d..30f027b 100644 --- a/src/modules/symbol.c +++ b/src/modules/symbol.c @@ -375,6 +375,9 @@ void init_symbol_module(void) { ant_value_t async_func_proto = js_get_slot(js_glob(js), SLOT_ASYNC_PROTO); js_set_sym(js, async_func_proto, g_toStringTag, js_mkstr(js, "AsyncFunction", 13)); + ant_value_t generator_func_proto = js_get_slot(js_glob(js), SLOT_GENERATOR_PROTO); + js_set_sym(js, generator_func_proto, g_toStringTag, js_mkstr(js, "GeneratorFunction", 17)); + ant_value_t async_generator_func_proto = js_get_slot(js_glob(js), SLOT_ASYNC_GENERATOR_PROTO); js_set_sym(js, async_generator_func_proto, g_toStringTag, js_mkstr(js, "AsyncGeneratorFunction", 22)); diff --git a/src/silver/compile_ctx.c b/src/silver/compile_ctx.c index 2d95994..fcba723 100644 --- a/src/silver/compile_ctx.c +++ b/src/silver/compile_ctx.c @@ -38,6 +38,7 @@ void sv_compile_ctx_init_root( ctx->source_len = source_len; ctx->mode = mode; ctx->is_strict = is_strict; + ctx->completion_local = -1; ctx->strict_args_local = -1; ctx->new_target_local = -1; ctx->super_local = -1; @@ -63,6 +64,7 @@ void sv_compile_ctx_init_child( ctx->is_async = node && !!(node->flags & FN_ASYNC); ctx->is_strict = enclosing->is_strict; ctx->mode = mode; + ctx->completion_local = -1; ctx->strict_args_local = -1; ctx->new_target_local = -1; ctx->super_local = -1; diff --git a/src/silver/compiler.c b/src/silver/compiler.c index 9baea70..93b26dc 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -387,6 +387,14 @@ static inline bool has_completion_value(const sv_compiler_t *c) { return c && (c->mode == SV_COMPILE_EVAL || c->mode == SV_COMPILE_REPL); } +static inline bool is_completion_top_level(const sv_compiler_t *c) { + return has_completion_value(c) && c->enclosing && !c->enclosing->enclosing; +} + +static inline bool has_completion_accumulator(const sv_compiler_t *c) { + return c && c->completion_local >= 0; +} + static inline bool has_module_import_binding(const sv_compiler_t *c) { for (const sv_compiler_t *cur = c; cur; cur = cur->enclosing) { if (cur->mode == SV_COMPILE_MODULE) return true; @@ -904,6 +912,17 @@ static void emit_put_local(sv_compiler_t *c, int local_idx) { emit_put_local_typed(c, local_idx, SV_TI_UNKNOWN); } +static void emit_set_completion_from_stack(sv_compiler_t *c) { + if (has_completion_accumulator(c)) emit_put_local(c, c->completion_local); + else emit_op(c, OP_POP); +} + +static void emit_set_completion_undefined(sv_compiler_t *c) { + if (!has_completion_accumulator(c)) return; + emit_op(c, OP_UNDEF); + emit_put_local(c, c->completion_local); +} + static uint8_t infer_expr_type(sv_compiler_t *c, sv_ast_t *node); static void emit_get_local(sv_compiler_t *c, int local_idx) { @@ -3087,23 +3106,25 @@ void compile_stmt(sv_compiler_t *c, sv_ast_t *node) { break; case N_ASSIGN: - if (compile_self_append_stmt(c, node)) break; + if (!has_completion_accumulator(c) && compile_self_append_stmt(c, node)) break; compile_expr(c, node); - emit_op(c, OP_POP); + emit_set_completion_from_stack(c); break; case N_FUNC: if (node->str && !(node->flags & FN_ARROW)) break; compile_expr(c, node); - emit_op(c, OP_POP); + emit_set_completion_from_stack(c); break; case N_CLASS: compile_class(c, node); - emit_op(c, OP_POP); + if (node->flags & FN_PAREN) emit_set_completion_from_stack(c); + else emit_op(c, OP_POP); break; case N_WITH: + emit_set_completion_undefined(c); compile_expr(c, node->left); emit_op(c, OP_ENTER_WITH); c->with_depth++; @@ -3114,7 +3135,7 @@ void compile_stmt(sv_compiler_t *c, sv_ast_t *node) { default: compile_expr(c, node); - emit_op(c, OP_POP); + emit_set_completion_from_stack(c); break; } } @@ -3447,6 +3468,8 @@ static bool fold_static_typeof_compare( } void compile_if(sv_compiler_t *c, sv_ast_t *node) { + emit_set_completion_undefined(c); + bool folded_truth = false; if (fold_static_typeof_compare(c, node->cond, &folded_truth)) { if (folded_truth) compile_stmt(c, node->left); @@ -3468,6 +3491,8 @@ void compile_if(sv_compiler_t *c, sv_ast_t *node) { } void compile_while(sv_compiler_t *c, sv_ast_t *node) { + emit_set_completion_undefined(c); + int loop_start = c->code_len; push_loop(c, loop_start, NULL, 0, false); @@ -3485,6 +3510,8 @@ void compile_while(sv_compiler_t *c, sv_ast_t *node) { } void compile_do_while(sv_compiler_t *c, sv_ast_t *node) { + emit_set_completion_undefined(c); + int loop_start = c->code_len; push_loop(c, loop_start, NULL, 0, false); compile_stmt(c, node->body); @@ -3562,6 +3589,7 @@ static void for_collect_var_decl_slots(sv_compiler_t *c, sv_ast_t *init_var, int } void compile_for(sv_compiler_t *c, sv_ast_t *node) { + emit_set_completion_undefined(c); begin_scope(c); int *iter_slots = NULL; @@ -3731,6 +3759,7 @@ static bool compile_using_push_target(sv_compiler_t *c, sv_ast_t *lhs, int stack } static void compile_for_each(sv_compiler_t *c, sv_ast_t *node, bool is_for_of) { + emit_set_completion_undefined(c); begin_scope(c); int *iter_slots = NULL; @@ -4023,6 +4052,8 @@ static void compile_catch_body(sv_compiler_t *c, sv_ast_t *node) { } void compile_try(sv_compiler_t *c, sv_ast_t *node) { + emit_set_completion_undefined(c); + c->try_depth++; int try_jump = emit_jump(c, OP_TRY_PUSH); @@ -4081,6 +4112,8 @@ void compile_try(sv_compiler_t *c, sv_ast_t *node) { void compile_switch(sv_compiler_t *c, sv_ast_t *node) { + emit_set_completion_undefined(c); + int case_count = node->args.count; int default_case = -1; int *match_to_stub = NULL; @@ -4450,7 +4483,10 @@ void compile_class(sv_compiler_t *c, sv_ast_t *node) { sv_ast_t *m = node->args.items[i]; if (m->type == N_STATIC_BLOCK) { begin_scope(c); + int saved_completion_local = c->completion_local; + c->completion_local = -1; compile_stmts(c, &m->args); + c->completion_local = saved_completion_local; end_scope(c); continue; } @@ -4830,21 +4866,17 @@ sv_func_t *compile_function_body( body_using_try_jump = emit_jump(&comp, OP_TRY_PUSH); } + bool completion_top = is_completion_top_level(&comp); + if (completion_top) { + emit_op(&comp, OP_UNDEF); + comp.completion_local = add_local(&comp, "", 0, false, comp.scope_depth); + emit_put_local(&comp, comp.completion_local); + } + if (node->body) { if (node->body->type == N_BLOCK) { - int last_expr_idx = -1; - if (has_completion_value(&comp) && node->body->args.count > 0) { - sv_ast_t *last = node->body->args.items[node->body->args.count - 1]; - if (sv_ast_can_be_expression_statement(last)) - last_expr_idx = node->body->args.count - 1; - } - for (int i = 0; i < node->body->args.count; i++) { - sv_ast_t *stmt = node->body->args.items[i]; - if (i == last_expr_idx) { - compile_expr(&comp, stmt); - emit_return_from_stack(&comp); - } else compile_stmt(&comp, stmt); - } + for (int i = 0; i < node->body->args.count; i++) + compile_stmt(&comp, node->body->args.items[i]); } else compile_tail_return_expr(&comp, node->body); } @@ -4874,6 +4906,11 @@ sv_func_t *compile_function_body( comp.using_stack_async = old_using_async; } + if (completion_top) { + emit_get_local(&comp, comp.completion_local); + emit_return_from_stack(&comp); + } + emit_using_cleanups_to_depth(&comp, -1); emit_close_upvals(&comp); emit_op(&comp, OP_RETURN_UNDEF);