[gnome-calculator] GCalc: added support to parse complex numbers



commit acaa1beccbd1eab8b9ec404d8fc9561f9a2da82f
Author: Daniel Espinosa <esodan gmail com>
Date:   Thu Oct 31 12:30:22 2019 -0600

    GCalc: added support to parse complex numbers
    
    The acceptable format is to prefix a complex number wih 'i'

 gcalc/gcalc-constant.vala      |  30 ++++++-
 gcalc/gcalc-math-term.vala     |   1 +
 gcalc/gcalc-parameter.vala     |  21 +++++
 gcalc/gcalc-parser.vala        | 168 ++++++++++++++++++++------------------
 tests/gcalc-solving-basic.vala | 177 ++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 316 insertions(+), 81 deletions(-)
---
diff --git a/gcalc/gcalc-constant.vala b/gcalc/gcalc-constant.vala
index f8d2e03e..24ee2561 100644
--- a/gcalc/gcalc-constant.vala
+++ b/gcalc/gcalc-constant.vala
@@ -126,10 +126,36 @@ public class GCalc.Constant : Expression,
 
   // Expression interface
   internal override string to_string () {
+    string s = "";
     if (imag () != 0.0) {
-      return MPC.Complex.to_string (10, 10, _complex);
+      bool par = false;
+      if (real () != 0.0) {
+        if (parent != null) {
+          s += "(";
+          par = true;
+        }
+        s += "%g".printf (real ());
+      }
+      var im = imag ();
+      if (im < 0.0) {
+        s += "-";
+        im = im * -1.0;
+      } else {
+        if (real () != 0.0) {
+          s += "+";
+        }
+      }
+      s += "i";
+      if (im != 1.0) {
+        s += "%g".printf (im);
+      }
+      if (par) {
+        s += ")";
+      }
+    } else {
+      s = "%g".printf (real ());
     }
-    return "%g".printf (real ());
+    return s;
   }
 
   internal override MathResult solve () {
diff --git a/gcalc/gcalc-math-term.vala b/gcalc/gcalc-math-term.vala
index 6f14426e..6bdb7066 100644
--- a/gcalc/gcalc-math-term.vala
+++ b/gcalc/gcalc-math-term.vala
@@ -110,6 +110,7 @@ public interface GCalc.MathTerm : Object, MathExpression {
   public static MathExpression evaluate_constants (MathConstant c1, MathConstant c2, MathOperator op)
     throws GLib.Error
   {
+      message ("Eval: %s : %s", c1.to_string (), c2.to_string ());
     MathExpression res = null;
     if (op is MathMinus) {
       res = c1.multiply (c2);
diff --git a/gcalc/gcalc-parameter.vala b/gcalc/gcalc-parameter.vala
index ee7fe57e..7022a7d4 100644
--- a/gcalc/gcalc-parameter.vala
+++ b/gcalc/gcalc-parameter.vala
@@ -46,4 +46,25 @@ public class GCalc.Parameter : GCalc.Variable, MathParameter {
     v = @value;
     return v;
   }
+  // Expression
+  internal override string to_string () {
+    string str = name;
+    if (@value is MathConstantNumber) {
+      var c = @value as MathConstantNumber;
+      str = "%g".printf (c.@value ());
+    } else if (@value is MathConstantComplex) {
+      var c = @value as MathConstantComplex;
+      str = "%g".printf (c.real ());
+      double i = c.imag ();
+      if (i != 0) {
+        if (i > 0) {
+          str += "+";
+        } else {
+          str += "-";
+        }
+        str += "%gi".printf (c.imag ());
+      }
+    }
+    return str;
+  }
 }
diff --git a/gcalc/gcalc-parser.vala b/gcalc/gcalc-parser.vala
index 1054b8a6..2668e84c 100644
--- a/gcalc/gcalc-parser.vala
+++ b/gcalc/gcalc-parser.vala
@@ -26,6 +26,7 @@ public class GCalc.Parser : Object {
   MathExpression current = null;
   MathExpression current_parent = null;
   MathExpression top_parent = null;
+  Equation eq = null;
   bool enable_parameter = false;
   Gee.ArrayList<TokenType> expected = new Gee.ArrayList<TokenType> ();
   GLib.Scanner scanner;
@@ -51,7 +52,7 @@ public class GCalc.Parser : Object {
    */
   public void parse (string str, MathEquationManager eqman) throws GLib.Error {
     TokenType token = TokenType.NONE;
-    Equation eq = new Equation ();
+    eq = new Equation ();
     scanner.input_text (str, str.length);
     current = null;
     current_parent = null;
@@ -64,76 +65,84 @@ public class GCalc.Parser : Object {
       }
       string n = token_to_string ();
       if (expected.size != 0 && !expected.contains (token)) {
-        throw new ParserError.INVALID_TOKEN_ERROR ("Found an unexpected expression");
+        throw new ParserError.INVALID_TOKEN_ERROR (_("Found an unexpected expression"));
       }
       switch (token) {
         case TokenType.IDENTIFIER:
-          MathExpression sfunc = eqman.functions.find_named (n);
-          if (sfunc != null) {
-            sfunc = Object.new (sfunc.get_type ()) as MathExpression;
-            if (current == null) {
-              var exp = new Polynomial ();
-              eq.expressions.add (exp);
-              var t = new Term ();
-              exp.expressions.add (t);
-              t.expressions.add (sfunc);
-              current = sfunc;
-              current_parent = t;
-              top_parent = exp;
-              expected.clear ();
-              expected.add(TokenType.OPEN_PARENS);
-            } else if (current is MathOperator && current_parent is MathTerm && top_parent is 
MathPolynomial) {
-                current_parent.expressions.add (sfunc);
+          Regex rg = new Regex ("i[0-9]*.*", RegexCompileFlags.ANCHORED, RegexMatchFlags.ANCHORED);
+          if (rg.match (n, RegexMatchFlags.ANCHORED, null)) {
+            string cxn = n.replace ("i", "");
+            double v = double.parse (cxn);
+            var cx = new Constant.complex (0, v);
+            add_constant (cx);
+          } else {
+            MathExpression sfunc = eqman.functions.find_named (n);
+            if (sfunc != null) {
+              sfunc = Object.new (sfunc.get_type ()) as MathExpression;
+              if (current == null) {
+                var exp = new Polynomial ();
+                eq.expressions.add (exp);
+                var t = new Term ();
+                exp.expressions.add (t);
+                t.expressions.add (sfunc);
                 current = sfunc;
+                current_parent = t;
+                top_parent = exp;
                 expected.clear ();
-            } else if (current is MathTerm && current_parent is MathPolynomial) {
-                current.expressions.add (sfunc);
-                current_parent = current;
-                current = sfunc;
-                top_parent = current_parent.parent;
-                expected.clear ();
-            }
-          } else if (n.down () == "def" && current == null) {
-            // FIXME: implement function definition
-          } else if (n.down () == "def" && current is MathFunction) {
-            throw new ParserError.INVALID_TOKEN_ERROR ("Found an unexpected function definition expression");
-          } else {
-            var v = new Variable (n) as MathExpression;
-            if (enable_parameter) {
-              v = new Parameter (n) as MathExpression;
-              enable_parameter = false;
-            }
-            var sv = eqman.find_variable (n) as MathVariable;
-            if (sv == null) {
-              sv = eq.variables.find_named (n) as MathVariable;
+                expected.add(TokenType.OPEN_PARENS);
+              } else if (current is MathOperator && current_parent is MathTerm && top_parent is 
MathPolynomial) {
+                  current_parent.expressions.add (sfunc);
+                  current = sfunc;
+                  expected.clear ();
+              } else if (current is MathTerm && current_parent is MathPolynomial) {
+                  current.expressions.add (sfunc);
+                  current_parent = current;
+                  current = sfunc;
+                  top_parent = current_parent.parent;
+                  expected.clear ();
+              }
+            } else if (n.down () == "def" && current == null) {
+              // FIXME: implement function definition
+            } else if (n.down () == "def" && current is MathFunction) {
+              throw new ParserError.INVALID_TOKEN_ERROR ("Found an unexpected function definition 
expression");
+            } else {
+              var v = new Variable (n) as MathExpression;
+              if (enable_parameter) {
+                v = new Parameter (n) as MathExpression;
+                enable_parameter = false;
+              }
+              var sv = eqman.find_variable (n) as MathVariable;
               if (sv == null) {
-                eq.variables.add (v);
+                sv = eq.variables.find_named (n) as MathVariable;
+                if (sv == null) {
+                  eq.variables.add (v);
+                } else {
+                  ((MathVariable) v).bind = sv;
+                }
               } else {
                 ((MathVariable) v).bind = sv;
               }
-            } else {
-              ((MathVariable) v).bind = sv;
-            }
-            if (current == null) {
-              var exp = new Polynomial ();
-              eq.expressions.add (exp);
-              var t = new Term ();
-              exp.expressions.add (t);
-              t.expressions.add (v);
-              current = v;
-              current_parent = v.parent;
-              top_parent = current_parent.parent;
-              expected.clear ();
-            } else if (current is MathOperator && current_parent is MathTerm && top_parent is 
MathPolynomial) {
-                current_parent.expressions.add (v);
-                current = v;
-                expected.clear ();
-            } else if (current is MathTerm) {
-                current.expressions.add (v);
+              if (current == null) {
+                var exp = new Polynomial ();
+                eq.expressions.add (exp);
+                var t = new Term ();
+                exp.expressions.add (t);
+                t.expressions.add (v);
                 current = v;
                 current_parent = v.parent;
                 top_parent = current_parent.parent;
                 expected.clear ();
+              } else if (current is MathOperator && current_parent is MathTerm && top_parent is 
MathPolynomial) {
+                  current_parent.expressions.add (v);
+                  current = v;
+                  expected.clear ();
+              } else if (current is MathTerm) {
+                  current.expressions.add (v);
+                  current = v;
+                  current_parent = v.parent;
+                  top_parent = current_parent.parent;
+                  expected.clear ();
+              }
             }
           }
           break;
@@ -144,26 +153,7 @@ public class GCalc.Parser : Object {
             throw new ParserError.INVALID_TOKEN_ERROR ("Found an unexpected expression for a constant");
           }
           var cexp = new Constant.@double (double.parse (n));
-          if (current == null) {
-            var exp = new Polynomial ();
-            eq.expressions.add (exp);
-            var t = new Term ();
-            exp.expressions.add (t);
-            t.expressions.add (cexp);
-            current = cexp;
-            current_parent = t;
-            top_parent = exp;
-          } else if ((current is MathOperator || current is MathTerm) && current_parent is MathTerm && 
top_parent is MathPolynomial) {
-            current_parent.expressions.add (cexp);
-            expected.clear ();
-            current = cexp;
-          } else if (current is MathTerm && current_parent is MathPolynomial && (top_parent is MathGroup || 
top_parent is MathFunction)) {
-            current.expressions.add (cexp);
-            top_parent = current_parent;
-            current_parent = current;
-            current = cexp;
-            expected.clear ();
-          }
+          add_constant (cexp);
           break;
         case TokenType.STAR:
           var op = new Multiply ();
@@ -411,6 +401,28 @@ public class GCalc.Parser : Object {
       expected.clear ();
     }
   }
+  private void add_constant (MathConstant c) {
+    if (current == null) {
+      var exp = new Polynomial ();
+      eq.expressions.add (exp);
+      var t = new Term ();
+      exp.expressions.add (t);
+      t.expressions.add (c);
+      current = c;
+      current_parent = t;
+      top_parent = exp;
+    } else if ((current is MathOperator || current is MathTerm) && current_parent is MathTerm && top_parent 
is MathPolynomial) {
+      current_parent.expressions.add (c);
+      expected.clear ();
+      current = c;
+    } else if (current is MathTerm && current_parent is MathPolynomial && (top_parent is MathGroup || 
top_parent is MathFunction)) {
+      current.expressions.add (c);
+      top_parent = current_parent;
+      current_parent = current;
+      current = c;
+      expected.clear ();
+    }
+  }
   /**
    * Reads a token at a given position
    */
diff --git a/tests/gcalc-solving-basic.vala b/tests/gcalc-solving-basic.vala
index a4fd4f15..cae20a6c 100644
--- a/tests/gcalc-solving-basic.vala
+++ b/tests/gcalc-solving-basic.vala
@@ -31,6 +31,13 @@ class Tests {
       assert (c3 != null);
       message (c3.to_string ());
       assert (c3.@value () == 6.0);
+      var c4 = new Constant.complex (0.0, -1.0);
+      var c5 = c1.add (c4) as MathConstantComplex;
+      assert (c5 != null);
+      assert (c5.real () == 3.0);
+      assert (c5.imag () == -1.0);
+      message (c5.to_string ());
+      assert (c5.to_string () == "3-i");
     });
     Test.add_func ("/gcalc/solve/constant/subtract",
     ()=>{
@@ -76,7 +83,7 @@ class Tests {
       assert (c3.real () == -10.0);
       assert (c3.imag () == -15.0);
     });
-    Test.add_func ("/gcalc/solve/constant",
+    Test.add_func ("/gcalc/solve/constant/number",
     ()=>{
       try {
         var parser = new Parser ();
@@ -102,6 +109,174 @@ class Tests {
         warning ("Error: %s", e.message);
       }
     });
+    Test.add_func ("/gcalc/solve/parse/constant/complex/positive",
+    ()=>{
+      try {
+        var parser = new Parser ();
+        var eqman = new EquationManager ();
+        parser.parse ("1+i3", eqman);
+        assert (eqman.equations.get_n_items () == 1);
+        var eq = eqman.equations.get_item (0) as MathEquation;
+        assert (eq != null);
+        message ("Equation: %s", eq.to_string ());
+        assert (eq.to_string () == "1+i3");
+        var e = eq.expressions.get_item (0) as MathPolynomial;
+        assert (e != null);
+        message ("Polynomial: %u items - exp: %s", e.expressions.get_n_items (), e.to_string ());
+        var t = e.expressions.get_item (0) as MathTerm;
+        assert (t != null);
+        message ("Real Term: %u items - exp: %s", t.expressions.get_n_items (), t.to_string ());
+        var n = t.expressions.get_item (0) as MathConstantNumber;
+        assert (n != null);
+        assert (n.@value () == 1);
+        var ct = e.expressions.get_item (1) as MathTerm;
+        assert (ct != null);
+        message ("Imag Term: %u items - exp: %s", ct.expressions.get_n_items (), ct.to_string ());
+        message ("1 Term: %s", ct.expressions.get_item (0).get_type ().name ());
+        message ("2 Term: %s", ct.expressions.get_item (1).get_type ().name ());
+        var c = ct.expressions.get_item (1) as MathConstantComplex;
+        assert (c != null);
+        assert (c.real () == 0.0);
+        assert (c.imag () == 3.0);
+        var res = eq.solve ();
+        assert (res != null);
+        assert (res.expression != null);
+        var rc = res.expression as MathConstantComplex;
+        assert (rc != null);
+        message ("MathConstant Result: %s", rc.to_string ());
+        assert (rc.to_string () == "1+i3");
+        assert (rc.real () == 1.0);
+        assert (rc.imag () == 3.0);
+      } catch (GLib.Error e) {
+        warning ("Error: %s", e.message);
+      }
+    });
+    Test.add_func ("/gcalc/solve/parse/constant/complex/negative",
+    ()=>{
+      try {
+        var parser = new Parser ();
+        var eqman = new EquationManager ();
+        parser.parse ("1-i3", eqman);
+        assert (eqman.equations.get_n_items () == 1);
+        var eq = eqman.equations.get_item (0) as MathEquation;
+        assert (eq != null);
+        message ("Equation: %s", eq.to_string ());
+        assert (eq.to_string () == "1-i3");
+        var e = eq.expressions.get_item (0) as MathPolynomial;
+        assert (e != null);
+        message ("Polynomial: %u items - exp: %s", e.expressions.get_n_items (), e.to_string ());
+        var t = e.expressions.get_item (0) as MathTerm;
+        assert (t != null);
+        message ("Real Term: %u items - exp: %s", t.expressions.get_n_items (), t.to_string ());
+        var n = t.expressions.get_item (0) as MathConstantNumber;
+        assert (n != null);
+        assert (n.@value () == 1);
+        var ct = e.expressions.get_item (1) as MathTerm;
+        assert (ct != null);
+        message ("Imag Term: %u items - exp: %s", ct.expressions.get_n_items (), ct.to_string ());
+        message ("1 Term: %s", ct.expressions.get_item (0).get_type ().name ());
+        message ("2 Term: %s", ct.expressions.get_item (1).get_type ().name ());
+        var c = ct.expressions.get_item (1) as MathConstantComplex;
+        assert (c != null);
+        assert (c.real () == 0.0);
+        assert (c.imag () == 3.0);
+        var res = eq.solve ();
+        assert (res != null);
+        assert (res.expression != null);
+        var rc = res.expression as MathConstantComplex;
+        assert (rc != null);
+        message ("MathConstant Result: %s", rc.to_string ());
+        assert (rc.to_string () == "1-i3");
+        assert (rc.real () == 1.0);
+        assert (rc.imag () == -3.0);
+      } catch (GLib.Error e) {
+        warning ("Error: %s", e.message);
+      }
+    });
+    Test.add_func ("/gcalc/solve/parse/constant/complex/real-negative",
+    ()=>{
+      try {
+        var parser = new Parser ();
+        var eqman = new EquationManager ();
+        parser.parse ("-1+i3", eqman);
+        assert (eqman.equations.get_n_items () == 1);
+        var eq = eqman.equations.get_item (0) as MathEquation;
+        assert (eq != null);
+        message ("Equation: %s", eq.to_string ());
+        assert (eq.to_string () == "-1+i3");
+        var e = eq.expressions.get_item (0) as MathPolynomial;
+        assert (e != null);
+        message ("Polynomial: %u items - exp: %s", e.expressions.get_n_items (), e.to_string ());
+        var t = e.expressions.get_item (0) as MathTerm;
+        assert (t != null);
+        message ("Real Term: %u items - exp: %s", t.expressions.get_n_items (), t.to_string ());
+        var n = t.expressions.get_item (1) as MathConstantNumber;
+        assert (n != null);
+        assert (n.@value () == 1);
+        var ct = e.expressions.get_item (1) as MathTerm;
+        assert (ct != null);
+        message ("Imag Term: %u items - exp: %s", ct.expressions.get_n_items (), ct.to_string ());
+        message ("1 Term: %s", ct.expressions.get_item (0).get_type ().name ());
+        message ("2 Term: %s", ct.expressions.get_item (1).get_type ().name ());
+        var c = ct.expressions.get_item (1) as MathConstantComplex;
+        assert (c != null);
+        assert (c.real () == 0.0);
+        assert (c.imag () == 3.0);
+        var res = eq.solve ();
+        assert (res != null);
+        assert (res.expression != null);
+        var rc = res.expression as MathConstantComplex;
+        assert (rc != null);
+        message ("MathConstant Result: %s", rc.to_string ());
+        assert (rc.to_string () == "-1+i3");
+        assert (rc.real () == -1.0);
+        assert (rc.imag () == 3.0);
+      } catch (GLib.Error e) {
+        warning ("Error: %s", e.message);
+      }
+    });
+    Test.add_func ("/gcalc/solve/parse/constant/complex/all-negative",
+    ()=>{
+      try {
+        var parser = new Parser ();
+        var eqman = new EquationManager ();
+        parser.parse ("-1-i3", eqman);
+        assert (eqman.equations.get_n_items () == 1);
+        var eq = eqman.equations.get_item (0) as MathEquation;
+        assert (eq != null);
+        message ("Equation: %s", eq.to_string ());
+        assert (eq.to_string () == "-1-i3");
+        var e = eq.expressions.get_item (0) as MathPolynomial;
+        assert (e != null);
+        message ("Polynomial: %u items - exp: %s", e.expressions.get_n_items (), e.to_string ());
+        var t = e.expressions.get_item (0) as MathTerm;
+        assert (t != null);
+        message ("Real Term: %u items - exp: %s", t.expressions.get_n_items (), t.to_string ());
+        var n = t.expressions.get_item (1) as MathConstantNumber;
+        assert (n != null);
+        assert (n.@value () == 1);
+        var ct = e.expressions.get_item (1) as MathTerm;
+        assert (ct != null);
+        message ("Imag Term: %u items - exp: %s", ct.expressions.get_n_items (), ct.to_string ());
+        message ("1 Term: %s", ct.expressions.get_item (0).get_type ().name ());
+        message ("2 Term: %s", ct.expressions.get_item (1).get_type ().name ());
+        var c = ct.expressions.get_item (1) as MathConstantComplex;
+        assert (c != null);
+        assert (c.real () == 0.0);
+        assert (c.imag () == 3.0);
+        var res = eq.solve ();
+        assert (res != null);
+        assert (res.expression != null);
+        var rc = res.expression as MathConstantComplex;
+        assert (rc != null);
+        message ("MathConstant Result: %s", rc.to_string ());
+        assert (rc.to_string () == "-1-i3");
+        assert (rc.real () == -1.0);
+        assert (rc.imag () == -3.0);
+      } catch (GLib.Error e) {
+        warning ("Error: %s", e.message);
+      }
+    });
     Test.add_func ("/gcalc/solve/term/constant",
     ()=>{
       try {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]