UNPKG

mathsteps-experimental-fork

Version:

Step by step math solutions. Experimental Fork

206 lines (195 loc) 7.36 kB
'use strict'; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } }); const clone = require('../newServices/nodeServices/clone.js'); const parseText = require('../newServices/nodeServices/parseText.js'); const index = require('../checks/index.js'); const ChangeTypes = require('../types/changeType/ChangeTypes.js'); const print = require('../util/print.js'); const getAllRuleResponsesForNextStep = require('./getAllRuleResponsesForNextStep.js'); const kemuSimplifyCommonServices = require('./kemuSimplifyCommonServices.js'); const kemuSortArgs = require('./kemuSortArgs.js'); const theIndexRulePool = require('./theIndexRulePool.js'); /* eslint-disable no-throw-literal */ const MAX_STEP_COUNT = 64; function _applyRulesOnChildNode(fct, nodeBox, nodeIdx, simplifyCtx, nodeParent) { let isChanged = false; if (simplifyCtx.iterIdx < MAX_STEP_COUNT) { // Apply simplify function on current node. const node = nodeBox[nodeIdx]; const status = fct(node, simplifyCtx, nodeParent); if (status) { // Current node changed. // Report simplify step. isChanged = true; nodeBox[nodeIdx] = status.rootNode; // Track last changed node. // We'll start next simplify step from this node. simplifyCtx.lastChangedNodeBox = nodeBox; simplifyCtx.lastChangedNodeIdx = nodeIdx; simplifyCtx.lastChangedNodeParent = nodeParent; simplifyCtx.onStepCb(status); } else if (node.args) { // Nothing changed on current node. // Go into child nodes recursively. for (const argIdx in node.args) isChanged |= _applyRulesOnChildNode(fct, node.args, argIdx, simplifyCtx, node); } } return isChanged } // Given a mathjs expression node, steps through simplifying the expression. // Returns a list of details about each step. function stepThrough(node, options = {}) { if (options.isDebugMode) console.log(`\n\nSimplifying: ${print.printAscii(node)}`); // // Pre-process node. // node = kemuSimplifyCommonServices.kemuFlatten(node); // Add hard-coded first step with original expression. if (options.onStepCb) { options.onStepCb({ changeType: ChangeTypes.ChangeTypes.KEMU_ORIGINAL_EXPRESSION, rootNode: clone(node), }); } if (!index.default.hasUnsupportedNodes(node)) { // // Set-up simplify context passed along all steps. // const simplifyCtx = { iterIdx: 0, rootNode: node, expressionCtx: options.expressionCtx, // Callback called when any part of expression (node) changed. // We use it to track/collect steps. onStepCb: (stepMeta) => { // Validate step fields. if (!stepMeta.changeType) throw 'missing change type' if (!stepMeta.rootNode) throw 'missing root node' if (options.isDebugMode) logSteps(stepMeta); simplifyCtx.rootNode = kemuSimplifyCommonServices.kemuFlatten(simplifyCtx.rootNode); simplifyCtx.rootNode = kemuSimplifyCommonServices.kemuNormalizeConstantNodes(simplifyCtx.rootNode); simplifyCtx.iterIdx++; // Possible improvement: Optimize it. if (stepMeta.altForms) ; stepMeta.rootNode = clone(simplifyCtx.rootNode); // eslint-disable-next-line no-use-before-define oldNode = stepMeta.rootNode; if (options.onStepCb) options.onStepCb(stepMeta); }, }; // // Apply simplify rules to the expression. // const originalNode = clone(node); let oldNode = originalNode; let goOn = true; let isShuffled = false; let expressionAsTextAfterShuffle = null; // Step through the math expression until nothing changes. while (goOn) { goOn = false; // Process last changed node or root. const nodeBox = simplifyCtx.lastChangedNodeBox || simplifyCtx; const nodeIdx = simplifyCtx.lastChangedNodeIdx || 'rootNode'; const nodeParent = simplifyCtx.lastChangedNodeParent; // Apply common rules first. for (const ruleIdx in theIndexRulePool.POOLED_TOGETHER_POOL_OF_RULES) { if (options[`disable${ruleIdx}`]) continue const fct = theIndexRulePool.POOLED_TOGETHER_POOL_OF_RULES[ruleIdx]; if (_applyRulesOnChildNode(fct, nodeBox, nodeIdx, simplifyCtx, nodeParent)) { goOn = true; isShuffled = false; break } } // Back to root if child node went to dead-end. if (!goOn && simplifyCtx.lastChangedNodeBox) { // Reset current node to the root. goOn = true; simplifyCtx.lastChangedNodeBox = null; simplifyCtx.lastChangedNodeIdx = null; simplifyCtx.lastChangedNodeParent = null; } // Try refactor whole expression from scratch if still dead-end. if (!goOn && !isShuffled) { const exprAsText = print.printAscii(simplifyCtx.rootNode); if (expressionAsTextAfterShuffle !== exprAsText) { // Try shuffle only once after each dead-end. isShuffled = true; goOn = true; // Reset current node to the root. simplifyCtx.lastChangedNodeBox = null; simplifyCtx.lastChangedNodeIdx = null; simplifyCtx.lastChangedNodeParent = null; simplifyCtx.rootNode = parseText.parseText(exprAsText); expressionAsTextAfterShuffle = exprAsText; } } // Limit iterations to avoid potentially hung-up. if (simplifyCtx.iterIdx === MAX_STEP_COUNT) { console.error(`Math error: Potential infinite loop for expression: ${print.printAscii(originalNode)}`); goOn = false; } } // // Post-process node. // node = oldNode; // Possible improvement: Optimize it. const newNode = kemuSortArgs(clone(oldNode)); const oldNodeAsText = print.printAscii(oldNode); const newNodeAsText = print.printAscii(newNode); if (oldNodeAsText !== newNodeAsText) { if (options.onStepCb) { options.onStepCb({ changeType: ChangeTypes.ChangeTypes.REARRANGE_COEFF, rootNode: newNode, }); } node = newNode; } } return node } function logSteps(nodeStatus) { console.log(nodeStatus.changeType); console.log(print.printAscii(nodeStatus.rootNode)); if (nodeStatus.substeps.length > 0) { console.log('substeps: '); nodeStatus.substeps.forEach((substep, idx) => { console.log('...', idx, '|', print.printAscii(substep.rootNode)); }); } } const oldApi = function (node, options = {}) { const steps = []; stepThrough(node, { isDebugMode: options.isDebugMode, expressionCtx: options.expressionCtx, onStepCb: (oneStep) => { steps.push(oneStep); }, }); return steps }; const newApi = function (node, options = {}) { if (options.getAllNextStepPossibilities) return getAllRuleResponsesForNextStep.getAllRuleResponsesForNextStep(node, options) else return stepThrough(node, options) }; const stepThrough$1 = { oldApi, newApi, }; exports.default = stepThrough$1; exports.newApi = newApi; exports.oldApi = oldApi;