import ParseNode from '/imports/parser/parseTree/ParseNode.js'; import ErrorNode from '/imports/parser/parseTree/ErrorNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; import functions from '/imports/parser/functions.js'; export default class CallNode extends ParseNode { constructor({functionName, args}) { super(...arguments); this.functionName = functionName; this.args = args; } resolve(fn, scope, context){ let func = functions[this.functionName]; // Check that the function exists if (!func) return new ErrorNode({ node: this, error: `${this.functionName} is not a supported function`, context, }); // Resolve the arguments let resolvedArgs = this.args.map(node => node[fn](scope, context)); // Check that the arguments match what is expected let checkFailed = this.checkArugments({ fn, context, resolvedArgs, argumentsExpected: func.arguments }); if (checkFailed){ if (fn !== 'reduce'){ return new ErrorNode({ node: this, error: `Invalid arguments to ${this.functionName} function`, }); } else { return new CallNode({ functionName: this.functionName, args: resolvedArgs, }); } } // Map contant nodes to constants before attempting to run the function let mappedArgs = resolvedArgs.map(node => { if (node instanceof ConstantNode){ return node.value; } else { return node; } }); try { // Run the function let value = func.fn.apply(null, mappedArgs); let type = typeof value; if (type === 'number' || type === 'string' || type === 'boolean'){ // Convert constant results into constant nodes return new ConstantNode({ value, type }); } else { return value; } } catch (error) { return new ErrorNode({ node: this, error: error.message || error, context, }); } } toString(){ return `${this.functionName}(${this.args.map(node => node.toString()).join(', ')})`; } traverse(fn){ fn(this); this.args.forEach(arg => arg.traverse(fn)); } replaceChildren(fn){ this.args = this.args.map(arg => arg.replaceNodes(fn)); } checkArugments({fn, context, argumentsExpected, resolvedArgs}){ // Check that the number of arguments matches the number expected if ( !argumentsExpected.anyLength && argumentsExpected.length !== resolvedArgs.length ){ context.storeError({ type: 'error', message: 'Incorrect number of arguments ' + `to ${this.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 (typeof type === 'string'){ // Type being a string means a constant node with matching type if (node.type !== type) failed = true; } else { // Otherwise check that the node is an instance of the given type if (!(node instanceof type)) failed = true; } if (failed && fn === 'reduce'){ let typeName = typeof type === 'string' ? type : type.constructor.name; let nodeName = node.type || node.constructor.name context.storeError({ type: 'error', message: `Incorrect arguments to ${this.functionName} function` + `expected ${typeName} got ${nodeName}` }); } }); return failed; } }