function declarations

This commit is contained in:
Dmitry Vasilev
2023-05-16 00:04:53 +03:00
parent 096ca5e495
commit 5b5938ee61
6 changed files with 202 additions and 41 deletions

View File

@@ -70,7 +70,11 @@ const codegen_function_expr = (node, cxt) => {
const args = node.function_args.children.map(do_codegen).join(',')
const call = (node.is_async ? 'async ' : '') + `(${args}) => ` + (
const decl = node.is_arrow
? `(${args}) => `
: `function ${node.name}(${args})`
const call = (node.is_async ? 'async ' : '') + decl + (
// TODO gensym __obj, __fn
(node.body.type == 'do')
? '{ let __obj, __fn; ' + do_codegen(node.body) + '}'
@@ -145,10 +149,14 @@ const codegen = (node, cxt, parent) => {
].includes(node.type)){
return node.value
} else if(node.type == 'do'){
return node.stmts.reduce(
(result, stmt) => result + (do_codegen(stmt)) + ';\n',
''
)
return [
// hoist function decls to the top
...node.stmts.filter(s => s.type == 'function_decl'),
...node.stmts.filter(s => s.type != 'function_decl'),
].reduce(
(result, stmt) => result + (do_codegen(stmt)) + ';\n',
''
)
} else if(node.type == 'return') {
return 'return ' + do_codegen(node.expr) + ';'
} else if(node.type == 'throw') {
@@ -266,6 +274,9 @@ ${JSON.stringify(errormessage)}, true)`
return do_codegen(node.binding)
+
`Object.assign(__exports, {${identifiers.join(',')}});`
} else if(node.type == 'function_decl') {
const expr = node.children[0]
return `const ${expr.name} = ${codegen_function_expr(expr, cxt)};`
} else {
console.error(node)
throw new Error('unknown node type: ' + node.type)
@@ -1030,7 +1041,7 @@ const do_eval_frame_expr = (node, scope, callsleft, context) => {
calls: callsleft,
children: node.children,
}
} else if(node.type == 'ternary'){
} else if(node.type == 'ternary') {
const {node: cond_evaled, calls: calls_after_cond} = eval_frame_expr(
node.cond,
scope,
@@ -1168,14 +1179,14 @@ const eval_children = (node, scope, calls, context) => {
({ok, children, calls}, child) => {
let next_child, next_ok, next_calls
if(!ok) {
next_child = child;
next_ok = false;
next_calls = calls;
next_child = child
next_ok = false
next_calls = calls
} else {
const result = eval_frame_expr(child, scope, calls, context)
next_child = result.node;
next_calls = result.calls;
next_ok = next_child.result.ok;
next_child = result.node
next_calls = result.calls
next_ok = next_child.result.ok
}
return {ok: next_ok, children: [...children, next_child], calls: next_calls}
},
@@ -1247,14 +1258,47 @@ const apply_assignments = (do_node, assignments) => {
return {node, scope}
}
const eval_statement = (s, scope, calls, context) => {
if(s.type == 'do') {
const node = s
const {ok, assignments, returned, stmts, calls: nextcalls} = node.stmts.reduce(
// hoist function decls to the top
const function_decls = node.stmts
.filter(s => s.type == 'function_decl')
.map(s => {
const {ok, children, calls: next_calls} = eval_children(s, scope, calls, context)
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}
const {ok, assignments, returned, stmts, calls: next_calls} = node.stmts.reduce(
({ok, returned, stmts, scope, calls, assignments}, s) => {
if(returned || !ok) {
return {ok, returned, scope, calls, stmts: [...stmts, s], assignments}
} else if(s.type == 'function_decl') {
const node = function_decls.find(decl => decl.index == s.index)
return {
ok: true,
returned: false,
node,
assignments,
scope,
calls,
stmts: [...stmts, node],
}
} else {
const {
ok,
@@ -1274,7 +1318,7 @@ const eval_statement = (s, scope, calls, context) => {
}
}
},
{ok: true, returned: false, stmts: [], scope, calls, assignments: {}}
{ok: true, returned: false, stmts: [], scope: initial_scope, calls, assignments: {}}
)
const {node: next_node, scope: next_scope} =
apply_assignments({...node, children: stmts, result: {ok}}, assignments)
@@ -1284,7 +1328,7 @@ const eval_statement = (s, scope, calls, context) => {
scope: {...scope, ...next_scope},
returned,
assignments,
calls: nextcalls,
calls: next_calls,
}
} else if(s.type == 'const' || s.type == 'assignment') {
// TODO default values for destructuring can be function calls
@@ -1365,6 +1409,7 @@ const eval_statement = (s, scope, calls, context) => {
)
: null
}
} else if(s.type == 'return') {
const {node, calls: next_calls} =

View File

@@ -3,7 +3,6 @@
import {set_push, set_diff, set_union, map_object, map_find, uniq} from './utils.js'
import {collect_destructuring_identifiers, collect_imports, ancestry, find_leaf} from './ast_utils.js'
// TODO get complete list of globals (borrow from eslint?)
import {globals} from './globals.js'
const map_find_definitions = (nodes, mapper) => {
@@ -20,7 +19,6 @@ const map_find_definitions = (nodes, mapper) => {
}
}
const scope_from_node = n => {
if(n.type == 'import') {
return Object.fromEntries(
@@ -34,6 +32,10 @@ const scope_from_node = n => {
node.value, node
])
)
} else if(n.type == 'function_decl') {
// Return null because of hoisting. We take function decls into account
// first before processing statements one by one
return null
} else {
return null
}
@@ -94,14 +96,20 @@ export const find_definitions = (ast, scope = {}, closure_scope = {}, module_nam
}
}
} else if(ast.type == 'do'){
const children_with_scope = ast.children.reduce(
({scope, children}, node) => ({
scope: {...scope, ...scope_from_node(node)},
children: children.concat([{node, scope}]),
})
,
{scope: {}, children: []}
const hoisted_functions_scope = Object.fromEntries(
ast.children
.filter(s => s.type == 'function_decl')
.map(s => [s.children[0].name, s.children[0]])
)
const children_with_scope = ast.children
.reduce(
({scope, children}, node) => ({
scope: {...scope, ...scope_from_node(node)},
children: children.concat([{node, scope}]),
})
,
{scope: hoisted_functions_scope, children: []}
)
const local_scope = children_with_scope.scope
const {nodes, undeclared, closed} = map_find_definitions(children_with_scope.children, cs =>
find_definitions(cs.node, {...scope, ...cs.scope}, local_scope, module_name)

View File

@@ -905,7 +905,43 @@ const object_literal =
)
)
const function_expr =
const block_function_body = if_ok(
seq_select(1, [
literal('{'),
cxt => parse_do(cxt),
literal('}'),
]),
({value, ...node}) => ({...value, ...node}),
)
const function_expr = must_have_name =>
if_ok(
seq([
optional(literal('async')),
literal('function'),
must_have_name ? identifier : optional(identifier),
list_destructuring(['(', ')'], 'function_args'),
block_function_body,
]),
({value, ...node}) => {
const [is_async, _fn, name, args, body] = value
const function_args = {...args,
not_evaluatable: args.children.length == 0
}
return {
...node,
type: 'function_expr',
is_async: is_async != null,
is_arrow: false,
name: name?.value,
body,
children: [function_args, body]
}
},
)
const arrow_function_expr =
if_ok(
seq([
optional(literal('async')),
@@ -920,16 +956,7 @@ const function_expr =
either(
// With curly braces
if_ok(
seq_select(1, [
literal('{'),
cxt => parse_do(cxt),
literal('}'),
]),
({value, ...node}) => ({...value, ...node}),
),
block_function_body,
// Just expression
cxt => expr(cxt),
)
@@ -953,6 +980,7 @@ const function_expr =
...node,
type: 'function_expr',
is_async: is_async != null,
is_arrow: true,
body,
children: [function_args, body]
}
@@ -1008,7 +1036,8 @@ const primary = if_fail(
new_expr,
object_literal,
array_literal,
function_expr,
function_expr(false),
arrow_function_expr,
// not_followed_by for better error messages
// y => { <garbage> } must parse as function expr, not as identifier `y`
@@ -1046,6 +1075,12 @@ const expr =
cxt => expr(cxt)
)
const function_decl = if_ok(
function_expr(true),
// wrap function_expr with function_decl
node => ({...node, type: 'function_decl', children: [node]})
)
// TODO multiple decls, like `const x = 1, y = 2`
const const_statement =
if_ok(
@@ -1246,6 +1281,7 @@ const do_statement = either(
if_statement,
throw_statement,
return_statement,
function_decl,
)
const module_statement = either(
@@ -1256,6 +1292,7 @@ const module_statement = either(
throw_statement,
import_statement,
export_statement,
function_decl,
)
const parse_do_or_module = (is_module) =>
@@ -1362,6 +1399,10 @@ const update_children_not_rec = (node, children = node.children) => {
stmts: children,
is_statement: true,
}
} else if(node.type == 'function_decl'){
return {...node,
is_statement: true,
}
} else if(node.type == 'unary') {
return {...node,
expr: children[0],
@@ -1486,7 +1527,12 @@ const do_deduce_fn_names = (node, parent) => {
}
}
if(node_result.type == 'function_expr') {
if(
node_result.type == 'function_expr'
&&
// not a named function
node_result.name == null
) {
let name
if(parent?.type == 'const') {
name = parent.name