@@ -96,6 +96,9 @@ export class CalculatorService {
9696 this.toString = function() {
9797 return "T";
9898 }
99+ this.toStringInner = function() {
100+ return "T";
101+ }
99102 this.equals = function(object) {
100103 return object == this;
101104 }
@@ -111,6 +114,9 @@ export class CalculatorService {
111114 this.toString = function() {
112115 return "F";
113116 }
117+ this.toStringInner = function() {
118+ return "F";
119+ }
114120 this.equals = function(object) {
115121 return object == this;
116122 }
@@ -128,6 +134,9 @@ export class CalculatorService {
128134 Variable.prototype.toString = function() {
129135 return this.variableName;
130136 }
137+ Variable.prototype.toStringInner = function() {
138+ return this.variableName;
139+ }
131140 Variable.prototype.equals = function(object) {
132141 if (!(object instanceof Variable)) {
133142 return false;
@@ -142,11 +151,43 @@ export class CalculatorService {
142151 return exp instanceof Variable;
143152 }
144153
154+ // Helper to check if an expression needs parentheses based on context
155+ function needsParens(child, parent) {
156+ // Variables, True, False, and NOT expressions don't need parens
157+ if (child instanceof Variable || child === True || child === False) {
158+ return false;
159+ }
160+ if (child instanceof NotExpression) {
161+ return false;
162+ }
163+ // If no parent, we're at the top level - no parens needed
164+ if (!parent) {
165+ return false;
166+ }
167+ // AND inside OR needs parens (lower precedence)
168+ if (child instanceof AndExpression && parent instanceof OrExpression) {
169+ return true;
170+ }
171+ // OR inside AND needs parens (for clarity)
172+ if (child instanceof OrExpression && parent instanceof AndExpression) {
173+ return true;
174+ }
175+ return false;
176+ }
177+
145178 function NotExpression(subs) {
146179 this.subs = subs;
147180 }
148- NotExpression.prototype.toString = function() {
149- return SYMBOL.NOT + this.subs[0];
181+ NotExpression.prototype.toString = function(parent) {
182+ var inner = this.subs[0];
183+ // NOT of compound expressions needs parens around the inner part
184+ if (inner instanceof AndExpression || inner instanceof OrExpression) {
185+ return SYMBOL.NOT + "(" + inner.toStringInner() + ")";
186+ }
187+ return SYMBOL.NOT + inner.toString(this);
188+ };
189+ NotExpression.prototype.toStringInner = function() {
190+ return this.toString(null);
150191 };
151192 NotExpression.prototype.equals = function(object) {
152193 if (!(object instanceof NotExpression)) {
@@ -162,8 +203,25 @@ export class CalculatorService {
162203 this.subs = subs;
163204 }
164205
165- OrExpression.prototype.toString = function() {
166- return Utils.parenthesize(Utils.arrayToString(this.subs, " " + SYMBOL.OR + " "));
206+ OrExpression.prototype.toString = function(parent) {
207+ var inner = this.toStringInner();
208+ // Only add parens if inside an AND expression
209+ if (parent instanceof AndExpression) {
210+ return "(" + inner + ")";
211+ }
212+ return inner;
213+ }
214+ OrExpression.prototype.toStringInner = function() {
215+ var parts = [];
216+ for (var i = 0; i < this.subs.length; i++) {
217+ var sub = this.subs[i];
218+ if (sub.toString) {
219+ parts.push(sub.toString(this));
220+ } else {
221+ parts.push(sub.toString());
222+ }
223+ }
224+ return parts.join(" " + SYMBOL.OR + " ");
167225 }
168226 OrExpression.prototype.contains = function(object) {
169227 if (!(object instanceof OrExpression)) {
@@ -231,8 +289,25 @@ export class CalculatorService {
231289 this.subs = subs;
232290 }
233291
234- AndExpression.prototype.toString = function() {
235- return Utils.parenthesize(Utils.arrayToString(this.subs, " " + SYMBOL.AND + " "));
292+ AndExpression.prototype.toString = function(parent) {
293+ var inner = this.toStringInner();
294+ // Only add parens if inside an OR expression (AND has higher precedence)
295+ if (parent instanceof OrExpression) {
296+ return "(" + inner + ")";
297+ }
298+ return inner;
299+ }
300+ AndExpression.prototype.toStringInner = function() {
301+ var parts = [];
302+ for (var i = 0; i < this.subs.length; i++) {
303+ var sub = this.subs[i];
304+ if (sub.toString) {
305+ parts.push(sub.toString(this));
306+ } else {
307+ parts.push(sub.toString());
308+ }
309+ }
310+ return parts.join(" " + SYMBOL.AND + " ");
236311 }
237312 AndExpression.prototype.contains = function(object) {
238313 if (!(object instanceof AndExpression)) {
@@ -1173,7 +1248,10 @@ export class CalculatorService {
11731248 universalBound: "Universal Bound",
11741249 deMorgans: "De Morgan's",
11751250 absorption: "Absorption",
1176- negationsOfTF: "Negations of T and F"
1251+ negationsOfTF: "Negations of T and F",
1252+ XOR: "XOR Definition",
1253+ IMP: "Implication Definition",
1254+ BIMP: "Biconditional Definition"
11771255 };
11781256 const shortDict = {
11791257 commutative: "COM",
@@ -1186,7 +1264,10 @@ export class CalculatorService {
11861264 universalBound: "UB",
11871265 deMorgans: "DM",
11881266 absorption: "ABS",
1189- negationsOfTF: "NTF"
1267+ negationsOfTF: "NTF",
1268+ XOR: "XOR",
1269+ IMP: "IMP",
1270+ BIMP: "BIMP"
11901271 };
11911272
11921273 function getLawName(lawFunction, useShort = Settings.getValue("short")) {
@@ -1347,22 +1428,40 @@ export class CalculatorService {
13471428 VariableManager.clear();
13481429 var parsed = Parser.parse(expr);
13491430 var steps = Equivalency.simplify(parsed);
1431+
1432+ // Check if original expression contained XOR, IF, or IFF that got converted
1433+ var hasXOR = expr.includes(SYMBOL.XOR) || /\bxor\b/i.test(expr);
1434+ var hasIF = expr.includes(SYMBOL.IF) || /->/g.test(expr) || /\bthen\b/i.test(expr);
1435+ var hasIFF = expr.includes(SYMBOL.IFF) || /<->/g.test(expr);
1436+
1437+ // Add conversion steps at the beginning if needed
1438+ var conversionSteps = [];
1439+ if (hasXOR || hasIF || hasIFF) {
1440+ // The first step after parsing shows the converted form
1441+ var conversionLaw = hasXOR ? "XOR" : (hasIF ? "IMP" : "BIMP");
1442+ conversionSteps.push({
1443+ expression: parsed.toString(),
1444+ law: "by " + conversionLaw,
1445+ lawName: conversionLaw
1446+ });
1447+ }
1448+
13501449 // Return previous shape but also allow frontend to request AST tokenization via annotate/tokenize helpers
13511450 return {
13521451 originalExpression: expr,
13531452 simplifiedExpression: steps[steps.length - 1].result,
1354- steps: steps
1453+ steps: conversionSteps.concat( steps
13551454 .filter(function(s) { return s.lawString !== 'result'; })
13561455 .map(function(step) {
13571456 return {
13581457 expression: step.result,
13591458 law: step.lawString,
13601459 lawName: step.lawString.replace('by ', '')
13611460 };
1362- }),
1363- stepLines: steps
1461+ })) ,
1462+ stepLines: (hasXOR || hasIF || hasIFF ? [parsed.toString() + "by " + (hasXOR ? "XOR" : (hasIF ? "IMP" : "BIMP"))] : []).concat( steps
13641463 .filter(function(s) { return s.lawString !== 'result'; })
1365- .map(function(step) { return step.result + step.lawString; })
1464+ .map(function(step) { return step.result + step.lawString; }))
13661465 };
13671466 };
13681467
0 commit comments