find_call_sync

This commit is contained in:
Dmitry Vasilev
2023-01-08 08:25:22 +08:00
parent 2ee6290452
commit 9ea3cb15b3
3 changed files with 121 additions and 170 deletions

View File

@@ -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) => ({ export const expand_path = (state, node) => ({
...state, ...state,
calltree_node_is_expanded: { calltree_node_is_expanded: {
@@ -651,16 +581,33 @@ export const find_call = (state, index) => {
} }
const loc = {index: node.index, module: state.current_module} const loc = {index: node.index, module: state.current_module}
const {
calltree, // First try to find node among existing calltree nodes
call, const call = find_node(state.calltree, node =>
is_found_deferred_call, true
deferred_call_index && node.fn != null
} = state.calltree_actions.find_call( && node.fn.__location != null
loc, && node.fn.__location.index == loc.index
get_deferred_calls(state) && node.fn.__location.module == loc.module
) )
if(call == null) {
let next_calltree, active_calltree_node
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 {
const find_result = state.calltree_actions.find_call(state.calltree, loc)
if(find_result == null) {
return add_calltree_node_by_loc( return add_calltree_node_by_loc(
// Remove active_calltree_node // Remove active_calltree_node
// current_calltree_node may stay not null, because it is calltree node // current_calltree_node may stay not null, because it is calltree node
@@ -671,35 +618,11 @@ export const find_call = (state, index) => {
) )
} }
let next_calltree, active_calltree_node active_calltree_node = find_result.call
next_calltree = replace_calltree_node(
if(is_found_deferred_call) { state.calltree,
const deferred_calls = get_deferred_calls(state) find_node(state.calltree, n => n.id == find_result.node.id),
const prev_call = deferred_calls[deferred_call_index] find_result.node,
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
)
} 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
) )
} }

View File

@@ -7,6 +7,7 @@ import {
import { import {
find_fn_by_location, find_fn_by_location,
find_node,
collect_destructuring_identifiers, collect_destructuring_identifiers,
map_destructuring_identifiers, map_destructuring_identifiers,
map_tree, map_tree,
@@ -334,19 +335,7 @@ export const eval_modules = (
} }
} }
const expand_calltree_node = (node) => { const do_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
if(node.fn.__location != null) { if(node.fn.__location != null) {
// fn is hosted, it created call, this time with children // fn is hosted, it created call, this time with children
const result = children[0] 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) => { const run_and_find_call = (location) => {
searched_location = location searched_location = location
@@ -390,46 +395,75 @@ 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 Function is synchronous, because we recorded calltree nodes for all async
if(found_call == null && deferred_calls != null) { function calls. Here we walk over calltree, find leaves that have
for(i = 0; i < deferred_calls.length; i++) { 'has_more_children' set to true, and rerunning fns in these leaves with
const c = deferred_calls[i] '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 { 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) { } catch(e) {
// do nothing. Exception was caught and recorded inside 'trace' // do nothing. Exception was caught and recorded inside 'trace'
} }
if(found_call != null) { if(found_call != null) {
is_found_deferred_call = true return {
calltree = children[0] node: do_expand_calltree_node(node),
children = null call: found_call,
break
} }
} else {
children = null
} }
} }
// 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 is_recording_deferred_calls = true
searched_location = null return result
const call = found_call
found_call = null
return {
is_found_deferred_call,
deferred_call_index: i,
calltree,
call,
modules,
logs,
} }
}
const __do_await = async value => { const __do_await = async value => {
// children is an array of child calls for current function call. But it // children is an array of child calls for current function call. But it
@@ -733,24 +767,17 @@ export const eval_modules = (
const expanded = actions.expand_calltree_node(node) const expanded = actions.expand_calltree_node(node)
return assign_code(parse_result.modules, expanded) return assign_code(parse_result.modules, expanded)
}, },
find_call: (loc, deferred_calls) => { find_call: (calltree, location) => {
const { const result = actions.find_call(calltree, location)
is_found_deferred_call, if(result == null) {
deferred_call_index, return null
calltree, }
call, const {node, call} = result
modules, const node_with_code = assign_code(parse_result.modules, node)
logs, const call_with_code = find_node(node_with_code, n => n.id == call.id)
} = actions.find_call(loc, deferred_calls)
return { return {
is_found_deferred_call, node: node_with_code,
deferred_call_index, call: call_with_code,
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,
} }
} }
} }

View File

@@ -1397,6 +1397,7 @@ export const tests = [
test('coloring nested', () => { test('coloring nested', () => {
const code = const code =
// TODO reformat using .trim()
`const x = () => { `const x = () => {
return () => { return () => {
return 123 return 123
@@ -1801,7 +1802,7 @@ const y = x()`
assert_equal(s.current_calltree_node.toplevel, true) 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', () => { test('unwind_stack overflow', () => {
const s = test_initial_state(` const s = test_initial_state(`