diff --git a/src/cmd.js b/src/cmd.js index 3c3a767..90ac18b 100644 --- a/src/cmd.js +++ b/src/cmd.js @@ -220,15 +220,6 @@ const eval_modules_finished = (state, result, node, toplevel) => { if(toplevel) { if(node == state.parse_result.modules[root_calltree_module(next)]) { active_calltree_node = root_calltree_node(next) - return add_frame( - default_expand_path( - expand_path( - next, - active_calltree_node - ) - ), - active_calltree_node, - ) } else { active_calltree_node = null } @@ -244,11 +235,13 @@ const eval_modules_finished = (state, result, node, toplevel) => { } } + let result_state + if(active_calltree_node == null) { const {node, state: next2} = initial_calltree_node(next) - return set_active_calltree_node(next2, null, node) + result_state = set_active_calltree_node(next2, null, node) } else { - return add_frame( + result_state = add_frame( default_expand_path( expand_path( next, @@ -258,6 +251,10 @@ const eval_modules_finished = (state, result, node, toplevel) => { active_calltree_node, ) } + + return result_state.eval_modules_state == null + ? result_state + : {...result_state, eval_modules_state: null} } const input = (state, code, index) => { diff --git a/src/effects.js b/src/effects.js index 18f481e..5f27114 100644 --- a/src/effects.js +++ b/src/effects.js @@ -8,6 +8,7 @@ import { import {current_cursor_position} from './calltree.js' import {FLAGS} from './feature_flags.js' import {exec, FILES_ROOT} from './index.js' +// TODO remove import {unwrap_settled_promises} from './unwrap_promises.js' // Imports in the context of `run_window`, so global variables in loaded diff --git a/src/eval.js b/src/eval.js index 3b43f21..2cfe0b6 100644 --- a/src/eval.js +++ b/src/eval.js @@ -14,6 +14,9 @@ import { import {has_toplevel_await} from './find_definitions.js' +// external +import {patch_promise} from './patch_promise.js' + // TODO: fix error messages. For example, "__fn is not a function" /* @@ -278,6 +281,13 @@ export const eval_modules = ( // TODO bug if module imported twice, once as external and as regular + patch_promise( + globalThis.run_window + ?? + // Code executed in test env + globalThis + ) + const is_async = has_toplevel_await(parse_result.modules) const codestring = @@ -937,21 +947,35 @@ const do_eval_frame_expr = (node, scope, callsleft) => { return {ok: false, children, calls} } else { const expr = children[0] - let value + let ok, value, error if(node.operator == '!') { + ok = true value = !expr.result.value } else if(node.operator == 'typeof') { + ok = true value = typeof(expr.result.value) } else if(node.operator == '-') { value = - expr.result.value } else if(node.operator == 'await') { - log('expr', expr.result.value.status) - value = expr.result.value - //throw new Error('not implemented') + const run_window = globalThis.run_window ?? globalThis + if(expr.result.value instanceof run_window.Promise.Original) { + const status = expr.result.value.status + if(status == null) { + // Promise must be already resolved + throw new Error('illegal state') + } else { + ok = status.ok + error = status.error + value = status.value + } + } else { + ok = true + value = expr.result.value + } } else { throw new Error('unknown op') } - return {ok: true, children, calls, value} + return {ok, children, calls, value, error} } } else if(node.type == 'binary' && !['&&', '||', '??'].includes(node.operator)){ diff --git a/src/globals.js b/src/globals.js index a2fea60..c9b2c2e 100644 --- a/src/globals.js +++ b/src/globals.js @@ -1,6 +1,6 @@ export const globals = new Set([ 'globalThis', - // TODO Promise, + 'Promise', // TODO Symbol 'URL', 'Set', diff --git a/src/patch_promise.js b/src/patch_promise.js index 1c1a4b6..b2bd462 100644 --- a/src/patch_promise.js +++ b/src/patch_promise.js @@ -1,8 +1,8 @@ export const patch_promise = window => { - // TODO check that it is not already patched if(window.Promise.Original != null) { - throw new Error('already patched') + // already patched + return } class PromiseWithStatus extends Promise { diff --git a/test/test.js b/test/test.js index 3a53335..ca523e3 100644 --- a/test/test.js +++ b/test/test.js @@ -15,10 +15,10 @@ import { test_only, assert_equal, stringify, - assert_code_evals_to, - assert_code_error, + assert_code_evals_to, assert_code_evals_to_async, + assert_code_error, assert_code_error_async, parse_modules, - test_initial_state, + test_initial_state, test_initial_state_async, test_deferred_calls_state, print_debug_ct_node, } from './utils.js' @@ -2589,15 +2589,80 @@ const y = x()` assert_equal(get_deferred_calls(result), null) }), - test('async/await', () => { - const code = ` - const x = async () => 123 - const y = async () => await x() - await y() - ` - const s = test_initial_state(code) - const move = COMMANDS.move_cursor(s, code.indexOf('await x()')).state - log('m', root_calltree_node(move).children[0].children[0].value) - //log(s.parse_result.modules['']) + test('async/await return from async function', async () => { + await assert_code_evals_to_async( + ` + const x = async () => 123 + const y = async () => await x() + await y() + `, + 123 + ) }), + + test('async/await await Promise', async () => { + await assert_code_evals_to_async( + ` + await Promise.resolve(123) + `, + 123 + ) + }), + + test('async/await await Promise returned from async function', async () => { + await assert_code_evals_to_async( + ` + const x = async () => { + return Promise.resolve(123) + } + await x() + `, + 123 + ) + }), + + test('async/await throw from async function', async () => { + await assert_code_error_async( + ` + const x = async () => { throw 'boom' } + await x() + `, + 'boom' + ) + }), + + test('async/await await rejected Promise', async () => { + await assert_code_error_async( + ` + await Promise.reject('boom') + `, + 'boom' + ) + }), + + test('async/await await rejected Promise', async () => { + await assert_code_error_async( + ` + await Promise.reject('boom') + `, + 'boom' + ) + }), + + test('async/await await rejected Promise returned from async', async () => { + await assert_code_error_async( + ` + const x = async () => Promise.reject('boom') + await x() + `, + 'boom' + ) + }), + + // TODO + //assert_equal('s', active_frame + //const result = await s.eval_modules_state + //const move = COMMANDS.move_cursor(s, code.indexOf('await x()')).state + //log('m', root_calltree_node(move).children[0].children[0].value) + //log(s.parse_result.modules['']) ] diff --git a/test/utils.js b/test/utils.js index 796e5d9..693cf5b 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,12 +1,8 @@ import {parse, print_debug_node, load_modules} from '../src/parse_js.js' import {eval_tree, eval_frame} from '../src/eval.js' +import {active_frame} from '../src/calltree.js' import {COMMANDS} from '../src/cmd.js' -// external -import {patch_promise} from '../src/patch_promise.js' - -patch_promise(globalThis) - Object.assign(globalThis, {log: console.log}) export const parse_modules = (entry, modules) => @@ -32,6 +28,22 @@ export const assert_code_error = (codestring, error) => { assert_equal(result.error, error) } +export const assert_code_evals_to_async = async (codestring, expected) => { + const s = await test_initial_state_async(codestring) + const frame = active_frame(s) + const result = frame.children[frame.children.length - 1].result + assert_equal(result.ok, true) + assert_equal(result.value, expected) +} + +export const assert_code_error_async = async (codestring, error) => { + const s = await test_initial_state_async(codestring) + const frame = active_frame(s) + const result = frame.children[frame.children.length - 1].result + assert_equal(result.ok, false) + assert_equal(result.error, error) +} + export const test_initial_state = (code, state) => { return COMMANDS.open_run_window( COMMANDS.get_initial_state( @@ -45,6 +57,18 @@ export const test_initial_state = (code, state) => { ) } +export const test_initial_state_async = async code => { + const s = test_initial_state(code) + assert_equal(s.eval_modules_state != null, true) + const result = await s.eval_modules_state.promise + return COMMANDS.eval_modules_finished( + s, + result, + s.eval_modules_state.node, + s.eval_modules_state.toplevel + ) +} + export const test_deferred_calls_state = code => { const {get_deferred_call, on_deferred_call} = (new Function(` let call, calltree_changed_token @@ -119,10 +143,13 @@ export const test_only = (message, t) => test(message, t, true) // Wrap to Function constructor to hide from calltree view // TODO in calltree view, hide fn which has special flag set (see // filter_calltree) -export const run = Object.defineProperty(new Function('tests', ` - const run_test = t => { + +const AsyncFunction = new Function(`return (async () => {}).constructor`)() + +export const run = Object.defineProperty(new AsyncFunction('tests', ` + const run_test = async t => { try { - t.test() + await t.test() } catch(e) { if(globalThis.process != null) { // In node.js runner, fail fast @@ -142,12 +169,13 @@ export const run = Object.defineProperty(new Function('tests', ` const only = tests.find(t => t.only) const tests_to_run = only == null ? tests : [only] - // Exec each test. After all tests are done, we rethrow first failer if + // Exec each test. After all tests are done, we rethrow first error if // any. So we will mark root calltree node if one of tests failed - const failure = tests_to_run.reduce( - (failure, t) => { - const next_failure = run_test(t) - return failure ?? next_failure + const failure = await tests_to_run.reduce( + async (failureP, t) => { + const failure = await failureP + const next_failure = await run_test(t) + return (await failure) ?? next_failure }, null ) @@ -165,7 +193,7 @@ export const run = Object.defineProperty(new Function('tests', ` if(test == null) { throw new Error('test not found') } else { - run_test(test) + await run_test(test) if(globalThis.process != null) { console.log('Ok') } diff --git a/x.js b/x.js index 1088838..7a2fe71 100644 --- a/x.js +++ b/x.js @@ -1,17 +1,31 @@ -/* -const delay = new Promise(resolve => { +const delay = () => new Promise(resolve => { setTimeout(resolve, 1000) }) + +await [1,2,3,4,5].reduce( + async (acc, x) => { + console.log('wait') + await acc + await delay() + console.log('wait finish') + }, + Promise.resolve(), +) + + +/* await delay console.log('x') export const x = 1 */ +/* const p = {then: y => y(3)} async function test() { return await p } +*/ // TODO remove //const x = new Promise((resolve, reject) => resolve(10)) @@ -26,4 +40,4 @@ async function test() { //x.then(() => { // console.log('x', x.status) //}) -console.log(await test()) +//console.log(await test())