import error from './error.js'; import constant from './constant.js'; import functions from '/imports/parser/functions.js'; import resolve, { toString, traverse, map } from '../resolve.js'; const call = { create({ functionName, args }) { return { parseType: 'call', functionName, args, } }, resolve(fn, node, scope, context) { let func = functions[node.functionName]; // Check that the function exists if (!func) { context.error(`${node.functionName} is not a supported function`); return { result: error.create({ node: node, error: `${node.functionName} is not a supported function`, }), context, }; } // Resolve a given node to a maximum depth of resolution const resolveToLevel = (node, maxResolveFn = 'reduce') => { // Determine the actual depth to resolve to let resolveFn = 'reduce'; if (fn === 'compile' || maxResolveFn === 'compile') { resolveFn = 'compile'; } else if (fn === 'roll' || maxResolveFn === 'roll') { resolveFn = 'roll'; } // Resolve return resolve(resolveFn, node, scope, context); } // Resolve the arguments let resolvedArgs = node.args.map((arg, i) => { let { result } = resolveToLevel(arg, func.maxResolveLevels?.[i]); return result; }); // Check that the arguments match what is expected let checkFailed = call.checkArugments({ node, fn, resolvedArgs, func, context, }); if (checkFailed) { if (fn === 'reduce') { context.error(`Invalid arguments to ${node.functionName} function`); return { result: error.create({ node: node, error: `Invalid arguments to ${node.functionName} function`, }), context, }; } else { return { result: call.create({ functionName: node.functionName, args: resolvedArgs, }), context, }; } } // Map contant nodes to constants before attempting to run the function let mappedArgs = resolvedArgs.map((arg, index) => { if ( arg.parseType === 'constant' && func.arguments[index] !== 'parseNode' ) { return arg.value; } else { return arg; } }); try { // Run the function let value = func.fn.apply({ scope, context, }, mappedArgs); let valueType = typeof value; if (valueType === 'number' || valueType === 'string' || valueType === 'boolean') { // Convert constant results into constant nodes return { result: constant.create({ value }), context, }; } else { // Resolve the return value return resolve(fn, value, scope, context); } } catch (err) { context.error(`Internal error: ${err.message || err}`); return { result: error.create({ node: node, error: `Internal error: ${err.message || err}`, }), context, } } }, toString(node) { return `${node.functionName}(${node.args.map(arg => toString(arg)).join(', ')})`; }, traverse(node, fn) { fn(node); node.args.forEach(arg => traverse(arg, fn)); }, map(node, fn) { const resultingNode = fn(node); if (resultingNode === node) { node.args = node.args.map(arg => map(arg, fn)); } return resultingNode; }, checkArugments({ node, fn, func, resolvedArgs, context }) { const argumentsExpected = func.arguments; // Check that the number of arguments matches the number expected if ( !argumentsExpected.anyLength && resolvedArgs.length > (func.maxArguments ?? argumentsExpected.length) || resolvedArgs.length < (func.minArguments ?? argumentsExpected.length) ) { context.error('Incorrect number of arguments ' + `to ${node.functionName} function, ` + `expected ${argumentsExpected.length} got ${resolvedArgs.length}`); return true; } let failed = false; // Check that each argument is of the correct type resolvedArgs.forEach((node, index) => { let type; if (argumentsExpected.anyLength) { type = argumentsExpected[0]; } else { type = argumentsExpected[index]; } if (type === 'parseNode') return; if (node.parseType !== type && node.valueType !== type) failed = true; if (failed && fn === 'reduce') { let typeName = typeof type === 'string' ? type : type.constructor.name; let nodeName = node.parseType; context.error(`Incorrect arguments to ${node.functionName} function` + `expected ${typeName} got ${nodeName}`); } }); return failed; } } export default call;