mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
find call with respect of cursor position inside branch
This commit is contained in:
203
src/calltree.js
203
src/calltree.js
@@ -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} from './eval.js'
|
||||
import {eval_frame, eval_expand_calltree_node, get_after_if_path} from './eval.js'
|
||||
|
||||
export const pp_calltree = tree => ({
|
||||
id: tree.id,
|
||||
@@ -91,27 +91,38 @@ export const get_calltree_node_by_loc = (state, index) => {
|
||||
}
|
||||
}
|
||||
|
||||
const get_selected_calltree_node_by_loc = (state, node) =>
|
||||
const get_selected_calltree_node_by_loc = (
|
||||
state,
|
||||
node,
|
||||
module = state.current_module
|
||||
) =>
|
||||
state.selected_calltree_node_by_loc
|
||||
?.[state.current_module]
|
||||
?.[module]
|
||||
?.[
|
||||
state.parse_result.modules[state.current_module] == node
|
||||
state.parse_result.modules[module] == node
|
||||
// identify toplevel by index `-1`, because function and toplevel can
|
||||
// have the same index (in case when module starts with function_expr)
|
||||
? -1
|
||||
: node.index
|
||||
]
|
||||
|
||||
const add_selected_calltree_node_by_loc = (state, loc, node_id) => {
|
||||
const set_node_by_loc = (node_by_loc, loc, node_id) => {
|
||||
return {...node_by_loc,
|
||||
[loc.module]: {
|
||||
...node_by_loc?.[loc.module],
|
||||
[loc.index ?? -1]: node_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const set_selected_calltree_node_by_loc = (state, loc, node_id) => {
|
||||
return {
|
||||
...state,
|
||||
selected_calltree_node_by_loc:
|
||||
{...state.selected_calltree_node_by_loc,
|
||||
[loc.module]: {
|
||||
...state.selected_calltree_node_by_loc?.[loc.module],
|
||||
[loc.index ?? -1]: node_id
|
||||
}
|
||||
}
|
||||
selected_calltree_node_by_loc: set_node_by_loc(
|
||||
state.selected_calltree_node_by_loc,
|
||||
loc,
|
||||
node_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,24 +144,38 @@ export const add_frame = (
|
||||
current_calltree_node = active_calltree_node,
|
||||
) => {
|
||||
let with_frame
|
||||
if(state.frames?.[active_calltree_node.id] == null) {
|
||||
const frame = eval_frame(active_calltree_node, state.modules)
|
||||
let frame
|
||||
frame = state.frames?.[active_calltree_node.id]
|
||||
if(frame == null) {
|
||||
frame = eval_frame(active_calltree_node, state.modules)
|
||||
const execution_paths = active_calltree_node.toplevel
|
||||
? null
|
||||
: get_execution_paths(frame)
|
||||
const coloring = color(frame)
|
||||
with_frame = {...state,
|
||||
frames: {...state.frames,
|
||||
[active_calltree_node.id]: {...frame, coloring}
|
||||
[active_calltree_node.id]: {...frame, coloring, execution_paths}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
with_frame = state
|
||||
}
|
||||
// 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,
|
||||
|
||||
const loc = calltree_node_loc(active_calltree_node)
|
||||
|
||||
const with_colored_frames = {...with_frame,
|
||||
colored_frames: set_node_by_loc(
|
||||
with_frame.colored_frames,
|
||||
loc,
|
||||
active_calltree_node.id,
|
||||
)
|
||||
}
|
||||
|
||||
return set_active_calltree_node(
|
||||
with_colored_frames,
|
||||
active_calltree_node,
|
||||
current_calltree_node
|
||||
)
|
||||
return set_active_calltree_node(result, active_calltree_node, current_calltree_node)
|
||||
}
|
||||
|
||||
const replace_calltree_node = (root, node, replacement) => {
|
||||
@@ -269,7 +294,13 @@ const jump_calltree_node = (_state, _current_calltree_node) => {
|
||||
// of body?
|
||||
: set_location(next, loc)
|
||||
|
||||
return {...with_location,
|
||||
const with_selected_calltree_node = set_selected_calltree_node_by_loc(
|
||||
with_location,
|
||||
calltree_node_loc(active_calltree_node),
|
||||
with_location.active_calltree_node.id,
|
||||
)
|
||||
|
||||
return {...with_selected_calltree_node,
|
||||
value_explorer: next.current_calltree_node.toplevel
|
||||
? null
|
||||
: {
|
||||
@@ -537,20 +568,88 @@ export const initial_calltree_node = state => {
|
||||
|
||||
export const default_expand_path = state => initial_calltree_node(state).state
|
||||
|
||||
export const get_execution_paths = frame => {
|
||||
/*
|
||||
- depth-first search tree. if 'result == null', then stop. Do not descend
|
||||
into function_expr
|
||||
- look for 'if' and ternary
|
||||
- Get executed branch (branch.result.ok != null). There may be no executed
|
||||
branch if cond executed with error
|
||||
- for 'if' statement we also add 'get_after_if_path(if_node.index)'
|
||||
*/
|
||||
const do_get = (node, next_node) => {
|
||||
if(node.type == 'function_expr' || node.result == null) {
|
||||
return []
|
||||
}
|
||||
let after_if, branch
|
||||
if(node.type == 'if' || node.type == 'ternary') {
|
||||
const [cond, ...branches] = node.children
|
||||
branch = branches.find(b => b.result != null)
|
||||
}
|
||||
if(node.type == 'if' && next_node != null && next_node.result != null) {
|
||||
after_if = get_after_if_path(node)
|
||||
}
|
||||
const paths = [branch?.index, after_if].filter(i => i != null)
|
||||
const children = node.children ?? []
|
||||
const child_paths = children
|
||||
.map((c, i) => do_get(c, children[i + 1]))
|
||||
.flat()
|
||||
return [...paths, ...child_paths]
|
||||
}
|
||||
|
||||
const [args, body] = frame.children
|
||||
|
||||
return new Set(do_get(body))
|
||||
}
|
||||
|
||||
const find_execution_path = (node, index) => {
|
||||
if(node.children == null) {
|
||||
return []
|
||||
}
|
||||
|
||||
const child = node.children.find(c =>
|
||||
c.index <= index && c.index + c.length > index
|
||||
)
|
||||
|
||||
if(child == null) {
|
||||
return []
|
||||
}
|
||||
|
||||
const prev_ifs = node
|
||||
.children
|
||||
.filter(c =>
|
||||
c.index < child.index && c.type == 'if'
|
||||
)
|
||||
.map(c => get_after_if_path(c))
|
||||
|
||||
const child_path = find_execution_path(child, index)
|
||||
|
||||
// TODO other conditionals, like &&, ||, ??, ?., ?.[]
|
||||
if(node.type == 'if' || node.type == 'ternary') {
|
||||
const [cond, left, right] = node.children
|
||||
if(child == left || child == right) {
|
||||
return [...prev_ifs, child.index, ...child_path]
|
||||
}
|
||||
}
|
||||
|
||||
return [...prev_ifs, ...child_path]
|
||||
}
|
||||
|
||||
export const find_call_node = (state, index) => {
|
||||
const module = state.parse_result.modules[state.current_module]
|
||||
|
||||
if(module == null) {
|
||||
// Module is not executed
|
||||
return null
|
||||
return {node: null, path: null}
|
||||
}
|
||||
|
||||
let node
|
||||
let node, path
|
||||
|
||||
if(index < module.index || index >= module.index + module.length) {
|
||||
// index is outside of module, it can happen because of whitespace and
|
||||
// comments in the beginning and the end
|
||||
node = module
|
||||
path = null
|
||||
} else {
|
||||
const leaf = find_leaf(module, index)
|
||||
const anc = ancestry_inc(leaf, module)
|
||||
@@ -558,44 +657,19 @@ export const find_call_node = (state, index) => {
|
||||
node = fn == null
|
||||
? module
|
||||
: fn
|
||||
path = find_execution_path(node, index)
|
||||
}
|
||||
|
||||
return node
|
||||
return {node, path}
|
||||
}
|
||||
|
||||
export const find_call = (state, index) => {
|
||||
const node = find_call_node(state, index)
|
||||
const {node, path} = find_call_node(state, index)
|
||||
|
||||
if(node == null) {
|
||||
return state
|
||||
}
|
||||
|
||||
if(state.active_calltree_node != null && is_eq(node, state.active_calltree_node.code)) {
|
||||
return state
|
||||
}
|
||||
|
||||
const selected_ct_node_id = get_selected_calltree_node_by_loc(state, node)
|
||||
|
||||
/*
|
||||
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(selected_ct_node_id != null) {
|
||||
const ct_node = find_node(
|
||||
state.calltree,
|
||||
n => n.id == selected_ct_node_id
|
||||
)
|
||||
if(ct_node == null) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
return set_active_calltree_node(state, ct_node, ct_node)
|
||||
}
|
||||
|
||||
if(node == state.parse_result.modules[root_calltree_module(state)]) {
|
||||
const toplevel = root_calltree_node(state)
|
||||
return add_frame(
|
||||
@@ -610,19 +684,42 @@ export const find_call = (state, index) => {
|
||||
return state
|
||||
}
|
||||
|
||||
const selected_ct_node_id = get_selected_calltree_node_by_loc(state, node)
|
||||
const execution_paths = selected_ct_node_id == null
|
||||
? null
|
||||
: state.frames[selected_ct_node_id].execution_paths
|
||||
|
||||
const ct_node_id = get_calltree_node_by_loc(state, node.index)
|
||||
|
||||
if(ct_node_id == null) {
|
||||
return set_active_calltree_node(state, null)
|
||||
}
|
||||
|
||||
const path_ct_node_id = [node.index, ...path]
|
||||
.map(path_elem => {
|
||||
const is_selected_node_hits_path =
|
||||
selected_ct_node_id != null
|
||||
&&
|
||||
(execution_paths.has(path_elem) || node.index == path_elem)
|
||||
|
||||
if(is_selected_node_hits_path) {
|
||||
return selected_ct_node_id
|
||||
}
|
||||
|
||||
// If there is no node selected for this fn, or it did not hit the path
|
||||
// when executed, try to find node calltree_node_by_loc
|
||||
return get_calltree_node_by_loc(state, path_elem)
|
||||
})
|
||||
.findLast(node_id => node_id != null)
|
||||
|
||||
const ct_node = find_node(
|
||||
state.calltree,
|
||||
n => n.id == ct_node_id
|
||||
n => n.id == path_ct_node_id
|
||||
)
|
||||
if(ct_node == null) {
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
|
||||
return add_frame(
|
||||
expand_path(state, ct_node),
|
||||
ct_node,
|
||||
|
||||
18
src/cmd.js
18
src/cmd.js
@@ -11,9 +11,9 @@ import {
|
||||
root_calltree_node, root_calltree_module, make_calltree,
|
||||
get_deferred_calls,
|
||||
calltree_commands,
|
||||
add_frame, calltree_node_loc, get_calltree_node_by_loc, expand_path,
|
||||
add_frame, calltree_node_loc, expand_path,
|
||||
initial_calltree_node, default_expand_path, toggle_expanded, active_frame,
|
||||
find_call, find_call_node, set_active_calltree_node,
|
||||
find_call, set_active_calltree_node,
|
||||
set_cursor_position, current_cursor_position, set_location,
|
||||
} from './calltree.js'
|
||||
|
||||
@@ -96,6 +96,7 @@ const run_code = (s, dirty_files) => {
|
||||
active_calltree_node: null,
|
||||
calltree_node_is_expanded: null,
|
||||
frames: null,
|
||||
colored_frames: null,
|
||||
calltree_node_by_loc: null,
|
||||
selected_calltree_node_by_loc: null,
|
||||
selection_state: null,
|
||||
@@ -223,14 +224,11 @@ const eval_modules_finished = (state, prev_state, result) => {
|
||||
const {node, state: next2} = initial_calltree_node(next)
|
||||
result_state = set_active_calltree_node(next2, null, node)
|
||||
} else {
|
||||
result_state = add_frame(
|
||||
default_expand_path(
|
||||
expand_path(
|
||||
next,
|
||||
next.active_calltree_node
|
||||
)
|
||||
),
|
||||
next.active_calltree_node,
|
||||
result_state = default_expand_path(
|
||||
expand_path(
|
||||
next,
|
||||
next.active_calltree_node
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -209,8 +209,7 @@ export const color = frame => {
|
||||
|
||||
export const color_file = (state, file) =>
|
||||
Object
|
||||
.values(state.selected_calltree_node_by_loc?.[file] ?? {})
|
||||
// node_id == null means it is unreachable, so do not color
|
||||
.values(state.colored_frames?.[file] ?? {})
|
||||
.filter(node_id => node_id != null)
|
||||
.map(node_id => state.frames[node_id].coloring)
|
||||
.flat()
|
||||
|
||||
@@ -52,6 +52,7 @@ const normalize_events = (ace_editor, {
|
||||
})
|
||||
|
||||
ace_editor.on('changeSelection', (...args) => {
|
||||
// TODO debounce changeSelection?
|
||||
if(is_change_selection_supressed()) {
|
||||
return
|
||||
}
|
||||
|
||||
2
src/effects.js
vendored
2
src/effects.js
vendored
@@ -249,7 +249,7 @@ export const apply_side_effects = (prev, next, command, ui) => {
|
||||
ui.calltree.render_select_node(prev, next)
|
||||
}
|
||||
|
||||
if(prev.selected_calltree_node_by_loc != next.selected_calltree_node_by_loc) {
|
||||
if(prev.colored_frames != next.colored_frames) {
|
||||
render_coloring(ui, next)
|
||||
}
|
||||
|
||||
|
||||
53
src/eval.js
53
src/eval.js
@@ -72,11 +72,13 @@ const codegen_function_expr = (node, node_cxt) => {
|
||||
? `(${args}) => `
|
||||
: `function(${args})`
|
||||
|
||||
// TODO gensym __obj, __fn, __call_id
|
||||
const prolog = '{const __call_id = __cxt.call_counter;'
|
||||
|
||||
const call = (node.is_async ? 'async ' : '') + decl + (
|
||||
// TODO gensym __obj, __fn
|
||||
(node.body.type == 'do')
|
||||
? '{ let __obj, __fn; ' + do_codegen(node.body) + '}'
|
||||
: '{ let __obj, __fn; return ' + do_codegen(node.body) + '}'
|
||||
? prolog + do_codegen(node.body) + '}'
|
||||
: prolog + 'return ' + do_codegen(node.body) + '}'
|
||||
)
|
||||
|
||||
const argscount = node
|
||||
@@ -135,6 +137,11 @@ ${JSON.stringify(errormessage)})`
|
||||
|
||||
}
|
||||
|
||||
// Note that we use 'node.index + 1' as index, which
|
||||
// does not correspond to any ast node, we just use it as a convenient
|
||||
// marker
|
||||
export const get_after_if_path = node => node.index + 1
|
||||
|
||||
const codegen = (node, node_cxt, parent) => {
|
||||
|
||||
const do_codegen = (n, parent) => codegen(n, node_cxt, parent)
|
||||
@@ -161,11 +168,20 @@ const codegen = (node, node_cxt, parent) => {
|
||||
} else if(node.type == 'throw') {
|
||||
return 'throw ' + do_codegen(node.expr) + ';'
|
||||
} else if(node.type == 'if') {
|
||||
const left = 'if(' + do_codegen(node.cond) + '){' +
|
||||
do_codegen(node.branches[0]) + ' } '
|
||||
return node.branches[1] == null
|
||||
const codegen_branch = branch =>
|
||||
`{ __save_ct_node_for_path(__cxt, __calltree_node_by_loc, ${branch.index}, __call_id);`
|
||||
+ do_codegen(branch)
|
||||
+ '}'
|
||||
const left = 'if(' + do_codegen(node.cond) + ')'
|
||||
+ codegen_branch(node.branches[0])
|
||||
const result = node.branches[1] == null
|
||||
? left
|
||||
: left + ' else { ' + do_codegen(node.branches[1]) + ' }'
|
||||
: left + ' else ' + codegen_branch(node.branches[1])
|
||||
// add path also for point after if statement, in case there was a return
|
||||
// inside if statement.
|
||||
return result +
|
||||
`__save_ct_node_for_path(__cxt, __calltree_node_by_loc, `
|
||||
+ `${get_after_if_path(node)}, __call_id);`
|
||||
} else if(node.type == 'array_literal'){
|
||||
return '[' + node.elements.map(c => do_codegen(c)).join(', ') + ']'
|
||||
} else if(node.type == 'object_literal'){
|
||||
@@ -189,13 +205,18 @@ const codegen = (node, node_cxt, parent) => {
|
||||
} else if(node.type == 'function_expr'){
|
||||
return codegen_function_expr(node, node_cxt)
|
||||
} else if(node.type == 'ternary'){
|
||||
const branches = node.branches.map(branch =>
|
||||
`(__save_ct_node_for_path(__cxt, __calltree_node_by_loc, ${branch.index}, __call_id), `
|
||||
+ do_codegen(branch)
|
||||
+ ')'
|
||||
)
|
||||
return ''
|
||||
+ '('
|
||||
+ do_codegen(node.cond)
|
||||
+ ')\n? '
|
||||
+ do_codegen(node.branches[0])
|
||||
+ branches[0]
|
||||
+'\n: '
|
||||
+ do_codegen(node.branches[1])
|
||||
+ branches[1]
|
||||
} else if(node.type == 'const'){
|
||||
const res = 'const ' + do_codegen(node.name_node) + ' = ' + do_codegen(node.expr, node) + ';'
|
||||
if(node.name_node.type == 'identifier' && node.expr.type == 'function_call') {
|
||||
@@ -297,7 +318,7 @@ export const eval_modules = (
|
||||
io_trace,
|
||||
location
|
||||
) => {
|
||||
// TODO gensym __cxt, __trace, __trace_call
|
||||
// TODO gensym __cxt, __trace, __trace_call, __calltree_node_by_loc, __do_await
|
||||
|
||||
// TODO bug if module imported twice, once as external and as regular
|
||||
|
||||
@@ -310,11 +331,21 @@ export const eval_modules = (
|
||||
const module_fns = parse_result.sorted.map(module => (
|
||||
{
|
||||
module,
|
||||
// TODO refactor, instead of multiple args prefixed with '__', pass
|
||||
// single arg called `runtime`
|
||||
fn: new Function(
|
||||
'__cxt',
|
||||
'__calltree_node_by_loc',
|
||||
'__trace',
|
||||
'__trace_call',
|
||||
'__do_await',
|
||||
'__save_ct_node_for_path',
|
||||
|
||||
/* Add dummy __call_id for toplevel. It does not make any sence
|
||||
* (toplevel is executed only once unlike function), we only add it
|
||||
* because we dont want to codegen differently for if statements in
|
||||
* toplevel and if statements within functions*/
|
||||
'const __call_id = "SOMETHING_WRONG_HAPPENED";' +
|
||||
codegen(parse_result.modules[module], {module})
|
||||
)
|
||||
}
|
||||
@@ -354,7 +385,7 @@ export const eval_modules = (
|
||||
logs: result.logs,
|
||||
eval_cxt: result.eval_cxt,
|
||||
calltree,
|
||||
calltree_node_by_loc: result.eval_cxt.calltree_node_by_loc,
|
||||
calltree_node_by_loc: result.calltree_node_by_loc,
|
||||
io_trace: result.eval_cxt.io_trace,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +246,7 @@ export const with_code_execution = (action, state = get_state()) => {
|
||||
*/
|
||||
if(state.eval_cxt != null) {
|
||||
state.eval_cxt.is_recording_deferred_calls = false
|
||||
state.eval_cxt.skip_save_ct_node_for_path = true
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -253,6 +254,7 @@ export const with_code_execution = (action, state = get_state()) => {
|
||||
} finally {
|
||||
if(state.eval_cxt != null) {
|
||||
state.eval_cxt.is_recording_deferred_calls = true
|
||||
state.eval_cxt.skip_save_ct_node_for_path = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,20 +38,24 @@ const make_promise_with_rejector = cxt => {
|
||||
const do_run = function*(module_fns, cxt, io_trace){
|
||||
let calltree
|
||||
|
||||
const calltree_node_by_loc = new Map(
|
||||
module_fns.map(({module}) => [module, new Map()])
|
||||
)
|
||||
|
||||
const [replay_aborted_promise, io_trace_abort_replay] =
|
||||
make_promise_with_rejector(cxt)
|
||||
|
||||
cxt = (io_trace == null || io_trace.length == 0)
|
||||
// TODO group all io_trace_ properties to single object?
|
||||
? {...cxt,
|
||||
calltree_node_by_loc,
|
||||
logs: [],
|
||||
calltree_node_by_loc: new Map(),
|
||||
io_trace_is_recording: true,
|
||||
io_trace: [],
|
||||
}
|
||||
: {...cxt,
|
||||
calltree_node_by_loc,
|
||||
logs: [],
|
||||
calltree_node_by_loc: new Map(),
|
||||
io_trace_is_recording: false,
|
||||
io_trace,
|
||||
io_trace_is_replay_aborted: false,
|
||||
@@ -79,7 +83,14 @@ const do_run = function*(module_fns, cxt, io_trace){
|
||||
|
||||
try {
|
||||
cxt.modules[module] = {}
|
||||
const result = fn(cxt, __trace, __trace_call, __do_await)
|
||||
const result = fn(
|
||||
cxt,
|
||||
calltree_node_by_loc.get(module),
|
||||
__trace,
|
||||
__trace_call,
|
||||
__do_await,
|
||||
__save_ct_node_for_path,
|
||||
)
|
||||
if(result instanceof cxt.window.Promise) {
|
||||
yield cxt.window.Promise.race([replay_aborted_promise, result])
|
||||
} else {
|
||||
@@ -108,6 +119,7 @@ const do_run = function*(module_fns, cxt, io_trace){
|
||||
calltree,
|
||||
logs: _logs,
|
||||
eval_cxt: cxt,
|
||||
calltree_node_by_loc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +256,7 @@ const __trace = (cxt, fn, name, argscount, __location, get_closure) => {
|
||||
const call_id = ++cxt.call_counter
|
||||
|
||||
// populate calltree_node_by_loc only for entrypoint module
|
||||
if(cxt.is_entrypoint) {
|
||||
if(cxt.is_entrypoint && !cxt.skip_save_ct_node_for_path) {
|
||||
let nodes_of_module = cxt.calltree_node_by_loc.get(__location.module)
|
||||
if(nodes_of_module == null) {
|
||||
nodes_of_module = new Map()
|
||||
@@ -392,7 +404,6 @@ const __trace_call = (cxt, fn, context, args, errormessage, is_new = false) => {
|
||||
}
|
||||
|
||||
if(is_log) {
|
||||
// TODO do not collect logs on find_call?
|
||||
cxt.logs.push(call)
|
||||
}
|
||||
|
||||
@@ -412,3 +423,17 @@ const __trace_call = (cxt, fn, context, args, errormessage, is_new = false) => {
|
||||
}
|
||||
}
|
||||
|
||||
const __save_ct_node_for_path = (cxt, __calltree_node_by_loc, index, __call_id) => {
|
||||
if(!cxt.is_entrypoint) {
|
||||
return
|
||||
}
|
||||
|
||||
if(cxt.skip_save_ct_node_for_path) {
|
||||
return
|
||||
}
|
||||
|
||||
if(__calltree_node_by_loc.get(index) == null) {
|
||||
__calltree_node_by_loc.set(index, __call_id)
|
||||
set_record_call(cxt)
|
||||
}
|
||||
}
|
||||
|
||||
297
test/test.js
297
test/test.js
@@ -9,6 +9,7 @@ import {
|
||||
pp_calltree,
|
||||
get_deferred_calls,
|
||||
current_cursor_position,
|
||||
get_execution_paths,
|
||||
} from '../src/calltree.js'
|
||||
import {color_file} from '../src/color.js'
|
||||
import {
|
||||
@@ -1802,14 +1803,10 @@ const y = x()`
|
||||
countdown(10)
|
||||
`)
|
||||
const first = root_calltree_node(s).children[0]
|
||||
assert_equal(first.children[0].children, undefined)
|
||||
assert_equal(first.children[0].has_more_children, true)
|
||||
assert_equal(first.value, 10)
|
||||
const s2 = COMMANDS.calltree.click(s, first.id)
|
||||
const first2 = root_calltree_node(s2).children[0]
|
||||
assert_equal(first2.children[0].value, 9)
|
||||
assert_equal(first2.children[0].children, undefined)
|
||||
assert_equal(first2.children[0].has_more_children, true)
|
||||
assert_equal(first2.code, first2.children[0].code)
|
||||
}),
|
||||
|
||||
@@ -2544,6 +2541,279 @@ const y = x()`
|
||||
assert_equal(root_calltree_node(s2).module, '')
|
||||
}),
|
||||
|
||||
test('find branch initial', () => {
|
||||
const code = `
|
||||
function x(cond) {
|
||||
if(cond) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
x(true)
|
||||
x(false)
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('return false'))
|
||||
assert_equal(i.value_explorer.result.value, false)
|
||||
}),
|
||||
|
||||
test('find branch empty branch', () => {
|
||||
const code = `
|
||||
function x(cond) {
|
||||
if(cond) {
|
||||
/* label */
|
||||
}
|
||||
}
|
||||
|
||||
x(false)
|
||||
x(true)
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('label'))
|
||||
assert_equal(i.active_calltree_node.args[0], true)
|
||||
}),
|
||||
|
||||
test('find branch move_cursor', () => {
|
||||
const code = `
|
||||
function x(cond) {
|
||||
if(cond) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
x(true)
|
||||
x(false)
|
||||
`
|
||||
const i = test_initial_state(code)
|
||||
const moved = COMMANDS.move_cursor(i, code.indexOf('return false'))
|
||||
assert_equal(moved.value_explorer.result.value, false)
|
||||
assert_equal(
|
||||
i.colored_frames != moved.colored_frames,
|
||||
true
|
||||
)
|
||||
}),
|
||||
|
||||
test('find branch ternary', () => {
|
||||
const code = `
|
||||
function x(cond) {
|
||||
return cond ? true : false
|
||||
}
|
||||
|
||||
x(true)
|
||||
x(false)
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('false'))
|
||||
assert_equal(i.value_explorer.result.value, false)
|
||||
}),
|
||||
|
||||
test('find branch move cursor within fn', () => {
|
||||
const code = `
|
||||
function x(cond) {
|
||||
if(cond) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
x(true)
|
||||
x(false)
|
||||
`
|
||||
const i = test_initial_state(code)
|
||||
const s1 = COMMANDS.move_cursor(i, code.indexOf('return false'))
|
||||
const s2 = COMMANDS.move_cursor(s1, code.indexOf('return true'))
|
||||
assert_equal(s2.value_explorer.result.value, true)
|
||||
assert_equal(
|
||||
s1.colored_frames != s2.colored_frames,
|
||||
true
|
||||
)
|
||||
}),
|
||||
|
||||
test('find branch fibonacci', () => {
|
||||
const code = `
|
||||
function fib(n) {
|
||||
if(n == 0 || n == 1) {
|
||||
return n
|
||||
} else {
|
||||
return fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
}
|
||||
|
||||
fib(6)
|
||||
`
|
||||
const i = test_initial_state(code)
|
||||
const moved = COMMANDS.move_cursor(i, code.indexOf('return n'))
|
||||
assert_equal(moved.value_explorer.result.value, 1)
|
||||
}),
|
||||
|
||||
test('find branch after if with return', () => {
|
||||
const code = `
|
||||
function x(cond) {
|
||||
if(cond) {
|
||||
return true
|
||||
}
|
||||
1
|
||||
}
|
||||
x(true)
|
||||
x(false)
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('1'))
|
||||
assert_equal(i.value_explorer.result.value, 1)
|
||||
}),
|
||||
|
||||
test('find branch after if with return complex', () => {
|
||||
const code = `
|
||||
function x(a, b) {
|
||||
if(a) {
|
||||
return true
|
||||
}
|
||||
if(a) {
|
||||
return true
|
||||
}
|
||||
if(b) {
|
||||
return true
|
||||
} else {
|
||||
if(false) {
|
||||
return null
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
}
|
||||
x(true)
|
||||
x(false, true)
|
||||
x(false, false)
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('1'))
|
||||
assert_equal(i.value_explorer.result.value, 1)
|
||||
assert_equal(i.active_calltree_node.args, [false, false])
|
||||
}),
|
||||
|
||||
test('find branch get_execution_paths', () => {
|
||||
const code = `
|
||||
function x() {
|
||||
if(true) {/*1*/
|
||||
}
|
||||
if(false) {
|
||||
} else {/*2*/
|
||||
if(true) {/*3*/
|
||||
true ? 4 : 5
|
||||
}
|
||||
return null
|
||||
}
|
||||
// not executed
|
||||
if(true) {
|
||||
}
|
||||
// not executed
|
||||
true ? 6 : 7
|
||||
}
|
||||
x()
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('if'))
|
||||
assert_equal(
|
||||
[...get_execution_paths(active_frame(i))].toSorted((a,b) => a - b),
|
||||
[
|
||||
code.indexOf('if(true)') + 1,
|
||||
code.indexOf('/*1*/') - 1,
|
||||
code.indexOf('/*2*/') - 1,
|
||||
code.indexOf('if(true) {/*3*/') + 1,
|
||||
code.indexOf('/*3*/') - 1,
|
||||
code.indexOf('4'),
|
||||
]
|
||||
)
|
||||
}),
|
||||
|
||||
test('find branch get_execution_paths consice body', () => {
|
||||
const code = `
|
||||
const x = () => true ? 1 : 2
|
||||
x()
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('true'))
|
||||
assert_equal(
|
||||
get_execution_paths(active_frame(i)),
|
||||
[code.indexOf('1')],
|
||||
)
|
||||
}),
|
||||
|
||||
test('find branch get_execution_paths nested fn', () => {
|
||||
const code = `
|
||||
function x() {
|
||||
function y() {
|
||||
true ? 1 : 2
|
||||
}
|
||||
}
|
||||
x()
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('{'))
|
||||
assert_equal(
|
||||
get_execution_paths(active_frame(i)),
|
||||
[],
|
||||
)
|
||||
}),
|
||||
|
||||
test('find branch jump_calltree_node', () => {
|
||||
const code = `
|
||||
function test(x) {
|
||||
if(x > 0) {
|
||||
'label'
|
||||
}
|
||||
}
|
||||
test(1)
|
||||
test(2)
|
||||
`
|
||||
const i = test_initial_state(code, code.indexOf('label'))
|
||||
assert_equal(i.active_calltree_node.args[0], 1)
|
||||
// select second call
|
||||
const second = COMMANDS.calltree.click(i, root_calltree_node(i).children[1].id)
|
||||
assert_equal(second.active_calltree_node.args[0], 2)
|
||||
}),
|
||||
|
||||
test('find branch preserve selected calltree node when moving inside fn', () => {
|
||||
const code = `
|
||||
function x(cond) {
|
||||
if(cond) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
'finish'
|
||||
}
|
||||
x(true)
|
||||
x(false)
|
||||
`
|
||||
const i = test_initial_state(code)
|
||||
const first_call_id = root_calltree_node(i).children[0].id
|
||||
// explicitly select first call
|
||||
const selected = COMMANDS.calltree.click(i, first_call_id)
|
||||
// implicitly select second call by moving cursor
|
||||
const moved = COMMANDS.move_cursor(selected, code.indexOf('false'))
|
||||
const finish = COMMANDS.move_cursor(moved, code.indexOf('finish'))
|
||||
assert_equal(finish.active_calltree_node.id, first_call_id)
|
||||
}),
|
||||
|
||||
test('find branch select calltree node from logs', () => {
|
||||
const code = `
|
||||
function f(x) {
|
||||
if(x > 1) {
|
||||
console.log(x)
|
||||
} else {
|
||||
console.log(x)
|
||||
}
|
||||
}
|
||||
f(5)
|
||||
f(10)
|
||||
`
|
||||
const i = test_initial_state(code)
|
||||
const log_selected = COMMANDS.calltree.navigate_logs_position(i, 1)
|
||||
const moved = COMMANDS.move_cursor(
|
||||
log_selected,
|
||||
code.indexOf('console.log')
|
||||
)
|
||||
assert_equal(moved.active_calltree_node.args, [10])
|
||||
}),
|
||||
|
||||
test('stale id in frame function_call.result.calls bug', () => {
|
||||
const code = `
|
||||
const x = () => {/*x*/
|
||||
@@ -2846,6 +3116,25 @@ const y = x()`
|
||||
expanded.modules[''].fn(10)
|
||||
}),
|
||||
|
||||
test('deferred_calls find call bug', () => {
|
||||
const code = `
|
||||
export const fn = () => 1
|
||||
`
|
||||
|
||||
const {state: i, on_deferred_call} = test_deferred_calls_state(code)
|
||||
|
||||
const moved = COMMANDS.move_cursor(i, code.indexOf('1'))
|
||||
assert_equal(moved.active_calltree_node, null)
|
||||
|
||||
// Make deferred call
|
||||
moved.modules[''].fn(10)
|
||||
|
||||
const after_call = on_deferred_call(moved)
|
||||
const moved2 = COMMANDS.move_cursor(after_call, code.indexOf('1'))
|
||||
|
||||
assert_equal(moved2.active_calltree_node.value, 1)
|
||||
}),
|
||||
|
||||
test('async/await await non promise', async () => {
|
||||
await assert_code_evals_to_async(
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user