2022-10-17 02:49:21 +08:00
|
|
|
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-18 23:23:47 +08:00
|
|
|
import {run, do_eval_expand_calltree_node, LetMultiversion} from './runtime/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-08-02 06:00:08 +03:00
|
|
|
|
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')
|
2023-08-02 06:00:08 +03:00
|
|
|
? 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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-02 06:00:08 +03: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') {
|
2023-08-02 06:00:08 +03:00
|
|
|
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
|
2023-08-02 06:00:08 +03:00
|
|
|
: 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'){
|
2023-08-02 06:00:08 +03:00
|
|
|
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? '
|
2023-08-02 06:00:08 +03:00
|
|
|
+ branches[0]
|
2022-09-10 02:48:13 +08:00
|
|
|
+'\n: '
|
2023-08-02 06:00:08 +03:00
|
|
|
+ branches[1]
|
2023-10-30 12:51:15 +08:00
|
|
|
} 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])
|
|
|
|
|
})
|
2023-10-30 12:51:15 +08:00
|
|
|
.join(',')
|
|
|
|
|
+ ';'
|
|
|
|
|
+ node.children.map(decl => {
|
2022-12-02 06:05:20 +08:00
|
|
|
if(
|
2023-10-30 12:51:15 +08:00
|
|
|
node.type != 'assignment'
|
|
|
|
|
&&
|
|
|
|
|
decl.type != 'identifier'
|
|
|
|
|
&&
|
|
|
|
|
decl.name_node.type == 'identifier'
|
2022-12-02 06:05:20 +08:00
|
|
|
&&
|
2023-10-30 12:51:15 +08:00
|
|
|
decl.expr.type == 'function_call'
|
2022-12-02 06:05:20 +08:00
|
|
|
) {
|
2023-10-30 12:51:15 +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
|
|
|
}
|
2023-10-30 12:51:15 +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 {
|
2023-10-30 12:51:15 +08:00
|
|
|
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,
|
2023-08-02 06:00:08 +03:00
|
|
|
// 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',
|
2023-08-02 06:00:08 +03:00
|
|
|
'__calltree_node_by_loc',
|
2023-02-06 01:53:34 +08:00
|
|
|
'__trace',
|
|
|
|
|
'__trace_call',
|
|
|
|
|
'__do_await',
|
2023-08-02 06:00:08 +03:00
|
|
|
'__save_ct_node_for_path',
|
2023-11-17 12:44:12 +08:00
|
|
|
'__Multiversion',
|
2023-08-02 06:00:08 +03:00
|
|
|
|
|
|
|
|
/* 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
|
|
|
}
|
2022-10-17 02:49:21 +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,
|
2023-11-23 14:51:10 +08:00
|
|
|
rt_cxt: result.rt_cxt,
|
2023-06-10 18:34:58 +03:00
|
|
|
calltree,
|
2023-08-02 06:00:08 +03:00
|
|
|
calltree_node_by_loc: result.calltree_node_by_loc,
|
2023-11-23 14:51:10 +08:00
|
|
|
io_trace: result.rt_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
|
|
|
}
|
|
|
|
|
|
2022-10-17 02:49:21 +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
|
2022-10-17 02:49:21 +08:00
|
|
|
// 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 16:35:03 +08:00
|
|
|
const eval_binary_expr = (node, eval_cxt, frame_cxt) => {
|
|
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = eval_children(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
if(!ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok, children, eval_cxt: next_eval_cxt}
|
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-23 16:35:03 +08:00
|
|
|
return {ok, children, value, eval_cxt: next_eval_cxt}
|
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 16:35:03 +08:00
|
|
|
const do_eval_frame_expr = (node, eval_cxt, 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 16:35:03 +08:00
|
|
|
value = eval_cxt.scope[symbol_for_identifier(node, frame_cxt)]
|
2023-11-17 13:02:58 +08:00
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
ok: true,
|
|
|
|
|
value,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt,
|
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-23 16:35:03 +08:00
|
|
|
return {...eval_codestring(node.value, eval_cxt.scope), eval_cxt}
|
2023-07-06 18:34:03 +03:00
|
|
|
} else if(node.type == 'array_spread') {
|
2023-11-23 16:35:03 +08:00
|
|
|
const result = eval_children(node, eval_cxt, 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,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: result.eval_cxt,
|
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 16:35:03 +08:00
|
|
|
return eval_children(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
} else if(node.type == 'array_literal' || node.type == 'call_args'){
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = eval_children(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
if(!ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok, children, eval_cxt: next_eval_cxt}
|
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-23 16:35:03 +08:00
|
|
|
return {ok, children, value, eval_cxt: next_eval_cxt}
|
2022-09-10 02:48:13 +08:00
|
|
|
} else if(node.type == 'object_literal'){
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = eval_children(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
if(!ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok, children, eval_cxt: next_eval_cxt}
|
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-23 16:35:03 +08:00
|
|
|
return {ok, children, value, eval_cxt: next_eval_cxt}
|
2022-12-16 18:23:55 +08:00
|
|
|
} else if(node.type == 'function_call' || node.type == 'new'){
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = eval_children(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
if(!ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok: false, children, eval_cxt: next_eval_cxt}
|
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,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
2023-11-24 14:45:17 +08:00
|
|
|
const calls = frame_cxt.calltree_node.children
|
|
|
|
|
const call = calls == null ? null : calls[next_eval_cxt.call_index]
|
|
|
|
|
if(call == null) {
|
2022-09-10 02:48:13 +08:00
|
|
|
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)
|
2023-11-18 23:23:47 +08:00
|
|
|
.filter(([k,value]) => value instanceof LetMultiversion)
|
2023-11-17 12:44:12 +08:00
|
|
|
.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) =>
|
2023-11-24 14:45:17 +08:00
|
|
|
v.last_version_number() >= call.id
|
2023-11-17 12:44:12 +08:00
|
|
|
)
|
|
|
|
|
|
2023-11-24 14:45:17 +08:00
|
|
|
const next_id = next_eval_cxt.call_index == calls.length - 1
|
2023-11-23 14:47:28 +08:00
|
|
|
? frame_cxt.calltree_node.next_id
|
2023-11-24 14:45:17 +08:00
|
|
|
: calls[next_eval_cxt.call_index + 1].id
|
2023-11-17 12:44:12 +08:00
|
|
|
|
|
|
|
|
const updated_let_scope = map_object(changed_vars, (name, v) =>
|
|
|
|
|
/*
|
2023-11-24 14:45:17 +08:00
|
|
|
We can't just use call.next_id here because it will break in async
|
2023-11-17 12:44:12 +08:00
|
|
|
context
|
|
|
|
|
*/
|
|
|
|
|
v.get_version(next_id)
|
|
|
|
|
)
|
|
|
|
|
|
2022-09-10 02:48:13 +08:00
|
|
|
return {
|
2023-11-24 14:45:17 +08:00
|
|
|
ok: call.ok,
|
|
|
|
|
call,
|
|
|
|
|
value: call.value,
|
|
|
|
|
error: call.error,
|
|
|
|
|
is_error_origin: !call.ok,
|
2022-09-10 02:48:13 +08:00
|
|
|
children,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: {
|
|
|
|
|
...next_eval_cxt,
|
2023-11-24 14:45:17 +08:00
|
|
|
call_index: next_eval_cxt.call_index + 1,
|
2023-11-23 16:35:03 +08:00
|
|
|
scope: {...next_eval_cxt.scope, ...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,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt,
|
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-23 16:35:03 +08:00
|
|
|
const {node: cond_evaled, eval_cxt: eval_cxt_after_cond} = eval_frame_expr(
|
2022-09-10 02:48:13 +08:00
|
|
|
node.cond,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt,
|
|
|
|
|
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]],
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: eval_cxt_after_cond,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
2023-11-23 16:35:03 +08:00
|
|
|
const {node: branch_evaled, eval_cxt: eval_cxt_after_branch}
|
2023-11-17 12:44:12 +08:00
|
|
|
= eval_frame_expr(
|
2022-09-10 02:48:13 +08:00
|
|
|
branches[value ? 0 : 1],
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt_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-23 16:35:03 +08:00
|
|
|
return {ok, children, eval_cxt: eval_cxt_after_branch,
|
2023-11-17 12:44:12 +08:00
|
|
|
value: branch_evaled.result.value}
|
2022-09-10 02:48:13 +08:00
|
|
|
} else {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok, children, eval_cxt: eval_cxt_after_branch}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if(node.type == 'member_access'){
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = eval_children(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
if(!ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok: false, children, eval_cxt: next_eval_cxt}
|
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,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if(node.type == 'unary') {
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = eval_children(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
if(!ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok: false, children, eval_cxt: next_eval_cxt}
|
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-23 16:35:03 +08:00
|
|
|
return {ok, children, value, error, is_error_origin, eval_cxt: next_eval_cxt}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
} else if(node.type == 'binary' && !['&&', '||', '??'].includes(node.operator)){
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
return eval_binary_expr(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
|
|
|
|
|
} else if(node.type == 'binary' && ['&&', '||', '??'].includes(node.operator)){
|
2023-11-23 16:35:03 +08:00
|
|
|
const {node: left_evaled, eval_cxt: next_eval_cxt} = eval_frame_expr(
|
2022-09-10 02:48:13 +08:00
|
|
|
node.children[0],
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt,
|
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]],
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
2023-11-23 16:35:03 +08:00
|
|
|
return eval_binary_expr(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if(node.type == 'grouping'){
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = eval_children(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
if(!ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok, children, eval_cxt: next_eval_cxt}
|
2022-09-10 02:48:13 +08:00
|
|
|
} else {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok: true, children, value: children[0].result.value, eval_cxt: next_eval_cxt}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.error(node)
|
|
|
|
|
throw new Error('unknown node type: ' + node.type)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const eval_children = (node, eval_cxt, frame_cxt) => {
|
2022-09-10 02:48:13 +08:00
|
|
|
return node.children.reduce(
|
2023-11-23 16:35:03 +08:00
|
|
|
({ok, children, eval_cxt}, child) => {
|
|
|
|
|
let next_child, next_ok, next_eval_cxt
|
2022-09-10 02:48:13 +08:00
|
|
|
if(!ok) {
|
2023-05-16 00:04:53 +03:00
|
|
|
next_child = child
|
|
|
|
|
next_ok = false
|
2023-11-23 16:35:03 +08:00
|
|
|
next_eval_cxt = eval_cxt
|
2022-09-10 02:48:13 +08:00
|
|
|
} else {
|
2023-11-23 16:35:03 +08:00
|
|
|
const result = eval_frame_expr(child, eval_cxt, frame_cxt)
|
2023-05-16 00:04:53 +03:00
|
|
|
next_child = result.node
|
|
|
|
|
next_ok = next_child.result.ok
|
2023-11-23 16:35:03 +08:00
|
|
|
next_eval_cxt = result.eval_cxt
|
2023-11-17 12:44:12 +08:00
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
ok: next_ok,
|
|
|
|
|
children: [...children, next_child],
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
},
|
2023-11-23 16:35:03 +08:00
|
|
|
{ok: true, children: [], eval_cxt}
|
2022-09-10 02:48:13 +08:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const eval_frame_expr = (node, eval_cxt, frame_cxt) => {
|
|
|
|
|
const {ok, error, is_error_origin, value, call, children, eval_cxt: next_eval_cxt}
|
|
|
|
|
= do_eval_frame_expr(node, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
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-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const eval_decl_pair = (s, eval_cxt, frame_cxt) => {
|
2023-10-30 12:51:15 +08:00
|
|
|
if(s.type != 'decl_pair') {
|
|
|
|
|
throw new Error('illegal state')
|
|
|
|
|
}
|
|
|
|
|
// TODO default values for destructuring can be function calls
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const {node, eval_cxt: next_eval_cxt}
|
|
|
|
|
= eval_frame_expr(s.expr, eval_cxt, frame_cxt)
|
2023-10-30 12:51:15 +08:00
|
|
|
const s_expr_evaled = {...s, children: [s.name_node, node]}
|
|
|
|
|
if(!node.result.ok) {
|
|
|
|
|
return {
|
|
|
|
|
ok: false,
|
|
|
|
|
node: {...s_expr_evaled, result: {ok: false}},
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2023-10-30 12:51:15 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-10-30 12:51:15 +08:00
|
|
|
|
2023-11-17 13:02:58 +08:00
|
|
|
// TODO if destructuring is just one id, then do not use eval_codestring
|
|
|
|
|
|
2023-10-30 12:51:15 +08:00
|
|
|
// 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(
|
2023-10-30 12:51:15 +08:00
|
|
|
codestring,
|
2023-11-23 16:35:03 +08:00
|
|
|
{...next_eval_cxt.scope, __value: node.result.value}
|
2023-10-30 12:51:15 +08:00
|
|
|
)
|
|
|
|
|
|
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
|
|
|
))
|
|
|
|
|
|
2023-10-30 12:51:15 +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
|
|
|
}
|
|
|
|
|
})
|
2023-10-30 12:51:15 +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-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2023-10-30 12:51:15 +08:00
|
|
|
}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
2023-10-30 12:51:15 +08:00
|
|
|
return {
|
|
|
|
|
ok: true,
|
2023-11-19 04:27:21 +08:00
|
|
|
node: {...s_evaled, result: node.result},
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: {
|
|
|
|
|
...next_eval_cxt,
|
|
|
|
|
scope: {...next_eval_cxt.scope, ...next_scope},
|
|
|
|
|
}
|
2023-10-30 12:51:15 +08:00
|
|
|
}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
2023-10-30 12:51:15 +08:00
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const eval_statement = (s, eval_cxt, frame_cxt) => {
|
2022-09-10 02:48:13 +08:00
|
|
|
if(s.type == 'do') {
|
2023-10-30 12:51:15 +08:00
|
|
|
const stmt = s
|
2023-05-16 00:04:53 +03:00
|
|
|
// hoist function decls to the top
|
2023-10-30 12:51:15 +08:00
|
|
|
const function_decls = s.children
|
2023-05-16 00:04:53 +03:00
|
|
|
.filter(s => s.type == 'function_decl')
|
|
|
|
|
.map(s => {
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = eval_children(s, eval_cxt, frame_cxt)
|
2023-05-16 00:04:53 +03:00
|
|
|
if(!ok) {
|
|
|
|
|
// Function decl can never fail
|
|
|
|
|
throw new Error('illegal state')
|
|
|
|
|
}
|
2023-11-23 16:35:03 +08:00
|
|
|
if(eval_cxt != next_eval_cxt) {
|
2023-05-16 00:04:53 +03:00
|
|
|
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]
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const initial_scope = {...eval_cxt.scope, ...hoisted_functions_scope}
|
|
|
|
|
const initial = {
|
|
|
|
|
ok: true,
|
|
|
|
|
returned: false,
|
|
|
|
|
children: [],
|
|
|
|
|
eval_cxt: {...eval_cxt, scope: initial_scope}
|
|
|
|
|
}
|
2023-05-16 00:04:53 +03:00
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, returned, children, eval_cxt: next_eval_cxt} =
|
|
|
|
|
s.children.reduce( ({ok, returned, children, eval_cxt}, s) => {
|
2022-09-10 02:48:13 +08:00
|
|
|
if(returned || !ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok, returned, eval_cxt, 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,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt,
|
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,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
|
|
|
|
} = eval_statement(s, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
return {
|
|
|
|
|
ok,
|
|
|
|
|
returned,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2023-10-26 10:29:57 +08:00
|
|
|
children: [...children, node],
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
2023-11-23 16:35:03 +08:00
|
|
|
initial,
|
2023-11-17 12:44:12 +08:00
|
|
|
)
|
2023-11-23 16:35:03 +08:00
|
|
|
const let_vars_scope = filter_object(next_eval_cxt.scope, (k, v) =>
|
2023-11-17 12:44:12 +08:00
|
|
|
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}},
|
2022-09-10 02:48:13 +08:00
|
|
|
returned,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: {
|
|
|
|
|
...next_eval_cxt,
|
|
|
|
|
scope: {...eval_cxt.scope, ...let_vars_scope},
|
|
|
|
|
}
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
2023-10-30 12:51:15 +08:00
|
|
|
} else if(['let', 'const', 'assignment'].includes(s.type)) {
|
|
|
|
|
const stmt = s
|
2022-09-10 02:48:13 +08:00
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const initial = {ok: true, children: [], eval_cxt}
|
2022-09-10 02:48:13 +08:00
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, children, eval_cxt: next_eval_cxt} = s.children.reduce(
|
|
|
|
|
({ok, children, eval_cxt}, s) => {
|
2023-10-30 12:51:15 +08:00
|
|
|
if(!ok) {
|
2023-11-23 16:35:03 +08:00
|
|
|
return {ok, eval_cxt, children: [...children, s]}
|
2023-10-30 12:51:15 +08:00
|
|
|
}
|
|
|
|
|
if(stmt.type == 'let' && s.type == 'identifier') {
|
|
|
|
|
const node = {...s, result: {ok: true}}
|
2023-11-23 16:35:03 +08:00
|
|
|
const scope = {...eval_cxt.scope, [symbol_for_identifier(s, frame_cxt)]: undefined}
|
2023-10-30 15:13:29 +08:00
|
|
|
return {
|
|
|
|
|
ok,
|
|
|
|
|
children: [...children, node],
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: {...eval_cxt, scope},
|
2023-10-30 15:13:29 +08:00
|
|
|
}
|
2023-10-30 12:51:15 +08:00
|
|
|
}
|
|
|
|
|
const {
|
|
|
|
|
ok: next_ok,
|
|
|
|
|
node,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
|
|
|
|
} = eval_decl_pair(s, eval_cxt, frame_cxt)
|
2023-10-30 12:51:15 +08:00
|
|
|
return {
|
|
|
|
|
ok: next_ok,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2023-10-30 12:51:15 +08:00
|
|
|
children: [...children, node],
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
initial
|
2022-09-10 02:48:13 +08:00
|
|
|
)
|
|
|
|
|
return {
|
2023-10-30 12:51:15 +08:00
|
|
|
ok,
|
|
|
|
|
node: {...s, children, result: {ok}},
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
} else if(s.type == 'return') {
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const {node, eval_cxt: next_eval_cxt} =
|
|
|
|
|
eval_frame_expr(s.expr, eval_cxt, 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-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if(s.type == 'export') {
|
2023-11-23 16:35:03 +08:00
|
|
|
const {ok, eval_cxt: next_eval_cxt, node}
|
|
|
|
|
= eval_statement(s.binding, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
return {
|
|
|
|
|
ok,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
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,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: {
|
|
|
|
|
...eval_cxt,
|
|
|
|
|
scope: {...eval_cxt.scope, ...imported_scope}
|
|
|
|
|
},
|
2022-09-10 02:48:13 +08:00
|
|
|
node: {...s, children, result: {ok: true}}
|
|
|
|
|
}
|
|
|
|
|
} else if(s.type == 'if') {
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const {node, eval_cxt: eval_cxt_after_cond} =
|
|
|
|
|
eval_frame_expr(s.cond, eval_cxt, 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}},
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: eval_cxt_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,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: eval_cxt_after_branch,
|
2022-09-10 02:48:13 +08:00
|
|
|
} = eval_statement(
|
|
|
|
|
s.branches[0],
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt_after_cond,
|
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}
|
|
|
|
|
},
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: eval_cxt_after_branch,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Branch is not executed
|
|
|
|
|
return {
|
|
|
|
|
ok: true,
|
|
|
|
|
node: {...s, children: [node, s.branches[0]], result: {ok: true}},
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: eval_cxt_after_cond,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// if with else
|
|
|
|
|
const active_branch = node.result.value ? s.branches[0] : s.branches[1]
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
node: evaled_branch,
|
|
|
|
|
returned,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: eval_cxt_after_branch,
|
2022-09-10 02:48:13 +08:00
|
|
|
} = eval_statement(
|
|
|
|
|
active_branch,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt_after_cond,
|
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}},
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: eval_cxt_after_branch,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if(s.type == 'throw') {
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const {node, eval_cxt: next_eval_cxt} =
|
|
|
|
|
eval_frame_expr(s.expr, eval_cxt, 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-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// stmt type is expression
|
2023-11-23 16:35:03 +08:00
|
|
|
const {node, eval_cxt: next_eval_cxt} = eval_frame_expr(s, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
return {
|
|
|
|
|
ok: node.result.ok,
|
|
|
|
|
node,
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_cxt: next_eval_cxt,
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const check_eval_result = result => {
|
|
|
|
|
// Ensure that eval_cxt is only constructed within eval_frame
|
|
|
|
|
if(!result.eval_cxt.__eval_cxt_marker) {
|
|
|
|
|
throw new Error('illegal state')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2023-11-23 16:35:03 +08:00
|
|
|
const eval_result = eval_statement(
|
|
|
|
|
node,
|
|
|
|
|
{
|
|
|
|
|
__eval_cxt_marker: true,
|
|
|
|
|
scope: {},
|
2023-11-24 14:45:17 +08:00
|
|
|
call_index: 0,
|
2023-11-23 16:35:03 +08:00
|
|
|
},
|
|
|
|
|
frame_cxt,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
check_eval_result(eval_result)
|
|
|
|
|
|
|
|
|
|
return eval_result.node
|
2022-09-10 02:48:13 +08:00
|
|
|
} 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) => {
|
2023-11-18 23:23:47 +08:00
|
|
|
return value instanceof LetMultiversion
|
2023-11-17 12:44:12 +08:00
|
|
|
? 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
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
const eval_cxt = {
|
|
|
|
|
__eval_cxt_marker: true,
|
|
|
|
|
scope,
|
2023-11-24 14:45:17 +08:00
|
|
|
call_index: 0,
|
2023-11-23 16:35:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let eval_result
|
2022-09-10 02:48:13 +08:00
|
|
|
if(body.type == 'do') {
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_result = eval_statement(body, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
} else {
|
2023-11-23 16:35:03 +08:00
|
|
|
eval_result = eval_frame_expr(body, eval_cxt, frame_cxt)
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
|
2023-11-23 16:35:03 +08:00
|
|
|
check_eval_result(eval_result)
|
|
|
|
|
|
2022-09-10 02:48:13 +08:00
|
|
|
return {...node,
|
2023-11-23 16:35:03 +08:00
|
|
|
result: {ok: eval_result.node.result.ok},
|
|
|
|
|
children: [function_args_with_result, eval_result.node],
|
2022-09-10 02:48:13 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|