diff --git a/src/cmd.js b/src/cmd.js index c683394..46505ed 100644 --- a/src/cmd.js +++ b/src/cmd.js @@ -216,6 +216,11 @@ const eval_modules_finished = (state, prev_state, result) => { return state } + if(result.rt_cxt.io_trace_is_replay_aborted) { + // execution was discarded, return state to execute `run_code` without io_trace + return clear_io_trace({...state, rt_cxt: result.rt_cxt}) + } + const next = find_call( apply_eval_result(state, result), current_cursor_position(state) diff --git a/src/effects.js b/src/effects.js index f49351e..e28db3e 100644 --- a/src/effects.js +++ b/src/effects.js @@ -7,7 +7,7 @@ import { get_deferred_calls } from './calltree.js' import {current_cursor_position} from './calltree.js' -import {exec, FILES_ROOT} from './index.js' +import {exec, reload_app_window, FILES_ROOT} from './index.js' // Imports in the context of `app_window`, so global variables in loaded // modules refer to that window's context @@ -209,7 +209,9 @@ export const apply_side_effects = (prev, next, ui) => { next.loading_external_imports_state != null || next.eval_modules_state != null - if(is_loading) { + if(next.rt_cxt?.io_trace_is_replay_aborted) { + reload_app_window() + } else if(is_loading) { ui.calltree.clear_calltree() clear_coloring(ui) ui.render_debugger_loading(next) diff --git a/src/index.js b/src/index.js index 3984531..cd5d882 100644 --- a/src/index.js +++ b/src/index.js @@ -129,7 +129,7 @@ const init_app_window = w => { // If by that time w.closed was set to true, then page was // closed. Get back to using iframe globalThis.app_window = iframe.contentWindow - reload_app_window(get_state()) + reload_app_window() } else { add_load_handler() } @@ -140,7 +140,7 @@ const init_app_window = w => { add_load_handler() } -const reload_app_window = state => { +export const reload_app_window = (state = get_state()) => { // after window location reload, `run_code` command will be fired. globalThis.app_window.location = get_html_url(state) } @@ -155,7 +155,7 @@ const get_entrypoint_settings = () => { export const exec_and_reload_app_window = (...exec_args) => { exec(...exec_args) - reload_app_window(get_state()) + reload_app_window() } export const open_directory = () => { diff --git a/src/runtime/runtime.js b/src/runtime/runtime.js index aeaa7ec..a14508f 100644 --- a/src/runtime/runtime.js +++ b/src/runtime/runtime.js @@ -39,7 +39,16 @@ const make_promise_with_rejector = cxt => { return [p, rejector] } -const do_run = function*(module_fns, cxt, io_trace){ +export const run = gen_to_promise(function*(module_fns, cxt, io_trace) { + if(!cxt.window.__is_initialized) { + defineMultiversion(cxt.window) + apply_io_patches(cxt.window) + inject_leporello_api(cxt) + cxt.window.__is_initialized = true + } else { + throw new Error('illegal state') + } + let calltree const calltree_node_by_loc = new Map( @@ -137,30 +146,6 @@ const do_run = function*(module_fns, cxt, io_trace){ rt_cxt: cxt, calltree_node_by_loc, } -} - -export const run = gen_to_promise(function*(module_fns, cxt, io_trace) { - if(!cxt.window.__is_initialized) { - defineMultiversion(cxt.window) - apply_io_patches(cxt.window) - inject_leporello_api(cxt) - cxt.window.__is_initialized = true - } else { - throw new Error('illegal state') - } - - const result = yield* do_run(module_fns, cxt, io_trace) - - if(result.rt_cxt.io_trace_is_replay_aborted) { - // TODO test next line - result.rt_cxt.is_recording_deferred_calls = false - - // run again without io trace - // TODO reload app_window before second run - return yield* do_run(module_fns, cxt, null) - } else { - return result - } }) const inject_leporello_api = cxt => { diff --git a/test/utils.js b/test/utils.js index 9364645..6e399f7 100644 --- a/test/utils.js +++ b/test/utils.js @@ -122,36 +122,48 @@ export const test_initial_state = (code, cursor_pos, options = {}) => { ) } -export const test_initial_state_async = async (code, ...args) => { - const s = test_initial_state(code, ...args) - assert_equal(s.eval_modules_state != null, true) - const result = await s.eval_modules_state.promise +const wait_for_result = async state => { + assert_equal(state.eval_modules_state != null, true) + const result = await state.eval_modules_state.promise return COMMANDS.eval_modules_finished( - s, - s, + state, + state, result, ) } +export const test_initial_state_async = async (code, ...args) => { + const s = test_initial_state(code, ...args) + return wait_for_result(s) +} + export const input = (s, code, index, options = {}) => { if(typeof(options) != 'object') { throw new Error('illegal state') } const {state, effects} = COMMANDS.input(s, code, index) - return { - state: run_code(state, options.app_window_patches), - effects, + const nextstate = run_code(state, options.app_window_patches) + if(nextstate.rt_cxt?.io_trace_is_replay_aborted) { + const with_clear_trace = run_code( + COMMANDS.clear_io_trace(nextstate), + options.app_window_patches + ) + return { state: with_clear_trace, effects } + } else { + return { state: nextstate, effects } } } -export const input_async = async (...args) => { - const after_input = input(...args).state - const result = await after_input.eval_modules_state.promise - return COMMANDS.eval_modules_finished( - after_input, - after_input, - result, - ) +export const input_async = async (s, code, index, options) => { + const after_input = input(s, code, index, options).state + const state = await wait_for_result(after_input) + if(state.rt_cxt?.io_trace_is_replay_aborted) { + return wait_for_result( + run_code(COMMANDS.clear_io_trace(state), options.app_window_patches) + ) + } else { + return state + } } export const test_deferred_calls_state = code => {