mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 21:14:28 -08:00
fix function names
This commit is contained in:
38
src/eval.js
38
src/eval.js
@@ -68,7 +68,7 @@ const make_function = (...args) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const codegen_function_expr = (node, cxt, name) => {
|
const codegen_function_expr = (node, cxt) => {
|
||||||
const do_codegen = n => codegen(n, cxt)
|
const do_codegen = n => codegen(n, cxt)
|
||||||
|
|
||||||
const args = node.function_args.children.map(do_codegen).join(',')
|
const args = node.function_args.children.map(do_codegen).join(',')
|
||||||
@@ -90,7 +90,7 @@ const codegen_function_expr = (node, cxt, name) => {
|
|||||||
// on first call (see `trace`)
|
// on first call (see `trace`)
|
||||||
const get_closure = `() => ({${[...node.closed].join(',')}})`
|
const get_closure = `() => ({${[...node.closed].join(',')}})`
|
||||||
|
|
||||||
return `trace(${call}, "${name}", ${argscount}, ${location}, ${get_closure})`
|
return `trace(${call}, "${node.name}", ${argscount}, ${location}, ${get_closure})`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO if statically can prove that function is hosted, then do not codegen
|
// TODO if statically can prove that function is hosted, then do not codegen
|
||||||
@@ -169,19 +169,7 @@ const codegen = (node, cxt, parent) => {
|
|||||||
} else if(node.type == 'function_call'){
|
} else if(node.type == 'function_call'){
|
||||||
return codegen_function_call(node, cxt)
|
return codegen_function_call(node, cxt)
|
||||||
} else if(node.type == 'function_expr'){
|
} else if(node.type == 'function_expr'){
|
||||||
let name
|
return codegen_function_expr(node, cxt)
|
||||||
// TODO here we deduce fn name from left-side of assignment
|
|
||||||
// TODO name inference is much more sophisticated, for example
|
|
||||||
// `{foo: () => {...}}` infers name `foo`
|
|
||||||
if(parent?.type == 'const') {
|
|
||||||
name = parent.name
|
|
||||||
} else if(parent?.type == 'key_value_pair') {
|
|
||||||
// unwrap quotes with JSON.parse
|
|
||||||
name = JSON.parse(parent.key.value)
|
|
||||||
} else {
|
|
||||||
name = 'anonymous'
|
|
||||||
}
|
|
||||||
return codegen_function_expr(node, cxt, name)
|
|
||||||
} else if(node.type == 'ternary'){
|
} else if(node.type == 'ternary'){
|
||||||
return ''
|
return ''
|
||||||
+ '('
|
+ '('
|
||||||
@@ -193,11 +181,20 @@ const codegen = (node, cxt, parent) => {
|
|||||||
} else if(node.type == 'const'){
|
} else if(node.type == 'const'){
|
||||||
const res = 'const ' + do_codegen(node.name_node) + ' = ' + do_codegen(node.expr, node) + ';'
|
const res = 'const ' + do_codegen(node.name_node) + ' = ' + do_codegen(node.expr, node) + ';'
|
||||||
if(node.name_node.type == 'identifier' && node.expr.type == 'function_call') {
|
if(node.name_node.type == 'identifier' && node.expr.type == 'function_call') {
|
||||||
// generate function name
|
// deduce function name from variable it was assigned to if anonymous
|
||||||
// TODO test it
|
// works for point-free programming, like
|
||||||
|
// const parse_statement = either(parse_import, parse_assignment, ...)
|
||||||
return res + `
|
return res + `
|
||||||
if(typeof(${node.name_node.value}) == 'function') {
|
if(
|
||||||
Object.defineProperty(${node.name_node.value}, "name", {value: "${node.name_node.value}"});
|
typeof(${node.name_node.value}) == 'function'
|
||||||
|
&&
|
||||||
|
${node.name_node.value}.name == 'anonymous'
|
||||||
|
) {
|
||||||
|
Object.defineProperty(
|
||||||
|
${node.name_node.value},
|
||||||
|
"name",
|
||||||
|
{value: "${node.name_node.value}"}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
} else {
|
} else {
|
||||||
@@ -833,11 +830,10 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
|
|||||||
} else if(node.type == 'function_expr'){
|
} else if(node.type == 'function_expr'){
|
||||||
// It will never be called, create empty function
|
// It will never be called, create empty function
|
||||||
// TODO use new Function constructor with code?
|
// TODO use new Function constructor with code?
|
||||||
// TODO generate function name
|
|
||||||
const fn_placeholder = Object.defineProperty(
|
const fn_placeholder = Object.defineProperty(
|
||||||
() => {},
|
() => {},
|
||||||
'name',
|
'name',
|
||||||
{value: 'anonymous'}
|
{value: node.name}
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
@@ -1452,6 +1452,44 @@ const update_children = node => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const do_deduce_fn_names = (node, parent) => {
|
||||||
|
let changed, node_result
|
||||||
|
if(node.children == null) {
|
||||||
|
node_result = node
|
||||||
|
changed = false
|
||||||
|
} else {
|
||||||
|
const children_results = node
|
||||||
|
.children
|
||||||
|
.map(c => do_deduce_fn_names(c, node))
|
||||||
|
changed = children_results.some(c => c[1])
|
||||||
|
if(changed) {
|
||||||
|
node_result = {...node, children: children_results.map(c => c[0])}
|
||||||
|
} else {
|
||||||
|
node_result = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node_result.type == 'function_expr') {
|
||||||
|
let name
|
||||||
|
if(parent?.type == 'const') {
|
||||||
|
name = parent.name
|
||||||
|
} else if(parent?.type == 'key_value_pair') {
|
||||||
|
// unwrap quotes with JSON.parse
|
||||||
|
name = JSON.parse(parent.key.value)
|
||||||
|
} else {
|
||||||
|
name = 'anonymous'
|
||||||
|
}
|
||||||
|
changed = true
|
||||||
|
node_result = {...node_result, name}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [node_result, changed]
|
||||||
|
}
|
||||||
|
|
||||||
|
const deduce_fn_names = node => {
|
||||||
|
return do_deduce_fn_names(node, null)[0]
|
||||||
|
}
|
||||||
|
|
||||||
export const parse = (str, is_module = false, module_name) => {
|
export const parse = (str, is_module = false, module_name) => {
|
||||||
|
|
||||||
// Add string to node for debugging
|
// Add string to node for debugging
|
||||||
@@ -1499,7 +1537,12 @@ export const parse = (str, is_module = false, module_name) => {
|
|||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const {node, undeclared} = find_definitions(update_children(result.value), null, null, module_name)
|
const {node, undeclared} = find_definitions(
|
||||||
|
update_children(result.value),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
module_name
|
||||||
|
)
|
||||||
if(undeclared.length != 0){
|
if(undeclared.length != 0){
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -1516,7 +1559,7 @@ export const parse = (str, is_module = false, module_name) => {
|
|||||||
// property to some nodes, and children no more equal to other properties
|
// property to some nodes, and children no more equal to other properties
|
||||||
// of nodes by idenitity, which somehow breaks code (i dont remember how
|
// of nodes by idenitity, which somehow breaks code (i dont remember how
|
||||||
// exactly). Refactor it?
|
// exactly). Refactor it?
|
||||||
const fixed_node = update_children(populate_string(node))
|
const fixed_node = update_children(deduce_fn_names(populate_string(node)))
|
||||||
const problems = analyze(fixed_node)
|
const problems = analyze(fixed_node)
|
||||||
if(problems.length != 0) {
|
if(problems.length != 0) {
|
||||||
return {ok: false, problems}
|
return {ok: false, problems}
|
||||||
|
|||||||
57
test/test.js
57
test/test.js
@@ -461,24 +461,61 @@ export const tests = [
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
test('function name from object literal', () => {
|
test('function name from object literal', () => {
|
||||||
const i = test_initial_state(`
|
const code = `
|
||||||
const fns = {x: () => 1}
|
const fns = {x: () => 1}
|
||||||
fns.x()
|
fns.x()
|
||||||
`)
|
fns.x.name
|
||||||
|
`
|
||||||
|
const i = test_initial_state(code)
|
||||||
assert_equal(root_calltree_node(i).children[0].fn.name, 'x')
|
assert_equal(root_calltree_node(i).children[0].fn.name, 'x')
|
||||||
|
assert_code_evals_to(code, 'x')
|
||||||
}),
|
}),
|
||||||
|
|
||||||
test('function name', () => {
|
test('function name from const decl', () => {
|
||||||
// TODO
|
const code = `
|
||||||
/*
|
const x = () => 1
|
||||||
assert_code_evals_to(
|
x()
|
||||||
|
x.name
|
||||||
`
|
`
|
||||||
const x = () => null();
|
const i = test_initial_state(code)
|
||||||
x.name;
|
assert_equal(root_calltree_node(i).children[0].fn.name, 'x')
|
||||||
`,
|
assert_code_evals_to(
|
||||||
|
code,
|
||||||
'x',
|
'x',
|
||||||
)
|
)
|
||||||
*/
|
}),
|
||||||
|
|
||||||
|
test('function name deduce', () => {
|
||||||
|
const code = `
|
||||||
|
const make_fn = () => () => 1
|
||||||
|
const x = make_fn()
|
||||||
|
x()
|
||||||
|
x.name
|
||||||
|
`
|
||||||
|
const i = test_initial_state(code)
|
||||||
|
assert_equal(root_calltree_node(i).children[1].fn.name, 'x')
|
||||||
|
assert_code_evals_to(
|
||||||
|
code,
|
||||||
|
'x',
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
|
test('function name dont deduce if already has name', () => {
|
||||||
|
const code = `
|
||||||
|
const make_fn = () => {
|
||||||
|
const y = () => 1
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
const x = make_fn()
|
||||||
|
x()
|
||||||
|
x.name
|
||||||
|
`
|
||||||
|
const i = test_initial_state(code)
|
||||||
|
assert_equal(root_calltree_node(i).children[1].fn.name, 'y')
|
||||||
|
assert_code_evals_to(
|
||||||
|
code,
|
||||||
|
'y',
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
test('record call chain', () => {
|
test('record call chain', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user