[gnome-calculator] number: add support for parsing integer literals



commit ad3b8d0a0e0e636b870117a873d50160f13fe0c5
Author: Marco Trevisan (Treviño) <mail 3v1n0 net>
Date:   Sat Aug 25 02:19:21 2018 +0200

    number: add support for parsing integer literals
    
    Make possible to use integer literals to use numbers with base other than the
    decimal without having to use the sub-fixed syntax.
    
    So, now calculator recognizes numbers such as:
      - 0b10000001 and 0B01111110 (binary)
      - 0xdeadbeef and 0XBAADA555 (hex)
      - 0o01234567 and 0O76543210 and 01234543210 (octal)
    
    And thus all the fancy operations like "0xff in dec" can be done now.
    
    Added various tests to check the new behavior, and nothing broke for the old
    cases.
    
    Fixes #36

 lib/equation-lexer.vala    | 10 ++++++++-
 lib/equation-parser.vala   |  5 +++++
 lib/equation.vala          | 13 +++++++++++
 lib/number.vala            | 48 ++++++++++++++++++++++++++++++++++++++---
 tests/test-equation.vala   | 54 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/test-serializer.vala | 17 ++++++++++++++-
 6 files changed, 142 insertions(+), 5 deletions(-)
---
diff --git a/lib/equation-lexer.vala b/lib/equation-lexer.vala
index 61bf356e..fbbcd03e 100644
--- a/lib/equation-lexer.vala
+++ b/lib/equation-lexer.vala
@@ -246,7 +246,7 @@ public class LexerToken : Object
     public string text;                /* Copy of token string. */
     public uint start_index;           /* Start index in original stream. */
     public uint end_index;             /* End index in original stream. */
-    public LexerTokenType type;  /* Type of token. */
+    public LexerTokenType type;        /* Type of token. */
 }
 
 /* Structure to hold lexer state and all the tokens. */
@@ -318,6 +318,12 @@ public class Lexer : Object
         return parser.unit_is_defined (name);
     }
 
+    private bool check_if_literal_base ()
+    {
+        var name = prelexer.get_marked_substring ();
+        return parser.literal_base_is_defined (name.down ());
+    }
+
     private bool check_if_number ()
     {
         int count = 0;
@@ -498,6 +504,8 @@ public class Lexer : Object
         }
         else if (type == LexerTokenType.PL_DECIMAL)
             return insert_decimal ();
+        else if (check_if_literal_base ())
+            return insert_hex ();
         else if (type == LexerTokenType.PL_HEX)
             return insert_hex_dec ();
         else
diff --git a/lib/equation-parser.vala b/lib/equation-parser.vala
index 668e1cd5..56acf166 100644
--- a/lib/equation-parser.vala
+++ b/lib/equation-parser.vala
@@ -1078,6 +1078,11 @@ public class Parser
         return false;
     }
 
+    public virtual bool literal_base_is_defined (string name)
+    {
+        return false;
+    }
+
     public virtual Number? convert (Number x, string x_units, string z_units)
     {
         return null;
diff --git a/lib/equation.vala b/lib/equation.vala
index 2e1e73d0..ad19b872 100644
--- a/lib/equation.vala
+++ b/lib/equation.vala
@@ -151,6 +151,11 @@ public class Equation : Object
         return false;
     }
 
+    public virtual bool literal_base_is_defined (string name)
+    {
+        return false;
+    }
+
     public virtual void set_variable (string name, Number x)
     {
     }
@@ -238,4 +243,12 @@ private class EquationParser : Parser
     {
         return equation.convert (x, x_units, z_units);
     }
+
+    protected override bool literal_base_is_defined (string name)
+    {
+        if (name == "0x" || name == "0b" || name == "0o")
+            return true;
+
+        return equation.literal_base_is_defined (name);
+    }
 }
diff --git a/lib/number.vala b/lib/number.vala
index 479a3643..9d340cf3 100644
--- a/lib/number.vala
+++ b/lib/number.vala
@@ -1105,6 +1105,40 @@ public class Number : Object
     }
 }
 
+private static int parse_literal_prefix (string str, ref int prefix_len)
+{
+    var new_base = 0;
+
+    if (str.length < 3 || str[0] != '0')
+        return new_base;
+
+    var prefix = str[1].tolower ();
+
+    if (prefix == 'b')
+        new_base = 2;
+    else if (prefix == 'o')
+        new_base = 8;
+    else if (prefix == 'x')
+        new_base = 16;
+
+    if (new_base != 0)
+        prefix_len = 2;
+
+    if (prefix.isdigit ())
+    {
+        unichar c;
+        bool all_digits = true;
+
+        for (int i = 2; str.get_next_char (ref i, out c) && all_digits;)
+            all_digits = c.isdigit ();
+
+        if (all_digits)
+            new_base = 8;
+    }
+
+    return new_base;
+}
+
 // FIXME: Should all be in the class
 
 // FIXME: Re-add overflow and underflow detection
@@ -1118,10 +1152,12 @@ public Number? mp_set_from_string (string str, int default_base = 10)
     /* Find the base */
     const unichar base_digits[] = {'₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'};
     var index = 0;
+    var base_prefix = 0;
     unichar c;
     while (str.get_next_char (ref index, out c));
     var end = index;
     var number_base = 0;
+    var literal_base = 0;
     var base_multiplier = 1;
     while (str.get_prev_char (ref index, out c))
     {
@@ -1141,12 +1177,18 @@ public Number? mp_set_from_string (string str, int default_base = 10)
         number_base += value * base_multiplier;
         base_multiplier *= 10;
     }
-    if (base_multiplier == 1)
-        number_base = default_base;
+
+    literal_base = parse_literal_prefix (str, ref base_prefix);
+
+    if (number_base != 0 && literal_base != 0 && literal_base != number_base)
+        return null;
+
+    if (number_base == 0)
+        number_base = (literal_base != 0) ? literal_base : default_base;
 
     /* Check if this has a sign */
     var negate = false;
-    index = 0;
+    index = base_prefix;
     str.get_next_char (ref index, out c);
     if (c == '+')
         negate = false;
diff --git a/tests/test-equation.vala b/tests/test-equation.vala
index 3a908395..e1fa390d 100644
--- a/tests/test-equation.vala
+++ b/tests/test-equation.vala
@@ -226,6 +226,50 @@ private void test_equations ()
     test ("e₂", "", ErrorCode.UNKNOWN_VARIABLE); test ("e₈", "", ErrorCode.UNKNOWN_VARIABLE); /* e is a 
built-in variable */              test ("e₁₆", "14", 0);
     test ("f₂", "", ErrorCode.UNKNOWN_VARIABLE); test ("f₈", "", ErrorCode.UNKNOWN_VARIABLE); test ("f", "", 
ErrorCode.INVALID); test ("f₁₆", "15", 0);
 
+    test ("0b0", "0", 0); test ("0B0", "0", 0); test ("0o0", "0", 0); test ("0O0", "0", 0); test ("000", 
"0", 0); test ("0", "0", 0); test ("0x0", "0", 0); test ("0X0", "0", 0);
+    test ("0b1", "1", 0); test ("0B1", "1", 0); test ("0o1", "1", 0); test ("0O1", "1", 0); test ("001", 
"1", 0); test ("1", "1", 0); test ("0x1", "1", 0); test ("0X1", "1", 0);
+    test ("0b2", "", ErrorCode.MP); test ("0B2", "", ErrorCode.MP); test ("0o2", "2", 0); test ("0O2", "2", 
0); test ("002", "2", 0); test ("2", "2", 0); test ("0x2", "2", 0); test ("0X2", "2", 0);
+    test ("0b3", "", ErrorCode.MP); test ("0B3", "", ErrorCode.MP); test ("0o3", "3", 0); test ("0O3", "3", 
0); test ("003", "3", 0); test ("3", "3", 0); test ("0x3", "3", 0); test ("0X3", "3", 0);
+    test ("0b4", "", ErrorCode.MP); test ("0B4", "", ErrorCode.MP); test ("0o4", "4", 0); test ("0O4", "4", 
0); test ("004", "4", 0); test ("4", "4", 0); test ("0x4", "4", 0); test ("0X4", "4", 0);
+    test ("0b5", "", ErrorCode.MP); test ("0B5", "", ErrorCode.MP); test ("0o5", "5", 0); test ("0O5", "5", 
0); test ("005", "5", 0); test ("5", "5", 0); test ("0x5", "5", 0); test ("0X5", "5", 0);
+    test ("0b6", "", ErrorCode.MP); test ("0B6", "", ErrorCode.MP); test ("0o6", "6", 0); test ("0O6", "6", 
0); test ("006", "6", 0); test ("6", "6", 0); test ("0x6", "6", 0); test ("0X6", "6", 0);
+    test ("0b7", "", ErrorCode.MP); test ("0B7", "", ErrorCode.MP); test ("0o7", "7", 0); test ("0O7", "7", 
0); test ("007", "7", 0); test ("7", "7", 0); test ("0x7", "7", 0); test ("0X7", "7", 0);
+    test ("0b8", "", ErrorCode.MP); test ("0B8", "", ErrorCode.MP); test ("0o8", "", 
ErrorCode.UNKNOWN_VARIABLE); test ("0O8", "", ErrorCode.UNKNOWN_VARIABLE); test ("008", "", 
ErrorCode.INVALID); test ("8", "8", 0); test ("0x8", "8", 0); test ("0X8", "8", 0);
+    test ("0b9", "", ErrorCode.MP); test ("0B9", "", ErrorCode.MP); test ("0o9", "", 
ErrorCode.UNKNOWN_VARIABLE); test ("0O9", "", ErrorCode.UNKNOWN_VARIABLE); test ("009", "", 
ErrorCode.INVALID); test ("9", "9", 0); test ("0x9", "9", 0); test ("0X9", "9", 0);
+    //  //  /* Note : "B", "b", "C", "c", "F", "f" are units, and hence have different error code. */
+    test ("0bA", "", ErrorCode.UNKNOWN_VARIABLE); test ("0BA", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oA", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0OA", "", ErrorCode.UNKNOWN_VARIABLE); test ("00A", "", 
ErrorCode.UNKNOWN_VARIABLE); test ("A", "", ErrorCode.UNKNOWN_VARIABLE); test ("0xA", "10", 0); test ("0XA", 
"10", 0);
+    test ("0bB", "", ErrorCode.UNKNOWN_VARIABLE); test ("0BB", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oB", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0OB", "", ErrorCode.UNKNOWN_VARIABLE); test ("00B", "", 
ErrorCode.INVALID); test ("B", "", ErrorCode.INVALID); test ("0xB", "11", 0); test ("0XB", "11", 0);
+    test ("0bC", "", ErrorCode.UNKNOWN_VARIABLE); test ("0BC", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oC", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0OC", "", ErrorCode.UNKNOWN_VARIABLE); test ("00C", "", 
ErrorCode.INVALID); test ("C", "", ErrorCode.INVALID); test ("0xC", "12", 0); test ("0XC", "12", 0);
+    test ("0bD", "", ErrorCode.UNKNOWN_VARIABLE); test ("0BD", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oD", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0OD", "", ErrorCode.UNKNOWN_VARIABLE); test ("00D", "", 
ErrorCode.UNKNOWN_VARIABLE); test ("D", "", ErrorCode.UNKNOWN_VARIABLE); test ("0xD", "13", 0); test ("0XD", 
"13", 0);
+    test ("0bE", "", ErrorCode.UNKNOWN_VARIABLE); test ("0BE", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oE", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0OE", "", ErrorCode.UNKNOWN_VARIABLE); test ("00E", "", 
ErrorCode.UNKNOWN_VARIABLE); test ("E", "", ErrorCode.UNKNOWN_VARIABLE); test ("0xE", "14", 0); test ("0XE", 
"14", 0);
+    test ("0bF", "", ErrorCode.UNKNOWN_VARIABLE); test ("0BF", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oF", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0OF", "", ErrorCode.UNKNOWN_VARIABLE); test ("00F", "", 
ErrorCode.INVALID); test ("F", "", ErrorCode.INVALID); test ("0xF", "15", 0); test ("0XF", "15", 0);
+    test ("0ba", "", ErrorCode.UNKNOWN_VARIABLE); test ("0Ba", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oa", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0Oa", "", ErrorCode.UNKNOWN_VARIABLE); test ("00a", "", 
ErrorCode.UNKNOWN_VARIABLE); test ("a", "", ErrorCode.UNKNOWN_VARIABLE); test ("0xa", "10", 0); test ("0Xa", 
"10", 0);
+    test ("0bb", "", ErrorCode.UNKNOWN_VARIABLE); test ("0Bb", "", ErrorCode.UNKNOWN_VARIABLE); test ("0ob", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0Ob", "", ErrorCode.UNKNOWN_VARIABLE); test ("00b", "", 
ErrorCode.INVALID); test ("b", "", ErrorCode.INVALID); test ("0xb", "11", 0); test ("0Xb", "11", 0);
+    test ("0bc", "", ErrorCode.UNKNOWN_VARIABLE); test ("0Bc", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oc", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0Oc", "", ErrorCode.UNKNOWN_VARIABLE); test ("00c", "", 
ErrorCode.INVALID); test ("c", "", ErrorCode.INVALID); test ("0xc", "12", 0); test ("0Xc", "12", 0);
+    test ("0bd", "", ErrorCode.UNKNOWN_VARIABLE); test ("0Bd", "", ErrorCode.UNKNOWN_VARIABLE); test ("0od", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0Od", "", ErrorCode.UNKNOWN_VARIABLE); test ("00d", "", 
ErrorCode.UNKNOWN_VARIABLE); test ("d", "", ErrorCode.UNKNOWN_VARIABLE); test ("0xd", "13", 0); test ("0Xd", 
"13", 0);
+    test ("0be", "", ErrorCode.UNKNOWN_VARIABLE); test ("0Be", "", ErrorCode.UNKNOWN_VARIABLE); test ("0oe", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0Oe", "", ErrorCode.UNKNOWN_VARIABLE); test ("00e", "0", 0); /* e is 
a built-in variable */              test ("0xe", "14", 0); test ("0Xe", "14", 0);
+    test ("0bf", "", ErrorCode.UNKNOWN_VARIABLE); test ("0Bf", "", ErrorCode.UNKNOWN_VARIABLE); test ("0of", 
"", ErrorCode.UNKNOWN_VARIABLE); test ("0Of", "", ErrorCode.UNKNOWN_VARIABLE); test ("f", "", 
ErrorCode.INVALID); test ("0xf", "15", 0); test ("0Xf", "15", 0);
+
+    test ("0b0b01", "", ErrorCode.MP);
+    test ("0B0b01", "", ErrorCode.MP);
+    test ("0b0B01", "", ErrorCode.MP);
+    test ("0B0B01", "", ErrorCode.MP);
+
+    test ("0x0x01", "", ErrorCode.INVALID);
+    test ("0X0x01", "", ErrorCode.INVALID);
+    test ("0x0X01", "", ErrorCode.UNKNOWN_VARIABLE);
+    test ("0X0X01", "", ErrorCode.UNKNOWN_VARIABLE);
+
+    test ("0o0o01", "", ErrorCode.UNKNOWN_VARIABLE);
+    test ("0O0o01", "", ErrorCode.UNKNOWN_VARIABLE);
+    test ("0o0O01", "", ErrorCode.UNKNOWN_VARIABLE);
+    test ("0O0O01", "", ErrorCode.UNKNOWN_VARIABLE);
+
+    test ("0b1₂", "1", 0); test ("0o2₈", "2", 0); test ("0x8₁₆", "8", 0);
+    test ("001₂", "1", 0); test ("002₈", "2", 0); test ("008₁₆", "8", 0);
+    test ("0x1₂", "", ErrorCode.INVALID); test ("0b1₈", "", ErrorCode.INVALID); test ("0o1₁₆", "", 
ErrorCode.INVALID);
+    test ("0o1₂", "", ErrorCode.INVALID); test ("0x1₈", "", ErrorCode.INVALID); test ("0b1₁₆", "", 
ErrorCode.INVALID);
+
     test ("+1", "1", 0);
     test ("−1", "−1", 0);
     test ("+ 1", "1", 0); // FIXME: Should this be allowed?
@@ -655,6 +699,16 @@ private void test_base_conversion ()
     test ("x in oct", "2₈", 0);
     test ("x in dec", "2", 0);
     test ("x in hex", "2₁₆", 0);
+
+    test ("012 in dec", "10", 0);
+    test ("0b1010 in dec", "10", 0);
+    test ("0B1010 in dec", "10", 0);
+    test ("0o012 in dec", "10", 0);
+    test ("0O012 in dec", "10", 0);
+    test ("0XA in dec", "10", 0);
+    test ("0Xa in dec", "10", 0);
+    test ("0xA in dec", "10", 0);
+    test ("0xa in dec", "10", 0);
 }
 
 private void test_precedence ()
diff --git a/tests/test-serializer.vala b/tests/test-serializer.vala
index fb3f83ce..67eba3ab 100644
--- a/tests/test-serializer.vala
+++ b/tests/test-serializer.vala
@@ -25,7 +25,7 @@ private void fail (string text)
     fail_count++;
 }
 
-private void test_number (Serializer s, string number, int base_value, int representation_base, string 
expected_string)
+private void test_number (Serializer s, string number, int base_value, int representation_base, string? 
expected_string)
 {
     var n = mp_set_from_string (number, base_value);
     s.set_base (base_value);
@@ -132,6 +132,21 @@ private void test_base_conversion (Serializer s)
     test_number (s, "10", 10, 16, "A₁₆");
     test_number (s, "1234567890123456789012345678901234567890", 10, 16, 
"3A0C92075C0DBF3B8ACBC5F96CE3F0AD2₁₆");
 
+    test_number (s, "0b1010", 10, 10, "10");
+    test_number (s, "0B1010", 10, 10, "10");
+    test_number (s, "012", 10, 10, "10");
+    test_number (s, "0o012", 10, 10, "10");
+    test_number (s, "0O012", 10, 10, "10");
+    test_number (s, "0xA", 10, 10, "10");
+    test_number (s, "0XA", 10, 10, "10");
+
+    test_number (s, "0b10103", 10, 10, null);
+    test_number (s, "0B10103", 10, 10, null);
+    test_number (s, "0128", 10, 10, null);
+    test_number (s, "0o0128", 10, 10, null);
+    test_number (s, "0O0128", 10, 10, null);
+    test_number (s, "0xAH", 10, 10, null);
+    test_number (s, "0XAH", 10, 10, null);
 }
 
 static int main (string[] args)


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