@teachinglab/omd 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/api/configuration-options.md +198 -198
- package/docs/api/eventManager.md +82 -82
- package/docs/api/focusFrameManager.md +144 -144
- package/docs/api/index.md +105 -105
- package/docs/api/main.md +62 -62
- package/docs/api/omdBinaryExpressionNode.md +86 -86
- package/docs/api/omdCanvas.md +83 -83
- package/docs/api/omdConfigManager.md +112 -112
- package/docs/api/omdConstantNode.md +52 -52
- package/docs/api/omdDisplay.md +87 -87
- package/docs/api/omdEquationNode.md +174 -174
- package/docs/api/omdEquationSequenceNode.md +258 -258
- package/docs/api/omdEquationStack.md +156 -156
- package/docs/api/omdFunctionNode.md +82 -82
- package/docs/api/omdGroupNode.md +78 -78
- package/docs/api/omdHelpers.md +87 -87
- package/docs/api/omdLeafNode.md +85 -85
- package/docs/api/omdNode.md +201 -201
- package/docs/api/omdOperationDisplayNode.md +117 -117
- package/docs/api/omdOperatorNode.md +91 -91
- package/docs/api/omdParenthesisNode.md +133 -133
- package/docs/api/omdPopup.md +191 -191
- package/docs/api/omdPowerNode.md +131 -131
- package/docs/api/omdRationalNode.md +144 -144
- package/docs/api/omdSimplification.md +78 -78
- package/docs/api/omdSqrtNode.md +144 -144
- package/docs/api/omdStepVisualizer.md +146 -146
- package/docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/docs/api/omdStepVisualizerLayout.md +70 -70
- package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/docs/api/omdTranscriptionService.md +95 -95
- package/docs/api/omdTreeDiff.md +169 -169
- package/docs/api/omdUnaryExpressionNode.md +137 -137
- package/docs/api/omdUtilities.md +82 -82
- package/docs/api/omdVariableNode.md +123 -123
- package/omd/nodes/omdConstantNode.js +141 -141
- package/omd/nodes/omdGroupNode.js +67 -67
- package/omd/nodes/omdLeafNode.js +76 -76
- package/omd/nodes/omdOperatorNode.js +108 -108
- package/omd/nodes/omdParenthesisNode.js +292 -292
- package/omd/nodes/omdPowerNode.js +235 -235
- package/omd/nodes/omdRationalNode.js +295 -295
- package/omd/nodes/omdVariableNode.js +122 -122
- package/omd/simplification/omdSimplification.js +140 -140
- package/omd/step-visualizer/omdStepVisualizer.js +947 -947
- package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
- package/package.json +1 -1
- package/src/index.js +11 -0
- package/src/omdBalanceHanger.js +2 -1
- package/src/omdEquation.js +1 -1
- package/src/omdNumber.js +1 -1
- package/src/omdNumberLine.js +13 -7
- package/src/omdRatioChart.js +11 -0
- package/src/omdShapes.js +1 -1
- package/src/omdTapeDiagram.js +1 -1
- package/src/omdTerm.js +1 -1
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import { omdLeafNode } from "./omdLeafNode.js";
|
|
2
|
-
import { omdColor } from "../../src/omdColor.js";
|
|
3
|
-
import { jsvgTextLine } from '@teachinglab/jsvg';
|
|
4
|
-
/**
|
|
5
|
-
* Leaf node that represents a variable.
|
|
6
|
-
* @extends omdLeafNode
|
|
7
|
-
*/
|
|
8
|
-
export class omdVariableNode extends omdLeafNode {
|
|
9
|
-
/**
|
|
10
|
-
* Creates a leaf node from the AST data.
|
|
11
|
-
* @param {Object} astNodeData - The AST node containing leaf information.
|
|
12
|
-
*/
|
|
13
|
-
constructor(nodeData) {
|
|
14
|
-
super(nodeData);
|
|
15
|
-
this.type = "omdVariableNode";
|
|
16
|
-
|
|
17
|
-
this.name = this.parseName(nodeData);
|
|
18
|
-
|
|
19
|
-
this.textElement = super.createTextElement(this.name);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
parseName(nodeData) {
|
|
23
|
-
if (typeof nodeData === "string")
|
|
24
|
-
return nodeData;
|
|
25
|
-
|
|
26
|
-
return nodeData.name;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
parseType() {
|
|
30
|
-
return "variable";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Calculates the dimensions of the node.
|
|
35
|
-
* Adds padding around the node.
|
|
36
|
-
* @override
|
|
37
|
-
*/
|
|
38
|
-
computeDimensions() {
|
|
39
|
-
super.computeDimensions();
|
|
40
|
-
|
|
41
|
-
const ratio = this.getFontSize() / this.getRootFontSize();
|
|
42
|
-
const padding = 4 * ratio;
|
|
43
|
-
let paddedWidth = this.width + padding;
|
|
44
|
-
let paddedHeight = this.height + padding;
|
|
45
|
-
this.setWidthAndHeight(paddedWidth, paddedHeight);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Updates the layout of the node.
|
|
50
|
-
* @override
|
|
51
|
-
*/
|
|
52
|
-
updateLayout() {
|
|
53
|
-
super.updateLayout();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Converts the omdVariableNode to a math.js AST node.
|
|
58
|
-
* @returns {Object} A math.js-compatible AST node.
|
|
59
|
-
*/
|
|
60
|
-
toMathJSNode() {
|
|
61
|
-
const astNode = {
|
|
62
|
-
type: 'SymbolNode',
|
|
63
|
-
name: this.name,
|
|
64
|
-
id: this.id,
|
|
65
|
-
provenance: this.provenance
|
|
66
|
-
};
|
|
67
|
-
astNode.clone = function() {
|
|
68
|
-
return { ...this };
|
|
69
|
-
};
|
|
70
|
-
return astNode;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
highlight(color) {
|
|
74
|
-
super.highlight(color);
|
|
75
|
-
if (this.textElement) {
|
|
76
|
-
this.textElement.setFillColor(omdColor.white);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
clearProvenanceHighlights() {
|
|
81
|
-
super.clearProvenanceHighlights();
|
|
82
|
-
if (this.textElement) {
|
|
83
|
-
this.textElement.setFillColor(omdColor.text);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Converts the variable node to a string.
|
|
89
|
-
* @returns {string} The name of the variable.
|
|
90
|
-
*/
|
|
91
|
-
toString() {
|
|
92
|
-
return this.name;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Evaluates the variable by looking up its value in a map.
|
|
97
|
-
* @param {Object} variables - A map of variable names to their numeric values.
|
|
98
|
-
* @returns {number} The value of the variable.
|
|
99
|
-
*/
|
|
100
|
-
evaluate(variables = {}) {
|
|
101
|
-
if (Object.prototype.hasOwnProperty.call(variables, this.name)) {
|
|
102
|
-
return variables[this.name];
|
|
103
|
-
}
|
|
104
|
-
throw new Error(`Variable '${this.name}' is not defined.`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Create a variable node from a name.
|
|
111
|
-
* @param {string} name - The variable name
|
|
112
|
-
* @returns {omdVariableNode}
|
|
113
|
-
* @static
|
|
114
|
-
*/
|
|
115
|
-
static fromName(name) {
|
|
116
|
-
// Create a minimal AST-like object for the constructor
|
|
117
|
-
const astNodeData = {
|
|
118
|
-
type: 'SymbolNode',
|
|
119
|
-
name: name
|
|
120
|
-
};
|
|
121
|
-
return new omdVariableNode(astNodeData);
|
|
122
|
-
}
|
|
1
|
+
import { omdLeafNode } from "./omdLeafNode.js";
|
|
2
|
+
import { omdColor } from "../../src/omdColor.js";
|
|
3
|
+
import { jsvgTextLine } from '@teachinglab/jsvg';
|
|
4
|
+
/**
|
|
5
|
+
* Leaf node that represents a variable.
|
|
6
|
+
* @extends omdLeafNode
|
|
7
|
+
*/
|
|
8
|
+
export class omdVariableNode extends omdLeafNode {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a leaf node from the AST data.
|
|
11
|
+
* @param {Object} astNodeData - The AST node containing leaf information.
|
|
12
|
+
*/
|
|
13
|
+
constructor(nodeData) {
|
|
14
|
+
super(nodeData);
|
|
15
|
+
this.type = "omdVariableNode";
|
|
16
|
+
|
|
17
|
+
this.name = this.parseName(nodeData);
|
|
18
|
+
|
|
19
|
+
this.textElement = super.createTextElement(this.name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
parseName(nodeData) {
|
|
23
|
+
if (typeof nodeData === "string")
|
|
24
|
+
return nodeData;
|
|
25
|
+
|
|
26
|
+
return nodeData.name;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
parseType() {
|
|
30
|
+
return "variable";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Calculates the dimensions of the node.
|
|
35
|
+
* Adds padding around the node.
|
|
36
|
+
* @override
|
|
37
|
+
*/
|
|
38
|
+
computeDimensions() {
|
|
39
|
+
super.computeDimensions();
|
|
40
|
+
|
|
41
|
+
const ratio = this.getFontSize() / this.getRootFontSize();
|
|
42
|
+
const padding = 4 * ratio;
|
|
43
|
+
let paddedWidth = this.width + padding;
|
|
44
|
+
let paddedHeight = this.height + padding;
|
|
45
|
+
this.setWidthAndHeight(paddedWidth, paddedHeight);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Updates the layout of the node.
|
|
50
|
+
* @override
|
|
51
|
+
*/
|
|
52
|
+
updateLayout() {
|
|
53
|
+
super.updateLayout();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Converts the omdVariableNode to a math.js AST node.
|
|
58
|
+
* @returns {Object} A math.js-compatible AST node.
|
|
59
|
+
*/
|
|
60
|
+
toMathJSNode() {
|
|
61
|
+
const astNode = {
|
|
62
|
+
type: 'SymbolNode',
|
|
63
|
+
name: this.name,
|
|
64
|
+
id: this.id,
|
|
65
|
+
provenance: this.provenance
|
|
66
|
+
};
|
|
67
|
+
astNode.clone = function() {
|
|
68
|
+
return { ...this };
|
|
69
|
+
};
|
|
70
|
+
return astNode;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
highlight(color) {
|
|
74
|
+
super.highlight(color);
|
|
75
|
+
if (this.textElement) {
|
|
76
|
+
this.textElement.setFillColor(omdColor.white);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
clearProvenanceHighlights() {
|
|
81
|
+
super.clearProvenanceHighlights();
|
|
82
|
+
if (this.textElement) {
|
|
83
|
+
this.textElement.setFillColor(omdColor.text);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Converts the variable node to a string.
|
|
89
|
+
* @returns {string} The name of the variable.
|
|
90
|
+
*/
|
|
91
|
+
toString() {
|
|
92
|
+
return this.name;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Evaluates the variable by looking up its value in a map.
|
|
97
|
+
* @param {Object} variables - A map of variable names to their numeric values.
|
|
98
|
+
* @returns {number} The value of the variable.
|
|
99
|
+
*/
|
|
100
|
+
evaluate(variables = {}) {
|
|
101
|
+
if (Object.prototype.hasOwnProperty.call(variables, this.name)) {
|
|
102
|
+
return variables[this.name];
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Variable '${this.name}' is not defined.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create a variable node from a name.
|
|
111
|
+
* @param {string} name - The variable name
|
|
112
|
+
* @returns {omdVariableNode}
|
|
113
|
+
* @static
|
|
114
|
+
*/
|
|
115
|
+
static fromName(name) {
|
|
116
|
+
// Create a minimal AST-like object for the constructor
|
|
117
|
+
const astNodeData = {
|
|
118
|
+
type: 'SymbolNode',
|
|
119
|
+
name: name
|
|
120
|
+
};
|
|
121
|
+
return new omdVariableNode(astNodeData);
|
|
122
|
+
}
|
|
123
123
|
}
|
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
import { getRulesForNode } from './simplificationRules.js';
|
|
2
|
-
import { setSimplifyStep } from '../nodes/omdNode.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Finds all simplification opportunities within an expression tree.
|
|
6
|
-
* Now much faster - only checks relevant rules for each node type!
|
|
7
|
-
* @param {omdNode} rootNode - The root of the expression tree to search.
|
|
8
|
-
* @returns {Array<Object>} Array of simplification opportunities.
|
|
9
|
-
*/
|
|
10
|
-
export function findSimplificationOpportunities(rootNode) {
|
|
11
|
-
const opportunities = [];
|
|
12
|
-
const visitedNodes = new Set();
|
|
13
|
-
|
|
14
|
-
function traverse(node) {
|
|
15
|
-
if (!node || visitedNodes.has(node)) return;
|
|
16
|
-
visitedNodes.add(node);
|
|
17
|
-
|
|
18
|
-
// Traverse children first (depth-first)
|
|
19
|
-
if (node.childList && node.childList.length > 0) {
|
|
20
|
-
|
|
21
|
-
for (const child of node.childList) {
|
|
22
|
-
traverse(child);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Only check rules that apply to this node type
|
|
27
|
-
const relevantRules = getRulesForNode(node);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
for (const rule of relevantRules) {
|
|
31
|
-
const ruleData = rule.canApply(node);
|
|
32
|
-
if (ruleData) {
|
|
33
|
-
|
|
34
|
-
opportunities.push({ node, rule, ruleData });
|
|
35
|
-
break; // Only apply the first matching rule
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
traverse(rootNode);
|
|
41
|
-
return opportunities;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function simplifyStep(rootNode) {
|
|
45
|
-
let foldedCount = 0;
|
|
46
|
-
let newRoot = rootNode;
|
|
47
|
-
let historyEntry = null;
|
|
48
|
-
|
|
49
|
-
// Find all simplification opportunities
|
|
50
|
-
const opportunities = findSimplificationOpportunities(rootNode);
|
|
51
|
-
|
|
52
|
-
if (opportunities.length > 0) {
|
|
53
|
-
// Apply the first opportunity
|
|
54
|
-
const opportunity = opportunities[0];
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const result = opportunity.rule.apply(opportunity.node, opportunity.ruleData, rootNode);
|
|
58
|
-
|
|
59
|
-
if (result && result.success && result.newRoot) {
|
|
60
|
-
newRoot = result.newRoot;
|
|
61
|
-
foldedCount = 1;
|
|
62
|
-
historyEntry = result.historyEntry || {
|
|
63
|
-
name: opportunity.rule.name,
|
|
64
|
-
affectedNodes: [rootNode.id],
|
|
65
|
-
message: `Applied ${opportunity.rule.name}`
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error(`Error applying rule '${opportunity.rule.name}':`, error);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { newRoot, foldedCount, historyEntry };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Simplifies an entire mathematical expression tree by repeatedly applying rules.
|
|
78
|
-
* @param {omdNode} rootNode - The root node of the expression to simplify.
|
|
79
|
-
* @returns {{foldedCount: number, newRoot: omdNode}}
|
|
80
|
-
*/
|
|
81
|
-
export function simplifyExpression(rootNode) {
|
|
82
|
-
let totalFolded = 0;
|
|
83
|
-
let currentRoot = rootNode;
|
|
84
|
-
let iterations = 0;
|
|
85
|
-
const maxIterations = 50; // Prevent infinite loops
|
|
86
|
-
|
|
87
|
-
while (iterations < maxIterations) {
|
|
88
|
-
const { foldedCount, newRoot } = simplifyStep(currentRoot);
|
|
89
|
-
|
|
90
|
-
if (foldedCount === 0) {
|
|
91
|
-
break; // No more simplifications possible
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
currentRoot = newRoot;
|
|
95
|
-
totalFolded += foldedCount;
|
|
96
|
-
iterations++;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Return a fresh clone if any simplifications occurred
|
|
100
|
-
if (totalFolded > 0) {
|
|
101
|
-
const cleanRoot = currentRoot.clone();
|
|
102
|
-
cleanRoot.setFontSize(rootNode.getFontSize());
|
|
103
|
-
return { foldedCount: totalFolded, newRoot: cleanRoot };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return { foldedCount: totalFolded, newRoot: currentRoot };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Debug function to show which rules are available for a node
|
|
111
|
-
* @param {omdNode} node - The node to check
|
|
112
|
-
* @returns {Array<string>} Array of rule names
|
|
113
|
-
*/
|
|
114
|
-
export function getAvailableRulesForNode(node) {
|
|
115
|
-
const rules = getRulesForNode(node);
|
|
116
|
-
return rules.map(rule => rule.name);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Debug function to show which rules can be applied to a node
|
|
121
|
-
* @param {omdNode} node - The node to check
|
|
122
|
-
* @returns {Array<string>} Array of applicable rule names
|
|
123
|
-
*/
|
|
124
|
-
export function getApplicableRulesForNode(node) {
|
|
125
|
-
const rules = getRulesForNode(node);
|
|
126
|
-
const applicable = [];
|
|
127
|
-
|
|
128
|
-
for (const rule of rules) {
|
|
129
|
-
try {
|
|
130
|
-
const ruleData = rule.canApply(node);
|
|
131
|
-
if (ruleData) {
|
|
132
|
-
applicable.push(rule.name);
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return applicable;
|
|
139
|
-
}
|
|
140
|
-
|
|
1
|
+
import { getRulesForNode } from './simplificationRules.js';
|
|
2
|
+
import { setSimplifyStep } from '../nodes/omdNode.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Finds all simplification opportunities within an expression tree.
|
|
6
|
+
* Now much faster - only checks relevant rules for each node type!
|
|
7
|
+
* @param {omdNode} rootNode - The root of the expression tree to search.
|
|
8
|
+
* @returns {Array<Object>} Array of simplification opportunities.
|
|
9
|
+
*/
|
|
10
|
+
export function findSimplificationOpportunities(rootNode) {
|
|
11
|
+
const opportunities = [];
|
|
12
|
+
const visitedNodes = new Set();
|
|
13
|
+
|
|
14
|
+
function traverse(node) {
|
|
15
|
+
if (!node || visitedNodes.has(node)) return;
|
|
16
|
+
visitedNodes.add(node);
|
|
17
|
+
|
|
18
|
+
// Traverse children first (depth-first)
|
|
19
|
+
if (node.childList && node.childList.length > 0) {
|
|
20
|
+
|
|
21
|
+
for (const child of node.childList) {
|
|
22
|
+
traverse(child);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Only check rules that apply to this node type
|
|
27
|
+
const relevantRules = getRulesForNode(node);
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
for (const rule of relevantRules) {
|
|
31
|
+
const ruleData = rule.canApply(node);
|
|
32
|
+
if (ruleData) {
|
|
33
|
+
|
|
34
|
+
opportunities.push({ node, rule, ruleData });
|
|
35
|
+
break; // Only apply the first matching rule
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
traverse(rootNode);
|
|
41
|
+
return opportunities;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function simplifyStep(rootNode) {
|
|
45
|
+
let foldedCount = 0;
|
|
46
|
+
let newRoot = rootNode;
|
|
47
|
+
let historyEntry = null;
|
|
48
|
+
|
|
49
|
+
// Find all simplification opportunities
|
|
50
|
+
const opportunities = findSimplificationOpportunities(rootNode);
|
|
51
|
+
|
|
52
|
+
if (opportunities.length > 0) {
|
|
53
|
+
// Apply the first opportunity
|
|
54
|
+
const opportunity = opportunities[0];
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const result = opportunity.rule.apply(opportunity.node, opportunity.ruleData, rootNode);
|
|
58
|
+
|
|
59
|
+
if (result && result.success && result.newRoot) {
|
|
60
|
+
newRoot = result.newRoot;
|
|
61
|
+
foldedCount = 1;
|
|
62
|
+
historyEntry = result.historyEntry || {
|
|
63
|
+
name: opportunity.rule.name,
|
|
64
|
+
affectedNodes: [rootNode.id],
|
|
65
|
+
message: `Applied ${opportunity.rule.name}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error(`Error applying rule '${opportunity.rule.name}':`, error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { newRoot, foldedCount, historyEntry };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Simplifies an entire mathematical expression tree by repeatedly applying rules.
|
|
78
|
+
* @param {omdNode} rootNode - The root node of the expression to simplify.
|
|
79
|
+
* @returns {{foldedCount: number, newRoot: omdNode}}
|
|
80
|
+
*/
|
|
81
|
+
export function simplifyExpression(rootNode) {
|
|
82
|
+
let totalFolded = 0;
|
|
83
|
+
let currentRoot = rootNode;
|
|
84
|
+
let iterations = 0;
|
|
85
|
+
const maxIterations = 50; // Prevent infinite loops
|
|
86
|
+
|
|
87
|
+
while (iterations < maxIterations) {
|
|
88
|
+
const { foldedCount, newRoot } = simplifyStep(currentRoot);
|
|
89
|
+
|
|
90
|
+
if (foldedCount === 0) {
|
|
91
|
+
break; // No more simplifications possible
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
currentRoot = newRoot;
|
|
95
|
+
totalFolded += foldedCount;
|
|
96
|
+
iterations++;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Return a fresh clone if any simplifications occurred
|
|
100
|
+
if (totalFolded > 0) {
|
|
101
|
+
const cleanRoot = currentRoot.clone();
|
|
102
|
+
cleanRoot.setFontSize(rootNode.getFontSize());
|
|
103
|
+
return { foldedCount: totalFolded, newRoot: cleanRoot };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { foldedCount: totalFolded, newRoot: currentRoot };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Debug function to show which rules are available for a node
|
|
111
|
+
* @param {omdNode} node - The node to check
|
|
112
|
+
* @returns {Array<string>} Array of rule names
|
|
113
|
+
*/
|
|
114
|
+
export function getAvailableRulesForNode(node) {
|
|
115
|
+
const rules = getRulesForNode(node);
|
|
116
|
+
return rules.map(rule => rule.name);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Debug function to show which rules can be applied to a node
|
|
121
|
+
* @param {omdNode} node - The node to check
|
|
122
|
+
* @returns {Array<string>} Array of applicable rule names
|
|
123
|
+
*/
|
|
124
|
+
export function getApplicableRulesForNode(node) {
|
|
125
|
+
const rules = getRulesForNode(node);
|
|
126
|
+
const applicable = [];
|
|
127
|
+
|
|
128
|
+
for (const rule of rules) {
|
|
129
|
+
try {
|
|
130
|
+
const ruleData = rule.canApply(node);
|
|
131
|
+
if (ruleData) {
|
|
132
|
+
applicable.push(rule.name);
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return applicable;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
141
|
setSimplifyStep(simplifyStep);
|