mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 21:14:28 -08:00
find_call_sync
This commit is contained in:
151
src/calltree.js
151
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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
137
src/eval.js
137
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(`
|
||||
|
||||
Reference in New Issue
Block a user