diff --git a/examples/demo/bunny.js b/examples/demo/bunny.js index 4f3f395..efc8bbd 100644 --- a/examples/demo/bunny.js +++ b/examples/demo/bunny.js @@ -340,9 +340,9 @@ fn add(a, b) { } bunny.squeak(); std.io.println(add(5, 10)); -`; +`.trim(); -console.log('Source:'); +console.log('Source:\n'); console.log(source); console.log('\n' + '='.repeat(40) + '\n'); diff --git a/examples/spec/async_loops.js b/examples/spec/async_loops.js index f157557..a6939d4 100644 --- a/examples/spec/async_loops.js +++ b/examples/spec/async_loops.js @@ -159,6 +159,31 @@ testConditionalAwait().then(v => { results.conditionalAwait = v; }); +async function inner(id, val) { + const arr = []; + for (let i = 0; i < 2; i++) { + await delay(1); + arr.push(`${id}:${val}:${i}`); + } + return arr; +} + +async function outer(id) { + const taskResults = []; + for (let j = 0; j < 2; j++) { + await delay(1); + const r = await inner(id, j); + taskResults.push(...r); + } + return taskResults; +} + +Promise.all([outer('A'), outer('B'), outer('C')]).then(([a, b, c]) => { + results.nestedAsyncA = a.join(','); + results.nestedAsyncB = b.join(','); + results.nestedAsyncC = c.join(','); +}); + setTimeout(() => { test('for-let with async closure', results.forLetClosure, '0,1,2'); test('block-scoped capture', results.blockScopedCapture, '0,10,20'); @@ -171,5 +196,8 @@ setTimeout(() => { test('try-catch in loop', results.tryCatchInLoop, '0,1,2'); test('do-while with const', results.doWhile, '0,1,2'); test('conditional await', results.conditionalAwait, '0,1,2'); + test('nested async calls A', results.nestedAsyncA, 'A:0:0,A:0:1,A:1:0,A:1:1'); + test('nested async calls B', results.nestedAsyncB, 'B:0:0,B:0:1,B:1:0,B:1:1'); + test('nested async calls C', results.nestedAsyncC, 'C:0:0,C:0:1,C:1:0,C:1:1'); summary(); }, 200); diff --git a/examples/spec/crypto.js b/examples/spec/crypto.js new file mode 100644 index 0000000..2e6ce23 --- /dev/null +++ b/examples/spec/crypto.js @@ -0,0 +1,52 @@ +import { test, summary } from './helpers.js'; + +console.log('Crypto Tests\n'); + +test('crypto exists', typeof crypto, 'object'); +test('crypto toStringTag', Object.prototype.toString.call(crypto), '[object Crypto]'); + +const rand1 = crypto.random(); +const rand2 = crypto.random(); +test('random returns number', typeof rand1, 'number'); +test('random returns positive', rand1 >= 0, true); +test('random returns different values', rand1 !== rand2, true); + +const bytes = crypto.randomBytes(16); +test('randomBytes returns object', typeof bytes, 'object'); +test('randomBytes length', bytes.length, 16); +test('randomBytes values are numbers', typeof bytes[0], 'number'); +test('randomBytes values in range', bytes[0] >= 0 && bytes[0] <= 255, true); + +const uuid1 = crypto.randomUUID(); +const uuid2 = crypto.randomUUID(); +test('randomUUID returns string', typeof uuid1, 'string'); +test('randomUUID length', uuid1.length, 36); +test('randomUUID format', /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(uuid1), true); +test('randomUUID unique', uuid1 !== uuid2, true); + +const uuidv7_1 = crypto.randomUUIDv7(); +const uuidv7_2 = crypto.randomUUIDv7(); +test('randomUUIDv7 returns string', typeof uuidv7_1, 'string'); +test('randomUUIDv7 length', uuidv7_1.length, 36); +test('randomUUIDv7 version 7', uuidv7_1[14], '7'); +test('randomUUIDv7 unique', uuidv7_1 !== uuidv7_2, true); +test('randomUUIDv7 monotonic', uuidv7_1 < uuidv7_2, true); + +const arr = new Uint8Array(8); +const result = crypto.getRandomValues(arr); +test('getRandomValues returns same array', result === arr, true); +test( + 'getRandomValues fills array', + Array.from(arr).some(v => v !== 0), + true +); + +const arr32 = new Uint32Array(4); +crypto.getRandomValues(arr32); +test( + 'getRandomValues works with Uint32Array', + Array.from(arr32).some(v => v !== 0), + true +); + +summary(); diff --git a/examples/spec/performance.js b/examples/spec/performance.js new file mode 100644 index 0000000..941bf60 --- /dev/null +++ b/examples/spec/performance.js @@ -0,0 +1,21 @@ +import { test, summary } from './helpers.js'; + +console.log('Performance Tests\n'); + +test('performance exists', typeof performance, 'object'); +test('performance toStringTag', Object.prototype.toString.call(performance), '[object Performance]'); + +const t1 = performance.now(); +test('now returns number', typeof t1, 'number'); +test('now returns positive', t1 >= 0, true); + +let sum = 0; +for (let i = 0; i < 10000; i++) sum += i; +const t2 = performance.now(); +test('now advances', t2 >= t1, true); + +test('timeOrigin exists', typeof performance.timeOrigin, 'number'); +test('timeOrigin positive', performance.timeOrigin > 0, true); +test('timeOrigin before now', performance.timeOrigin <= Date.now(), true); + +summary(); diff --git a/examples/spec/shell.js b/examples/spec/shell.js new file mode 100644 index 0000000..0f4e67d --- /dev/null +++ b/examples/spec/shell.js @@ -0,0 +1,36 @@ +import { test, summary } from './helpers.js'; +import { $ } from 'ant:shell'; + +console.log('Shell Tests\n'); + +const result = $`echo hello`; +test('shell returns object', typeof result, 'object'); +test('shell has exitCode', typeof result.exitCode, 'number'); +test('shell exitCode success', result.exitCode, 0); +test('shell has text method', typeof result.text, 'function'); +test('shell has lines method', typeof result.lines, 'function'); + +test('shell text returns string', typeof result.text(), 'string'); +test('shell text content', result.text(), 'hello'); + +const lines = result.lines(); +test('shell lines returns array', Array.isArray(lines), true); +test('shell lines content', lines[0], 'hello'); + +const multiResult = $`printf "line1\nline2\nline3"`; +const multiLines = multiResult.lines(); +test('shell multi-line count', multiLines.length, 3); +test('shell multi-line first', multiLines[0], 'line1'); +test('shell multi-line last', multiLines[2], 'line3'); + +const name = 'world'; +const interpResult = $`echo hello ${name}`; +test('shell interpolation', interpResult.text(), 'hello world'); + +const failResult = $`exit 1`; +test('shell exit code failure', failResult.exitCode, 1); + +const strResult = $('echo string arg'); +test('shell string arg', strResult.text(), 'string arg'); + +summary(); diff --git a/src/ant.c b/src/ant.c index 9b3fc86..3347a69 100644 --- a/src/ant.c +++ b/src/ant.c @@ -117,6 +117,7 @@ typedef struct coroutine { struct for_let_ctx *for_let_stack; int for_let_stack_len; int for_let_stack_cap; + UT_array *scope_stack; // coroutine's own scope stack } coroutine_t; typedef struct { @@ -914,6 +915,29 @@ static bool coro_stack_size_initialized = false; static void free_coroutine(coroutine_t *coro); static void for_let_swap_with_coro(struct js *js, coroutine_t *coro); +typedef struct { + jsval_t scope; + UT_array *scope_stack; +} coro_saved_state_t; + +static inline coro_saved_state_t coro_enter(struct js *js, coroutine_t *coro) { + extern UT_array *global_scope_stack; + coro_saved_state_t saved = { js->scope, global_scope_stack }; + js->scope = coro->scope; + global_scope_stack = coro->scope_stack; + for_let_swap_with_coro(js, coro); + return saved; +} + +static inline void coro_leave(struct js *js, coroutine_t *coro, coro_saved_state_t saved) { + extern UT_array *global_scope_stack; + coro->scope = js->scope; + coro->scope_stack = global_scope_stack; + js->scope = saved.scope; + global_scope_stack = saved.scope_stack; + for_let_swap_with_coro(js, coro); +} + static size_t calculate_coro_stack_size(void) { if (coro_stack_size_initialized) return 0; coro_stack_size_initialized = true; @@ -1018,15 +1042,9 @@ void js_poll_events(struct js *js) { if (!temp->is_ready || !temp->mco || mco_status(temp->mco) != MCO_SUSPENDED) continue; remove_coroutine(temp); - jsval_t saved_scope = temp->js->scope; - temp->js->scope = temp->scope; - - for_let_swap_with_coro(temp->js, temp); + coro_saved_state_t saved = coro_enter(temp->js, temp); mco_result res = mco_resume(temp->mco); - - temp->scope = temp->js->scope; - temp->js->scope = saved_scope; - for_let_swap_with_coro(temp->js, temp); + coro_leave(temp->js, temp, saved); if (res == MCO_SUCCESS && mco_status(temp->mco) != MCO_DEAD) { temp->is_ready = false; @@ -1144,17 +1162,17 @@ static jsval_t start_async_in_coroutine(struct js *js, const char *code, size_t coro->for_let_stack_len = 0; coro->for_let_stack_cap = 0; + extern UT_array *global_scope_stack; + utarray_new(coro->scope_stack, &jsoff_icd); + jsoff_t glob_off = (jsoff_t)vdata(js_glob(js)); + utarray_push_back(coro->scope_stack, &glob_off); + ctx->coro = coro; enqueue_coroutine(coro); - jsval_t saved_scope = js->scope; - js->scope = coro->scope; - for_let_swap_with_coro(js, coro); + coro_saved_state_t saved = coro_enter(js, coro); res = mco_resume(mco); - - coro->scope = js->scope; - js->scope = saved_scope; - for_let_swap_with_coro(js, coro); + coro_leave(js, coro, saved); if (res != MCO_SUCCESS && mco_status(mco) != MCO_DEAD) { remove_coroutine(coro); @@ -1182,6 +1200,7 @@ static void free_coroutine(coroutine_t *coro) { } if (coro->args) CORO_FREE(coro->args); if (coro->for_let_stack) free(coro->for_let_stack); + if (coro->scope_stack) utarray_free(coro->scope_stack); CORO_FREE(coro); } } @@ -4619,7 +4638,6 @@ jsval_t js_mkscope(struct js *js) { jsoff_t prev = (jsoff_t) vdata(js->scope); utarray_push_back(global_scope_stack, &prev); js->scope = mkobj(js, prev); - return js->scope; } @@ -6976,10 +6994,24 @@ static jsval_t do_call_op(struct js *js, jsval_t func, jsval_t args) { } skip_fields: if (is_async) { - res = start_async_in_coroutine(js, code_str, fnlen, closure_scope, bound_args, bound_argc); - } else { - res = call_js_internal(js, code_str, fnlen, closure_scope, bound_args, bound_argc, func); - } + UT_array *call_args; + utarray_new(call_args, &jsval_icd); + for (int i = 0; i < bound_argc; i++) utarray_push_back(call_args, &bound_args[i]); + jsval_t err; + int call_argc = parse_call_args(js, call_args, &err); + if (call_argc < 0) { + utarray_free(call_args); + pop_call_frame(); + if (bound_args) ANT_GC_FREE(bound_args); + js->super_val = saved_super; + js->current_func = saved_func; + return err; + } + jsval_t *argv = (jsval_t *)utarray_front(call_args); + int argc = (int)utarray_len(call_args); + res = start_async_in_coroutine(js, code_str, fnlen, closure_scope, argv, argc); + utarray_free(call_args); + } else res = call_js_internal(js, code_str, fnlen, closure_scope, bound_args, bound_argc, func); pop_call_frame(); if (bound_args) ANT_GC_FREE(bound_args); js->super_val = saved_super; @@ -22572,13 +22604,10 @@ void js_gc_update_roots(GC_UPDATE_ARGS) { FWD_VAL(coro->for_let_stack[i].body_scope); FWD_OFF(coro->for_let_stack[i].prop_off); } + if (coro->scope_stack) UTARRAY_EACH(coro->scope_stack, jsoff_t, off) FWD_OFF(*off); if (coro->mco) { async_exec_context_t *actx = (async_exec_context_t *)mco_get_user_data(coro->mco); - if (actx) { - FWD_VAL(actx->closure_scope); - FWD_VAL(actx->result); - FWD_VAL(actx->promise); - } + if (actx) { FWD_VAL(actx->closure_scope); FWD_VAL(actx->result); FWD_VAL(actx->promise); } } }