Files
leporello-js/src/eval.js

1442 lines
42 KiB
JavaScript
Raw Normal View History

import {
zip,
stringify,
map_object,
filter_object,
} from './utils.js'
2022-09-10 02:48:13 +08:00
import {
find_fn_by_location,
2023-11-17 12:44:12 +08:00
find_declaration,
find_leaf,
2022-09-10 02:48:13 +08:00
collect_destructuring_identifiers,
map_destructuring_identifiers,
map_tree,
} from './ast_utils.js'
2022-12-02 08:17:49 +08:00
import {has_toplevel_await} from './find_definitions.js'
2023-02-05 03:04:39 +08:00
// import runtime as external because it has non-functional code
2023-02-04 22:37:35 +08:00
// external
2023-11-17 12:44:12 +08:00
import {run, do_eval_expand_calltree_node, Multiversion} from './runtime.js'
2023-02-04 22:37:35 +08:00
2022-09-10 02:48:13 +08:00
// TODO: fix error messages. For example, "__fn is not a function"
/*
Generate code that records all function invocations.
for each invokation, record
- function that was called with its closed variables
- args
- return value (or exception)
- child invocations (deeper in the stack)
When calling function, we check if it is native or not (call it hosted). If
it is native, we record invocation at call site. If it is hosted, we dont
record invocation at call site, but function expression was wrapped in code
that records invocation. So its call will be recorded.
Note that it is not enough to record all invocation at call site, because
hosted function can be called by native functions (for example Array::map).
2022-11-15 20:53:16 +08:00
For each invocation, we can replay function body with metacircular interpreter,
2022-09-10 02:48:13 +08:00
collecting information for editor
*/
/*
type ToplevelCall = {
toplevel: true,
code,
ok,
value,
error,
children
}
type Call = {
args,
code,
fn,
ok,
value,
error,
children,
}
type Node = ToplevelCall | Call
*/
2023-02-05 03:04:39 +08:00
const codegen_function_expr = (node, node_cxt) => {
const do_codegen = n => codegen(n, node_cxt)
2022-09-10 02:48:13 +08:00
const args = node.function_args.children.map(do_codegen).join(',')
2023-05-16 00:04:53 +03:00
const decl = node.is_arrow
? `(${args}) => `
2023-06-10 20:17:04 +03:00
: `function(${args})`
2023-05-16 00:04:53 +03:00
2023-11-17 12:44:12 +08:00
// TODO gensym __obj, __fn, __call_id, __let_vars
const prolog =
'{const __call_id = __cxt.call_counter;'
+ (
node.has_versioned_let_vars
? 'const __let_vars = __cxt.let_vars;'
: ''
)
2023-05-16 00:04:53 +03:00
const call = (node.is_async ? 'async ' : '') + decl + (
2022-09-10 02:48:13 +08:00
(node.body.type == 'do')
? prolog + do_codegen(node.body) + '}'
: prolog + 'return ' + do_codegen(node.body) + '}'
2022-09-10 02:48:13 +08:00
)
2022-12-27 19:00:37 +08:00
const argscount = node
.function_args
.children
.find(a => a.type == 'destructuring_rest') == null
? node.function_args.children.length
: null
2022-09-10 02:48:13 +08:00
2023-02-05 03:04:39 +08:00
const location = `{index: ${node.index}, length: ${node.length}, module: '${node_cxt.module}'}`
2022-09-10 02:48:13 +08:00
// TODO first create all functions, then assign __closure, after everything
// is declared. See 'out of order decl' test. Currently we assign __closure
2023-01-15 22:13:10 +08:00
// on first call (see `__trace`)
2022-09-10 02:48:13 +08:00
const get_closure = `() => ({${[...node.closed].join(',')}})`
2023-02-04 22:37:35 +08:00
return `__trace(__cxt, ${call}, "${node.name}", ${argscount}, ${location}, \
2023-11-17 12:44:12 +08:00
${get_closure}, ${node.has_versioned_let_vars})`
2022-09-10 02:48:13 +08:00
}
2023-01-18 19:21:25 +08:00
/*
in v8 `foo().bar().baz()` gives error `foo(...).bar(...).baz is not a function`
*/
const not_a_function_error = node => node.string.replaceAll(
new RegExp('\\(.*\\)', 'g'),
'(...)'
)
2022-09-10 02:48:13 +08:00
// TODO if statically can prove that function is hosted, then do not codegen
2023-01-15 22:13:10 +08:00
// __trace
2023-02-05 03:04:39 +08:00
const codegen_function_call = (node, node_cxt) => {
2022-09-10 02:48:13 +08:00
2023-02-05 03:04:39 +08:00
const do_codegen = n => codegen(n, node_cxt)
2022-09-10 02:48:13 +08:00
const args = `[${node.args.children.map(do_codegen).join(',')}]`
2023-01-18 19:21:25 +08:00
const errormessage = not_a_function_error(node.fn)
2022-09-10 02:48:13 +08:00
let call
if(node.fn.type == 'member_access') {
const op = node.fn.is_optional_chaining ? '?.' : ''
// TODO gensym __obj, __fn
2022-12-31 20:40:59 +08:00
// We cant do `codegen(obj)[prop].bind(codegen(obj))` because codegen(obj)
// can be expr we dont want to eval twice. Use comma operator to perform
// assignments in expression context
return `(
__obj = ${do_codegen(node.fn.object)},
__fn = __obj${op}[${do_codegen(node.fn.property)}],
2023-02-04 22:37:35 +08:00
__trace_call(__cxt, __fn, __obj, ${args}, ${JSON.stringify(errormessage)})
2022-12-31 20:40:59 +08:00
)`
2022-09-10 02:48:13 +08:00
} else {
2023-02-04 22:37:35 +08:00
return `__trace_call(__cxt, ${do_codegen(node.fn)}, null, ${args}, \
2023-01-18 19:21:25 +08:00
${JSON.stringify(errormessage)})`
2022-09-10 02:48:13 +08:00
}
}
// 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
2023-11-17 12:44:12 +08:00
const codegen = (node, node_cxt) => {
const do_codegen = n => codegen(n, node_cxt)
if(node.type == 'identifier') {
2022-09-10 02:48:13 +08:00
2023-11-17 12:44:12 +08:00
if(node.definition == 'self' || node.definition == 'global') {
return node.value
}
2022-09-10 02:48:13 +08:00
2023-11-17 12:44:12 +08:00
if(node.definition.index == null) {
throw new Error('illegal state')
}
if(
!node_cxt.literal_identifiers &&
find_leaf(node_cxt.toplevel, node.definition.index).is_versioned_let_var
) {
return node.value + '.get()'
}
return node.value
} else if([
2022-09-10 02:48:13 +08:00
'number',
'string_literal',
'builtin_identifier',
'backtick_string',
].includes(node.type)){
return node.value
} else if(node.type == 'do'){
2023-05-16 00:04:53 +03:00
return [
// hoist function decls to the top
2023-10-26 10:29:57 +08:00
...node.children.filter(s => s.type == 'function_decl'),
...node.children.filter(s => s.type != 'function_decl'),
2023-05-16 00:04:53 +03:00
].reduce(
(result, stmt) => result + (do_codegen(stmt)) + ';\n',
''
)
2022-09-10 02:48:13 +08:00
} else if(node.type == 'return') {
return 'return ' + do_codegen(node.expr) + ';'
} else if(node.type == 'throw') {
return 'throw ' + do_codegen(node.expr) + ';'
} else if(node.type == 'if') {
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
2022-09-10 02:48:13 +08:00
? left
: 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);`
2022-09-10 02:48:13 +08:00
} else if(node.type == 'array_literal'){
return '[' + node.elements.map(c => do_codegen(c)).join(', ') + ']'
} else if(node.type == 'object_literal'){
const elements =
node.elements.map(el => {
2023-07-06 18:34:03 +03:00
if(el.type == 'object_spread'){
2022-09-10 02:48:13 +08:00
return do_codegen(el)
} else if(el.type == 'identifier') {
2023-11-17 12:44:12 +08:00
const value = do_codegen(el)
return el.value + ': ' + value
2022-09-10 02:48:13 +08:00
} else if(el.type == 'key_value_pair') {
return '[' + do_codegen(el.key.type == 'computed_property' ? el.key.expr : el.key) + ']'
2022-11-08 20:06:26 +08:00
+ ': (' + do_codegen(el.value, el) + ')'
2022-09-10 02:48:13 +08:00
} else {
throw new Error('unknown node type ' + el.type)
}
})
.join(',')
return '({' + elements + '})'
} else if(node.type == 'function_call'){
2023-02-05 03:04:39 +08:00
return codegen_function_call(node, node_cxt)
2022-09-10 02:48:13 +08:00
} else if(node.type == 'function_expr'){
2023-02-05 03:04:39 +08:00
return codegen_function_expr(node, node_cxt)
2022-09-10 02:48:13 +08:00
} 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)
+ ')'
)
2022-09-10 02:48:13 +08:00
return ''
+ '('
+ do_codegen(node.cond)
+ ')\n? '
+ branches[0]
2022-09-10 02:48:13 +08:00
+'\n: '
+ branches[1]
} else if(['const', 'let', 'assignment'].includes(node.type)) {
const prefix = node.type == 'assignment'
? ''
: node.type + ' '
return prefix + node
.children
2023-11-17 12:44:12 +08:00
.map(c => {
if(node.type == 'let') {
let lefthand, righthand
if(c.type == 'identifier') {
// let decl without assignment, like 'let x'
lefthand = c
if(!lefthand.is_versioned_let_var) {
return lefthand.value
}
} else if(c.type == 'decl_pair') {
lefthand = c.children[0], righthand = c.children[1]
if(lefthand.type != 'identifier') {
// TODO
// See comment for 'simple_decl_pair' in 'src/parse_js.js'
// Currently it is unreachable
throw new Error('illegal state')
}
} else {
throw new Error('illegal state')
}
if(lefthand.is_versioned_let_var) {
const name = lefthand.value
const symbol = symbol_for_let_var(lefthand)
return name + (
righthand == null
? ` = __let_vars['${symbol}'] = new __Multiversion(__cxt)`
: ` = __let_vars['${symbol}'] = new __Multiversion(__cxt, ${do_codegen(righthand)})`
)
} // Otherwise goes to the end of the func
}
if(node.type == 'assignment') {
const [lefthand, righthand] = c.children
if(lefthand.type != 'identifier') {
// TODO
// See comment for 'simple_decl_pair' in 'src/parse_js.js'
// Currently it is unreachable
throw new Error('TODO: illegal state')
}
const let_var = find_leaf(node_cxt.toplevel, lefthand.definition.index)
if(let_var.is_versioned_let_var){
return lefthand.value + '.set(' + do_codegen(righthand, node) + ')'
}
}
return do_codegen(c.children[0]) + ' = ' + do_codegen(c.children[1])
})
.join(',')
+ ';'
+ node.children.map(decl => {
2022-12-02 06:05:20 +08:00
if(
node.type != 'assignment'
&&
decl.type != 'identifier'
&&
decl.name_node.type == 'identifier'
2022-12-02 06:05:20 +08:00
&&
decl.expr.type == 'function_call'
2022-12-02 06:05:20 +08:00
) {
// deduce function name from variable it was assigned to if anonymous
// works for point-free programming, like
// const parse_statement = either(parse_import, parse_assignment, ...)
return `
if(
typeof(${decl.name_node.value}) == 'function'
&&
${decl.name_node.value}.name == 'anonymous'
) {
Object.defineProperty(
${decl.name_node.value},
"name",
{value: "${decl.name_node.value}"}
);
}
`
} else {
return ''
2022-09-10 02:48:13 +08:00
}
})
.join('')
2022-09-10 02:48:13 +08:00
} else if(node.type == 'member_access'){
return '('
+ do_codegen(node.object)
+ (node.is_optional_chaining ? ')?.[' : ')[')
+ do_codegen(node.property)
+ ']'
} else if(node.type == 'unary') {
2022-12-08 09:42:42 +08:00
if(node.operator == 'await') {
2023-02-04 22:37:35 +08:00
return `(await __do_await(__cxt, ${do_codegen(node.expr)}))`
2022-12-08 09:42:42 +08:00
} else {
return '(' + node.operator + ' ' + do_codegen(node.expr) + ')'
}
2022-09-10 02:48:13 +08:00
} else if(node.type == 'binary'){
return ''
+ do_codegen(node.args[0])
+ ' '
+ node.operator
+ ' '
+ do_codegen(node.args[1])
2023-07-06 18:34:03 +03:00
} else if(node.type == 'array_spread' || node.type == 'object_spread'){
2022-09-10 02:48:13 +08:00
return '...(' + do_codegen(node.expr) + ')'
} else if(node.type == 'new') {
2022-12-16 18:23:55 +08:00
const args = `[${node.args.children.map(do_codegen).join(',')}]`
2023-01-18 19:21:25 +08:00
const errormessage = not_a_function_error(node.constructor)
2023-02-04 22:37:35 +08:00
return `__trace_call(__cxt, ${do_codegen(node.constructor)}, null, ${args},\
2023-01-18 19:21:25 +08:00
${JSON.stringify(errormessage)}, true)`
2022-09-10 02:48:13 +08:00
} else if(node.type == 'grouping'){
return '(' + do_codegen(node.expr) + ')'
} else if(node.type == 'array_destructuring') {
return '[' + node.elements.map(n => do_codegen(n)).join(', ') + ']'
} else if(node.type == 'object_destructuring') {
return '{' + node.elements.map(n => do_codegen(n)).join(', ') + '}'
} else if(node.type == 'destructuring_rest') {
return '...' + do_codegen(node.name_node)
} else if(node.type == 'destructuring_default') {
return do_codegen(node.name_node) + ' = ' + do_codegen(node.expr);
} else if(node.type == 'destructuring_pair') {
return do_codegen(node.key) + ' : ' + do_codegen(node.value);
} else if(node.type == 'import') {
2023-06-05 12:08:20 +03:00
let names, def
if(node.default_import != null) {
def = `default: ${node.default_import},`
names = node.children.slice(1).map(n => n.value)
2022-12-01 02:07:55 +08:00
} else {
2023-06-05 12:08:20 +03:00
def = ''
names = node.children.map(n => n.value)
2022-12-01 02:07:55 +08:00
}
2023-06-05 12:08:20 +03:00
return `const {${def} ${names.join(',')}} = __cxt.modules['${node.full_import_path}'];`;
2022-09-10 02:48:13 +08:00
} else if(node.type == 'export') {
2023-06-05 12:08:20 +03:00
if(node.is_default) {
return `__cxt.modules['${node_cxt.module}'].default = ${do_codegen(node.children[0])};`
} else {
const identifiers = node
.binding
.children
.flatMap(n => collect_destructuring_identifiers(n.name_node))
2023-06-05 12:08:20 +03:00
.map(i => i.value)
return do_codegen(node.binding)
+
`Object.assign(__cxt.modules['${node_cxt.module}'], {${identifiers.join(',')}});`
}
2023-05-16 00:04:53 +03:00
} else if(node.type == 'function_decl') {
const expr = node.children[0]
2023-02-05 03:04:39 +08:00
return `const ${expr.name} = ${codegen_function_expr(expr, node_cxt)};`
2022-09-10 02:48:13 +08:00
} else {
console.error(node)
throw new Error('unknown node type: ' + node.type)
}
}
2023-02-05 02:08:53 +08:00
export const eval_modules = (
2022-11-15 16:58:15 +08:00
parse_result,
2022-10-25 02:29:59 +08:00
external_imports,
2022-12-02 04:31:16 +08:00
on_deferred_call,
2022-11-29 04:22:56 +08:00
calltree_changed_token,
2023-06-27 15:03:03 +03:00
io_trace,
2022-10-25 02:29:59 +08:00
location
2023-02-05 02:08:53 +08:00
) => {
2023-11-17 12:44:12 +08:00
// TODO gensym __cxt, __trace, __trace_call, __calltree_node_by_loc,
// __do_await, __Multiversion
2022-09-10 02:48:13 +08:00
2022-10-19 03:22:48 +08:00
// TODO bug if module imported twice, once as external and as regular
2022-12-02 08:17:49 +08:00
const is_async = has_toplevel_await(parse_result.modules)
2023-02-06 01:53:34 +08:00
const Function = is_async
2023-07-11 18:24:28 +03:00
? globalThis.app_window.eval('(async function(){})').constructor
: globalThis.app_window.Function
2023-02-06 01:53:34 +08:00
2023-11-17 12:44:12 +08:00
const module_fns = parse_result.sorted.map(module => {
const code = codegen(
parse_result.modules[module],
{
module,
toplevel: parse_result.modules[module],
}
)
return {
2023-02-06 01:53:34 +08:00
module,
// TODO refactor, instead of multiple args prefixed with '__', pass
// single arg called `runtime`
2023-02-06 01:53:34 +08:00
fn: new Function(
'__cxt',
2023-11-17 12:44:12 +08:00
'__let_vars',
'__calltree_node_by_loc',
2023-02-06 01:53:34 +08:00
'__trace',
'__trace_call',
'__do_await',
'__save_ct_node_for_path',
2023-11-17 12:44:12 +08:00
'__Multiversion',
/* 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*/
2023-11-17 12:44:12 +08:00
'const __call_id = __cxt.call_counter;' +
code
2023-02-06 01:53:34 +08:00
)
}
2023-11-17 12:44:12 +08:00
})
2023-02-06 01:53:34 +08:00
2023-02-04 22:37:35 +08:00
const cxt = {
2023-02-05 03:04:39 +08:00
modules: external_imports == null
? {}
: map_object(external_imports, (name, {module}) => module),
2023-02-04 22:37:35 +08:00
call_counter: 0,
children: null,
prev_children: null,
2023-02-05 03:04:39 +08:00
// TODO use native array for stack for perf? stack contains booleans
stack: new Array(),
is_recording_deferred_calls: false,
2023-02-04 22:37:35 +08:00
on_deferred_call: (call, calltree_changed_token, logs) => {
return on_deferred_call(
assign_code(parse_result.modules, call),
calltree_changed_token,
logs,
)
},
2023-02-05 02:08:53 +08:00
calltree_changed_token,
2023-02-05 03:04:39 +08:00
is_toplevel_call: true,
2023-07-11 18:24:28 +03:00
window: globalThis.app_window,
2023-02-04 22:37:35 +08:00
}
2023-06-27 15:03:03 +03:00
const result = run(module_fns, cxt, io_trace)
2023-02-04 22:37:35 +08:00
2023-06-10 18:34:58 +03:00
const make_result = result => {
const calltree = assign_code(parse_result.modules, result.calltree)
return {
modules: result.modules,
logs: result.logs,
eval_cxt: result.eval_cxt,
calltree,
calltree_node_by_loc: result.calltree_node_by_loc,
2023-06-27 15:03:03 +03:00
io_trace: result.eval_cxt.io_trace,
2023-06-10 18:34:58 +03:00
}
}
2023-02-04 22:37:35 +08:00
2023-02-06 01:53:34 +08:00
if(is_async) {
2023-06-05 15:53:08 +03:00
return result.__original_then(make_result)
2023-02-04 22:37:35 +08:00
} else {
2023-02-05 02:08:53 +08:00
return make_result(result)
2023-02-04 22:37:35 +08:00
}
}
2022-12-25 16:28:06 +08:00
2023-01-08 08:25:22 +08:00
2023-02-05 02:08:53 +08:00
export const eval_expand_calltree_node = (cxt, parse_result, node) => {
return assign_code(
parse_result.modules,
do_eval_expand_calltree_node(cxt, node)
)
2022-09-10 02:48:13 +08:00
}
// TODO: assign_code: benchmark and use imperative version for perf?
2022-11-15 16:58:15 +08:00
const assign_code = (modules, call) => {
2022-09-10 02:48:13 +08:00
if(call.toplevel) {
return {...call,
2022-11-15 16:58:15 +08:00
code: modules[call.module],
2022-09-10 02:48:13 +08:00
children: call.children && call.children.map(call => assign_code(modules, call)),
}
} else {
return {...call,
code: call.fn == null || call.fn.__location == null
? null
// TODO cache find_fn_by_location calls?
2022-09-10 02:48:13 +08:00
: find_fn_by_location(modules[call.fn.__location.module], call.fn.__location),
children: call.children && call.children.map(call => assign_code(modules, call)),
}
}
}
2023-02-05 02:08:53 +08:00
2022-09-10 02:48:13 +08:00
/* ------------- Metacircular interpreter ---------------------------- */
/*
Evaluate single function call
For each statement or expression, calculate if it was executed or not.
Add evaluation result to each statement or expression and put it to `result`
prop. Evaluate expressions from leaves to root, substituting function calls for
already recorded results.
Add `result` prop to each local variable.
Eval statements from top to bottom, selecting effective if branch and stopping
on `return` and `throw`. When descending to nested blocks, take scope into
account
*/
// Workaround with statement forbidden in strict mode (imposed by ES6 modules)
// Also currently try/catch is not implemented TODO
2023-06-22 15:49:12 +03:00
export const eval_codestring = (codestring, scope) =>
2023-07-11 18:24:28 +03:00
// Note that we eval code in context of app_window
(new (globalThis.app_window.Function)('codestring', 'scope',
2023-06-22 15:49:12 +03:00
// Make a copy of `scope` to not mutate it with assignments
`
try {
return {ok: true, value: eval('with({...scope}){' + codestring + '}')}
} catch(error) {
2023-10-26 12:25:35 +08:00
return {ok: false, error, is_error_origin: true}
2023-06-22 15:49:12 +03:00
}
`
))(codestring, scope)
2022-09-10 02:48:13 +08:00
2023-06-21 13:50:01 +03:00
const get_args_scope = (fn_node, args, closure) => {
2022-09-10 02:48:13 +08:00
const arg_names =
collect_destructuring_identifiers(fn_node.function_args)
.map(i => i.value)
2023-11-17 12:44:12 +08:00
const destructuring = fn_node
.function_args
.children.map(n => codegen(n, {literal_identifiers: true}))
.join(',')
2022-09-10 02:48:13 +08:00
/*
// TODO gensym __args. Or
new Function(`
return ({foo, bar}) => ({foo, bar})
`)(args)
to avoid gensym
*/
const codestring = `(([${destructuring}]) => [${arg_names.join(',')}])(__args)`
2023-06-21 13:50:01 +03:00
const {ok, value, error} = eval_codestring(codestring, {
...closure,
__args: args,
})
2022-09-10 02:48:13 +08:00
if(!ok) {
// TODO show exact destructuring error
2023-10-26 12:25:35 +08:00
return {ok, error, is_error_origin: true}
2022-09-10 02:48:13 +08:00
} else {
return {
ok,
value: Object.fromEntries(
zip(
arg_names,
value,
)
),
}
}
}
2023-11-23 14:47:28 +08:00
const eval_binary_expr = (node, scope, callsleft, frame_cxt) => {
const {ok, children, calls, scope: nextscope} = eval_children(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
if(!ok) {
2023-11-17 12:44:12 +08:00
return {ok, children, calls, scope: nextscope}
2022-09-10 02:48:13 +08:00
}
const op = node.operator
const a = children[0].result.value
const b = children[1].result.value
const value = (new Function('a', 'b', ' return a ' + op + ' b'))(a, b)
2023-11-17 12:44:12 +08:00
return {ok, children, calls, value, scope: nextscope}
2022-09-10 02:48:13 +08:00
}
2023-11-17 12:44:12 +08:00
const is_symbol_for_let_var = symbol =>
symbol.startsWith('!')
const symbol_for_let_var = let_var_node =>
'!' + let_var_node.value + '_' + let_var_node.index
const symbol_for_closed_let_var = name =>
'!' + name + '_' + 'closed'
/*
For versioned let vars, any function call within expr can mutate let vars, so
it can change current scope and outer scopes. Since current scope and outer
scope can have let var with the same name, we need to address them with
different names. Prepend '!' symbol because it is not a valid js identifier,
so it will not be mixed with other variables
*/
2023-11-23 14:47:28 +08:00
const symbol_for_identifier = (node, frame_cxt) => {
2023-11-17 12:44:12 +08:00
if(node.definition == 'global') {
return node.value
}
2022-09-10 02:48:13 +08:00
2023-11-17 12:44:12 +08:00
const index = node.definition == 'self' ? node.index : node.definition.index
2023-11-23 14:47:28 +08:00
const declaration = find_declaration(frame_cxt.calltree_node.code, index)
2023-11-17 12:44:12 +08:00
const variable = node.definition == 'self'
? node
2023-11-23 14:47:28 +08:00
: find_leaf(frame_cxt.calltree_node.code, node.definition.index)
2023-11-17 12:44:12 +08:00
if(declaration == null) {
/*
Variable was declared in outer scope. Since there can be only one variable
with given name in outer scope, generate a name for it
*/
return symbol_for_closed_let_var(node.value)
}
if(declaration.type == 'let'){
return symbol_for_let_var(variable)
} else {
return node.value
}
}
2023-11-23 14:47:28 +08:00
const do_eval_frame_expr = (node, scope, callsleft, frame_cxt) => {
2023-11-17 12:44:12 +08:00
if(node.type == 'identifier') {
2023-11-17 13:02:58 +08:00
let value
2023-11-17 12:44:12 +08:00
if(node.definition == 'global') {
2023-11-17 13:02:58 +08:00
value = globalThis.app_window[node.value]
2023-11-17 12:44:12 +08:00
} else {
2023-11-23 14:47:28 +08:00
value = scope[symbol_for_identifier(node, frame_cxt)]
2023-11-17 13:02:58 +08:00
}
return {
ok: true,
value,
calls: callsleft,
scope,
2023-11-17 12:44:12 +08:00
}
} else if([
2022-09-10 02:48:13 +08:00
'builtin_identifier',
'number',
'string_literal',
'backtick_string',
].includes(node.type)){
// TODO exprs inside backtick string
2023-11-17 13:02:58 +08:00
// TODO for string literal and number, do not use eval
2023-11-17 12:44:12 +08:00
return {...eval_codestring(node.value, scope), calls: callsleft, scope}
2023-07-06 18:34:03 +03:00
} else if(node.type == 'array_spread') {
2023-11-23 14:47:28 +08:00
const result = eval_children(node, scope, callsleft, frame_cxt)
2023-07-06 18:34:03 +03:00
if(!result.ok) {
return result
}
const child = result.children[0]
if((typeof(child.result.value?.[Symbol.iterator])) == 'function') {
return result
} else {
return {
ok: false,
children: result.children,
calls: result.calls,
2023-11-17 12:44:12 +08:00
scope: result.scope,
2023-07-06 18:34:03 +03:00
error: new TypeError(child.string + ' is not iterable'),
2023-10-26 12:25:35 +08:00
is_error_origin: true,
2023-07-06 18:34:03 +03:00
}
}
2022-09-10 02:48:13 +08:00
} else if([
2023-07-06 18:34:03 +03:00
'object_spread',
2022-09-10 02:48:13 +08:00
'key_value_pair',
'computed_property'
].includes(node.type)) {
2023-11-23 14:47:28 +08:00
return eval_children(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
} else if(node.type == 'array_literal' || node.type == 'call_args'){
2023-11-23 14:47:28 +08:00
const {ok, children, calls, scope: nextscope} = eval_children(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
if(!ok) {
2023-11-17 12:44:12 +08:00
return {ok, children, calls, scope: nextscope}
2022-09-10 02:48:13 +08:00
}
const value = children.reduce(
(arr, el) => {
2023-07-06 18:34:03 +03:00
if(el.type == 'array_spread') {
2022-09-10 02:48:13 +08:00
return [...arr, ...el.children[0].result.value]
} else {
return [...arr, el.result.value]
}
},
[],
)
2023-11-17 12:44:12 +08:00
return {ok, children, calls, value, scope: nextscope}
2022-09-10 02:48:13 +08:00
} else if(node.type == 'object_literal'){
2023-11-23 14:47:28 +08:00
const {ok, children, calls, scope: nextscope} = eval_children(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
if(!ok) {
2023-11-17 12:44:12 +08:00
return {ok, children, calls, scope: nextscope}
2022-09-10 02:48:13 +08:00
}
const value = children.reduce(
(value, el) => {
2023-07-06 18:34:03 +03:00
if(el.type == 'object_spread'){
2022-09-10 02:48:13 +08:00
return {...value, ...el.children[0].result.value}
} else if(el.type == 'identifier') {
// TODO check that it works
return {...value, ...{[el.value]: el.result.value}}
} else if(el.type == 'key_value_pair') {
const [key, val] = el.children
let k
if(key.type == 'computed_property') {
k = key.children[0].result.value
} else {
k = key.result.value
}
return {
...value,
...{[k]: val.result.value},
}
} else {
throw new Error('unknown node type ' + el.type)
}
},
{}
)
2023-11-17 12:44:12 +08:00
return {ok, children, value, calls, scope: nextscope}
2022-12-16 18:23:55 +08:00
} else if(node.type == 'function_call' || node.type == 'new'){
2023-11-23 14:47:28 +08:00
const {ok, children, calls, scope: nextscope} = eval_children(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
if(!ok) {
2023-11-17 12:44:12 +08:00
return {ok: false, children, calls, scope: nextscope}
2022-09-10 02:48:13 +08:00
} else {
if(typeof(children[0].result.value) != 'function') {
return {
ok: false,
2023-11-23 14:47:28 +08:00
error: frame_cxt.calltree_node.error,
2023-10-26 12:25:35 +08:00
is_error_origin: true,
2022-09-10 02:48:13 +08:00
children,
2022-12-01 02:08:02 +08:00
calls,
2023-11-17 12:44:12 +08:00
scope: nextscope,
2022-09-10 02:48:13 +08:00
}
}
2023-11-17 12:44:12 +08:00
const [c, ...next_calls] = calls
2022-09-10 02:48:13 +08:00
if(c == null) {
throw new Error('illegal state')
}
2023-11-17 12:44:12 +08:00
2023-11-23 14:47:28 +08:00
const closure = frame_cxt.calltree_node.fn?.__closure
2023-11-17 12:44:12 +08:00
const closure_let_vars = closure == null
? null
: Object.fromEntries(
Object.entries(closure)
.filter(([k,value]) => value instanceof Multiversion)
.map(([k,value]) => [symbol_for_closed_let_var(k), value])
)
const let_vars = {
2023-11-23 14:47:28 +08:00
...frame_cxt.calltree_node.let_vars,
2023-11-17 12:44:12 +08:00
...closure_let_vars,
}
const changed_vars = filter_object(let_vars, (name, v) =>
v.last_version_number() >= c.id
)
const next_id = next_calls.length == 0
2023-11-23 14:47:28 +08:00
? frame_cxt.calltree_node.next_id
2023-11-17 12:44:12 +08:00
: next_calls[0].id
const updated_let_scope = map_object(changed_vars, (name, v) =>
/*
We can't just use c.next_id here because it will break in async
context
*/
v.get_version(next_id)
)
2022-09-10 02:48:13 +08:00
return {
ok: c.ok,
call: c,
value: c.value,
error: c.error,
2023-10-26 12:25:35 +08:00
is_error_origin: !c.ok,
2022-09-10 02:48:13 +08:00
children,
2023-11-17 12:44:12 +08:00
calls: next_calls,
scope: {...nextscope, ...updated_let_scope},
2022-09-10 02:48:13 +08:00
}
}
} else if(node.type == 'function_expr'){
// It will never be called, create empty function
// TODO use new Function constructor with code?
const fn_placeholder = Object.defineProperty(
() => {},
'name',
2022-12-02 06:05:20 +08:00
{value: node.name}
2022-09-10 02:48:13 +08:00
)
return {
ok: true,
value: fn_placeholder,
calls: callsleft,
2023-11-17 12:44:12 +08:00
scope,
2022-09-10 02:48:13 +08:00
children: node.children,
}
2023-05-16 00:04:53 +03:00
} else if(node.type == 'ternary') {
2023-11-17 12:44:12 +08:00
const {node: cond_evaled, calls: calls_after_cond, scope: scope_after_cond} = eval_frame_expr(
2022-09-10 02:48:13 +08:00
node.cond,
scope,
2023-01-18 19:21:25 +08:00
callsleft,
2023-11-23 14:47:28 +08:00
frame_cxt
2022-09-10 02:48:13 +08:00
)
const {ok, value} = cond_evaled.result
const branches = node.branches
if(!ok) {
return {
ok: false,
children: [cond_evaled, branches[0], branches[1]],
calls: calls_after_cond,
2023-11-17 12:44:12 +08:00
scope: scope_after_cond,
2022-09-10 02:48:13 +08:00
}
} else {
2023-11-17 12:44:12 +08:00
const {node: branch_evaled, calls: calls_after_branch, scope: scope_after_branch}
= eval_frame_expr(
2022-09-10 02:48:13 +08:00
branches[value ? 0 : 1],
2023-11-17 12:44:12 +08:00
scope_after_cond,
2023-01-18 19:21:25 +08:00
calls_after_cond,
2023-11-23 14:47:28 +08:00
frame_cxt
2022-09-10 02:48:13 +08:00
)
const children = value
? [cond_evaled, branch_evaled, branches[1]]
: [cond_evaled, branches[0], branch_evaled]
const ok = branch_evaled.result.ok
if(ok) {
2023-11-17 12:44:12 +08:00
return {ok, children, calls: calls_after_branch, scope: scope_after_branch,
value: branch_evaled.result.value}
2022-09-10 02:48:13 +08:00
} else {
2023-11-17 12:44:12 +08:00
return {ok, children, calls: calls_after_branch, scope: scope_after_branch}
2022-09-10 02:48:13 +08:00
}
}
} else if(node.type == 'member_access'){
2023-11-23 14:47:28 +08:00
const {ok, children, calls, scope: nextscope} = eval_children(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
if(!ok) {
2023-11-17 12:44:12 +08:00
return {ok: false, children, calls, scope: nextscope}
2022-09-10 02:48:13 +08:00
}
const [obj, prop] = children
const codestring = node.is_optional_chaining ? 'obj?.[prop]' : 'obj[prop]'
// TODO do not use eval here
return {
...eval_codestring(codestring, {
obj: obj.result.value,
prop: prop.result.value,
}),
children,
calls,
2023-11-17 12:44:12 +08:00
scope: nextscope
2022-09-10 02:48:13 +08:00
}
} else if(node.type == 'unary') {
2023-11-23 14:47:28 +08:00
const {ok, children, calls, scope: nextscope} = eval_children(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
if(!ok) {
2023-11-17 12:44:12 +08:00
return {ok: false, children, calls, scope: nextscope}
2022-09-10 02:48:13 +08:00
} else {
const expr = children[0]
2023-10-26 12:25:35 +08:00
let ok, value, error, is_error_origin
2022-09-10 02:48:13 +08:00
if(node.operator == '!') {
2022-12-07 05:06:15 +08:00
ok = true
2022-09-10 02:48:13 +08:00
value = !expr.result.value
} else if(node.operator == 'typeof') {
2022-12-07 05:06:15 +08:00
ok = true
2022-09-10 02:48:13 +08:00
value = typeof(expr.result.value)
2022-12-07 05:52:13 +08:00
} else if(node.operator == '-') {
2022-12-07 05:42:33 +08:00
ok = true
2022-12-07 05:52:13 +08:00
value = - expr.result.value
2022-12-02 04:13:32 +08:00
} else if(node.operator == 'await') {
2023-07-11 18:24:28 +03:00
if(expr.result.value instanceof globalThis.app_window.Promise) {
2022-12-07 05:06:15 +08:00
const status = expr.result.value.status
if(status == null) {
// Promise must be already resolved
throw new Error('illegal state')
} else {
ok = status.ok
error = status.error
value = status.value
2023-10-26 12:25:35 +08:00
is_error_origin = !ok
2022-12-07 05:06:15 +08:00
}
} else {
ok = true
value = expr.result.value
}
2022-09-10 02:48:13 +08:00
} else {
throw new Error('unknown op')
}
2023-11-17 12:44:12 +08:00
return {ok, children, calls, value, error, is_error_origin, scope: nextscope}
2022-09-10 02:48:13 +08:00
}
} else if(node.type == 'binary' && !['&&', '||', '??'].includes(node.operator)){
2023-11-23 14:47:28 +08:00
return eval_binary_expr(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
} else if(node.type == 'binary' && ['&&', '||', '??'].includes(node.operator)){
2023-11-17 12:44:12 +08:00
const {node: left_evaled, calls, scope: nextscope} = eval_frame_expr(
2022-09-10 02:48:13 +08:00
node.children[0],
scope,
2023-01-18 19:21:25 +08:00
callsleft,
2023-11-23 14:47:28 +08:00
frame_cxt
2022-09-10 02:48:13 +08:00
)
const {ok, value} = left_evaled.result
if(
!ok
||
(node.operator == '&&' && !value)
||
(node.operator == '||' && value)
||
(node.operator == '??' && value != null)
) {
return {
ok,
value,
children: [left_evaled, node.children[1]],
calls,
2023-11-17 12:44:12 +08:00
scope: nextscope,
2022-09-10 02:48:13 +08:00
}
} else {
2023-11-23 14:47:28 +08:00
return eval_binary_expr(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
}
} else if(node.type == 'grouping'){
2023-11-23 14:47:28 +08:00
const {ok, children, calls, scope: nextscope} = eval_children(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
if(!ok) {
2023-11-17 12:44:12 +08:00
return {ok, children, calls, scope: nextscope}
2022-09-10 02:48:13 +08:00
} else {
2023-11-17 12:44:12 +08:00
return {ok: true, children, calls, scope: nextscope, value: children[0].result.value}
2022-09-10 02:48:13 +08:00
}
} else {
console.error(node)
throw new Error('unknown node type: ' + node.type)
}
}
2023-11-23 14:47:28 +08:00
const eval_children = (node, scope, calls, frame_cxt) => {
2022-09-10 02:48:13 +08:00
return node.children.reduce(
2023-11-17 12:44:12 +08:00
({ok, children, calls, scope}, child) => {
let next_child, next_ok, next_calls, next_scope
2022-09-10 02:48:13 +08:00
if(!ok) {
2023-05-16 00:04:53 +03:00
next_child = child
next_ok = false
next_calls = calls
2023-11-17 12:44:12 +08:00
next_scope = scope
2022-09-10 02:48:13 +08:00
} else {
2023-11-23 14:47:28 +08:00
const result = eval_frame_expr(child, scope, calls, frame_cxt)
2023-05-16 00:04:53 +03:00
next_child = result.node
next_calls = result.calls
next_ok = next_child.result.ok
2023-11-17 12:44:12 +08:00
next_scope = result.scope
}
return {
ok: next_ok,
children: [...children, next_child],
calls: next_calls,
scope: next_scope,
2022-09-10 02:48:13 +08:00
}
},
2023-11-17 12:44:12 +08:00
{ok: true, children: [], calls, scope}
2022-09-10 02:48:13 +08:00
)
}
2023-11-23 14:47:28 +08:00
const eval_frame_expr = (node, scope, callsleft, frame_cxt) => {
2023-11-17 12:44:12 +08:00
const {ok, error, is_error_origin, value, call, children, calls, scope: nextscope}
2023-11-23 14:47:28 +08:00
= do_eval_frame_expr(node, scope, callsleft, frame_cxt)
2022-09-10 02:48:13 +08:00
if(callsleft != null && calls == null) {
// TODO remove it, just for debug
console.error('node', node)
throw new Error('illegal state')
}
return {
node: {
...node,
children,
// Add `call` for step_into
2023-10-26 12:25:35 +08:00
result: {ok, error, value, call, is_error_origin}
2022-09-10 02:48:13 +08:00
},
2023-11-17 12:44:12 +08:00
scope: nextscope,
2022-09-10 02:48:13 +08:00
calls,
}
}
2023-11-23 14:47:28 +08:00
const eval_decl_pair = (s, scope, calls, frame_cxt) => {
if(s.type != 'decl_pair') {
throw new Error('illegal state')
}
// TODO default values for destructuring can be function calls
2023-11-17 12:44:12 +08:00
const {node, calls: next_calls, scope: scope_after_expr}
2023-11-23 14:47:28 +08:00
= eval_frame_expr(s.expr, scope, calls, frame_cxt)
const s_expr_evaled = {...s, children: [s.name_node, node]}
if(!node.result.ok) {
return {
ok: false,
node: {...s_expr_evaled, result: {ok: false}},
scope,
calls: next_calls,
2023-11-17 12:44:12 +08:00
scope: scope_after_expr,
}
}
const name_nodes = collect_destructuring_identifiers(s.name_node)
const names = name_nodes.map(n => n.value)
2023-11-17 12:44:12 +08:00
const destructuring = codegen(s.name_node, {literal_identifiers: true})
2023-11-17 13:02:58 +08:00
// TODO if destructuring is just one id, then do not use eval_codestring
// TODO unique name for __value (gensym)
const codestring = `
const ${destructuring} = __value;
({${names.join(',')}});
`
2023-11-17 12:44:12 +08:00
const {ok, value: values, error} = eval_codestring(
codestring,
2023-11-17 12:44:12 +08:00
{...scope_after_expr, __value: node.result.value}
)
2023-11-17 12:44:12 +08:00
const next_scope = Object.fromEntries(name_nodes.map(n =>
2023-11-23 14:47:28 +08:00
[symbol_for_identifier(n, frame_cxt), values[n.value]]
2023-11-17 12:44:12 +08:00
))
// TODO fine-grained destructuring error, only for identifiers that failed
// destructuring
const name_node_with_result = map_tree(
map_destructuring_identifiers(
s.name_node,
node => ({...node,
result: {
ok,
error: ok ? null : error,
2023-11-23 14:47:28 +08:00
value: !ok ? null : next_scope[symbol_for_identifier(node, frame_cxt)],
2022-09-10 02:48:13 +08:00
}
})
),
n => n.result == null
? {...n, result: {ok}}
: n
)
const s_evaled = {...s_expr_evaled, children: [
name_node_with_result,
s_expr_evaled.children[1],
]}
if(!ok) {
return {
ok: false,
// TODO assign error to node where destructuring failed, not to every node
node: {...s_evaled, result: {ok, error, is_error_origin: true}},
2023-11-17 12:44:12 +08:00
scope: scope_after_expr,
calls,
}
2022-09-10 02:48:13 +08:00
}
return {
ok: true,
node: {...s_evaled, result: node.result},
2023-11-17 12:44:12 +08:00
scope: {...scope_after_expr, ...next_scope},
calls: next_calls,
}
2022-09-10 02:48:13 +08:00
}
2023-11-23 14:47:28 +08:00
const eval_statement = (s, scope, calls, frame_cxt) => {
2022-09-10 02:48:13 +08:00
if(s.type == 'do') {
const stmt = s
2023-05-16 00:04:53 +03:00
// hoist function decls to the top
const function_decls = s.children
2023-05-16 00:04:53 +03:00
.filter(s => s.type == 'function_decl')
.map(s => {
2023-11-23 14:47:28 +08:00
const {ok, children, calls: next_calls} = eval_children(s, scope, calls, frame_cxt)
2023-05-16 00:04:53 +03:00
if(!ok) {
// Function decl can never fail
throw new Error('illegal state')
}
if(next_calls != calls) {
throw new Error('illegal state')
}
return {...s, children, result: {ok: true}}
})
const hoisted_functions_scope = Object.fromEntries(
function_decls.map(decl =>
[decl.children[0].name, decl.children[0].result.value]
)
)
const initial_scope = {...scope, ...hoisted_functions_scope}
2023-11-17 12:44:12 +08:00
const {ok, returned, children, calls: next_calls, scope: next_scope} =
s.children.reduce( ({ok, returned, children, scope, calls}, s) => {
2022-09-10 02:48:13 +08:00
if(returned || !ok) {
2023-11-17 12:44:12 +08:00
return {ok, returned, scope, calls, children: [...children, s]}
2023-05-16 00:04:53 +03:00
} else if(s.type == 'function_decl') {
const node = function_decls.find(decl => decl.index == s.index)
return {
ok: true,
returned: false,
node,
scope,
calls,
2023-10-26 10:29:57 +08:00
children: [...children, node],
2023-05-16 00:04:53 +03:00
}
2022-09-10 02:48:13 +08:00
} else {
const {
ok,
returned,
node,
scope: nextscope,
calls: next_calls,
2023-11-23 14:47:28 +08:00
} = eval_statement(s, scope, calls, frame_cxt)
2022-09-10 02:48:13 +08:00
return {
ok,
returned,
scope: nextscope,
calls: next_calls,
2023-10-26 10:29:57 +08:00
children: [...children, node],
2022-09-10 02:48:13 +08:00
}
}
},
2023-11-17 12:44:12 +08:00
{ok: true, returned: false, children: [], scope: initial_scope, calls}
)
const let_vars_scope = filter_object(next_scope, (k, v) =>
is_symbol_for_let_var(k)
2022-09-10 02:48:13 +08:00
)
return {
ok,
2023-11-17 12:44:12 +08:00
node: {...s, children: children, result: {ok}},
scope: {...scope, ...let_vars_scope},
2022-09-10 02:48:13 +08:00
returned,
2023-05-16 00:04:53 +03:00
calls: next_calls,
2022-09-10 02:48:13 +08:00
}
} else if(['let', 'const', 'assignment'].includes(s.type)) {
const stmt = s
2022-09-10 02:48:13 +08:00
2023-11-17 12:44:12 +08:00
const initial = {ok: true, children: [], scope, calls}
2022-09-10 02:48:13 +08:00
2023-11-17 12:44:12 +08:00
const {ok, children, calls: next_calls, scope: next_scope} = s.children.reduce(
({ok, children, scope, calls}, s) => {
if(!ok) {
return {ok, scope, calls, children: [...children, s]}
}
if(stmt.type == 'let' && s.type == 'identifier') {
const node = {...s, result: {ok: true}}
2023-10-30 15:13:29 +08:00
return {
ok,
children: [...children, node],
2023-11-23 14:47:28 +08:00
scope: {...scope, [symbol_for_identifier(s, frame_cxt)]: undefined},
2023-10-30 15:13:29 +08:00
calls
}
}
const {
ok: next_ok,
node,
scope: nextscope,
calls: next_calls,
2023-11-23 14:47:28 +08:00
} = eval_decl_pair(s, scope, calls, frame_cxt)
return {
ok: next_ok,
scope: nextscope,
calls: next_calls,
children: [...children, node],
}
},
initial
2022-09-10 02:48:13 +08:00
)
return {
ok,
node: {...s, children, result: {ok}},
2022-09-10 02:48:13 +08:00
scope: {...scope, ...next_scope},
calls: next_calls,
}
} else if(s.type == 'return') {
2023-11-17 12:44:12 +08:00
const {node, calls: next_calls, scope: nextscope} =
2023-11-23 14:47:28 +08:00
eval_frame_expr(s.expr, scope, calls, frame_cxt)
2022-09-10 02:48:13 +08:00
return {
ok: node.result.ok,
returned: node.result.ok,
node: {...s, children: [node], result: {ok: node.result.ok}},
2023-11-17 12:44:12 +08:00
scope: nextscope,
2022-09-10 02:48:13 +08:00
calls: next_calls,
}
} else if(s.type == 'export') {
2023-01-18 19:21:25 +08:00
const {ok, scope: nextscope, calls: next_calls, node}
2023-11-23 14:47:28 +08:00
= eval_statement(s.binding, scope, calls, frame_cxt)
2022-09-10 02:48:13 +08:00
return {
ok,
scope: nextscope,
calls: next_calls,
node: {...s, children: [node], result: {ok: node.result.ok}}
}
} else if(s.type == 'import') {
2023-11-23 14:47:28 +08:00
const module = frame_cxt.modules[s.full_import_path]
2023-06-05 12:08:20 +03:00
const children = s.children.map((imp, i) => (
{...imp,
result: {
ok: true,
value: imp.definition.is_default
? module['default']
: module[imp.value]
}
2022-09-10 02:48:13 +08:00
}
))
2023-06-05 12:08:20 +03:00
const imported_scope = Object.fromEntries(
children.map(imp => [imp.value, imp.result.value])
)
2022-09-10 02:48:13 +08:00
return {
ok: true,
scope: {...scope, ...imported_scope},
calls,
node: {...s, children, result: {ok: true}}
}
} else if(s.type == 'if') {
2023-11-17 12:44:12 +08:00
const {node, calls: next_calls, scope: scope_after_cond} =
2023-11-23 14:47:28 +08:00
eval_frame_expr(s.cond, scope, calls, frame_cxt)
2022-09-10 02:48:13 +08:00
if(!node.result.ok) {
return {
ok: false,
node: {...s, children: [node, ...s.branches], result: {ok: false}},
scope,
calls: next_calls,
2023-11-17 12:44:12 +08:00
scope: scope_after_cond,
2022-09-10 02:48:13 +08:00
}
}
if(s.branches.length == 1) {
// if without else
if(node.result.value) {
// Execute branch
const {
node: evaled_branch,
returned,
scope: next_scope,
calls: next_calls2,
} = eval_statement(
s.branches[0],
2023-11-17 12:44:12 +08:00
scope_after_cond,
2022-09-10 02:48:13 +08:00
next_calls,
2023-11-23 14:47:28 +08:00
frame_cxt
2022-09-10 02:48:13 +08:00
)
return {
ok: evaled_branch.result.ok,
returned,
node: {...s,
children: [node, evaled_branch],
result: {ok: evaled_branch.result.ok}
},
scope: next_scope,
calls: next_calls2,
}
} else {
// Branch is not executed
return {
ok: true,
node: {...s, children: [node, s.branches[0]], result: {ok: true}},
scope,
calls: next_calls,
}
}
} else {
// if with else
const active_branch = node.result.value ? s.branches[0] : s.branches[1]
const {
node: evaled_branch,
returned,
scope: next_scope,
calls: next_calls2
} = eval_statement(
active_branch,
2023-11-17 12:44:12 +08:00
scope_after_cond,
2022-09-10 02:48:13 +08:00
next_calls,
2023-11-23 14:47:28 +08:00
frame_cxt,
2022-09-10 02:48:13 +08:00
)
const children = node.result.value
? [node, evaled_branch, s.branches[1]]
: [node, s.branches[0], evaled_branch]
return {
ok: evaled_branch.result.ok,
returned,
node: {...s, children, result: {ok: evaled_branch.result.ok}},
scope: next_scope,
calls: next_calls2,
}
}
} else if(s.type == 'throw') {
2023-11-17 12:44:12 +08:00
const {node, calls: next_calls, scope: next_scope} =
2023-11-23 14:47:28 +08:00
eval_frame_expr(s.expr, scope, calls, frame_cxt)
2022-09-10 02:48:13 +08:00
return {
ok: false,
node: {...s,
children: [node],
result: {
ok: false,
2023-10-26 12:25:35 +08:00
is_error_origin: node.result.ok,
2022-09-10 02:48:13 +08:00
error: node.result.ok ? node.result.value : null,
}
},
2023-11-17 12:44:12 +08:00
scope: next_scope,
2022-09-10 02:48:13 +08:00
calls: next_calls,
}
} else {
// stmt type is expression
2023-11-23 14:47:28 +08:00
const {node, calls: next_calls, scope: next_scope} = eval_frame_expr(s, scope, calls, frame_cxt)
2022-09-10 02:48:13 +08:00
return {
ok: node.result.ok,
node,
2023-11-17 12:44:12 +08:00
scope: next_scope,
2022-09-10 02:48:13 +08:00
calls: next_calls,
}
}
}
2022-11-15 20:53:16 +08:00
export const eval_frame = (calltree_node, modules) => {
2022-09-10 02:48:13 +08:00
if(calltree_node.has_more_children) {
throw new Error('illegal state')
}
const node = calltree_node.code
2023-11-23 14:47:28 +08:00
const frame_cxt = {calltree_node, modules}
2022-09-10 02:48:13 +08:00
if(node.type == 'do') {
2023-11-17 12:44:12 +08:00
// eval module toplevel
2022-09-10 02:48:13 +08:00
return eval_statement(
node,
{},
calltree_node.children,
2023-11-23 14:47:28 +08:00
frame_cxt,
2022-09-10 02:48:13 +08:00
).node
} else {
// TODO default values for destructuring can be function calls
2023-11-17 12:44:12 +08:00
const closure = map_object(calltree_node.fn.__closure, (_key, value) => {
return value instanceof Multiversion
? value.get_version(calltree_node.id)
: value
})
2023-06-21 13:50:01 +03:00
const args_scope_result = get_args_scope(
node,
calltree_node.args,
2023-11-17 12:44:12 +08:00
closure
2023-06-21 13:50:01 +03:00
)
2022-09-10 02:48:13 +08:00
// TODO fine-grained destructuring error, only for identifiers that
// failed destructuring
const function_args_with_result = {
...node.function_args,
result: args_scope_result,
children: node.function_args.children.map(arg =>
map_tree(
map_destructuring_identifiers(
arg,
a => ({...a,
result: {
ok: args_scope_result.ok,
2023-10-26 12:25:35 +08:00
error: args_scope_result.ok ? null : args_scope_result.error,
is_error_origin: !args_scope_result.ok,
2022-09-10 02:48:13 +08:00
value: !args_scope_result.ok ? null : args_scope_result.value[a.value],
}
})
),
n => n.result == null
? {...n, result: {ok: args_scope_result.ok}}
: n
)
)
}
const body = node.body
if(!args_scope_result.ok) {
return {...node,
result: {ok: false},
children: [function_args_with_result, body],
}
}
2023-11-17 12:44:12 +08:00
const closure_scope = Object.fromEntries(
Object.entries(closure).map(([key, value]) => {
return [
symbol_for_closed_let_var(key),
value,
]
})
)
2022-09-10 02:48:13 +08:00
2023-11-17 12:44:12 +08:00
const scope = {...closure_scope, ...args_scope_result.value}
2022-09-10 02:48:13 +08:00
let nextbody
if(body.type == 'do') {
nextbody = eval_statement(
body,
scope,
calltree_node.children,
2023-11-23 14:47:28 +08:00
frame_cxt,
2022-09-10 02:48:13 +08:00
).node
} else {
2023-11-23 14:47:28 +08:00
nextbody = eval_frame_expr(body, scope, calltree_node.children, frame_cxt)
2022-09-10 02:48:13 +08:00
.node
}
return {...node,
result: {ok: nextbody.result.ok},
children: [function_args_with_result, nextbody],
}
}
}