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 {is_eq, find_error_origin_node} from './ast_utils.js'
import {find_node, find_leaf, ancestry_inc} from './ast_utils.js' import {find_node, find_leaf, ancestry_inc} from './ast_utils.js'
import {color} from './color.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 => ({ export const pp_calltree = tree => ({
id: tree.id, id: tree.id,
@@ -176,7 +176,11 @@ const replace_calltree_node = (root, node, replacement) => {
const expand_calltree_node = (state, node) => { const expand_calltree_node = (state, node) => {
if(node.has_more_children) { 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 { return {
state: {...state, state: {...state,
calltree: replace_calltree_node(state.calltree, node, next_node) calltree: replace_calltree_node(state.calltree, node, next_node)
@@ -594,7 +598,11 @@ export const find_call = (state, index) => {
if(call != null) { if(call != null) {
if(call.has_more_children) { 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( next_calltree = replace_calltree_node(
state.calltree, state.calltree,
call, call,
@@ -605,7 +613,12 @@ export const find_call = (state, index) => {
next_calltree = state.calltree next_calltree = state.calltree
} }
} else { } 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) { if(find_result == null) {
return add_calltree_node_by_loc( return add_calltree_node_by_loc(
// Remove active_calltree_node // Remove active_calltree_node

View File

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

View File

@@ -15,6 +15,10 @@ import {
import {has_toplevel_await} from './find_definitions.js' 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" // 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`) // on first call (see `__trace`)
const get_closure = `() => ({${[...node.closed].join(',')}})` 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 `( return `(
__obj = ${do_codegen(node.fn.object)}, __obj = ${do_codegen(node.fn.object)},
__fn = __obj${op}[${do_codegen(node.fn.property)}], __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 { } else {
return `__trace_call(${do_codegen(node.fn)}, null, ${args}, \ return `__trace_call(__cxt, ${do_codegen(node.fn)}, null, ${args}, \
${JSON.stringify(errormessage)})` ${JSON.stringify(errormessage)})`
} }
} }
// TODO rename cxt, to not confuse with another cxt
const codegen = (node, cxt, parent) => { const codegen = (node, cxt, parent) => {
const do_codegen = (n, parent) => codegen(n, 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') { } else if(node.type == 'unary') {
if(node.operator == 'await') { if(node.operator == 'await') {
return `(await __do_await(${do_codegen(node.expr)}))` return `(await __do_await(__cxt, ${do_codegen(node.expr)}))`
} else { } else {
return '(' + node.operator + ' ' + do_codegen(node.expr) + ')' return '(' + node.operator + ' ' + do_codegen(node.expr) + ')'
} }
@@ -241,7 +247,7 @@ const codegen = (node, cxt, parent) => {
} else if(node.type == 'new') { } else if(node.type == 'new') {
const args = `[${node.args.children.map(do_codegen).join(',')}]` const args = `[${node.args.children.map(do_codegen).join(',')}]`
const errormessage = not_a_function_error(node.constructor) 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)` ${JSON.stringify(errormessage)}, true)`
} else if(node.type == 'grouping'){ } else if(node.type == 'grouping'){
return '(' + do_codegen(node.expr) + ')' return '(' + do_codegen(node.expr) + ')'
@@ -260,14 +266,14 @@ ${JSON.stringify(errormessage)}, true)`
if(names.length == 0) { if(names.length == 0) {
return '' return ''
} else { } 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') { } else if(node.type == 'export') {
const identifiers = collect_destructuring_identifiers(node.binding.name_node) const identifiers = collect_destructuring_identifiers(node.binding.name_node)
.map(i => i.value) .map(i => i.value)
return do_codegen(node.binding) return do_codegen(node.binding)
+ +
`Object.assign(__exports, {${identifiers.join(',')}});` `Object.assign(__cxt.modules[cxt.module], {${identifiers.join(',')}});`
} else if(node.type == 'function_decl') { } else if(node.type == 'function_decl') {
const expr = node.children[0] const expr = node.children[0]
return `const ${expr.name} = ${codegen_function_expr(expr, cxt)};` return `const ${expr.name} = ${codegen_function_expr(expr, cxt)};`
@@ -284,34 +290,118 @@ export const eval_modules = (
calltree_changed_token, calltree_changed_token,
location 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 // TODO bug if module imported twice, once as external and as regular
const is_async = has_toplevel_await(parse_result.modules) const is_async = has_toplevel_await(parse_result.modules)
const codestring = ` /*
TODO remove
let children, prev_children 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 // 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 const Function = is_async
let found_call ? globalThis.run_window.eval('(async function(){})').constructor
: globalThis.run_window.Function
let is_recording_deferred_calls let calltree
let is_toplevel_call = true
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 promise_then = Promise.prototype.then
@@ -343,21 +433,19 @@ export const eval_modules = (
make_callback(on_reject, false), make_callback(on_reject, false),
) )
} }
} }
function remove_promise_patch() { const remove_promise_patch = cxt => {
Promise.prototype.then = promise_then Promise.prototype.then = promise_then
} }
apply_promise_patch() 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
} }
} }
const do_expand_calltree_node = node => { const do_expand_calltree_node = node => {
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]
@@ -372,9 +460,9 @@ export const eval_modules = (
has_more_children: false, has_more_children: false,
} }
} }
} }
const expand_calltree_node = (node) => { export const eval_expand_calltree_node = (parse_result, node) => {
is_recording_deferred_calls = false is_recording_deferred_calls = false
children = null children = null
try { try {
@@ -387,38 +475,11 @@ export const eval_modules = (
// do nothing. Exception was caught and recorded inside '__trace' // do nothing. Exception was caught and recorded inside '__trace'
} }
is_recording_deferred_calls = true is_recording_deferred_calls = true
return do_expand_calltree_node(node) return assign_code(parse_result.modules, 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)
}
}
/* /*
Try to find call of function with given 'location' Try to find call of function with given 'location'
Function is synchronous, because we recorded calltree nodes for all async Function is synchronous, because we recorded calltree nodes for all async
@@ -429,8 +490,8 @@ export const eval_modules = (
We dont rerun entire execution because we want find_call to be We dont rerun entire execution because we want find_call to be
synchronous for simplicity synchronous for simplicity
*/ */
const find_call = (calltree, location) => { export const eval_find_call = (cxt, parse_result, calltree, location) => {
// TODO remove // TODO remove
if(children != null) { if(children != null) {
throw new Error('illegal state') throw new Error('illegal state')
@@ -484,10 +545,19 @@ export const eval_modules = (
found_call = null found_call = null
is_recording_deferred_calls = true 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 // 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 // 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 // calls. Allocate array now, so we can have a reference to this array
@@ -511,9 +581,9 @@ export const eval_modules = (
} finally { } finally {
children = children_copy children = children_copy
} }
} }
const __trace = (fn, name, argscount, __location, get_closure) => { const __trace = (cxt, fn, name, argscount, __location, get_closure) => {
const result = (...args) => { const result = (...args) => {
if(result.__closure == null) { if(result.__closure == null) {
result.__closure = get_closure() result.__closure = get_closure()
@@ -607,9 +677,9 @@ export const eval_modules = (
Object.defineProperty(result, 'name', {value: name}) Object.defineProperty(result, 'name', {value: name})
result.__location = __location result.__location = __location
return result 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) { if(fn != null && fn.__location != null && !is_new) {
// Call will be traced, because tracing code is already embedded inside // Call will be traced, because tracing code is already embedded inside
// fn // fn
@@ -692,131 +762,6 @@ export const eval_modules = (
} }
children.push(call) children.push(call)
} }
}
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? // TODO: assign_code: benchmark and use imperative version for perf?

0
src/runtime.js Normal file
View File