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) => ({
|
export const expand_path = (state, node) => ({
|
||||||
...state,
|
...state,
|
||||||
calltree_node_is_expanded: {
|
calltree_node_is_expanded: {
|
||||||
@@ -651,55 +581,48 @@ 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) {
|
|
||||||
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
|
let next_calltree, active_calltree_node
|
||||||
|
|
||||||
if(is_found_deferred_call) {
|
if(call != null) {
|
||||||
const deferred_calls = get_deferred_calls(state)
|
if(call.has_more_children) {
|
||||||
const prev_call = deferred_calls[deferred_call_index]
|
active_calltree_node = state.calltree_actions.expand_calltree_node(call)
|
||||||
const merged = merge_calltrees(prev_call, calltree)
|
next_calltree = replace_calltree_node(
|
||||||
const next_deferred_calls = deferred_calls.map((c, i) =>
|
state.calltree,
|
||||||
i == deferred_call_index
|
call,
|
||||||
? merged
|
active_calltree_node
|
||||||
: c
|
)
|
||||||
)
|
} else {
|
||||||
next_calltree = make_calltree(
|
active_calltree_node = call
|
||||||
root_calltree_node(state),
|
next_calltree = state.calltree
|
||||||
next_deferred_calls,
|
}
|
||||||
)
|
|
||||||
active_calltree_node = find_same_node(
|
|
||||||
merged,
|
|
||||||
calltree,
|
|
||||||
call.id
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
next_calltree = make_calltree(
|
const find_result = state.calltree_actions.find_call(state.calltree, loc)
|
||||||
merge_calltrees(root_calltree_node(state), calltree),
|
if(find_result == null) {
|
||||||
get_deferred_calls(state),
|
return add_calltree_node_by_loc(
|
||||||
)
|
// Remove active_calltree_node
|
||||||
active_calltree_node = find_same_node(
|
// current_calltree_node may stay not null, because it is calltree node
|
||||||
root_calltree_node({calltree: next_calltree}),
|
// explicitly selected by user in calltree view
|
||||||
calltree,
|
set_active_calltree_node(state, null),
|
||||||
call.id
|
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 {
|
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,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
|
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),
|
||||||
|
call: found_call,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
children = null
|
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
|
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
|
||||||
// can be null to save one empty array allocation in case it has no child
|
// 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)
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(`
|
||||||
|
|||||||
Reference in New Issue
Block a user