Skip to content

Commit 1a0fd7e

Browse files
committed
Update AngouriMath and add logical operators
1 parent 574d4d2 commit 1a0fd7e

File tree

4 files changed

+173
-11
lines changed

4 files changed

+173
-11
lines changed

CSharpMath.Evaluation.Tests/EvaluationTests.cs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ void Test(string input) {
5252
[InlineData(".9876543210", "0.9876543210", "0.9876543210")]
5353
[InlineData("1234.5678", @"\frac{6172839}{5000}", @"\frac{6172839}{5000}")]
5454
[InlineData(@"\infty", @"\infty ", @"\infty ")]
55+
[InlineData(@"1_2", @"1", @"1")]
56+
[InlineData(@"10_2", @"2", @"2")]
57+
[InlineData(@"1._2", @"1", @"1")]
58+
[InlineData(@"1.1_2", @"\frac{3}{2}", @"\frac{3}{2}")]
59+
[InlineData(@".1_3", @"\frac{1}{3}", @"\frac{1}{3}")]
60+
[InlineData(@"10_3", @"3", @"3")]
61+
[InlineData(@"10.1_3", @"\frac{10}{3}", @"\frac{10}{3}")]
5562
public void Numbers(string input, string converted, string output) =>
5663
Test(input, converted, output);
5764
[Theory]
@@ -174,10 +181,10 @@ public void Numbers(string input, string converted, string output) =>
174181
[InlineData("a/-a", @"\frac{a}{-a}", "-1")]
175182
[InlineData("+a/+a", @"\frac{a}{a}", "1")]
176183
[InlineData("-a/-a", @"\frac{-a}{-a}", "1")]
177-
[InlineData("-2+-2+-2", @"-2-2-2", "-6")]
178-
[InlineData("-2--2--2", @"-2--2--2", "2")]
179-
[InlineData("-2*-2*-2", @"\left( -1\right) \cdot 2\cdot \left( -1\right) \cdot 2\cdot \left( -1\right) \cdot 2", "-8")]
180-
[InlineData("-2/-2/-2", @"\frac{\frac{-2}{-2}}{-2}", @"\frac{-1}{2}")]
184+
[InlineData(@"-2+-2+-2", @"-2-2-2", "-6")]
185+
[InlineData(@"-2--2--2", @"-2--2--2", "2")]
186+
[InlineData(@"-2*-2*-2", @"\left( -1\right) \cdot 2\cdot \left( -1\right) \cdot 2\cdot \left( -1\right) \cdot 2", "-8")]
187+
[InlineData(@"-2/-2/-2", @"\frac{\frac{-2}{-2}}{-2}", @"\frac{-1}{2}")]
181188
public void UnaryOperators(string latex, string converted, string result) => Test(latex, converted, result);
182189
[Theory]
183190
[InlineData(@"9\%", @"\frac{9}{100}", @"\frac{9}{100}")]
@@ -556,7 +563,6 @@ public void Intervals(string latex, string converted, string result) {
556563
[InlineData(@"1\degree_7", "Subscripts are unsupported for Ordinary °")]
557564
[InlineData(@"\dagger_8", "Unsupported Unary Operator †")]
558565
[InlineData(@".", "Invalid number: .")]
559-
[InlineData(@"1._2", "Subscripts are unsupported for Number 1.")]
560566
[InlineData(@"..", "Invalid number: ..")]
561567
[InlineData(@"1..", "Invalid number: 1..")]
562568
[InlineData(@"..1", "Invalid number: ..1")]
@@ -703,5 +709,35 @@ public void Error(string badLaTeX, string error) =>
703709
[InlineData("i+2i", @"i+2i")]
704710
public void SimpleArithmeticSyntax(string simpleSyntax, string latex) =>
705711
Assert.Equal(latex, LaTeXParser.MathListToLaTeX(Evaluation.Visualize((Entity)simpleSyntax)).ToString());
712+
[Theory]
713+
[InlineData(@"\operatorname{true}", @"\operatorname{True} ", @"\operatorname{True} ", Skip = "Awaiting AngouriMath update")]
714+
[InlineData(@"\operatorname{false}", @"\operatorname{False} ", @"\operatorname{False} ", Skip = "Awaiting AngouriMath update")]
715+
[InlineData(@"\neg\operatorname{true}", @"\neg \operatorname{True} ", @"\operatorname{False} ", Skip = "Awaiting AngouriMath update")]
716+
[InlineData(@"\neg\operatorname{false}", @"\neg \operatorname{False} ", @"\operatorname{True} ", Skip = "Awaiting AngouriMath update")]
717+
[InlineData(@"\operatorname{true}\land\operatorname{true}", @"\operatorname{True} \land \operatorname{True} ", @"\operatorname{True} ", Skip = "Awaiting AngouriMath update")]
718+
[InlineData(@"\operatorname{true}\land\operatorname{false}", @"\operatorname{True} \land \operatorname{False} ", @"\operatorname{False} ", Skip = "Awaiting AngouriMath update")]
719+
[InlineData(@"\operatorname{false}\land\operatorname{true}", @"\operatorname{False} \land \operatorname{True} ", @"\operatorname{False} ", Skip = "Awaiting AngouriMath update")]
720+
[InlineData(@"\operatorname{false}\land\operatorname{false}", @"\operatorname{False} \land \operatorname{False} ", @"\operatorname{False} ", Skip = "Awaiting AngouriMath update")]
721+
[InlineData(@"\operatorname{true}\lor\operatorname{true}", @"\operatorname{True} \lor \operatorname{True} ", @"\operatorname{True} ", Skip = "Awaiting AngouriMath update")]
722+
[InlineData(@"\operatorname{true}\lor\operatorname{false}", @"\operatorname{True} \lor \operatorname{False} ", @"\operatorname{True} ", Skip = "Awaiting AngouriMath update")]
723+
[InlineData(@"\operatorname{false}\lor\operatorname{true}", @"\operatorname{False} \lor \operatorname{True} ", @"\operatorname{True} ", Skip = "Awaiting AngouriMath update")]
724+
[InlineData(@"\operatorname{false}\lor\operatorname{false}", @"\operatorname{False} \lor \operatorname{False} ", @"\operatorname{False} ", Skip = "Awaiting AngouriMath update")]
725+
[InlineData(@"\operatorname{true}\oplus\operatorname{true}", @"\operatorname{True} \oplus \operatorname{True} ", @"\operatorname{False} ", Skip = "Awaiting AngouriMath update")]
726+
[InlineData(@"\operatorname{true}\oplus\operatorname{false}", @"\operatorname{True} \oplus \operatorname{False} ", @"\operatorname{True} ", Skip = "Awaiting AngouriMath update")]
727+
[InlineData(@"\operatorname{false}\oplus\operatorname{true}", @"\operatorname{False} \oplus \operatorname{True} ", @"\operatorname{True} ", Skip = "Awaiting AngouriMath update")]
728+
[InlineData(@"\operatorname{false}\oplus\operatorname{false}", @"\operatorname{False} \oplus \operatorname{False} ", @"\operatorname{False} ", Skip = "Awaiting AngouriMath update")]
729+
[InlineData(@"x=x", @"x=x", @"\operatorname{True} ")]
730+
[InlineData(@"x\leq x", @"x\leqslant x", @"\operatorname{True} ")]
731+
[InlineData(@"x\neq y", @"\neg x=y", @"\neg x=y")]
732+
[InlineData(@"1<2", @"1<2", @"\operatorname{True} ")]
733+
[InlineData(@"2<1", @"2<1", @"\operatorname{False} ")]
734+
[InlineData(@"1\le1", @"1\leqslant 1", @"\operatorname{True} ")]
735+
[InlineData(@"2>1", @"2>1", @"\operatorname{True} ")]
736+
[InlineData(@"1>2", @"1>2", @"\operatorname{False} ")]
737+
[InlineData(@"1\ge1", @"1\geqslant 1", @"\operatorname{True} ")]
738+
[InlineData(@"x\in\{x,y\}", @"x\in \left\{ x,y\right\} ", @"\operatorname{True} ")]
739+
[InlineData(@"z\notin\{x,y\}", @"\neg z\in \left\{ x,y\right\} ", @"\operatorname{True} ")]
740+
[InlineData(@"\{x,y\}\ni x", @"x\in \left\{ x,y\right\} ", @"\operatorname{True} ")]
741+
public void LogicalAndRelationalOperators(string latex, string converted, string result) => Test(latex, converted, result);
706742
}
707743
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Xunit;
2+
using AngouriMath;
3+
4+
namespace CSharpMath.EvaluationTests {
5+
using Atom;
6+
7+
public class LogicalOperatorPrecedenceTests {
8+
static MathList ParseLaTeX(string latex) =>
9+
LaTeXParser.MathListFromLaTeX(latex).Match(list => list, e => throw new Xunit.Sdk.XunitException(e));
10+
11+
static Evaluation.MathItem ParseMath(string latex) =>
12+
Evaluation.Evaluate(ParseLaTeX(latex)).Match(entity => entity, e => throw new Xunit.Sdk.XunitException(e));
13+
14+
static void AssertConversion(string input, string expectedConverted) {
15+
var math = ParseMath(input);
16+
Assert.NotNull(math);
17+
var actual = LaTeXParser.MathListToLaTeX(Evaluation.Visualize(math)).ToString();
18+
Assert.Equal(expectedConverted, actual);
19+
}
20+
21+
[Theory(Skip = "Awaiting AngouriMath update")]
22+
// Test that ∧ binds tighter than ∨
23+
[InlineData(@"a\land b\lor c", @"\left( a\land b\right) \lor c")]
24+
[InlineData(@"a\lor b\land c", @"a\lor \left( b\land c\right) ")]
25+
// Test that ∨ binds tighter than ⊕
26+
[InlineData(@"a\oplus b\land c", @"a\oplus \left( b\land c\right) ")]
27+
[InlineData(@"a\land b\oplus c", @"\left( a\land b\right) \oplus c")]
28+
// Test that → is right associative and lowest precedence
29+
[InlineData(@"a\to b\lor c", @"a\to \left( b\lor c\right) ")]
30+
[InlineData(@"a\lor b\to c", @"\left( a\lor b\right) \to c")]
31+
[InlineData(@"a\to b\to c", @"a\to \left( b\to c\right) ")]
32+
// Complex expression
33+
[InlineData(@"a\to b\land c\lor d", @"a\to \left( \left( b\land c\right) \lor d\right) ")]
34+
// Test ¬ has highest precedence
35+
[InlineData(@"\neg a\land b", @"\left( \neg a\right) \land b")]
36+
[InlineData(@"\neg a\lor\neg b", @"\left( \neg a\right) \lor \left( \neg b\right) ")]
37+
// Test relational operators bind tighter than logical
38+
[InlineData(@"a=b\land c=d", @"\left( a=b\right) \land \left( c=d\right) ")]
39+
[InlineData(@"a<b\lor c>d", @"\left( a<b\right) \lor \left( c>d\right) ")]
40+
public void LogicalOperatorPrecedence(string input, string expectedOutput) {
41+
AssertConversion(input, expectedOutput);
42+
}
43+
}
44+
}

CSharpMath.Evaluation/Evaluation.cs

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ enum Precedence {
1818
ParenthesisContext,
1919
// Lowest
2020
Comma,
21+
Implication,
22+
Disjunction,
23+
Conjunction,
24+
Negation,
25+
Relation,
2126
SetOperation,
2227
AddSubtract,
2328
MultiplyDivide,
@@ -161,6 +166,7 @@ static Result<MathItem> TryMakeSet(MathItem.Comma c, bool leftClosed, bool right
161166
for (; i < mathList.Count; i++) {
162167
var atom = mathList[i];
163168
MathItem? @this;
169+
bool subscriptAllowed = false;
164170
Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
165171
switch(superscript) {
166172
case { Count: 1 } when superscript[0] is Atoms.Ordinary { Nucleus: "∁" }:
@@ -184,6 +190,12 @@ Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
184190
switch (atom) {
185191
case Atoms.Placeholder _:
186192
return "Placeholders should be filled";
193+
case Atoms.Number { Subscript: [Atoms.Number numericBase] } n:
194+
if (int.TryParse(numericBase.Nucleus, out var @base)) {
195+
try { @this = MathS.FromBaseN(atom.Nucleus, @base); } catch (Exception e) { return e.Message; }
196+
subscriptAllowed = true;
197+
goto handleThis;
198+
} else return "Invalid numeric base: " + numericBase.Nucleus;
187199
case Atoms.Number n:
188200
if (Entity.Number.Complex.TryParse(n.Nucleus, out var number)) {
189201
@this = number;
@@ -222,7 +234,7 @@ Result HandleSuperscript(ref MathItem? @this, MathList superscript) {
222234
_ when LaTeXSettings.CommandForAtom(atom) is string s => MathS.Var(string.Concat(s.TrimStart('\\'), underscore, subscript)),
223235
(var name, _) => MathS.Var(name + underscore + subscript.ToString())
224236
};
225-
v.Subscript.Clear();
237+
subscriptAllowed = true;
226238
goto handleThis;
227239
case Atoms.Ordinary { Nucleus: "∞" }:
228240
@this = Entity.Number.Real.PositiveInfinity;
@@ -303,19 +315,23 @@ _ when LaTeXSettings.CommandForAtom(atom) is string s => MathS.Var(string.Concat
303315
handleFunction = MathS.Arccosec;
304316
handleFunctionInverse = MathS.Cosec;
305317
goto handleFunction;
306-
case Atoms.LargeOperator { Nucleus: "log", Subscript: var @base }:
318+
case Atoms.LargeOperator { Nucleus: "log", Subscript: var logBaseList }:
307319
Entity? logBase;
308-
(logBase, error) = Transform(@base).ExpectEntityOrNull(nameof(logBase));
320+
(logBase, error) = Transform(logBaseList).ExpectEntityOrNull(nameof(logBase));
309321
if (error != null) return error;
310-
@base.Clear();
311322
logBase ??= 10;
312323
handleFunction = arg => MathS.Log(logBase, arg);
313324
handleFunctionInverse = arg => MathS.Pow(logBase, arg);
325+
subscriptAllowed = true;
314326
goto handleFunction;
315327
case Atoms.LargeOperator { Nucleus: "ln" }:
316328
handleFunction = MathS.Ln;
317329
handleFunctionInverse = arg => MathS.Pow(MathS.e, arg);
318330
goto handleFunction;
331+
case Atoms.LargeOperator { Nucleus: "sgn" }:
332+
handleFunction = MathS.Signum;
333+
handleFunctionInverse = arg => MathS.NaN;
334+
goto handleFunction;
319335
case Atoms.BinaryOperator { Nucleus: "+" }:
320336
handlePrecendence = Precedence.AddSubtract;
321337
handleBinary = (a, b) => a + b;
@@ -378,6 +394,72 @@ _ when LaTeXSettings.CommandForAtom(atom) is string s => MathS.Var(string.Concat
378394
handlePrecendence = Precedence.SetOperation;
379395
handleBinary = MathS.SetSubtraction;
380396
goto handleBinary;
397+
case Atoms.LargeOperator { Nucleus: "true" or "True" }:
398+
@this = MathS.Boolean.Create(true);
399+
goto handleThis;
400+
case Atoms.LargeOperator { Nucleus: "false" or "False" }:
401+
@this = MathS.Boolean.Create(false);
402+
goto handleThis;
403+
case Atoms.Ordinary { Nucleus: "¬" }:
404+
handlePrecendence = Precedence.Negation;
405+
handlePrefix = MathS.Negation;
406+
goto handlePrefix;
407+
case Atoms.BinaryOperator { Nucleus: "∧" }:
408+
handlePrecendence = Precedence.Conjunction;
409+
handleBinary = MathS.Conjunction;
410+
goto handleBinary;
411+
case Atoms.BinaryOperator { Nucleus: "∨" }:
412+
handlePrecendence = Precedence.Disjunction;
413+
handleBinary = MathS.Disjunction;
414+
goto handleBinary;
415+
case Atoms.BinaryOperator { Nucleus: "⊕" }:
416+
handlePrecendence = Precedence.Disjunction;
417+
handleBinary = MathS.ExclusiveDisjunction;
418+
goto handleBinary;
419+
case Atoms.Relation { Nucleus: "→" }:
420+
handlePrecendence = Precedence.Implication;
421+
handleBinary = MathS.Implication;
422+
goto handleBinary;
423+
case Atoms.Relation { Nucleus: "↛" }:
424+
handlePrecendence = Precedence.Implication;
425+
handleBinary = (x, y) => MathS.Negation(MathS.Implication(x, y));
426+
goto handleBinary;
427+
case Atoms.Relation { Nucleus: "∈" }:
428+
handlePrecendence = Precedence.Relation;
429+
handleBinary = MathS.Sets.ElementInSet;
430+
goto handleBinary;
431+
case Atoms.Relation { Nucleus: "∉" }:
432+
handlePrecendence = Precedence.Relation;
433+
handleBinary = (element, set) => MathS.Negation(MathS.Sets.ElementInSet(element, set));
434+
goto handleBinary;
435+
case Atoms.Relation { Nucleus: "∋" }:
436+
handlePrecendence = Precedence.Relation;
437+
handleBinary = (set, element) => MathS.Sets.ElementInSet(element, set);
438+
goto handleBinary;
439+
case Atoms.Relation { Nucleus: "=" }:
440+
handlePrecendence = Precedence.Relation;
441+
handleBinary = MathS.Equality;
442+
goto handleBinary;
443+
case Atoms.Relation { Nucleus: "≠" }:
444+
handlePrecendence = Precedence.Relation;
445+
handleBinary = (element, set) => MathS.Negation(MathS.Equality(element, set));
446+
goto handleBinary;
447+
case Atoms.Relation { Nucleus: "<" }:
448+
handlePrecendence = Precedence.Relation;
449+
handleBinary = MathS.LessThan;
450+
goto handleBinary;
451+
case Atoms.Relation { Nucleus: "≤" }:
452+
handlePrecendence = Precedence.Relation;
453+
handleBinary = MathS.LessOrEqualThan;
454+
goto handleBinary;
455+
case Atoms.Relation { Nucleus: ">" }:
456+
handlePrecendence = Precedence.Relation;
457+
handleBinary = MathS.GreaterThan;
458+
goto handleBinary;
459+
case Atoms.Relation { Nucleus: "≥" }:
460+
handlePrecendence = Precedence.Relation;
461+
handleBinary = MathS.GreaterOrEqualThan;
462+
goto handleBinary;
381463
case Atoms.Table { Environment: "matrix" } matrix:
382464
var (rows, cols, cells) = (matrix.NRows, matrix.NColumns, matrix.Cells);
383465
var matrixElements = new Entity[rows * cols];
@@ -567,7 +649,7 @@ _ when LaTeXSettings.CommandForAtom(atom) is string s => MathS.Var(string.Concat
567649
goto handleThis;
568650

569651
handleThis:
570-
if (atom.Subscript.Count > 0)
652+
if (!subscriptAllowed && atom.Subscript.Count > 0)
571653
return $"Subscripts are unsupported for {atom.TypeName} {atom.Nucleus}";
572654
error = HandleSuperscript(ref @this, atom.Superscript).Error;
573655
if (error != null) return error;

CSharpMath/Atom/LaTeXSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@ atom is Accent accent
769769
{ @"\longrightarrow", new Relation("⟶") },
770770
{ @"\downarrow", new Relation("↓") },
771771
{ @"\Rightarrow", new Relation("⇒") },
772-
{ @"\Longrightarrow", new Relation("⟹") },
772+
{ @"\implies", @"\Longrightarrow", new Relation("⟹") },
773773
{ @"\Downarrow", new Relation("⇓") },
774774
{ @"\leftrightarrow", new Relation("↔") },
775775
{ @"\Leftrightarrow", new Relation("⇔") },

0 commit comments

Comments
 (0)