diff --git a/src/calltree.js b/src/calltree.js index 9242c1a..a190140 100644 --- a/src/calltree.js +++ b/src/calltree.js @@ -468,76 +468,6 @@ const click = (state, id) => { } } -/* -After find_call, we have two calltrees that have common subtree (starting from -root node). Nodes in these subtrees are the same, but have different ids. Copy -nodes that are in the second tree that are not in the first tree -*/ -const merge_calltrees = (a, b) => do_merge_calltrees(a, b)[1] - -const do_merge_calltrees = (a, b) => { - // TODO Quick workaround, should be fixed by not saving stack in eval.js in - // case of stackoverflow - if(!a.ok && is_stackoverflow(a)) { - return [false, a] - } - - if(a.has_more_children) { - if(b.has_more_children) { - return [false, a] - } else { - // Preserve id - return [true, {...b, id: a.id}] - } - } - - if(a.children == null) { - return [false, a] - } - - if(b.has_more_children) { - return [false, a] - } - - const [has_update, children] = map_accum( - (has_update, c, i) => { - const [upd, merged] = do_merge_calltrees(c, b.children[i]) - return [has_update || upd, merged] - }, - false, - a.children - ) - - if(has_update) { - return [true, {...a, children}] - } else { - return [false, a] - } -} - -/* - Finds node in calltree `a` that has the same position that node with id `id` - in calltree `b`. Null if not found -*/ -const find_same_node = (a, b, id) => { - if(b == null) { - return null - } - - if(b.id == id) { - return a - } - - if(a.children == null || b.children == null) { - return null - } - - return map_find( - a.children, - (c, i) => find_same_node(c, b.children[i], id) - ) -} - export const expand_path = (state, node) => ({ ...state, calltree_node_is_expanded: { @@ -651,55 +581,48 @@ export const find_call = (state, index) => { } const loc = {index: node.index, module: state.current_module} - const { - calltree, - call, - is_found_deferred_call, - deferred_call_index - } = state.calltree_actions.find_call( - loc, - get_deferred_calls(state) + + // First try to find node among existing calltree nodes + const call = find_node(state.calltree, node => + true + && node.fn != null + && node.fn.__location != null + && node.fn.__location.index == loc.index + && node.fn.__location.module == loc.module ) - if(call == null) { - return add_calltree_node_by_loc( - // Remove active_calltree_node - // current_calltree_node may stay not null, because it is calltree node - // explicitly selected by user in calltree view - set_active_calltree_node(state, null), - loc, - null - ) - } let next_calltree, active_calltree_node - if(is_found_deferred_call) { - const deferred_calls = get_deferred_calls(state) - const prev_call = deferred_calls[deferred_call_index] - const merged = merge_calltrees(prev_call, calltree) - const next_deferred_calls = deferred_calls.map((c, i) => - i == deferred_call_index - ? merged - : c - ) - next_calltree = make_calltree( - root_calltree_node(state), - next_deferred_calls, - ) - active_calltree_node = find_same_node( - merged, - calltree, - call.id - ) + if(call != null) { + if(call.has_more_children) { + active_calltree_node = state.calltree_actions.expand_calltree_node(call) + next_calltree = replace_calltree_node( + state.calltree, + call, + active_calltree_node + ) + } else { + active_calltree_node = call + next_calltree = state.calltree + } } else { - next_calltree = make_calltree( - merge_calltrees(root_calltree_node(state), calltree), - get_deferred_calls(state), - ) - active_calltree_node = find_same_node( - root_calltree_node({calltree: next_calltree}), - calltree, - call.id + const find_result = state.calltree_actions.find_call(state.calltree, loc) + if(find_result == null) { + return add_calltree_node_by_loc( + // Remove active_calltree_node + // current_calltree_node may stay not null, because it is calltree node + // explicitly selected by user in calltree view + set_active_calltree_node(state, null), + loc, + null + ) + } + + active_calltree_node = find_result.call + next_calltree = replace_calltree_node( + state.calltree, + find_node(state.calltree, n => n.id == find_result.node.id), + find_result.node, ) } diff --git a/src/eval.js b/src/eval.js index b741f3d..e1a962b 100644 --- a/src/eval.js +++ b/src/eval.js @@ -7,6 +7,7 @@ import { import { find_fn_by_location, + find_node, collect_destructuring_identifiers, map_destructuring_identifiers, map_tree, @@ -334,19 +335,7 @@ export const eval_modules = ( } } - const expand_calltree_node = (node) => { - is_recording_deferred_calls = false - children = null - try { - if(node.is_new) { - new node.fn(...node.args) - } else { - node.fn.apply(node.context, node.args) - } - } catch(e) { - // do nothing. Exception was caught and recorded inside 'trace' - } - is_recording_deferred_calls = true + const do_expand_calltree_node = node => { if(node.fn.__location != null) { // fn is hosted, it created call, this time with children const result = children[0] @@ -363,6 +352,22 @@ export const eval_modules = ( } } + const expand_calltree_node = (node) => { + is_recording_deferred_calls = false + children = null + try { + if(node.is_new) { + new node.fn(...node.args) + } else { + node.fn.apply(node.context, node.args) + } + } catch(e) { + // do nothing. Exception was caught and recorded inside 'trace' + } + is_recording_deferred_calls = true + return do_expand_calltree_node(node) + } + const run_and_find_call = (location) => { searched_location = location @@ -390,47 +395,76 @@ export const eval_modules = ( } } - const find_call = (location, deferred_calls) => { - searched_location = location - let is_found_deferred_call = false - let i - let {calltree, modules, logs} = run() + /* + Try to find call of function with given 'location' - is_recording_deferred_calls = false - if(found_call == null && deferred_calls != null) { - for(i = 0; i < deferred_calls.length; i++) { - const c = deferred_calls[i] + Function is synchronous, because we recorded calltree nodes for all async + function calls. Here we walk over calltree, find leaves that have + 'has_more_children' set to true, and rerunning fns in these leaves with + 'searched_location' being set, until we find find call or no children + left. + + We dont rerun entire execution because we want find_call to be + synchronous for simplicity + */ + const find_call = (calltree, location) => { + // TODO remove + if(children != null) { + throw new Error('illegal state') + } + + const do_find = node => { + if(node.children != null) { + for(let c of node.children) { + const result = do_find(c) + if(result != null) { + return result + } + } + // call was not find in children, return null + return null + } + + + if(node.has_more_children) { try { - c.fn.apply(c.context, c.args) + if(node.is_new) { + new node.fn(...node.args) + } else { + node.fn.apply(node.context, node.args) + } } catch(e) { // do nothing. Exception was caught and recorded inside 'trace' } + if(found_call != null) { - is_found_deferred_call = true - calltree = children[0] + return { + node: do_expand_calltree_node(node), + call: found_call, + } + } else { children = null - break } } + + // node has no children, return null + return null } + is_recording_deferred_calls = false + searched_location = location + + const result = do_find(calltree) + + children = null + searched_location = null + found_call = null is_recording_deferred_calls = true - searched_location = null - const call = found_call - found_call = null - return { - is_found_deferred_call, - deferred_call_index: i, - calltree, - call, - modules, - logs, - } + return result } - const __do_await = async value => { // children is an array of child calls for current function call. But it // can be null to save one empty array allocation in case it has no child @@ -733,24 +767,17 @@ export const eval_modules = ( const expanded = actions.expand_calltree_node(node) return assign_code(parse_result.modules, expanded) }, - find_call: (loc, deferred_calls) => { - const { - is_found_deferred_call, - deferred_call_index, - calltree, - call, - modules, - logs, - } = actions.find_call(loc, deferred_calls) + find_call: (calltree, location) => { + const result = actions.find_call(calltree, location) + if(result == null) { + return null + } + const {node, call} = result + const node_with_code = assign_code(parse_result.modules, node) + const call_with_code = find_node(node_with_code, n => n.id == call.id) return { - is_found_deferred_call, - deferred_call_index, - calltree: assign_code(parse_result.modules, calltree), - // TODO: `call` does not have `code` property here. Currently it is - // worked around by callers. Refactor - call, - modules, - logs, + node: node_with_code, + call: call_with_code, } } } diff --git a/test/test.js b/test/test.js index 0ae49d2..5b5173f 100644 --- a/test/test.js +++ b/test/test.js @@ -1397,6 +1397,7 @@ export const tests = [ test('coloring nested', () => { const code = + // TODO reformat using .trim() `const x = () => { return () => { return 123 @@ -1801,7 +1802,7 @@ const y = x()` assert_equal(s.current_calltree_node.toplevel, true) }), - //TODO this test is fine standalone, but it breaks self-hosted + //TODO this test is fine standalone, but it breaks self-hosted test /* test('unwind_stack overflow', () => { const s = test_initial_state(`