diff --git a/src/eval.js b/src/eval.js index d130679..707003d 100644 --- a/src/eval.js +++ b/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 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`) 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 @@ -169,19 +169,7 @@ const codegen = (node, cxt, parent) => { } else if(node.type == 'function_call'){ return codegen_function_call(node, cxt) } else if(node.type == 'function_expr'){ - let name - // 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) + return codegen_function_expr(node, cxt) } else if(node.type == 'ternary'){ return '' + '(' @@ -193,11 +181,20 @@ const codegen = (node, cxt, parent) => { } else if(node.type == 'const'){ const res = 'const ' + do_codegen(node.name_node) + ' = ' + do_codegen(node.expr, node) + ';' if(node.name_node.type == 'identifier' && node.expr.type == 'function_call') { - // generate function name - // TODO test it + // 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 res + ` - if(typeof(${node.name_node.value}) == 'function') { - Object.defineProperty(${node.name_node.value}, "name", {value: "${node.name_node.value}"}); + if( + 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 { @@ -833,11 +830,10 @@ const do_eval_frame_expr = (node, scope, callsleft) => { } else if(node.type == 'function_expr'){ // It will never be called, create empty function // TODO use new Function constructor with code? - // TODO generate function name const fn_placeholder = Object.defineProperty( () => {}, 'name', - {value: 'anonymous'} + {value: node.name} ) return { ok: true, diff --git a/src/parse_js.js b/src/parse_js.js index fd82b57..cec0423 100644 --- a/src/parse_js.js +++ b/src/parse_js.js @@ -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) => { // Add string to node for debugging @@ -1499,7 +1537,12 @@ export const parse = (str, is_module = false, module_name) => { }], } } 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){ return { 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 // of nodes by idenitity, which somehow breaks code (i dont remember how // 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) if(problems.length != 0) { return {ok: false, problems} diff --git a/test/test.js b/test/test.js index c12b25d..a5f12d4 100644 --- a/test/test.js +++ b/test/test.js @@ -461,24 +461,61 @@ export const tests = [ }), test('function name from object literal', () => { - const i = test_initial_state(` + const code = ` const fns = {x: () => 1} fns.x() - `) + fns.x.name + ` + const i = test_initial_state(code) assert_equal(root_calltree_node(i).children[0].fn.name, 'x') + assert_code_evals_to(code, 'x') }), - test('function name', () => { - // TODO - /* + test('function name from const decl', () => { + const code = ` + const x = () => 1 + x() + x.name + ` + const i = test_initial_state(code) + assert_equal(root_calltree_node(i).children[0].fn.name, 'x') assert_code_evals_to( - ` - const x = () => null(); - x.name; - `, + code, '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', () => {