async calls WIP

This commit is contained in:
Dmitry Vasilev
2022-10-25 02:29:59 +08:00
parent 7c9c74988d
commit 1d1215b718
5 changed files with 98 additions and 12 deletions

View File

@@ -185,7 +185,8 @@ const do_external_imports_loaded = (
const result = eval_modules( const result = eval_modules(
state.parse_result.modules, state.parse_result.modules,
state.parse_result.sorted, state.parse_result.sorted,
external_imports external_imports,
state.on_async_call,
) )
const next = apply_eval_result(state, result) const next = apply_eval_result(state, result)
@@ -208,6 +209,7 @@ const do_external_imports_loaded = (
state.parse_result.modules, state.parse_result.modules,
state.parse_result.sorted, state.parse_result.sorted,
external_imports, external_imports,
state.on_async_call,
{index: node.index, module: state.current_module}, {index: node.index, module: state.current_module},
) )
@@ -737,6 +739,10 @@ const move_cursor = (s, index) => {
return do_move_cursor(state, index) return do_move_cursor(state, index)
} }
const on_async_call = (state, ...args) => {
console.log('on_async_call', state, args)
}
const load_dir = (state, dir) => { const load_dir = (state, dir) => {
const collect_files = dir => dir.kind == 'file' const collect_files = dir => dir.kind == 'file'
? [dir] ? [dir]
@@ -792,5 +798,6 @@ export const COMMANDS = {
move_cursor, move_cursor,
eval_selection, eval_selection,
external_imports_loaded, external_imports_loaded,
on_async_call,
calltree: calltree_commands, calltree: calltree_commands,
} }

View File

@@ -56,6 +56,16 @@ type Call = {
type Node = ToplevelCall | Call type Node = ToplevelCall | Call
*/ */
// TODO just export const Iframe_Function?
const make_function = globalThis.process != null
// Tests are run in Node.js, no iframe
? (...args) => new Function(...args)
// Browser context, run code in iframe
: (...args) => {
const fn_constructor = globalThis.run_code.contentWindow.Function
return new fn_constructor(...args)
}
const codegen_function_expr = (node, cxt, name) => { const codegen_function_expr = (node, cxt, name) => {
const do_codegen = n => codegen(n, cxt) const do_codegen = n => codegen(n, cxt)
@@ -235,7 +245,13 @@ const codegen = (node, cxt, parent) => {
} }
} }
export const eval_modules = (modules, sorted, external_imports, location) => { export const eval_modules = (
modules,
sorted,
external_imports,
on_async_call,
location
) => {
// TODO gensym __modules, __exports // TODO gensym __modules, __exports
// TODO bug if module imported twice, once as external and as regular // TODO bug if module imported twice, once as external and as regular
@@ -243,7 +259,7 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
const codestring = const codestring =
` `
let children, prev_children let children, prev_children
// TODO use native array for stack? // TODO use native array for stack for perf?
const stack = new Array() const stack = new Array()
let call_counter = 0 let call_counter = 0
@@ -252,6 +268,9 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
let searched_location let searched_location
let found_call let found_call
let is_recording_async_calls
let is_toplevel_call = true
const set_record_call = () => { const set_record_call = () => {
for(let i = 0; i < stack.length; i++) { for(let i = 0; i < stack.length; i++) {
stack[i] = true stack[i] = true
@@ -259,12 +278,14 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
} }
const expand_calltree_node = (node) => { const expand_calltree_node = (node) => {
is_recording_async_calls = false
children = null children = null
try { try {
node.fn.apply(node.context, node.args) 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'
} }
is_recording_async_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]
@@ -319,6 +340,9 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
let ok, value, error let ok, value, error
const is_toplevel_call_copy = is_toplevel_call
is_toplevel_call = false
try { try {
value = fn(...args) value = fn(...args)
ok = true ok = true
@@ -361,6 +385,12 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
children = [] children = []
} }
children.push(call) children.push(call)
is_toplevel_call = is_toplevel_call_copy
if(is_recording_async_calls && is_toplevel_call) {
on_async_call(children)
}
} }
} }
@@ -437,7 +467,14 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
} }
const run = entrypoint => { const run = entrypoint => {
const __modules = {...external_imports}
is_recording_async_calls = false
const __modules = {
/* external_imports passed as an argument to function generated with
* 'new Function' constructor */
...external_imports
}
let current_call let current_call
` `
@@ -468,6 +505,8 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
})() })()
current_call.children = children current_call.children = children
if(!__modules['${m}'].calls.ok) { if(!__modules['${m}'].calls.ok) {
is_recording_async_calls = true
children = null
return __modules return __modules
} }
` `
@@ -475,6 +514,8 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
.join('') .join('')
+ +
` `
is_recording_async_calls = true
children = null
return __modules return __modules
} }
@@ -485,12 +526,27 @@ export const eval_modules = (modules, sorted, external_imports, location) => {
} }
` `
const actions = new Function('external_imports', codestring)( const actions = make_function(
'external_imports',
'on_async_call',
codestring
)(
/* external_imports */
external_imports == null external_imports == null
? null ? null
: map_object(external_imports, (name, {module}) => : map_object(external_imports, (name, {module}) =>
({exports: module, is_external: true}) ({exports: module, is_external: true})
) )
,
/* on_async_call */
calls => {
on_async_call(
calls.map(c =>
assign_code(modules, c)
)
)
},
) )
const calltree_actions = { const calltree_actions = {
@@ -581,6 +637,9 @@ account
// Workaround with statement forbidden in strict mode (imposed by ES6 modules) // Workaround with statement forbidden in strict mode (imposed by ES6 modules)
// Also currently try/catch is not implemented TODO // Also currently try/catch is not implemented TODO
// TODO also create in Iframe Context?
const eval_codestring = new Function('codestring', 'scope', const eval_codestring = new Function('codestring', 'scope',
// Make a copy of `scope` to not mutate it with assignments // Make a copy of `scope` to not mutate it with assignments
` `

View File

@@ -46,7 +46,10 @@ export const init = (container) => {
}) })
read_modules().then(initial_state => { read_modules().then(initial_state => {
state = get_initial_state(initial_state) state = get_initial_state({
...initial_state,
on_async_call: (...args) => exec('on_async_call', ...args)
})
// Expose state for debugging // Expose state for debugging
globalThis.__state = state globalThis.__state = state
ui = new UI(container, state) ui = new UI(container, state)

View File

@@ -2227,4 +2227,18 @@ const y = x()`
) )
}), }),
test_only('async calls', () => {
const code = `
const fn = () => {
}
// Use Function constructor to exec impure code for testing
new Function('fn', 'globalThis.__run_async_call = fn')(fn)
`
const i = test_initial_state(code, {
on_async_call: (calls) => {console.log('test on async call', calls)}
})
globalThis.__run_async_call()
delete globalThis.__run_async_call
}),
] ]

View File

@@ -27,12 +27,15 @@ export const assert_code_error = (codestring, error) => {
assert_equal(result.error, error) assert_equal(result.error, error)
} }
export const test_initial_state = code => { export const test_initial_state = (code, state) => {
return get_initial_state({ return get_initial_state(
{
...state,
files: typeof(code) == 'object' ? code : { '' : code}, files: typeof(code) == 'object' ? code : { '' : code},
entrypoint: '', entrypoint: '',
current_module: '', current_module: '',
}) },
)
} }
export const stringify = val => export const stringify = val =>