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) => ({
...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,
)
}

View File

@@ -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,
}
}
}