diff --git a/compiler/codegen.js b/compiler/codegen.js index 6efb3ca3..8fb31c9e 100644 --- a/compiler/codegen.js +++ b/compiler/codegen.js @@ -1413,6 +1413,7 @@ const getNodeType = (scope, node) => { if (Object.hasOwn(builtinFuncs, name) && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number; if (Object.hasOwn(internalConstrs, name)) return internalConstrs[name].type; + if (name === 'Function') return TYPES.function; // check if this is a prototype function // if so and there is only one impl (eg charCodeAt) @@ -1874,45 +1875,89 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => { name = func.name; } - if (!decl._new && name === 'eval' && decl.arguments[0]?.type === 'Literal') { - // literal eval hack - const code = decl.arguments[0]?.value ?? ''; + if (name === 'eval') { + if (!decl._new && decl.arguments[0]?.type === 'Literal') { + // literal eval hack + const code = decl.arguments[0]?.value ?? ''; - let parsed; - try { - parsed = parse(code, []); - } catch (e) { - if (e.name === 'SyntaxError') { - // throw syntax errors of evals at runtime instead - return internalThrow(scope, 'SyntaxError', e.message, true); + let parsed; + try { + parsed = parse(code, []); + } catch (e) { + if (e.name === 'SyntaxError') { + // throw syntax errors of evals at runtime instead + return internalThrow(scope, 'SyntaxError', e.message, true); + } + + throw e; } - throw e; - } + const out = generate(scope, { + type: 'BlockStatement', + body: parsed.body + }); - const out = generate(scope, { - type: 'BlockStatement', - body: parsed.body - }); + const lastInst = out[out.length - 1]; + if (lastInst && lastInst[0] === Opcodes.drop) { + out.splice(out.length - 1, 1); + + const finalStatement = parsed.body[parsed.body.length - 1]; + out.push(...setLastType(scope, getNodeType(scope, finalStatement))); + } else if (countLeftover(out) === 0) { + out.push(...number(UNDEFINED)); + out.push(...setLastType(scope, TYPES.undefined)); + } - const lastInst = out[out.length - 1]; - if (lastInst && lastInst[0] === Opcodes.drop) { - out.splice(out.length - 1, 1); + // if (lastInst && lastInst[0] === Opcodes.drop) { + // out.splice(out.length - 1, 1); + // } else if (countLeftover(out) === 0) { + // out.push(...number(UNDEFINED)); + // } - const finalStatement = parsed.body[parsed.body.length - 1]; - out.push(...setLastType(scope, getNodeType(scope, finalStatement))); - } else if (countLeftover(out) === 0) { - out.push(...number(UNDEFINED)); - out.push(...setLastType(scope, TYPES.undefined)); + return out; + } else { + if (decl._new) return internalThrow(scope, 'TypeError', 'eval is not a constructor'); + return todo(scope, 'dynamic eval is not currently supported', true); } + } - // if (lastInst && lastInst[0] === Opcodes.drop) { - // out.splice(out.length - 1, 1); - // } else if (countLeftover(out) === 0) { - // out.push(...number(UNDEFINED)); - // } + if (name === 'Function') { + if (decl.arguments[0]?.type === 'Literal' && (decl.arguments[1] ? decl.arguments[1]?.type === 'Literal' : true)) { + // literal eval hack - return out; + let args = ''; + let code = ''; + if (decl.arguments.length >= 2) { + args = decl.arguments[0]?.value; + code = decl.arguments[1]?.value; + } else { + code = decl.arguments[0]?.value; + } + + // hack: use this template to parse the arguments and code, void is used to force this to be an expression + const template = `void function (${args}) { ${code} }`; + + let parsed; + try { + parsed = parse(template, []); + } catch (e) { + if (e.name === 'SyntaxError') { + // throw syntax errors of evals at runtime instead + return internalThrow(scope, 'SyntaxError', e.message, true); + } + + throw e; + } + + // Program > ExpressionStatement > UnaryExpression > FunctionExpression + parsed = parsed.body[0].expression.argument; + + const [ func, out ] = generateFunc(scope, parsed); + + return out; + } else { + return todo(scope, 'dynamic eval is not currently supported', true); + } } let protoName, target;