import computeStat from '/imports/api/creature/computation/engine/computeStat.js'; import { prettifyParseError, parse, CompilationContext } from '/imports/parser/parser.js'; import SymbolNode from '/imports/parser/parseTree/SymbolNode.js'; import AccessorNode from '/imports/parser/parseTree/AccessorNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; import ErrorNode from '/imports/parser/parseTree/ErrorNode.js'; import findAncestorByType from '/imports/api/creature/computation/engine/findAncestorByType.js'; import { union } from 'lodash'; /* Convert a calculation into a constant output and errors*/ export default function evaluateCalculation({ string, prop, memo, fn = 'reduce', }){ let dependencies = []; let context = new CompilationContext(); if (!string) return { result: new ConstantNode({value: string, type: 'string'}), context, dependencies, }; if (typeof string !== 'string'){ string = string.toString(); } // Parse the string let calc; try { calc = parse(string); } catch (e) { let error = prettifyParseError(e); return { result: new ErrorNode({context, error}), context, dependencies, }; } // Replace constants with their parsed constant let replaceResults = replaceConstants({ calc, memo, prop, dependencies, context }); dependencies = replaceResults.dependencies; calc = replaceResults.calc; if (replaceResults.failed){ return { result: new ConstantNode({value: string, type: 'string'}), context, dependencies, }; } // Ensure all symbol nodes are defined and computed dependencies = computeSymbols({calc, memo, prop, dependencies}) // Evaluate let result = calc[fn](memo.statsByVariableName, context); return {result, context, dependencies}; } // Replace constants in the calc with the right ParseNodes function replaceConstants({calc, memo, prop, dependencies, context}){ let constFailed = []; calc = calc.replaceNodes(node => { if (!(node instanceof SymbolNode)) return; let stat, constant; if (node.name[0] !== '#'){ stat = memo.statsByVariableName[node.name] constant = memo.constantsByVariableName[node.name]; } else if (node.name === '#constant'){ constant = findAncestorByType({type: 'constant', prop, memo}); } // replace constants that aren't overridden by stats or disabled by a toggle if (constant && !constant.deactivatedByToggle && !stat){ dependencies = union(dependencies, [ constant._id, ...constant.dependencies ]); // Fail if the constant has errors if (constant.errors && constant.errors.length){ constFailed.push(node.name); return; } let parsedConstantNode; try { parsedConstantNode = parse(constant.calculation); } catch(e){ constFailed.push(node.name); return; } if (!parsedConstantNode) constFailed.push(node.name); return parsedConstantNode; } }); constFailed.forEach(name => { context.storeError({ type: 'error', message: `${name} is a constant property with parsing errors` }); }); let failed = !!constFailed.length; if (failed){ calc = new ErrorNode({error: 'Failed to replace constants'}); } return { failed, dependencies, calc }; } // Ensure all symbol nodes are defined and computed function computeSymbols({calc, memo, prop, dependencies}){ calc.traverse(node => { if (node instanceof SymbolNode || node instanceof AccessorNode){ let stat; // References up the tree start with # if (node.name[0] === '#'){ stat = findAncestorByType({type: node.name.slice(1), prop, memo}); memo.statsByVariableName[node.name] = stat; } else { stat = memo.statsByVariableName[node.name]; } if (stat && stat.computationDetails && !stat.computationDetails.computed){ computeStat(stat, memo); } if (stat){ if (stat.dependencies){ dependencies = union(dependencies, [ stat._id || node.name, ...stat.dependencies ]); } else { dependencies = union(dependencies, [stat._id || node.name]); } } } }); return dependencies; }