diff --git a/src/calltree.js b/src/calltree.js index 1ed846f..9242c1a 100644 --- a/src/calltree.js +++ b/src/calltree.js @@ -7,6 +7,7 @@ import {eval_frame} from './eval.js' export const pp_calltree = tree => ({ id: tree.id, ok: tree.ok, + args: tree.args, value: tree.value, is_log: tree.is_log, has_more_children: tree.has_more_children, diff --git a/src/cmd.js b/src/cmd.js index 90ac18b..d774fc8 100644 --- a/src/cmd.js +++ b/src/cmd.js @@ -18,31 +18,39 @@ import { set_cursor_position, current_cursor_position, set_location } from './calltree.js' -const collect_logs = call => - collect_nodes_with_parents(call, n => n.is_log) - .map(({parent, node}) => ( - { - id: node.id, - toplevel: parent.toplevel, - module: parent.toplevel - ? parent.module - : parent.fn.__location.module, - parent_name: parent.fn?.name, - args: node.args, - log_fn_name: node.fn.name, - } - )) +const collect_logs = (logs, call) => { + const id_to_log = new Map( + collect_nodes_with_parents(call, n => n.is_log) + .map(({parent, node}) => ( + [ + node.id, + { + id: node.id, + toplevel: parent.toplevel, + module: parent.toplevel + ? parent.module + : parent.fn.__location.module, + parent_name: parent.fn?.name, + args: node.args, + log_fn_name: node.fn.name, + } + ] + )) + ) + return logs.map(l => id_to_log.get(l.id)) +} const apply_eval_result = (state, eval_result) => { // TODO what if console.log called from native fn (like Array::map)? // Currently it is not recorded. Maybe we should monkey patch `console`? - const logs = collect_logs(eval_result.calltree) - return { ...state, calltree: make_calltree(eval_result.calltree, null), calltree_actions: eval_result.calltree_actions, - logs: {logs, log_position: null}, + logs: { + logs: collect_logs(eval_result.logs, eval_result.calltree), + log_position: null + }, modules: eval_result.modules, } } @@ -751,7 +759,7 @@ const move_cursor = (s, index) => { return do_move_cursor(state, index) } -const on_deferred_call = (state, call, calltree_changed_token) => { +const on_deferred_call = (state, call, calltree_changed_token, logs) => { if(state.calltree_changed_token != calltree_changed_token) { return state } @@ -760,7 +768,10 @@ const on_deferred_call = (state, call, calltree_changed_token) => { root_calltree_node(state), [...(get_deferred_calls(state) ?? []), call], ), - logs: {...state.logs, logs: state.logs.logs.concat(collect_logs(call))}, + logs: { + ...state.logs, + logs: state.logs.logs.concat(collect_logs(logs, call)) + }, } } diff --git a/src/eval.js b/src/eval.js index 3b8e576..fecb2b7 100644 --- a/src/eval.js +++ b/src/eval.js @@ -290,6 +290,8 @@ export const eval_modules = ( // TODO use native array for stack for perf? const stack = new Array() + let logs = [] + let call_counter = 0 let current_module @@ -336,7 +338,7 @@ export const eval_modules = ( let is_found_deferred_call = false let i - let {calltree} = run() + let {calltree, logs} = run() is_recording_deferred_calls = false if(found_call == null && deferred_calls != null) { @@ -365,7 +367,8 @@ export const eval_modules = ( is_found_deferred_call, deferred_call_index: i, calltree, - call + call, + logs, } } @@ -418,17 +421,20 @@ export const eval_modules = ( try { value = fn(...args) ok = true - return value instanceof Promise.Original - ? value - .then(v => { - value.status = {ok: true, value: v} - return v - }) - .catch(e => { - value.status = {ok: false, error: e} - throw e - }) - : value + if(value instanceof Promise.Original) { + set_record_call() + return value + .then(v => { + value.status = {ok: true, value: v} + return v + }) + .catch(e => { + value.status = {ok: false, error: e} + throw e + }) + } else { + return value + } } catch(_error) { ok = false error = _error @@ -476,7 +482,9 @@ export const eval_modules = ( } const call = children[0] children = null - on_deferred_call(call, calltree_changed_token) + const _logs = logs + logs = [] + on_deferred_call(call, calltree_changed_token, _logs) } } } @@ -547,6 +555,11 @@ export const eval_modules = ( is_log, is_new, } + + if(is_log) { + // TODO do not collect logs on find_call? + logs.push(call) + } const should_record_call = stack.pop() @@ -602,8 +615,10 @@ export const eval_modules = ( current_call.children = children if(!current_call.ok) { is_recording_deferred_calls = true + const _logs = logs + logs = [] children = null - return { modules: __modules, calltree: current_call } + return { modules: __modules, calltree: current_call, logs: _logs } } ` ) @@ -611,8 +626,10 @@ export const eval_modules = ( + ` is_recording_deferred_calls = true + const _logs = logs + logs = [] children = null - return { modules: __modules, calltree: current_call } + return { modules: __modules, calltree: current_call, logs: _logs } } return { @@ -634,10 +651,11 @@ export const eval_modules = ( : map_object(external_imports, (name, {module}) => module), /* on_deferred_call */ - (call, calltree_changed_token) => { + (call, calltree_changed_token, logs) => { return on_deferred_call( assign_code(parse_result.modules, call), calltree_changed_token, + logs, ) }, @@ -655,7 +673,8 @@ export const eval_modules = ( is_found_deferred_call, deferred_call_index, calltree, - call + call, + logs, } = actions.find_call(loc, deferred_calls) return { is_found_deferred_call, @@ -664,6 +683,7 @@ export const eval_modules = ( // TODO: `call` does not have `code` property here. Currently it is // worked around by callers. Refactor call, + logs, } } } @@ -676,6 +696,7 @@ export const eval_modules = ( modules: result.modules, calltree: assign_code(parse_result.modules, result.calltree), call: result.call, + logs: result.logs, calltree_actions, }) diff --git a/test/test.js b/test/test.js index 5ff6ec6..5781c75 100644 --- a/test/test.js +++ b/test/test.js @@ -25,6 +25,20 @@ import { export const tests = [ + // TODO + /* + test('trace', () => { + assert_code_evals_to(` + const trace = () => 1; + trace() + + `, + + 1 + ) + }), + */ + test('invalid token in the beginning', () => { const result = parse('# import') assert_equal(result, { @@ -2697,38 +2711,57 @@ const y = x()` ) }), - //test('async/await calltree', async () => { - // const i = await test_initial_state_async(` - // const x = () => 1 - // const delay = async time => { - // await 1 //Promise.resolve() - // x() - // } - // await delay(3) - // /* TODO - // await Promise.all([ - // delay(3), - // ]) - // */ - // `) - // log(pp_calltree(root_calltree_node(i))) - // assert_equal(root_calltree_node(i).children.length, 1) - //}), + test('async/await calltree', async () => { + const i = await test_initial_state_async(` + const x = () => 1 + const delay = async time => { + await 1 //TODO Promise.resolve() + x() + } + await delay(3) + /* TODO + await Promise.all([ + delay(3), + ]) + */ + `) + const root = root_calltree_node(i) + assert_equal(root.children.length, 1) + const call_delay = root.children[0] + assert_equal(call_delay.fn.name, 'delay') + assert_equal(call_delay.fn.name, 'delay') + }), + // TODO test('async/await logs out of order', async () => { const i = await test_initial_state_async(` const delay = async time => { await new Promise(res => globalThis.setTimeout(res, time*10)) console.log(time) } - await Promise.all([ - delay(3), - delay(2), - delay(1), - ]) + + await Promise.all([delay(2), delay(1)]) `) const logs = i.logs.logs.map(l => l.args[0]) - assert_equal(logs, [1,2,3]) - }) + assert_equal(logs, [1, 2]) + }), + + test('async/await logs out of order', async () => { + const i = await test_initial_state_async(` + // Init promises p1 and p2 that are resolved in different order (p2 then + // p1) + const p2 = Promise.resolve(2) + const p1 = p2.then(() => 1) + + const log = async p => { + const v = await p + console.log(v) + } + + await Promise.all([log(p1), log(p2)]) + `) + const logs = i.logs.logs.map(l => l.args[0]) + assert_equal(logs, [2, 1]) + }), ] diff --git a/test/utils.js b/test/utils.js index 85e8902..51efd21 100644 --- a/test/utils.js +++ b/test/utils.js @@ -71,14 +71,13 @@ export const test_initial_state_async = async code => { export const test_deferred_calls_state = code => { const {get_deferred_call, on_deferred_call} = (new Function(` - let call, calltree_changed_token + let args return { get_deferred_call() { - return [call, calltree_changed_token] + return args }, - on_deferred_call(_call, _calltree_changed_token) { - call = _call - calltree_changed_token = _calltree_changed_token + on_deferred_call(..._args) { + args = _args } } `))()