find_call eagerly

This commit is contained in:
Dmitry Vasilev
2023-08-02 05:59:49 +03:00
parent 800c301cc1
commit adbca074c2
9 changed files with 144 additions and 304 deletions

View File

@@ -2,7 +2,7 @@ import {map_accum, map_find, map_object, stringify, findLast} from './utils.js'
import {is_eq, find_error_origin_node} from './ast_utils.js'
import {find_node, find_leaf, ancestry_inc} from './ast_utils.js'
import {color} from './color.js'
import {eval_frame, eval_expand_calltree_node, eval_find_call} from './eval.js'
import {eval_frame, eval_expand_calltree_node} from './eval.js'
export const pp_calltree = tree => ({
id: tree.id,
@@ -82,8 +82,17 @@ export const is_native_fn = calltree_node =>
export const active_frame = state =>
state.frames[state.active_calltree_node.id]
const get_calltree_node_by_loc = (state, node) =>
state.calltree_node_by_loc
export const get_calltree_node_by_loc = (state, index) => {
const nodes_of_module = state.calltree_node_by_loc.get(state.current_module)
if(nodes_of_module == null) {
return null
} else {
return nodes_of_module.get(index)
}
}
const get_selected_calltree_node_by_loc = (state, node) =>
state.selected_calltree_node_by_loc
?.[state.current_module]
?.[
state.parse_result.modules[state.current_module] == node
@@ -93,13 +102,13 @@ const get_calltree_node_by_loc = (state, node) =>
: node.index
]
const add_calltree_node_by_loc = (state, loc, node_id) => {
const add_selected_calltree_node_by_loc = (state, loc, node_id) => {
return {
...state,
calltree_node_by_loc:
{...state.calltree_node_by_loc,
selected_calltree_node_by_loc:
{...state.selected_calltree_node_by_loc,
[loc.module]: {
...state.calltree_node_by_loc?.[loc.module],
...state.selected_calltree_node_by_loc?.[loc.module],
[loc.index ?? -1]: node_id
}
}
@@ -111,18 +120,11 @@ export const set_active_calltree_node = (
active_calltree_node,
current_calltree_node = state.current_calltree_node,
) => {
const result = {
return {
...state,
active_calltree_node,
current_calltree_node,
}
// TODO currently commented, required to implement livecoding second and
// subsequent fn calls
/*
// Record last_good_state every time active_calltree_node changes
return {...result, last_good_state: result}
*/
return result
}
export const add_frame = (
@@ -142,7 +144,8 @@ export const add_frame = (
} else {
with_frame = state
}
const result = add_calltree_node_by_loc(
// TODO only add if it is not the same
const result = add_selected_calltree_node_by_loc(
with_frame,
calltree_node_loc(active_calltree_node),
active_calltree_node.id,
@@ -571,18 +574,21 @@ export const find_call = (state, index) => {
return state
}
const ct_node_id = get_calltree_node_by_loc(state, node)
const selected_ct_node_id = get_selected_calltree_node_by_loc(state, node)
if(ct_node_id === null) {
/*
TODO remove because it interferes with find_call deferred calls
if(selected_ct_node_id === null) {
// strict compare (===) with null, to check if we put null earlier to
// designate that fn is not reachable
return set_active_calltree_node(state, null)
}
*/
if(ct_node_id != null) {
if(selected_ct_node_id != null) {
const ct_node = find_node(
state.calltree,
n => n.id == ct_node_id
n => n.id == selected_ct_node_id
)
if(ct_node == null) {
throw new Error('illegal state')
@@ -604,69 +610,22 @@ export const find_call = (state, index) => {
return state
}
const loc = {index: node.index, module: state.current_module}
// 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
)
const ct_node_id = get_calltree_node_by_loc(state, node.index)
let next_calltree, active_calltree_node
if(call != null) {
if(call.has_more_children) {
active_calltree_node = eval_expand_calltree_node(
// TODO copy eval_cxt?
state.eval_cxt,
state.parse_result,
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 = eval_find_call(
// TODO copy eval_cxt?
state.eval_cxt,
state.parse_result,
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,
)
if(ct_node_id == null) {
return set_active_calltree_node(state, null)
}
const ct_node = find_node(
state.calltree,
n => n.id == ct_node_id
)
if(ct_node == null) {
throw new Error('illegal state')
}
return add_frame(
expand_path(
{...state, calltree: next_calltree},
active_calltree_node
),
active_calltree_node,
expand_path(state, ct_node),
ct_node,
)
}

View File

@@ -11,10 +11,10 @@ import {
root_calltree_node, root_calltree_module, make_calltree,
get_deferred_calls,
calltree_commands,
add_frame, calltree_node_loc, expand_path,
add_frame, calltree_node_loc, get_calltree_node_by_loc, expand_path,
initial_calltree_node, default_expand_path, toggle_expanded, active_frame,
find_call, find_call_node, set_active_calltree_node,
set_cursor_position, current_cursor_position, set_location
set_cursor_position, current_cursor_position, set_location,
} from './calltree.js'
const collect_logs = (logs, call) => {
@@ -45,6 +45,8 @@ const apply_eval_result = (state, eval_result) => {
return {
...state,
calltree: make_calltree(eval_result.calltree, null),
calltree_node_by_loc: eval_result.calltree_node_by_loc,
// TODO copy eval_cxt?
eval_cxt: eval_result.eval_cxt,
logs: {
logs: collect_logs(eval_result.logs, eval_result.calltree),
@@ -95,6 +97,7 @@ const run_code = (s, dirty_files) => {
calltree_node_is_expanded: null,
frames: null,
calltree_node_by_loc: null,
selected_calltree_node_by_loc: null,
selection_state: null,
loading_external_imports_state: null,
value_explorer: null,
@@ -185,75 +188,38 @@ const external_imports_loaded = (
}
}
const node = find_call_node(state, current_cursor_position(state))
let toplevel, result
if(
// edit module that is not imported (maybe recursively by state.entrypoint)
// TODO if module not imported, then do not run code on edit at all
node == null
||
node.type == 'do' /* toplevel AST node */
) {
result = eval_modules(
state.parse_result,
external_imports,
state.on_deferred_call,
state.calltree_changed_token,
state.io_trace,
)
toplevel = true
} else {
result = eval_modules(
state.parse_result,
external_imports,
state.on_deferred_call,
state.calltree_changed_token,
state.io_trace,
{index: node.index, module: state.current_module},
)
toplevel = false
}
// TODO if module not imported, then do not run code on edit at all
const result = eval_modules(
state.parse_result,
external_imports,
state.on_deferred_call,
state.calltree_changed_token,
state.io_trace,
)
if(result.then != null) {
return {...state,
eval_modules_state: {
promise: result, node, toplevel,
}
eval_modules_state: { promise: result }
}
} else {
return eval_modules_finished(state, state, result, node, toplevel)
return eval_modules_finished(state, state, result)
}
}
const eval_modules_finished = (state, prev_state, result, node, toplevel) => {
const eval_modules_finished = (state, prev_state, result) => {
if(state.calltree_changed_token != prev_state.calltree_changed_token) {
// code was modified after prev vesion of code was executed, discard
return state
}
const next = apply_eval_result(state, result)
let active_calltree_node
if(toplevel) {
if(node == state.parse_result.modules[root_calltree_module(next)]) {
active_calltree_node = root_calltree_node(next)
} else {
active_calltree_node = null
}
} else {
if(result.call == null) {
// Unreachable call
active_calltree_node = null
} else {
active_calltree_node = result.call
}
}
const next = find_call(
apply_eval_result(state, result),
current_cursor_position(state)
)
let result_state
if(active_calltree_node == null) {
if(next.active_calltree_node == null) {
const {node, state: next2} = initial_calltree_node(next)
result_state = set_active_calltree_node(next2, null, node)
} else {
@@ -261,10 +227,10 @@ const eval_modules_finished = (state, prev_state, result, node, toplevel) => {
default_expand_path(
expand_path(
next,
active_calltree_node
next.active_calltree_node
)
),
active_calltree_node,
next.active_calltree_node,
)
}
@@ -883,7 +849,7 @@ const open_app_window = (state, globals) => {
})
}
const get_initial_state = (state, entrypoint_settings) => {
const get_initial_state = (state, entrypoint_settings, cursor_pos = 0) => {
const with_files = state.project_dir == null
? state
: load_files(state, state.project_dir)
@@ -892,7 +858,7 @@ const get_initial_state = (state, entrypoint_settings) => {
return {
...with_settings,
cursor_position_by_file: {[with_settings.current_module]: 0},
cursor_position_by_file: {[with_settings.current_module]: cursor_pos},
}
}

View File

@@ -209,7 +209,7 @@ export const color = frame => {
export const color_file = (state, file) =>
Object
.values(state.calltree_node_by_loc?.[file] ?? {})
.values(state.selected_calltree_node_by_loc?.[file] ?? {})
// node_id == null means it is unreachable, so do not color
.filter(node_id => node_id != null)
.map(node_id => state.frames[node_id].coloring)

4
src/effects.js vendored
View File

@@ -185,8 +185,6 @@ export const apply_side_effects = (prev, next, command, ui) => {
exec('eval_modules_finished',
next, /* becomes prev_state */
result,
s.node,
s.toplevel
)
})
}
@@ -251,7 +249,7 @@ export const apply_side_effects = (prev, next, command, ui) => {
ui.calltree.render_select_node(prev, next)
}
if(prev.calltree_node_by_loc != next.calltree_node_by_loc) {
if(prev.selected_calltree_node_by_loc != next.selected_calltree_node_by_loc) {
render_coloring(ui, next)
}

View File

@@ -17,7 +17,7 @@ import {has_toplevel_await} from './find_definitions.js'
// import runtime as external because it has non-functional code
// external
import {run, do_eval_expand_calltree_node, do_eval_find_call} from './runtime.js'
import {run, do_eval_expand_calltree_node} from './runtime.js'
// TODO: fix error messages. For example, "__fn is not a function"
@@ -331,9 +331,6 @@ export const eval_modules = (
// TODO use native array for stack for perf? stack contains booleans
stack: new Array(),
searched_location: location,
found_call: null,
is_recording_deferred_calls: false,
on_deferred_call: (call, calltree_changed_token, logs) => {
return on_deferred_call(
@@ -352,15 +349,12 @@ export const eval_modules = (
const make_result = result => {
const calltree = assign_code(parse_result.modules, result.calltree)
const call = result.call == null
? null
: find_node(calltree, node => node.id == result.call.id)
return {
modules: result.modules,
logs: result.logs,
eval_cxt: result.eval_cxt,
calltree,
call,
calltree_node_by_loc: result.eval_cxt.calltree_node_by_loc,
io_trace: result.eval_cxt.io_trace,
}
}
@@ -372,19 +366,6 @@ export const eval_modules = (
}
}
export const eval_find_call = (cxt, parse_result, calltree, location) => {
const result = do_eval_find_call(cxt, 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 {
node: node_with_code,
call: call_with_code,
}
}
export const eval_expand_calltree_node = (cxt, parse_result, node) => {
return assign_code(

View File

@@ -45,11 +45,13 @@ const do_run = function*(module_fns, cxt, io_trace){
// TODO group all io_trace_ properties to single object?
? {...cxt,
logs: [],
calltree_node_by_loc: new Map(),
io_trace_is_recording: true,
io_trace: [],
}
: {...cxt,
logs: [],
calltree_node_by_loc: new Map(),
io_trace_is_recording: false,
io_trace,
io_trace_is_replay_aborted: false,
@@ -63,13 +65,16 @@ const do_run = function*(module_fns, cxt, io_trace){
apply_promise_patch(cxt)
set_current_context(cxt)
for(let {module, fn} of module_fns) {
cxt.found_call = null
for(let i = 0; i < module_fns.length; i++) {
const {module, fn} = module_fns[i]
cxt.is_entrypoint = i == module_fns.length - 1
cxt.children = null
calltree = {
toplevel: true,
module,
id: cxt.call_counter++
id: ++cxt.call_counter,
}
try {
@@ -98,14 +103,9 @@ const do_run = function*(module_fns, cxt, io_trace){
remove_promise_patch(cxt)
cxt.searched_location = null
const call = cxt.found_call
cxt.found_call = null
return {
modules: cxt.modules,
calltree,
call,
logs: _logs,
eval_cxt: cxt,
}
@@ -170,23 +170,6 @@ export const set_record_call = cxt => {
}
}
const do_expand_calltree_node = (cxt, node) => {
if(node.fn.__location != null) {
// fn is hosted, it created call, this time with children
const result = cxt.children[0]
result.id = node.id
result.children = cxt.prev_children
result.has_more_children = false
return result
} else {
// fn is native, it did not created call, only its child did
return {...node,
children: cxt.children,
has_more_children: false,
}
}
}
export const do_eval_expand_calltree_node = (cxt, node) => {
cxt.is_recording_deferred_calls = false
cxt.children = null
@@ -199,80 +182,29 @@ export const do_eval_expand_calltree_node = (cxt, node) => {
} catch(e) {
// do nothing. Exception was caught and recorded inside '__trace'
}
cxt.is_recording_deferred_calls = true
const result = do_expand_calltree_node(cxt, node)
cxt.children = null
return result
}
/*
Try to find call of function with given 'location'
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
*/
export const do_eval_find_call = (cxt, calltree, location) => {
// TODO always cleanup children when work finished, not before it started
const children = cxt.children
cxt.children = null
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.fn.__location != null) {
// fn is hosted, it created call, this time with children
const result = children[0]
result.id = node.id
result.children = cxt.prev_children
result.has_more_children = false
return result
} else {
// fn is native, it did not created call, only its child did
return {...node,
children: children,
has_more_children: false,
}
if(node.has_more_children) {
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'
}
if(cxt.found_call != null) {
return {
node: do_expand_calltree_node(cxt, node),
call: cxt.found_call,
}
} else {
cxt.children = null
}
}
// node has no children, return null
return null
}
cxt.is_recording_deferred_calls = false
cxt.searched_location = location
const result = do_find(calltree)
cxt.children = null
cxt.searched_location = null
cxt.found_call = null
cxt.is_recording_deferred_calls = true
return result
}
const __do_await = async (cxt, 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
@@ -309,19 +241,19 @@ const __trace = (cxt, fn, name, argscount, __location, get_closure) => {
cxt.children = null
cxt.stack.push(false)
const is_found_call =
(cxt.searched_location != null && cxt.found_call == null)
&&
(
__location.index == cxt.searched_location.index
&&
__location.module == cxt.searched_location.module
)
const call_id = ++cxt.call_counter
if(is_found_call) {
// Assign temporary value to prevent nested calls from populating
// found_call
cxt.found_call = {}
// populate calltree_node_by_loc only for entrypoint module
if(cxt.is_entrypoint) {
let nodes_of_module = cxt.calltree_node_by_loc.get(__location.module)
if(nodes_of_module == null) {
nodes_of_module = new Map()
cxt.calltree_node_by_loc.set(__location.module, nodes_of_module)
}
if(nodes_of_module.get(__location.index) == null) {
set_record_call(cxt)
nodes_of_module.set(__location.index, call_id)
}
}
let ok, value, error
@@ -351,7 +283,7 @@ const __trace = (cxt, fn, name, argscount, __location, get_closure) => {
cxt.prev_children = cxt.children
const call = {
id: cxt.call_counter++,
id: call_id,
ok,
value,
error,
@@ -362,11 +294,6 @@ const __trace = (cxt, fn, name, argscount, __location, get_closure) => {
: args.slice(0, argscount),
}
if(is_found_call) {
cxt.found_call = call
set_record_call(cxt)
}
const should_record_call = cxt.stack.pop()
if(should_record_call) {
@@ -453,7 +380,7 @@ const __trace_call = (cxt, fn, context, args, errormessage, is_new = false) => {
cxt.prev_children = cxt.children
const call = {
id: cxt.call_counter++,
id: ++cxt.call_counter,
ok,
value,
error,