This commit is contained in:
Dmitry Vasilev
2023-02-04 22:37:35 +08:00
parent f37bfd4627
commit 28d5776219
4 changed files with 437 additions and 479 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} from './eval.js'
import {eval_frame, eval_expand_calltree_node, eval_find_call} from './eval.js'
export const pp_calltree = tree => ({
id: tree.id,
@@ -176,7 +176,11 @@ const replace_calltree_node = (root, node, replacement) => {
const expand_calltree_node = (state, node) => {
if(node.has_more_children) {
const next_node = state.calltree_actions.expand_calltree_node(node)
const next_node = eval_expand_calltree_node(
state.eval_cxt,
state.parse_result,
node
)
return {
state: {...state,
calltree: replace_calltree_node(state.calltree, node, next_node)
@@ -594,7 +598,11 @@ export const find_call = (state, index) => {
if(call != null) {
if(call.has_more_children) {
active_calltree_node = state.calltree_actions.expand_calltree_node(call)
active_calltree_node = eval_expand_calltree_node(
state.eval_cxt,
state.parse_result,
call
)
next_calltree = replace_calltree_node(
state.calltree,
call,
@@ -605,7 +613,12 @@ export const find_call = (state, index) => {
next_calltree = state.calltree
}
} else {
const find_result = state.calltree_actions.find_call(state.calltree, loc)
const find_result = eval_find_call(
state.eval_cxt,
state.parse_result,
state.calltree,
loc
)
if(find_result == null) {
return add_calltree_node_by_loc(
// Remove active_calltree_node

View File

@@ -46,7 +46,7 @@ const apply_eval_result = (state, eval_result) => {
return {
...state,
calltree: make_calltree(eval_result.calltree, null),
calltree_actions: eval_result.calltree_actions,
eval_cxt: eval_result.eval_cxt,
logs: {
logs: collect_logs(eval_result.logs, eval_result.calltree),
log_position: null
@@ -84,7 +84,7 @@ const run_code = (s, dirty_files) => {
// Shows that calltree is brand new and requires entire rerender
calltree_changed_token: {},
calltree_actions: null,
eval_cxt: null,
logs: null,
current_calltree_node: null,
active_calltree_node: null,

View File

@@ -15,6 +15,10 @@ import {
import {has_toplevel_await} from './find_definitions.js'
// external
// TODO
// import {} from './runtime.js'
// TODO: fix error messages. For example, "__fn is not a function"
/*
@@ -89,7 +93,8 @@ const codegen_function_expr = (node, cxt) => {
// on first call (see `__trace`)
const get_closure = `() => ({${[...node.closed].join(',')}})`
return `__trace(${call}, "${node.name}", ${argscount}, ${location}, ${get_closure})`
return `__trace(__cxt, ${call}, "${node.name}", ${argscount}, ${location}, \
${get_closure})`
}
/*
@@ -121,15 +126,16 @@ const codegen_function_call = (node, cxt) => {
return `(
__obj = ${do_codegen(node.fn.object)},
__fn = __obj${op}[${do_codegen(node.fn.property)}],
__trace_call(__fn, __obj, ${args}, ${JSON.stringify(errormessage)})
__trace_call(__cxt, __fn, __obj, ${args}, ${JSON.stringify(errormessage)})
)`
} else {
return `__trace_call(${do_codegen(node.fn)}, null, ${args}, \
return `__trace_call(__cxt, ${do_codegen(node.fn)}, null, ${args}, \
${JSON.stringify(errormessage)})`
}
}
// TODO rename cxt, to not confuse with another cxt
const codegen = (node, cxt, parent) => {
const do_codegen = (n, parent) => codegen(n, cxt, parent)
@@ -225,7 +231,7 @@ const codegen = (node, cxt, parent) => {
+ ']'
} else if(node.type == 'unary') {
if(node.operator == 'await') {
return `(await __do_await(${do_codegen(node.expr)}))`
return `(await __do_await(__cxt, ${do_codegen(node.expr)}))`
} else {
return '(' + node.operator + ' ' + do_codegen(node.expr) + ')'
}
@@ -241,7 +247,7 @@ const codegen = (node, cxt, parent) => {
} else if(node.type == 'new') {
const args = `[${node.args.children.map(do_codegen).join(',')}]`
const errormessage = not_a_function_error(node.constructor)
return `__trace_call(${do_codegen(node.constructor)}, null, ${args}, \
return `__trace_call(__cxt, ${do_codegen(node.constructor)}, null, ${args},\
${JSON.stringify(errormessage)}, true)`
} else if(node.type == 'grouping'){
return '(' + do_codegen(node.expr) + ')'
@@ -260,14 +266,14 @@ ${JSON.stringify(errormessage)}, true)`
if(names.length == 0) {
return ''
} else {
return `const {${names.join(',')}} = __modules['${node.full_import_path}'];`;
return `const {${names.join(',')}} = __cxt.modules['${node.full_import_path}'];`;
}
} else if(node.type == 'export') {
const identifiers = collect_destructuring_identifiers(node.binding.name_node)
.map(i => i.value)
return do_codegen(node.binding)
+
`Object.assign(__exports, {${identifiers.join(',')}});`
`Object.assign(__cxt.modules[cxt.module], {${identifiers.join(',')}});`
} else if(node.type == 'function_decl') {
const expr = node.children[0]
return `const ${expr.name} = ${codegen_function_expr(expr, cxt)};`
@@ -284,34 +290,118 @@ export const eval_modules = (
calltree_changed_token,
location
) => {
// TODO gensym __modules, __exports, __trace, __trace_call
// TODO gensym __cxt, __trace, __trace_call
// TODO bug if module imported twice, once as external and as regular
const is_async = has_toplevel_await(parse_result.modules)
const codestring = `
let children, prev_children
/*
TODO remove
cxt vars:
- modules
- is_recording_deferred_calls
- logs
- children
- prev_children
- call_counter
- is_toplevel_call
- searched_location
- found_call
- promise_then
- stack
- on_deferred_call
- calltree_changed_token
*/
// TODO sort
const cxt = {
is_recording_deferred_calls: false,
call_counter: 0,
logs: [],
is_toplevel_call: true,
// TODO use native array for stack for perf? stack contains booleans
const stack = new Array()
stack: new Array(),
children: null,
prev_children: null,
searched_location: location,
found_call: null,
promise_then: null,
let logs = []
modules: external_imports == null
? null
: map_object(external_imports, (name, {module}) => module),
let call_counter = 0
on_deferred_call: (call, calltree_changed_token, logs) => {
return on_deferred_call(
assign_code(parse_result.modules, call),
calltree_changed_token,
logs,
)
},
let current_module
calltree_changed_token
}
let searched_location
let found_call
const Function = is_async
? globalThis.run_window.eval('(async function(){})').constructor
: globalThis.run_window.Function
let is_recording_deferred_calls
let is_toplevel_call = true
let calltree
let promise_then
apply_promise_patch(cxt)
function apply_promise_patch() {
for(let current_module of parse_result.sorted) {
cxt.found_call = null
cxt.children = null
calltree = {
toplevel: true,
module: current_module,
id: cxt.call_counter++
}
const module_fn = new Function(
'__cxt',
codegen(node, {module: module_name})
)
cxt.modules[current_module] =
try {
// cxt.modules[current_module] = {}
// TODO await
module_fn(cxt)
calltree.ok = true
} catch(error) {
calltree.ok = false
calltree.error = error
}
calltree.children = cxt.children
if(!calltree.ok) {
break
}
}
cxt.is_recording_deferred_calls = true
const _logs = cxt.logs
cxt.logs = []
cxt.children = null
remove_promise_patch(cxt)
searched_location = null
const call = found_call
found_call = null
return {
modules: cxt.modules,
calltree: assign_code(parse_result.modules, calltree),
call,
logs: _logs,
eval_cxt: cxt,
}
}
const apply_promise_patch = cxt => {
promise_then = Promise.prototype.then
@@ -345,12 +435,10 @@ export const eval_modules = (
}
}
function remove_promise_patch() {
const remove_promise_patch = cxt => {
Promise.prototype.then = promise_then
}
apply_promise_patch()
const set_record_call = () => {
for(let i = 0; i < stack.length; i++) {
stack[i] = true
@@ -374,7 +462,7 @@ export const eval_modules = (
}
}
const expand_calltree_node = (node) => {
export const eval_expand_calltree_node = (parse_result, node) => {
is_recording_deferred_calls = false
children = null
try {
@@ -387,34 +475,7 @@ export const eval_modules = (
// 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) => {
searched_location = location
const run_result = run()
const after_run = ({calltree, modules, logs}) => {
searched_location = null
const call = found_call
found_call = null
return {
calltree,
modules,
logs,
call,
}
}
// Support to paths, one for async 'run', and one for sync, to avoid
// refactoring code (mostly test code) to always async
if(run_result instanceof Promise) {
return run_result.then(after_run)
} else {
return after_run(run_result)
}
return assign_code(parse_result.modules, do_expand_calltree_node(node))
}
@@ -430,7 +491,7 @@ export const eval_modules = (
We dont rerun entire execution because we want find_call to be
synchronous for simplicity
*/
const find_call = (calltree, location) => {
export const eval_find_call = (cxt, parse_result, calltree, location) => {
// TODO remove
if(children != null) {
throw new Error('illegal state')
@@ -484,10 +545,19 @@ export const eval_modules = (
found_call = null
is_recording_deferred_calls = true
return result
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,
}
}
const __do_await = async value => {
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
// calls. Allocate array now, so we can have a reference to this array
@@ -513,7 +583,7 @@ export const eval_modules = (
}
}
const __trace = (fn, name, argscount, __location, get_closure) => {
const __trace = (cxt, fn, name, argscount, __location, get_closure) => {
const result = (...args) => {
if(result.__closure == null) {
result.__closure = get_closure()
@@ -609,7 +679,7 @@ export const eval_modules = (
return result
}
const __trace_call = (fn, context, args, errormessage, is_new = false) => {
const __trace_call = (cxt, fn, context, args, errormessage, is_new = false) => {
if(fn != null && fn.__location != null && !is_new) {
// Call will be traced, because tracing code is already embedded inside
// fn
@@ -694,131 +764,6 @@ export const eval_modules = (
}
}
const run = ${is_async ? 'async' : ''} () => {
is_recording_deferred_calls = false
const finish = () => {
is_recording_deferred_calls = true
const _logs = logs
logs = []
children = null
remove_promise_patch()
return { modules: __modules, calltree: current_call, logs: _logs }
}
const __modules = {
/* external_imports passed as an argument to function generated with
* 'new Function' constructor */
...external_imports
}
let current_call
`
+
parse_result.sorted
.map((m, i) =>
`
current_module = '${m}'
found_call = null
children = null
current_call = {
toplevel: true,
module: current_module,
id: call_counter++
}
__modules[current_module] = ${is_async ? 'await (async' : '('} () => {
try {
const __exports = {};
${codegen(parse_result.modules[m], {module: m})};
current_call.ok = true
return __exports
} catch(error) {
current_call.ok = false
current_call.error = error
}
})()
current_call.children = children
if(!current_call.ok) {
return finish()
}
`
)
.join('')
+
`
return finish()
}
return {
run,
run_and_find_call,
expand_calltree_node,
find_call,
}
`
const actions = new (globalThis.run_window.Function)(
'external_imports',
'on_deferred_call',
'calltree_changed_token',
codestring
)(
/* external_imports */
external_imports == null
? null
: map_object(external_imports, (name, {module}) => module),
/* on_deferred_call */
(call, calltree_changed_token, logs) => {
return on_deferred_call(
assign_code(parse_result.modules, call),
calltree_changed_token,
logs,
)
},
/* calltree_changed_token */
calltree_changed_token
)
const calltree_actions = {
expand_calltree_node: (node) => {
const expanded = actions.expand_calltree_node(node)
return assign_code(parse_result.modules, expanded)
},
find_call: (calltree, location) => {
const result = actions.find_call(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,
}
}
}
const result = location == null
? actions.run()
: actions.run_and_find_call(location)
const make_result = result => ({
modules: result.modules,
calltree: assign_code(parse_result.modules, result.calltree),
call: result.call,
logs: result.logs,
calltree_actions,
})
return is_async
? result.then(make_result)
: make_result(result)
}
// TODO: assign_code: benchmark and use imperative version for perf?
const assign_code = (modules, call) => {
if(call.toplevel) {

0
src/runtime.js Normal file
View File