[gnome-calculator] Implement support for custom functions



commit 4d64dc9180fd8c54368e26b2da08fcc95f232c10
Author: Garima Joshi <gjoshi0311 gmail com>
Date:   Fri Oct 18 12:56:33 2013 +0530

    Implement support for custom functions

 configure.ac                 |    2 +
 help/C/functions.page        |   31 +++-
 help/C/variables.page        |    2 +-
 src/Makefile.am              |   12 +-
 src/equation-lexer.vala      |    8 +-
 src/equation-parser.vala     |  495 +++++++++++++++++++++++++++---------------
 src/equation.vala            |  108 +---------
 src/function-manager.vala    |  369 +++++++++++++++++++++++++++++++
 src/math-buttons.vala        |   40 +---
 src/math-display.vala        |  207 ++++++++++++++++--
 src/math-equation.vala       |    6 +-
 src/math-function-popup.vala |  209 ++++++++++++++++++
 src/math-function.vala       |  284 ++++++++++++++++++++++++
 src/math-variables.vala      |   43 ++++-
 src/test-equation.vala       |   51 +++++-
 15 files changed, 1527 insertions(+), 340 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 89b3b9b..9a24fcf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -20,11 +20,13 @@ dnl ###########################################################################
 GLIB_REQUIRED=2.31
 GIO_REQUIRED=2.25.10
 GTK_REQUIRED=3.0
+GTKSOURCEVIEW_REQUIRED=3.0.0
 
 PKG_CHECK_MODULES(GNOME_CALCULATOR, [
     gtk+-3.0 >= $GTK_REQUIRED
     glib-2.0 >= $GLIB_REQUIRED
     gio-2.0 >= $GIO_REQUIRED
+    gtksourceview-3.0 >= $GTKSOURCEVIEW_REQUIRED
     libxml-2.0
     gmodule-export-2.0
 ])
diff --git a/help/C/functions.page b/help/C/functions.page
index a8d17ed..e2e3938 100644
--- a/help/C/functions.page
+++ b/help/C/functions.page
@@ -9,8 +9,8 @@
        <title>Functions</title>
     
     <p>
-    Functions can be used by inserting the name of the function followed by the function argument.
-    If the argument is not a number or <link xref="variable">variable</link> then use parenthesis around the 
argument.
+    Functions can be used by inserting the name of the function followed by function arguments. 
<app>Calculator</app> also support user-defined functions.
+    If the argument is not a number or <link xref="variable">a single variable</link> then write each 
argument separated by semicolon and use parenthesis around the argument list.
     </p>
     <example>
     <p>
@@ -19,6 +19,30 @@
     <p>
     abs (5−9)
     </p>
+    <p>
+    fun (9;10)
+    </p>
+    </example>
+    <p>
+    To add a new function type the function in the format as given below. A function name and arguments must 
only contain upper or lower case characters. The part after @ is the function description which is optional.
+    </p>
+    <example>
+    <p>
+    SimpleInterest (principal; rate; time) = principal * rate * time @ Simple Interest formula
+    </p>
+    </example>
+    <p>
+    Functions can also be added and inserted using the f(x) button.
+    </p>
+    <example>
+    <p>
+    Click on f(x). Type the function name in the box, choose the number of arguments and click on the plus 
sign button next to it. On the calculator screen type the function expression and press <key>Enter</key>.
+    </p>
+    </example>
+    <example>
+    <p>
+    Click on f(x). Choose the function and click on it.
+    </p>
     </example>
     <p>
     The following functions are defined.
@@ -85,7 +109,4 @@
     <td><p>Twos complement</p></td>
     </tr>
     </table>
-    <p>
-    <app>Calculator</app> does not support user-defined functions.
-    </p>
 </page>
diff --git a/help/C/variables.page b/help/C/variables.page
index 148c017..47c9f43 100644
--- a/help/C/variables.page
+++ b/help/C/variables.page
@@ -10,7 +10,7 @@
     
     <p>
     To assign a value to a variable use the = symbol or choose the variable to assign to with the 
<gui>x</gui> button in <link xref="mouse">advanced mode</link>.
-    A variable name must only contain upper or lower characters.
+    A variable name must only contain upper or lower case characters.
     </p>
     <example>
     <p>
diff --git a/src/Makefile.am b/src/Makefile.am
index 5c0611f..10bbb2f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -36,12 +36,16 @@ gnome_calculator_SOURCES = \
        number.vala \
        serializer.vala \
        unit.vala \
+       math-function.vala \
+       function-manager.vala \
+       math-function-popup.vala \
        $(BUILT_SOURCES)
 
 gnome_calculator_VALAFLAGS = \
        --target-glib 2.32 \
        --pkg posix \
        --pkg gtk+-3.0 \
+       --pkg gtksourceview-3.0 \
        --pkg libxml-2.0
 
 gnome_calculator_LDADD = \
@@ -58,7 +62,9 @@ gcalccmd_SOURCES = \
        equation-parser.vala \
        number.vala \
        serializer.vala \
-       unit.vala
+       unit.vala \
+       math-function.vala \
+       function-manager.vala
 
 gcalccmd_VALAFLAGS = \
        --target-glib 2.32 \
@@ -97,7 +103,9 @@ test_equation_SOURCES = \
        equation-parser.vala \
        number.vala \
        serializer.vala \
-       unit.vala
+       unit.vala \
+       math-function.vala \
+       function-manager.vala
 
 test_equation_VALAFLAGS = \
        --target-glib 2.32 \
diff --git a/src/equation-lexer.vala b/src/equation-lexer.vala
index 6d62f4d..d5ff7eb 100644
--- a/src/equation-lexer.vala
+++ b/src/equation-lexer.vala
@@ -63,7 +63,8 @@ public enum LexerTokenType
     ABS,                /* | */
     POWER,              /* ^ */
     FACTORIAL,          /* ! */
-    PERCENTAGE          /* % */
+    PERCENTAGE,         /* % */
+    ARGUMENT_SEPARATOR  /* ; (Function argument separator) */
 }
 
 // FIXME: Merge into lexer
@@ -228,6 +229,9 @@ public class PreLexer
         if (c == '%')
             return LexerTokenType.PERCENTAGE;
 
+        if (c == ';')
+            return LexerTokenType.ARGUMENT_SEPARATOR;
+
         if (c == ' ' || c == '\r' || c == '\t' || c == '\n')
             return LexerTokenType.PL_SKIP;
 
@@ -353,7 +357,7 @@ public class Lexer
             type = prelexer.get_next_token ();
         }
 
-        if (type == LexerTokenType.AND || type == LexerTokenType.OR || type == LexerTokenType.XOR || type == 
LexerTokenType.NOT || type == LexerTokenType.ADD || type == LexerTokenType.SUBTRACT || type == 
LexerTokenType.MULTIPLY || type == LexerTokenType.DIVIDE || type == LexerTokenType.L_FLOOR || type == 
LexerTokenType.R_FLOOR || type == LexerTokenType.L_CEILING || type == LexerTokenType.R_CEILING || type == 
LexerTokenType.ROOT || type == LexerTokenType.ROOT_3 || type == LexerTokenType.ROOT_4 || type == 
LexerTokenType.ASSIGN || type == LexerTokenType.L_R_BRACKET || type == LexerTokenType.R_R_BRACKET || type == 
LexerTokenType.L_S_BRACKET || type == LexerTokenType.R_S_BRACKET || type == LexerTokenType.L_C_BRACKET || 
type == LexerTokenType.R_C_BRACKET || type == LexerTokenType.ABS || type == LexerTokenType.POWER || type == 
LexerTokenType.FACTORIAL || type == LexerTokenType.PERCENTAGE)
+        if (type == LexerTokenType.AND || type == LexerTokenType.OR || type == LexerTokenType.XOR || type == 
LexerTokenType.NOT || type == LexerTokenType.ADD || type == LexerTokenType.SUBTRACT || type == 
LexerTokenType.MULTIPLY || type == LexerTokenType.DIVIDE || type == LexerTokenType.L_FLOOR || type == 
LexerTokenType.R_FLOOR || type == LexerTokenType.L_CEILING || type == LexerTokenType.R_CEILING || type == 
LexerTokenType.ROOT || type == LexerTokenType.ROOT_3 || type == LexerTokenType.ROOT_4 || type == 
LexerTokenType.ASSIGN || type == LexerTokenType.L_R_BRACKET || type == LexerTokenType.R_R_BRACKET || type == 
LexerTokenType.L_S_BRACKET || type == LexerTokenType.R_S_BRACKET || type == LexerTokenType.L_C_BRACKET || 
type == LexerTokenType.R_C_BRACKET || type == LexerTokenType.ABS || type == LexerTokenType.POWER || type == 
LexerTokenType.FACTORIAL || type == LexerTokenType.PERCENTAGE || type == LexerTokenType.ARGUMENT_SEPARATOR)
             return insert_token (type);
 
         /* [LexerTokenType.PL_SUPER_MINUS][LexerTokenType.PL_SUPER_DIGIT]+ */
diff --git a/src/equation-parser.vala b/src/equation-parser.vala
index fcb7910..362dbcf 100644
--- a/src/equation-parser.vala
+++ b/src/equation-parser.vala
@@ -130,6 +130,30 @@ public class AssignNode : RNode
     }
 }
 
+public class AssignFunctionNode : ParseNode
+{
+    public AssignFunctionNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity)
+    {
+        base (parser, token, precedence, associativity);
+    }
+
+    public override Number? solve ()
+    {
+        if (left == null || right == null || left.left == null || left.right == null)
+            return null;
+
+        var function_name = left.left.value;
+        var arguments = left.right.value;
+        var description = right.value;
+
+        FunctionManager function_manager = FunctionManager.get_default_function_manager();
+        if (function_manager.add_function_with_properties (function_name, arguments, description, parser))
+            return new Number.integer (0);
+
+        return null;
+    }
+}
+
 public class NameNode : ParseNode
 {
     public NameNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, string? 
text = null)
@@ -167,7 +191,6 @@ public class VariableNode : ParseNode
             }
             value = value.multiply (t);
         }
-
         return value;
     }
 }
@@ -216,81 +239,121 @@ public class VariableWithPowerNode : ParseNode
     }
 }
 
-public class FunctionNode : RNode
+public class FunctionNameNode : NameNode
 {
-    public FunctionNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+    public FunctionNameNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, 
string name)
     {
-        base (parser, token, precedence, associativity);
+        base (parser, token, precedence, associativity, name);
     }
+}
 
-    public override Number solve_r (Number r)
+public class FunctionArgumentsNode : NameNode
+{
+    public FunctionArgumentsNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, string arguments)
     {
-        var ans = parser.get_function (token.text, r);
-        if (ans == null)
-            parser.set_error (ErrorCode.UNKNOWN_FUNCTION, token.text, token.start_index, token.end_index);
+        base (parser, token, precedence, associativity, arguments);
+    }
+}
 
-        return ans;
+public class FunctionDescriptionNode : NameNode
+{
+    public FunctionDescriptionNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, string description)
+    {
+        base (parser, token, precedence, associativity, description);
     }
 }
 
-public class FunctionWithPowerNode : ParseNode
+public class FunctionNode : ParseNode
 {
-    public FunctionWithPowerNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, string text)
+    public FunctionNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, 
string? text)
     {
         base (parser, token, precedence, associativity, text);
     }
 
     public override Number? solve ()
     {
-        var val = right.solve ();
-        if (val == null)
+        if (right == null || left == null)
         {
-            value = null;
+            parser.set_error (ErrorCode.UNKNOWN_FUNCTION);
             return null;
         }
-        var tmp = parser.get_function (token.text, val);
-        if (tmp == null)
+
+        var name = left.value;
+        if (name == null)
         {
-            value = null;
-            parser.set_error (ErrorCode.UNKNOWN_FUNCTION, token.text, token.start_index, token.end_index);
+            parser.set_error (ErrorCode.UNKNOWN_FUNCTION);
             return null;
         }
 
-        var pow = super_atoi (value);
-        value = null;
-
-        return tmp.xpowy_integer (pow);
-    }
-}
+        int pow = 1;
+        if (this.value != null)
+            pow = super_atoi (this.value);
 
-public class FunctionWithNegativePowerNode : ParseNode
-{
-    public FunctionWithNegativePowerNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, string text)
-    {
-        base (parser, token, precedence, associativity, text);
-    }
+        if (pow < 0)
+        {
+            name = name + "⁻¹";
+            pow = -pow;
+        }
 
-    public override Number? solve ()
-    {
-        var val = right.solve ();
-        if (val == null)
+        Number[] args = {};
+        if (right is FunctionArgumentsNode)
         {
-            value = null;
-            return null;
+            var argument_list = right.value;
+            var temp = "";
+            int depth = 0;
+            for (int i = 0; i < argument_list.length; i++)
+            {
+                string ss = argument_list.substring (i, 1);
+                if (ss == "(")
+                    depth++;
+                else if (ss == ")")
+                    depth--;
+                else if (ss == ";" && depth != 0)
+                    ss = "$";
+                temp += ss;
+            }
+            var arguments = temp.split_set (";");
+
+            foreach (var argument in arguments)
+            {
+                argument = argument.replace ("$", ";").strip ();
+                var argument_parser = new ExpressionParser (argument, parser);
+
+                uint representation_base;
+                ErrorCode error_code;
+                string? error_token;
+                uint error_start;
+                uint error_end;
+
+                var ans = argument_parser.parse (out representation_base, out error_code, out error_token, 
out error_start, out error_end);
+
+                if (error_code == ErrorCode.NONE && ans != null)
+                    args += ans;
+                else
+                {
+                    parser.set_error (ErrorCode.UNKNOWN_VARIABLE, error_token, error_start, error_end);
+                    return null;
+                }
+            }
         }
-        var inv_name = token.text + "⁻¹";
-        var tmp = parser.get_function (inv_name, val);
-        if (tmp == null)
+        else
         {
-            value = null;
-            parser.set_error (ErrorCode.UNKNOWN_FUNCTION, token.text, token.start_index, token.end_index);
-            return null;
+            var ans = right.solve ();
+            if (ans != null)
+                args += ans;
+            else
+            {
+                parser.set_error (ErrorCode.UNKNOWN_FUNCTION);
+                return null;
+            }
         }
 
-        var pow = super_atoi (value);
-        value = null;
+        FunctionManager function_manager = FunctionManager.get_default_function_manager ();
+        var tmp = function_manager.evaluate_function (name, args, parser);
 
-        return tmp.xpowy_integer (-pow);
+        if (tmp != null)
+            tmp = tmp.xpowy_integer (pow);
+        return tmp;
     }
 }
 
@@ -733,6 +796,7 @@ public class Parser
     private Lexer lexer;
     public int number_base;
     public int wordlen;
+    public AngleUnit angle_units;
     private uint depth_level;
     private ErrorCode error;
     private string error_token;
@@ -740,7 +804,7 @@ public class Parser
     private int error_token_end;
     private uint representation_base;
 
-    public Parser (string input, int number_base, int wordlen)
+    public Parser (string input, int number_base, int wordlen, AngleUnit angle_units)
     {
         this.input = input;
         lexer = new Lexer (input, this, number_base);
@@ -750,56 +814,14 @@ public class Parser
         this.number_base = number_base;
         this.representation_base = number_base;
         this.wordlen = wordlen;
+        this.angle_units = angle_units;
         error = ErrorCode.NONE;
         error_token = null;
         error_token_start = 0;
         error_token_end = 0;
     }
 
-    public void set_error (ErrorCode errorno, string? token = null, uint token_start = 0, uint token_end = 0)
-    {
-        error = errorno;
-        error_token = token;
-        error_token_start = input.char_count (token_start);
-        error_token_end = input.char_count (token_end);
-    }
-
-    public void set_representation_base (uint new_base)
-    {
-        representation_base = new_base;
-    }
-
-    public virtual bool variable_is_defined (string name)
-    {
-        return false;
-    }
-
-    public virtual Number? get_variable (string name)
-    {
-        return null;
-    }
-
-    public virtual void set_variable (string name, Number x)
-    {
-    }
-
-    public virtual bool function_is_defined (string name)
-    {
-        return false;
-    }
-
-    public virtual Number? get_function (string name, Number x)
-    {
-        return null;
-    }
-
-    public virtual Number? convert (Number x, string x_units, string z_units)
-    {
-        return null;
-    }
-
-    /* Start parsing input string. And call evaluate on success. */
-    public Number? parse (out uint representation_base, out ErrorCode error_code, out string? error_token, 
out uint error_start, out uint error_end)
+    public bool create_parse_tree (out uint representation_base, out ErrorCode error_code, out string? 
error_token, out uint error_start, out uint error_end)
     {
         representation_base = number_base;
         /* Scan string and split into tokens */
@@ -822,7 +844,7 @@ public class Parser
                 error_token = this.error_token;
                 error_start = error_token_start;
                 error_end = error_token_end;
-                return null;
+                return false;
             }
         }
         if (token.type != LexerTokenType.PL_EOS)
@@ -835,7 +857,7 @@ public class Parser
             error_token = this.error_token;
             error_start = error_token_start;
             error_end = error_token_end;
-            return null;
+            return false;
         }
 
         /* Input can't be parsed with grammar. */
@@ -848,8 +870,62 @@ public class Parser
             error_token = this.error_token;
             error_start = error_token_start;
             error_end = error_token_end;
-            return null;
+            return false;
         }
+
+        error_code = ErrorCode.NONE;
+        error_token = null;
+        error_start = 0;
+        error_end = 0;
+
+        return true;
+    }
+
+    public void set_error (ErrorCode errorno, string? token = null, uint token_start = 0, uint token_end = 0)
+    {
+        error = errorno;
+        error_token = token;
+        error_token_start = input.char_count (token_start);
+        error_token_end = input.char_count (token_end);
+    }
+
+    public void set_representation_base (uint new_base)
+    {
+        representation_base = new_base;
+    }
+
+    public virtual bool variable_is_defined (string name)
+    {
+        return false;
+    }
+
+    public virtual Number? get_variable (string name)
+    {
+        return null;
+    }
+
+    public virtual void set_variable (string name, Number x)
+    {
+    }
+
+    public virtual bool function_is_defined (string name)
+    {
+        return false;
+    }
+
+    public virtual Number? convert (Number x, string x_units, string z_units)
+    {
+        return null;
+    }
+
+    /* Start parsing input string. And call evaluate on success. */
+    public Number? parse (out uint representation_base, out ErrorCode error_code, out string? error_token, 
out uint error_start, out uint error_end)
+    {
+        var is_successfully_parsed = create_parse_tree (out representation_base, out error_code, out 
error_token, out error_start, out error_end);
+
+        if (!is_successfully_parsed)
+            return null;
+
         var ans = root.solve ();
         if (ans == null)
         {
@@ -1056,7 +1132,7 @@ public class Parser
     private bool statement ()
     {
         var token = lexer.get_next_token ();
-        if (token.type == LexerTokenType.VARIABLE)
+        if (token.type == LexerTokenType.VARIABLE || token.type == LexerTokenType.FUNCTION)
         {
             var token_old = token;
             token = lexer.get_next_token ();
@@ -1144,6 +1220,12 @@ public class Parser
                 lexer.roll_back ();
                 lexer.roll_back ();
 
+                if (token.type == LexerTokenType.L_R_BRACKET)
+                {
+                    if (function_definition ())
+                        return true;
+                }
+
                 if (!expression ())
                     return false;
 
@@ -1253,6 +1335,60 @@ public class Parser
         }
     }
 
+    private bool function_definition ()
+    {
+        int num_token_parsed = 0;
+        var token = lexer.get_next_token ();
+        num_token_parsed++;
+
+        string function_name = token.text;
+        lexer.get_next_token ();
+        num_token_parsed++;
+
+        token = lexer.get_next_token ();
+        num_token_parsed++;
+        string argument_list = "";
+
+        while (token.type != LexerTokenType.R_R_BRACKET && token.type != LexerTokenType.PL_EOS)
+        {
+            argument_list += token.text;
+            token = lexer.get_next_token ();
+            num_token_parsed++;
+        }
+
+        if (token.type == LexerTokenType.PL_EOS)
+        {
+            while (num_token_parsed-- > 0)
+                lexer.roll_back ();
+            return false;
+        }
+
+        var assign_token = lexer.get_next_token ();
+        num_token_parsed++;
+        if (assign_token.type != LexerTokenType.ASSIGN)
+        {
+            while (num_token_parsed-- > 0)
+                lexer.roll_back ();
+            return false;
+        }
+
+        string expression = "";
+        token = lexer.get_next_token ();
+        while (token.type != LexerTokenType.PL_EOS)
+        {
+            expression += token.text;
+            token = lexer.get_next_token ();
+        }
+
+        insert_into_tree (new FunctionNameNode (this, null, make_precedence_p (Precedence.NUMBER_VARIABLE), 
get_associativity_p (Precedence.NUMBER_VARIABLE), function_name));
+        insert_into_tree (new FunctionNode (this, null, make_precedence_p (Precedence.FUNCTION), 
get_associativity_p (Precedence.FUNCTION), null));
+        insert_into_tree (new FunctionArgumentsNode (this, null, make_precedence_p 
(Precedence.NUMBER_VARIABLE), get_associativity_p (Precedence.NUMBER_VARIABLE), argument_list));
+        insert_into_tree (new AssignFunctionNode (this, assign_token, 0, get_associativity (assign_token)));
+        insert_into_tree (new FunctionDescriptionNode (this, null, make_precedence_p 
(Precedence.NUMBER_VARIABLE), get_associativity_p (Precedence.NUMBER_VARIABLE), expression));
+
+        return true;
+    }
+
     private bool check_base ()
     {
         var token = lexer.get_next_token ();
@@ -1710,80 +1846,10 @@ public class Parser
         var token = lexer.get_next_token ();
         if (token.type == LexerTokenType.FUNCTION)
         {
-            depth_level++;
-            var token_old = token;
-            token = lexer.get_next_token ();
-            if (token.type == LexerTokenType.SUP_NUMBER)
-            {
-                /* Pass power as void * value. That will be taken care in pf_apply_func_with_power. */
-
-                insert_into_tree_unary (new FunctionWithPowerNode (this, token_old, make_precedence_t 
(token_old.type), get_associativity (token_old), token.text));
-                if (!expression_1 ())
-                {
-                    depth_level--;
-                    return false;
-                }
-
-                token = lexer.get_next_token ();
-                if (token.type == LexerTokenType.FACTORIAL)
-                    insert_into_tree_unary (new FactorialNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
-                else
-                    lexer.roll_back ();
-
-                depth_level--;
-
-                if (!expression_2 ())
-                    return false;
-
-                return true;
-            }
-            else if (token.type == LexerTokenType.NSUP_NUMBER)
-            {
-                /* Pass power as void * value. That will be taken care in pf_apply_func_with_npower. */
-
-                insert_into_tree_unary (new FunctionWithNegativePowerNode (this, token_old, 
make_precedence_t (token_old.type), get_associativity (token_old), token.text));
-                if (!expression_1 ())
-                {
-                    depth_level--;
-                    return false;
-                }
-
-                token = lexer.get_next_token ();
-                if (token.type == LexerTokenType.FACTORIAL)
-                    insert_into_tree_unary (new FactorialNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
-                else
-                    lexer.roll_back ();
-
-                depth_level--;
-
-                if (!expression_2 ())
-                    return false;
-
-                return true;
-            }
-            else
-            {
-                lexer.roll_back ();
-                insert_into_tree_unary (new FunctionNode (this, token_old, make_precedence_t 
(token_old.type), get_associativity (token_old)));
-                if (!expression_1 ())
-                {
-                    depth_level--;
-                    return false;
-                }
-
-                token = lexer.get_next_token ();
-                if (token.type == LexerTokenType.FACTORIAL)
-                    insert_into_tree_unary (new FactorialNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
-                else
-                    lexer.roll_back ();
-
-                depth_level--;
-
-                if (!expression_2 ())
-                    return false;
-
-                return true;
-            }
+            lexer.roll_back ();
+            if (!function_invocation ())
+                return false;
+            return true;
         }
         else if (token.type == LexerTokenType.SUB_NUMBER)
         {
@@ -1840,6 +1906,90 @@ public class Parser
             return false;
     }
 
+    private bool function_invocation ()
+    {
+        depth_level++;
+        int num_token_parsed = 0;
+        var fun_token = lexer.get_next_token ();
+        num_token_parsed ++;
+        string function_name = fun_token.text;
+
+        insert_into_tree (new FunctionNameNode (this, null, make_precedence_p (Precedence.NUMBER_VARIABLE), 
get_associativity_p (Precedence.NUMBER_VARIABLE), function_name));
+
+        var token = lexer.get_next_token ();
+        num_token_parsed++;
+        string? power = null;
+        if (token.type == LexerTokenType.SUP_NUMBER || token.type == LexerTokenType.NSUP_NUMBER)
+        {
+            power = token.text;
+            token = lexer.get_next_token ();
+            num_token_parsed++;
+        }
+
+        insert_into_tree (new FunctionNode (this, fun_token, make_precedence_t (fun_token.type), 
get_associativity (fun_token), power));
+
+        if (token.type == LexerTokenType.L_R_BRACKET)
+        {
+            token = lexer.get_next_token ();
+            num_token_parsed++;
+            int m_depth = 1;
+            string argument_list = "";
+
+            while (token.type != LexerTokenType.PL_EOS && token.type != LexerTokenType.ASSIGN)
+            {
+                if (token.type == LexerTokenType.L_R_BRACKET)
+                    m_depth++;
+                else if (token.type == LexerTokenType.R_R_BRACKET)
+                {
+                    m_depth--;
+                    if (m_depth == 0)
+                        break;
+                }
+                argument_list += token.text;
+                token = lexer.get_next_token ();
+                num_token_parsed++;
+            }
+
+            if (token.type != LexerTokenType.R_R_BRACKET)
+            {
+                while (num_token_parsed-- > 0)
+                    lexer.roll_back ();
+                depth_level--;
+                return false;
+            }
+
+            insert_into_tree (new FunctionArgumentsNode (this, null, make_precedence_p 
(Precedence.NUMBER_VARIABLE), get_associativity_p (Precedence.NUMBER_VARIABLE), argument_list));
+        }
+        else
+        {
+            lexer.roll_back ();
+            if (!expression_1 ())
+            {
+                lexer.roll_back ();
+                depth_level--;
+                return false;
+            }
+
+            token = lexer.get_next_token ();
+            if (token.type == LexerTokenType.FACTORIAL)
+                insert_into_tree_unary (new FactorialNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+            else
+                lexer.roll_back ();
+
+            depth_level--;
+
+            if (!expression_2 ())
+            {
+                lexer.roll_back ();
+                return false;
+            }
+            return true;
+        }
+
+        depth_level--;
+        return true;
+    }
+
     private bool term ()
     {
         var token = lexer.get_next_token ();
@@ -1847,13 +1997,16 @@ public class Parser
         if (token.type == LexerTokenType.VARIABLE)
         {
             var token_old = token;
+            token = lexer.get_next_token ();
             /* Check if the token is a valid variable or not. */
-            if (!check_variable (token.text))
+            if (!check_variable (token_old.text))
             {
-                set_error (ErrorCode.UNKNOWN_VARIABLE, token.text, token.start_index, token.end_index);
+                if (token.text == "(")
+                    set_error (ErrorCode.UNKNOWN_FUNCTION, token_old.text, token_old.start_index, 
token_old.end_index);
+                else
+                    set_error (ErrorCode.UNKNOWN_VARIABLE, token_old.text, token_old.start_index, 
token_old.end_index);
                 return false;
             }
-            token = lexer.get_next_token ();
             if (token.type == LexerTokenType.SUP_NUMBER)
                 insert_into_tree (new VariableWithPowerNode (this, token_old, make_precedence_t 
(token_old.type), get_associativity (token_old), token.text));
             else
diff --git a/src/equation.vala b/src/equation.vala
index 0669205..e56d1e3 100644
--- a/src/equation.vala
+++ b/src/equation.vala
@@ -153,11 +153,6 @@ public class Equation
         return false;
     }
 
-    public virtual Number? get_function (string name, Number x)
-    {
-        return null;
-    }
-
     public virtual Number? convert (Number x, string x_units, string z_units)
     {
         return null;
@@ -170,7 +165,7 @@ private class EquationParser : Parser
 
     public EquationParser (Equation equation, string expression)
     {
-        base (expression, equation.base, equation.wordlen);
+        base (expression, equation.base, equation.wordlen, equation.angle_units);
         this.equation = equation;
     }
 
@@ -211,111 +206,14 @@ private class EquationParser : Parser
 
     protected override bool function_is_defined (string name)
     {
-        var lower_name = name.down ();
+        var function_manager = FunctionManager.get_default_function_manager();
 
-        /* FIXME: Make more generic */
-        if (lower_name == "log" ||
-            (lower_name.has_prefix ("log") && sub_atoi (lower_name.substring (3)) >= 0) ||
-            lower_name == "ln" ||
-            lower_name == "sqrt" ||
-            lower_name == "abs" ||
-            lower_name == "sgn" ||
-            lower_name == "arg" ||
-            lower_name == "conj" ||
-            lower_name == "int" ||
-            lower_name == "frac" ||
-            lower_name == "floor" ||
-            lower_name == "ceil" ||
-            lower_name == "round" ||
-            lower_name == "re" ||
-            lower_name == "im" ||
-            lower_name == "sin" || lower_name == "cos" || lower_name == "tan" ||
-            lower_name == "asin" || lower_name == "acos" || lower_name == "atan" ||
-            lower_name == "sin⁻¹" || lower_name == "cos⁻¹" || lower_name == "tan⁻¹" ||
-            lower_name == "sinh" || lower_name == "cosh" || lower_name == "tanh" ||
-            lower_name == "sinh⁻¹" || lower_name == "cosh⁻¹" || lower_name == "tanh⁻¹" ||
-            lower_name == "asinh" || lower_name == "acosh" || lower_name == "atanh" ||
-            lower_name == "ones" ||
-            lower_name == "twos")
+        if (function_manager.is_function_defined (name))
             return true;
 
         return equation.function_is_defined (name);
     }
 
-    protected override Number? get_function (string name, Number x)
-    {
-        var lower_name = name.down ();
-
-        // FIXME: Re Im ?
-
-        if (lower_name == "log")
-            return x.logarithm (10); // FIXME: Default to ln
-        else if (lower_name.has_prefix ("log"))
-        {
-            var number_base = sub_atoi (lower_name.substring (3));
-            if (number_base < 0)
-                return null;
-            else
-                return x.logarithm (number_base);
-        }
-        else if (lower_name == "ln")
-            return x.ln ();
-        else if (lower_name == "sqrt") // √x
-            return x.sqrt ();
-        else if (lower_name == "abs") // |x|
-            return x.abs ();
-        else if (lower_name == "sgn")
-            return x.sgn ();
-        else if (lower_name == "arg")
-            return x.arg (equation.angle_units);
-        else if (lower_name == "conj")
-            return x.conjugate ();
-        else if (lower_name == "int")
-            return x.integer_component ();
-        else if (lower_name == "frac")
-            return x.fractional_component ();
-        else if (lower_name == "floor")
-            return x.floor ();
-        else if (lower_name == "ceil")
-            return x.ceiling ();
-        else if (lower_name == "round")
-            return x.round ();
-        else if (lower_name == "re")
-            return x.real_component ();
-        else if (lower_name == "im")
-            return x.imaginary_component ();
-        else if (lower_name == "sin")
-            return x.sin (equation.angle_units);
-        else if (lower_name == "cos")
-            return x.cos (equation.angle_units);
-        else if (lower_name == "tan")
-            return x.tan (equation.angle_units);
-        else if (lower_name == "sin⁻¹" || lower_name == "asin")
-            return x.asin (equation.angle_units);
-        else if (lower_name == "cos⁻¹" || lower_name == "acos")
-            return x.acos (equation.angle_units);
-        else if (lower_name == "tan⁻¹" || lower_name == "atan")
-            return x.atan (equation.angle_units);
-        else if (lower_name == "sinh")
-            return x.sinh ();
-        else if (lower_name == "cosh")
-            return x.cosh ();
-        else if (lower_name == "tanh")
-            return x.tanh ();
-        else if (lower_name == "sinh⁻¹" || lower_name == "asinh")
-            return x.asinh ();
-        else if (lower_name == "cosh⁻¹" || lower_name == "acosh")
-            return x.acosh ();
-        else if (lower_name == "tanh⁻¹" || lower_name == "atanh")
-            return x.atanh ();
-        else if (lower_name == "ones")
-            return x.ones_complement (equation.wordlen);
-        else if (lower_name == "twos")
-            return x.twos_complement (equation.wordlen);
-        else
-            return equation.get_function (name, x);
-    }
-
     protected override Number? convert (Number x, string x_units, string z_units)
     {
         return equation.convert (x, x_units, z_units);
diff --git a/src/function-manager.vala b/src/function-manager.vala
new file mode 100644
index 0000000..66ae52c
--- /dev/null
+++ b/src/function-manager.vala
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2013 Garima Joshi
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+private FunctionManager? default_function_manager = null;
+
+public class FunctionManager : Object
+{
+    private string file_name;
+    private HashTable<string, MathFunction> functions;
+    private Serializer serializer;
+
+    public FunctionManager ()
+    {
+        functions = new HashTable <string, MathFunction> (str_hash, str_equal);
+        file_name = Path.build_filename (Environment.get_user_data_dir (), "gnome-calculator", 
"custom-functions");
+        serializer = new Serializer (DisplayFormat.SCIENTIFIC, 10, 50);
+        serializer.set_radix ('.');
+        reload_functions ();
+    }
+
+    public static FunctionManager get_default_function_manager ()
+    {
+        if (default_function_manager == null)
+            default_function_manager = new FunctionManager ();
+        return default_function_manager;
+    }
+
+    private void reload_functions ()
+    {
+        functions.remove_all ();
+        reload_custom_functions ();
+        reload_builtin_functions ();
+    }
+
+    private void reload_builtin_functions ()
+    {
+        add (new BuiltInMathFunction ("log", "Logarithm"));
+
+        add (new BuiltInMathFunction ("ln", "Natural logarithm"));
+
+        add (new BuiltInMathFunction ("sqrt", "Square root"));
+
+        add (new BuiltInMathFunction ("abs", "Absolute value"));
+
+        add (new BuiltInMathFunction ("sgn", "Signum"));
+
+        add (new BuiltInMathFunction ("arg", "Argument"));
+
+        add (new BuiltInMathFunction ("conj", "Conjugate"));
+
+        add (new BuiltInMathFunction ("int", "Integer"));
+
+        add (new BuiltInMathFunction ("frac", "Fraction"));
+
+        add (new BuiltInMathFunction ("floor", "Floor"));
+
+        add (new BuiltInMathFunction ("ceil", "Ceiling"));
+
+        add (new BuiltInMathFunction ("round", "Round"));
+
+        add (new BuiltInMathFunction ("re", "Real"));
+
+        add (new BuiltInMathFunction ("im", "Imaginary"));
+
+        add (new BuiltInMathFunction ("sin", "Sine"));
+
+        add (new BuiltInMathFunction ("cos", "Cosine"));
+
+        add (new BuiltInMathFunction ("tan", "Tangent"));
+
+        add (new BuiltInMathFunction ("asin", "Arc sine"));
+
+        add (new BuiltInMathFunction ("acos", "Arc cosine"));
+
+        add (new BuiltInMathFunction ("atan", "Arc tangent"));
+
+        add (new BuiltInMathFunction ("sin⁻¹", "Inverse sine"));
+
+        add (new BuiltInMathFunction ("cos⁻¹", "Inverse cosine"));
+
+        add (new BuiltInMathFunction ("tan⁻¹", "Inverse tangent"));
+
+        add (new BuiltInMathFunction ("sinh", "Hyperbolic sine"));
+
+        add (new BuiltInMathFunction ("cosh", "Hyperbolic cosine"));
+
+        add (new BuiltInMathFunction ("tanh", "Hyperbolic tangent"));
+
+        add (new BuiltInMathFunction ("sinh⁻¹", "Hyperbolic arcsine"));
+
+        add (new BuiltInMathFunction ("cosh⁻¹", "Hyperbolic arccosine"));
+
+        add (new BuiltInMathFunction ("tanh⁻¹", "Hyperbolic arctangent"));
+
+        add (new BuiltInMathFunction ("asinh", "Inverse hyperbolic sine"));
+
+        add (new BuiltInMathFunction ("acosh", "Inverse hyperbolic cosine"));
+
+        add (new BuiltInMathFunction ("atanh", "Inverse hyperbolic tangent"));
+
+        add (new BuiltInMathFunction ("ones", "One's complement"));
+
+        add (new BuiltInMathFunction ("twos", "Two's complement"));
+    }
+
+    private void reload_custom_functions ()
+    {
+        string data;
+        try
+        {
+            FileUtils.get_contents (file_name, out data);
+        }
+        catch (FileError e)
+        {
+            return;
+        }
+        var lines = data.split ("\n");
+
+        foreach (var line in lines)
+        {
+            MathFunction? function = parse_function_from_string (line);
+            if (function != null)
+                functions.insert (function.name, function);
+        }
+    }
+
+    private MathFunction? parse_function_from_string (string? data)
+    {
+        // pattern: <name> (<a1>;<a2>;<a3>;...) = <expression> @ <description>
+
+        if (data == null)
+            return null;
+
+        var i = data.index_of_char ('=');
+        if (i < 0)
+            return null;
+        var left = data.substring (0, i).strip ();
+        var right = data.substring (i+1).strip ();
+        if (left == null || right == null)
+            return null;
+
+        var expression = "";
+        var description = "";
+        i = right.index_of_char ('@');
+        if (i < 0)
+            expression = right;
+        else
+        {
+            expression = right.substring (0, i).strip ();
+            description = right.substring (i+1).strip ();
+        }
+        if (expression == null)
+            return null;
+
+        i = left.index_of_char('(');
+        if (i < 0)
+            return null;
+        var name = left.substring (0, i).strip ();
+        var argument_list = left.substring (i+1).strip ();
+        if (name == null || argument_list == null)
+            return null;
+
+        argument_list = argument_list.replace (")", "");
+        string[] arguments = argument_list.split_set (";");
+
+        return (new MathFunction (name, arguments, expression, description));
+    }
+
+    private void save ()
+    {
+        var data = "";
+        var iter = HashTableIter<string, MathFunction> (functions);
+        string name;
+        MathFunction math_function;
+        while (iter.next (out name, out math_function))
+        {
+            if (!math_function.is_custom_function ())
+                continue;       //skip builtin functions
+
+            data += "%s(%s)=%s %s\n".printf (math_function.name,
+                                             string.joinv (";", math_function.arguments),
+                                             math_function.expression,
+                                             math_function.description);
+        }
+
+        var dir = Path.get_dirname (file_name);
+        DirUtils.create_with_parents (dir, 0700);
+        try
+        {
+            FileUtils.set_contents (file_name, data);
+        }
+        catch (FileError e)
+        {
+        }
+    }
+
+    private string[] array_sort_string (string[] array)
+    {
+        bool swapped = true;
+        int j = (array[array.length - 1] == null ? 1 : 0);
+        string tmp;
+
+        while (swapped)
+        {
+            swapped = false;
+            j++;
+            for (int i = 0; i < array.length - j; i++)
+            {
+                if (array[i] < array[i + 1])
+                {
+                    tmp = array[i];
+                    array[i] = array[i + 1];
+                    array[i + 1] = tmp;
+                    swapped = true;
+                }
+            }
+        }
+        return array;
+    }
+
+    public string[] get_names ()
+    {
+        var names = new string[functions.size () + 1];
+
+        var iter = HashTableIter<string, MathFunction> (functions);
+        var i = 0;
+        string name;
+        MathFunction? definition;
+        while (iter.next (out name, out definition))
+        {
+            names[i] = name;
+            i++;
+        }
+        names[i] = null;
+
+        return array_sort_string (names);
+    }
+
+    private bool add (MathFunction new_function)
+    {
+        MathFunction? existing_function = get (new_function.name);
+
+        if (existing_function != null && !existing_function.is_custom_function ())
+            return false;
+
+        if (existing_function != null)
+            functions.replace (new_function.name, new_function);
+        else
+            functions.insert (new_function.name, new_function);
+
+        return true;
+    }
+
+    public bool add_function_with_properties (string name, string arguments, string description, Parser? 
root_parser = null)
+    {
+        var function_string = name + "(" + arguments + ")=" + description;
+        MathFunction? new_function = this.parse_function_from_string (function_string);
+
+        if (new_function == null || new_function.validate (root_parser) == false)
+        {
+            root_parser.set_error (ErrorCode.INVALID);
+            return false;
+        }
+
+        var is_function_added = this.add (new_function);
+        if (is_function_added)
+            save ();
+
+        return is_function_added;
+    }
+
+    public new MathFunction? get (string name)
+    {
+        MathFunction? function = functions.lookup (name);
+        if (function != null)
+            return function;
+        return functions.lookup (name.down ());
+    }
+
+    public void delete (string name)
+    {
+        MathFunction? function = get (name);
+        if (function != null && function.is_custom_function ())
+        {
+            functions.remove (name);
+            save ();
+        }
+    }
+
+    public bool is_function_defined (string name)
+    {
+        var lower_name = name.down ();
+        if (lower_name.has_prefix ("log") && sub_atoi (lower_name.substring (3)) >= 0)
+            return true;
+        return functions.contains (name) || functions.contains (lower_name);
+    }
+
+    public Number? evaluate_function (string name, Number[] arguments, Parser parser)
+    {
+        var lower_name = name.down ();
+        var args = arguments;
+        if (lower_name.has_prefix ("log") && sub_atoi (lower_name.substring (3)) > 0)
+        {
+            Number log_base = new Number.integer (sub_atoi (lower_name.substring (3)));
+            args += log_base;
+            name = "log";
+        }
+
+        MathFunction? function = this.get (name);
+        if (function == null)
+        {
+            parser.set_error (ErrorCode.UNKNOWN_FUNCTION);
+            return null;
+        }
+
+        return function.evaluate (args, parser);
+    }
+
+    private MathFunction[] array_sort_math_function (MathFunction[] array)
+    {
+        bool swapped = true;
+        int j = (array[array.length - 1] == null ? 1 : 0);
+        MathFunction tmp;
+
+        while (swapped)
+        {
+            swapped = false;
+            j++;
+            for (int i = 0; i < array.length - j; i++)
+            {
+                if (array[i].name > array[i + 1].name)
+                {
+                    tmp = array[i];
+                    array[i] = array[i + 1];
+                    array[i + 1] = tmp;
+                    swapped = true;
+                }
+            }
+        }
+        return array;
+    }
+
+    public MathFunction[] functions_eligible_for_autocompletion_for_text (string display_text)
+    {
+        MathFunction[] eligible_functions = {};
+        if (display_text.length <= 1)
+            return eligible_functions;
+
+        string display_text_case_insensitive = display_text.down ();
+        var iter = HashTableIter<string, MathFunction> (functions);
+        string function_name;
+        MathFunction function;
+        while (iter.next (out function_name, out function))
+        {
+            string function_name_case_insensitive = function_name.down ();
+            if (function_name_case_insensitive.has_prefix (display_text_case_insensitive))
+                eligible_functions += function;
+        }
+
+        return array_sort_math_function (eligible_functions);
+    }
+}
diff --git a/src/math-buttons.vala b/src/math-buttons.vala
index 755334e..dab7030 100644
--- a/src/math-buttons.vala
+++ b/src/math-buttons.vala
@@ -68,8 +68,6 @@ public class MathButtons : Gtk.Box
     private Gtk.Menu shift_left_menu;
     private Gtk.Menu shift_right_menu;
 
-    private Gtk.Menu function_menu;
-
     private List<Gtk.ToggleButton> superscript_toggles;
     private List<Gtk.ToggleButton> subscript_toggles;
 
@@ -801,37 +799,17 @@ public class MathButtons : Gtk.Box
         popup_button_menu (button, shift_right_menu);
     }
 
-    private void function_cb (Gtk.Button button)
+    private void function_cb (Gtk.Widget widget)
     {
-        if (function_menu == null)
-        {
-            function_menu = new Gtk.Menu ();
-            function_menu.set_reserve_toggle_size (false);
-
-            /* Tooltip for the integer component button */
-            add_function_menu_item (function_menu, _("Integer Component"), "int ");
-            /* Tooltip for the fractional component button */
-            add_function_menu_item (function_menu, _("Fractional Component"), "frac ");
-            /* Tooltip for the round button */
-            add_function_menu_item (function_menu, _("Round"), "round ");
-            /* Tooltip for the floor button */
-            add_function_menu_item (function_menu, _("Floor"), "floor ");
-            /* Tooltip for the ceiling button */
-            add_function_menu_item (function_menu, _("Ceiling"), "ceil ");
-            /* Tooltip for the ceiling button */
-            add_function_menu_item (function_menu, _("Sign"), "sgn ");
-        }
-
-        popup_button_menu (button, function_menu);
-    }
+        var popup = new MathFunctionPopup (equation);
+        popup.set_transient_for (widget.get_toplevel () as Gtk.Window);
 
-    private void add_function_menu_item (Gtk.Menu menu, string label, string function)
-    {
-        var item = new Gtk.MenuItem.with_label (label);
-        item.set_data<string> ("function", function);
-        menu.append (item);
-        item.activate.connect ((widget) => { equation.insert (widget.get_data<string> ("function")); });
-        item.show ();
+        Gtk.Allocation allocation;
+        widget.get_allocation (out allocation);
+        int x, y;
+        widget.get_window ().get_root_coords (allocation.x, allocation.y, out x, out y);
+        popup.move (x, y);
+        popup.show ();
     }
 
     private void finc_cb (Gtk.Widget widget)
diff --git a/src/math-display.vala b/src/math-display.vala
index 463ec78..b0c68b0 100644
--- a/src/math-display.vala
+++ b/src/math-display.vala
@@ -15,7 +15,7 @@ public class MathDisplay : Gtk.Viewport
     public MathEquation equation { get { return _equation; } }
 
     /* Display widget */
-    Gtk.TextView text_view;
+    Gtk.SourceView source_view;
 
     /* Buffer that shows errors etc */
     Gtk.TextBuffer info_buffer;
@@ -30,24 +30,25 @@ public class MathDisplay : Gtk.Viewport
         var main_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
         add (main_box);
 
-        text_view = new Gtk.TextView.with_buffer (equation);
-        text_view.set_wrap_mode (Gtk.WrapMode.WORD);
-        text_view.set_accepts_tab (false);
-        text_view.set_pixels_above_lines (8);
-        text_view.set_pixels_below_lines (2);
+        source_view = new Gtk.SourceView.with_buffer (equation);
+        source_view.set_wrap_mode (Gtk.WrapMode.WORD);
+        source_view.set_accepts_tab (false);
+        source_view.set_pixels_above_lines (8);
+        source_view.set_pixels_below_lines (2);
         /* TEMP: Disabled for now as GTK+ doesn't properly render a right aligned right margin, see bug 
#482688 */
-        /*text_view.set_right_margin (6);*/
-        text_view.set_justification (Gtk.Justification.RIGHT);
-        text_view.ensure_style ();
-        var font_desc = text_view.get_style_context ().get_font (Gtk.StateFlags.NORMAL);
+        /*source_view.set_right_margin (6);*/
+        source_view.set_justification (Gtk.Justification.RIGHT);
+        source_view.ensure_style ();
+        var font_desc = source_view.get_style_context ().get_font (Gtk.StateFlags.NORMAL);
         font_desc.set_size (16 * Pango.SCALE);
-        text_view.override_font (font_desc);
-        text_view.set_name ("displayitem");
-        text_view.get_accessible ().set_role (Atk.Role.EDITBAR);
+        source_view.override_font (font_desc);
+        source_view.set_name ("displayitem");
+        source_view.get_accessible ().set_role (Atk.Role.EDITBAR);
         //FIXME:<property name="AtkObject::accessible-description" translatable="yes" comments="Accessible 
description for the area in which results are displayed">Result Region</property>
-        text_view.key_press_event.connect (key_press_cb);
+        source_view.key_press_event.connect (key_press_cb);
+        create_autocompletion ();
 
-        main_box.pack_start (text_view, true, true, 0);
+        main_box.pack_start (source_view, true, true, 0);
 
         var info_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
         main_box.pack_start (info_box, false, true, 0);
@@ -68,7 +69,7 @@ public class MathDisplay : Gtk.Viewport
 
         info_box.show ();
         info_view.show ();
-        text_view.show ();
+        source_view.show ();
         main_box.show ();
 
         equation.notify["status"].connect ((pspec) => { status_changed_cb (); });
@@ -77,9 +78,35 @@ public class MathDisplay : Gtk.Viewport
         equation.notify["error-token-end"].connect ((pspec) => { error_status_changed_cb (); });
     }
 
+    private void create_autocompletion ()
+    {
+        Gtk.SourceCompletion completion = source_view.get_completion ();
+        try
+        {
+            completion.add_provider (new FunctionCompletionProvider ());
+            completion.add_provider (new VariableCompletionProvider (equation));
+        }
+        catch (Error e)
+        {
+            warning ("Could not add CompletionProvider to source-view");
+        }
+    }
+
+    private bool function_completion_window_visible ()
+    {
+        unowned List<Gtk.SourceCompletionProvider> providers_list = source_view.get_completion 
().get_providers ();
+        if (providers_list.length () > 0)
+        {
+            MathFunction[] functions = FunctionCompletionProvider.get_matches_for_completion_at_cursor 
(equation);
+            string[] variables = VariableCompletionProvider.get_matches_for_completion_at_cursor (equation, 
equation.variables);
+            return ((functions.length > 0 || variables.length > 0) ? true : false);
+        }
+        return false;
+    }
+
     protected override bool key_press_event (Gdk.EventKey event)
     {
-        return text_view.key_press_event (event);
+        return source_view.key_press_event (event);
     }
 
     private bool key_press_cb (Gdk.EventKey event)
@@ -133,6 +160,8 @@ public class MathDisplay : Gtk.Viewport
         /* Solve on enter */
         if (event.keyval == Gdk.Key.Return || event.keyval == Gdk.Key.KP_Enter)
         {
+            if (function_completion_window_visible ())
+                return false;
             equation.solve ();
             return true;
         }
@@ -350,3 +379,147 @@ public class MathDisplay : Gtk.Viewport
         equation.select_range (start, end);
     }
 }
+
+public class CompletionProvider : GLib.Object, Gtk.SourceCompletionProvider
+{
+    public virtual string get_name ()
+    {
+        return "";
+    }
+
+    public static void move_iter_to_name_start (ref Gtk.TextIter iter)
+    {
+        while (iter.backward_char ())
+        {
+            unichar current_char = iter.get_char ();
+            if (!current_char.isalpha ())
+            {
+                iter.forward_char ();
+                break;
+            }
+        }
+    }
+
+    public virtual bool get_start_iter (Gtk.SourceCompletionContext context, Gtk.SourceCompletionProposal 
proposal, Gtk.TextIter iter)
+    {
+        return false;
+    }
+
+    public virtual bool activate_proposal (Gtk.SourceCompletionProposal proposal, Gtk.TextIter iter)
+    {
+        string proposed_string = proposal.get_text ();
+        Gtk.TextBuffer buffer = iter.get_buffer ();
+
+        Gtk.TextIter start_iter, end;
+        buffer.get_iter_at_offset (out start_iter, iter.get_offset ());
+        move_iter_to_name_start (ref start_iter);
+
+        buffer.place_cursor (start_iter);
+        buffer.delete_range (start_iter, iter);
+        buffer.insert_at_cursor (proposed_string, proposed_string.length);
+        if (proposed_string.contains ("()"))
+        {
+            buffer.get_iter_at_mark (out end, buffer.get_insert ());
+            end.backward_chars (1);
+            buffer.place_cursor (end);
+        }
+        return true;
+    }
+
+    public virtual void populate (Gtk.SourceCompletionContext context) {}
+}
+
+public class FunctionCompletionProvider : CompletionProvider
+{
+    public override string get_name ()
+    {
+        return "Defined Functions";
+    }
+
+    public static MathFunction[] get_matches_for_completion_at_cursor (Gtk.TextBuffer text_buffer)
+    {
+        Gtk.TextIter start_iter, end_iter;
+        text_buffer.get_iter_at_mark (out end_iter, text_buffer.get_insert ());
+        text_buffer.get_iter_at_mark (out start_iter, text_buffer.get_insert ());
+        CompletionProvider.move_iter_to_name_start (ref start_iter);
+
+        string search_pattern = text_buffer.get_slice (start_iter, end_iter, false);
+
+        FunctionManager function_manager = FunctionManager.get_default_function_manager ();
+        MathFunction[] functions = function_manager.functions_eligible_for_autocompletion_for_text 
(search_pattern);
+        return functions;
+    }
+
+    public override void populate (Gtk.SourceCompletionContext context)
+    {
+        Gtk.TextBuffer text_buffer = context.get_iter ().get_buffer ();
+        MathFunction[] functions = get_matches_for_completion_at_cursor (text_buffer);
+
+        List<Gtk.SourceCompletionItem>? proposals = null;
+        if (functions.length > 0)
+        {
+            proposals = new List<Gtk.SourceCompletionItem> ();
+            foreach (var function in functions)
+            {
+                string display_text = "%s(%s)".printf (function.name, string.joinv (";", 
function.arguments));
+                string details_text = "%s".printf (function.description);
+                string label_text = function.name + "()";
+                if (function.is_custom_function ())
+                    details_text = "%s(%s)=%s\n%s".printf (function.name, string.joinv (";", 
function.arguments),
+                                                           function.expression, function.description);
+                var proposal = new Gtk.SourceCompletionItem (display_text, label_text, null, details_text);
+                proposals.append (proposal);
+            }
+        }
+        context.add_proposals (this, proposals, true);
+    }
+}
+
+public class VariableCompletionProvider : CompletionProvider
+{
+    private MathEquation _equation;
+
+    public VariableCompletionProvider (MathEquation equation)
+    {
+        _equation = equation;
+    }
+
+    public override string get_name ()
+    {
+        return "Defined Variables";
+    }
+
+    public static string[] get_matches_for_completion_at_cursor (Gtk.TextBuffer text_buffer, MathVariables 
variables )
+    {
+        Gtk.TextIter start_iter, end_iter;
+        text_buffer.get_iter_at_mark (out end_iter, text_buffer.get_insert ());
+        text_buffer.get_iter_at_mark (out start_iter, text_buffer.get_insert ());
+        CompletionProvider.move_iter_to_name_start (ref start_iter);
+
+        string search_pattern = text_buffer.get_slice (start_iter, end_iter, false);
+        string[] math_variables = variables.variables_eligible_for_autocompletion (search_pattern);
+        return math_variables;
+    }
+
+    public override void populate (Gtk.SourceCompletionContext context)
+    {
+        Gtk.TextBuffer text_buffer = context.get_iter ().get_buffer ();
+        string[] variables = get_matches_for_completion_at_cursor (text_buffer, _equation.variables);
+
+        List<Gtk.SourceCompletionItem>? proposals = null;
+        if (variables.length > 0)
+        {
+            proposals = new List<Gtk.SourceCompletionItem> ();
+            foreach (var variable in variables)
+            {
+                string display_text = "%s".printf (variable);
+                string details_text = _equation.serializer.to_string (_equation.variables.get (variable));
+                string label_text = variable;
+
+                var proposal = new Gtk.SourceCompletionItem (display_text, label_text, null, details_text);
+                proposals.append (proposal);
+            }
+        }
+        context.add_proposals (this, proposals, true);
+    }
+}
diff --git a/src/math-equation.vala b/src/math-equation.vala
index fb2051b..da415fa 100644
--- a/src/math-equation.vala
+++ b/src/math-equation.vala
@@ -43,7 +43,7 @@ private class SolveData
     public uint representation_base;
 }
 
-public class MathEquation : Gtk.TextBuffer
+public class MathEquation : Gtk.SourceBuffer
 {
     private Gtk.TextTag ans_tag;
 
@@ -473,7 +473,7 @@ public class MathEquation : Gtk.TextBuffer
             insert (text);
     }
 
-    public void undo ()
+    public override void undo ()
     {
         if (undo_stack == null)
         {
@@ -489,7 +489,7 @@ public class MathEquation : Gtk.TextBuffer
         apply_state (state);
     }
 
-    public void redo ()
+    public override void redo ()
     {
         if (redo_stack == null)
         {
diff --git a/src/math-function-popup.vala b/src/math-function-popup.vala
new file mode 100644
index 0000000..ba89188
--- /dev/null
+++ b/src/math-function-popup.vala
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2013 Garima Joshi
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class MathFunctionPopup : Gtk.Window
+{
+    private MathEquation equation;
+
+    private Gtk.ScrolledWindow scrolled_window;
+    private Gtk.Box vbox;
+    private Gtk.Entry function_name_entry;
+    private Gtk.Button add_function_button;
+    private Gtk.SpinButton add_arguments_button;
+
+    public MathFunctionPopup (MathEquation equation)
+    {
+        this.equation = equation;
+
+        decorated = false;
+        skip_taskbar_hint = true;
+        border_width = 6;
+        this.set_default_size (200, 260);
+
+        /* Destroy this window when it loses focus */
+        focus_out_event.connect ((event) => { destroy (); return false; });
+
+        scrolled_window = new Gtk.ScrolledWindow (null, null);
+        this.add (scrolled_window);
+        scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
+        scrolled_window.show();
+
+        vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        vbox.homogeneous = true;
+        scrolled_window.add_with_viewport (vbox);
+        vbox.show ();
+
+        FunctionManager function_manager = FunctionManager.get_default_function_manager ();
+        var names = function_manager.get_names ();
+
+        for (var i = 0; names[i] != null; i++)
+        {
+            var math_function = function_manager.get (names[i]);
+            bool is_custom = math_function.is_custom_function ();
+
+            var expression = "(x)";
+            if (is_custom)
+                expression = "(%s)".printf (string.joinv (";", math_function.arguments));
+            var entry = make_function_entry (names[i], expression, math_function, is_custom);
+            entry.show ();
+            vbox.pack_end (entry, false, true, 0);
+        }
+
+        var entry = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+        entry.show ();
+
+        function_name_entry = new Gtk.Entry ();
+        function_name_entry.set_text ("Type function name");
+        function_name_entry.key_press_event.connect (function_name_key_press_cb);
+        function_name_entry.changed.connect (function_name_changed_cb);
+        function_name_entry.set_margin_right (5);
+        function_name_entry.activate.connect (add_function_cb);
+        entry.pack_start (function_name_entry, true, true, 0);
+        function_name_entry.show ();
+        vbox.pack_start (entry, false, true, 0);
+
+        entry = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+        entry.show ();
+        add_arguments_button = new Gtk.SpinButton.with_range (1, 10, 1);
+        entry.pack_start (add_arguments_button, false, true, 0);
+        add_arguments_button.show ();
+        add_function_button = new Gtk.Button ();
+        add_function_button.sensitive = false;
+        add_function_button.clicked.connect (add_function_cb);
+        var image = new Gtk.Image.from_stock (Gtk.Stock.ADD, Gtk.IconSize.BUTTON);
+        add_function_button.add (image);
+        entry.pack_end (add_function_button, false, true, 0);
+        image.show ();
+        add_function_button.show ();
+
+        vbox.pack_end (entry, false, true, 0);
+    }
+
+    private void insert_function_cb (Gtk.Widget widget)
+    {
+        var name = widget.get_data<string> ("function_name");
+        name += "()";
+        equation.insert (name);
+        Gtk.TextIter end;
+        equation.get_iter_at_mark (out end, equation.get_insert ());
+        end.backward_chars (1);
+        equation.place_cursor (end);
+        widget.get_toplevel ().destroy ();
+    }
+
+    private bool function_name_key_press_cb (Gtk.Widget widget, Gdk.EventKey event)
+    {
+        /* Can't have whitespace in names, so replace with underscores */
+        if (event.keyval == Gdk.Key.space || event.keyval == Gdk.Key.KP_Space)
+            event.keyval = Gdk.Key.underscore;
+
+        return false;
+    }
+
+    private void function_name_changed_cb ()
+    {
+        add_function_button.sensitive = function_name_entry.get_text () != "";
+    }
+
+    private void add_function_cb (Gtk.Widget widget)
+    {
+        var name = function_name_entry.get_text ();
+        if (name == "")
+            return;
+
+        var arguments = add_arguments_button.get_value_as_int ();
+        name += "(";
+        if (arguments > 0)
+        {
+            string s = "xyzuvwabcd";
+            for (int i = 0; i < arguments; i++)
+            {
+                name += s.substring(i, 1);
+                if (i < arguments - 1)
+                    name += "; ";
+            }
+        }
+        name += ")=";
+        equation.clear ();
+        equation.insert (name);
+
+        widget.get_toplevel ().destroy ();
+    }
+
+    private void save_function_cb (Gtk.Widget widget)
+    {
+        var name = widget.get_data<string> ("function_name");
+        FunctionManager function_manager = FunctionManager.get_default_function_manager ();
+        MathFunction function = function_manager.get (name);
+        var function_to_edit = "%s(%s)=%s %s".printf (function.name,
+                                                      string.joinv (";", function.arguments),
+                                                      function.expression,
+                                                      function.description);
+        equation.clear ();
+        equation.insert (function_to_edit);
+
+        widget.get_toplevel ().destroy ();
+    }
+
+    private void delete_function_cb (Gtk.Widget widget)
+    {
+        var name = widget.get_data<string> ("function_name");
+
+        FunctionManager function_manager = FunctionManager.get_default_function_manager ();
+        function_manager.delete (name);
+
+        widget.get_toplevel ().destroy ();
+    }
+
+    private Gtk.Box make_function_entry (string name, string expression, MathFunction math_function, bool 
writable)
+    {
+        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+
+        string text = "<b>%s</b>%s".printf (name, expression);
+
+        var button = new Gtk.Button ();
+        button.set_data<string> ("function_name", name);
+        string function_details = "%s".printf (math_function.description);
+        button.set_tooltip_text (function_details);
+        button.clicked.connect (insert_function_cb);
+        button.set_relief (Gtk.ReliefStyle.NONE);
+        hbox.pack_start (button, true, true, 0);
+        button.show ();
+
+        var label = new Gtk.Label (text);
+        label.set_use_markup (true);
+        label.set_alignment (0.0f, 0.5f);
+        button.add (label);
+        label.show ();
+
+        if (writable)
+        {
+            button = new Gtk.Button ();
+            button.set_data<string> ("function_name", name);
+            var image = new Gtk.Image.from_stock (Gtk.Stock.EDIT, Gtk.IconSize.BUTTON);
+            button.add (image);
+            hbox.pack_start (button, false, true, 0);
+            button.clicked.connect (save_function_cb);
+            image.show ();
+            button.show ();
+
+            button = new Gtk.Button ();
+            button.set_data<string> ("function_name", name);
+            image = new Gtk.Image.from_stock (Gtk.Stock.DELETE, Gtk.IconSize.BUTTON);
+            button.add (image);
+            hbox.pack_start (button, false, true, 0);
+            button.clicked.connect (delete_function_cb);
+            image.show ();
+            button.show ();
+        }
+
+        return hbox;
+    }
+}
diff --git a/src/math-function.vala b/src/math-function.vala
new file mode 100644
index 0000000..a2f0b3e
--- /dev/null
+++ b/src/math-function.vala
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2013 Garima Joshi
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class MathFunction : Object
+{
+    private string _name;
+    private string[] _arguments;
+    private string? _expression;
+    private string? _description;
+
+    public string name {
+        get { return _name; }
+    }
+
+    public string[] arguments {
+        get { return _arguments; }
+    }
+
+    public string? expression {
+        get { return _expression; }
+    }
+
+    public string? description {
+        get { return _description; }
+    }
+
+    public MathFunction (string function_name, string[] arguments, string? expression, string? description)
+    {
+        _name = function_name;
+        _arguments = arguments;
+
+        if (expression != null)
+            _expression = expression;
+        else
+            _expression = "";
+
+        if (description != null)
+            _description = description;
+        else
+            _description = "";
+    }
+
+    public virtual Number? evaluate (Number[] args, Parser? root_parser = null)
+    {
+        FunctionParser parser = new FunctionParser (this, root_parser, args);
+
+        uint representation_base;
+        ErrorCode error_code;
+        string? error_token;
+        uint error_start;
+        uint error_end;
+
+        var ans = parser.parse (out representation_base, out error_code, out error_token, out error_start, 
out error_end);
+        if (error_code == ErrorCode.NONE)
+            return ans;
+
+        root_parser.set_error (error_code, error_token, error_start, error_end);
+        return null;
+    }
+
+    public bool validate (Parser? root_parser = null)
+    {
+        if (!is_name_valid (name))
+        {
+            root_parser.set_error (ErrorCode.INVALID);
+            return false;
+        }
+        foreach (var argument in arguments)
+        {
+            if (!is_name_valid (argument))
+            {
+                root_parser.set_error (ErrorCode.INVALID);
+                return false;
+            }
+        }
+
+        Number[] args = {};
+        FunctionParser parser = new FunctionParser (this, root_parser, args);
+
+        uint representation_base;
+        ErrorCode error_code;
+        string? error_token;
+        uint error_start;
+        uint error_end;
+
+        parser.create_parse_tree (out representation_base, out error_code, out error_token, out error_start, 
out error_end);
+        if (error_code == ErrorCode.NONE)
+            return true;
+
+        root_parser.set_error (error_code, error_token, error_start, error_end);
+        return false;
+    }
+
+    private bool is_name_valid (string x)
+    {
+        for (int i = 0; i < x.length; i++)
+        {
+            unichar current_char = x.get_char (i);
+            if (!current_char.isalpha ())
+                return false;
+        }
+        return true;
+    }
+
+    public virtual bool is_custom_function ()
+    {
+        return true;
+    }
+}
+
+public class ExpressionParser : Parser
+{
+    private Parser? _root_parser;
+
+    public ExpressionParser (string expression, Parser? root_parser = null)
+    {
+        base (expression, root_parser.number_base, root_parser.wordlen, root_parser.angle_units);
+        _root_parser = root_parser;
+    }
+
+    protected override bool variable_is_defined (string name)
+    {
+        if (base.variable_is_defined (name))
+            return true;
+
+        return _root_parser.variable_is_defined (name);
+    }
+
+    protected override Number? get_variable (string name)
+    {
+        var value = base.get_variable (name);
+        if (value != null)
+            return value;
+        return _root_parser.get_variable (name);
+    }
+
+    protected override bool function_is_defined (string name)
+    {
+        if (base.function_is_defined (name))
+            return true;
+        return _root_parser.function_is_defined (name);
+    }
+}
+
+private class FunctionParser : ExpressionParser
+{
+    private Number[] _parameters;
+    private MathFunction _function;
+    public FunctionParser (MathFunction function, Parser? root_parser = null, Number[] parameters)
+    {
+        base (function.expression, root_parser);
+        _function = function;
+        _parameters = parameters;
+    }
+
+    protected override bool variable_is_defined (string name)
+    {
+        string[] argument_names = _function.arguments;
+        for (int i = 0; i < argument_names.length; i++)
+        {
+            if (argument_names[i] == name)
+                return true;
+        }
+        return base.variable_is_defined (name);
+    }
+
+    protected override Number? get_variable (string name)
+    {
+        string[] argument_names = _function.arguments;
+        for (int i = 0; i < argument_names.length; i++)
+        {
+            if (argument_names[i] == name)
+            {
+                if (_parameters.length > i)
+                    return _parameters[i];
+                return null;
+            }
+        }
+        return base.get_variable (name);
+    }
+}
+
+public class BuiltInMathFunction : MathFunction
+{
+    public BuiltInMathFunction (string function_name, string? description)
+    {
+        string[] arguments = {};
+        string expression = "";
+        base (function_name, arguments, expression, description);
+    }
+
+    public override Number? evaluate (Number[] args, Parser? root_parser = null)
+    {
+        return evaluate_built_in_function (name, args, root_parser);
+    }
+
+    public override bool is_custom_function ()
+    {
+        return false;
+    }
+}
+
+private Number? evaluate_built_in_function (string name, Number[] args, Parser? root_parser = null)
+{
+    var lower_name = name.down ();
+    var x = args[0];
+    // FIXME: Re Im ?
+
+    if (lower_name == "log")
+    {
+        if (args.length <= 1)
+            return x.logarithm (10); // FIXME: Default to ln
+        else
+        {
+            var log_base = args[1].to_integer ();
+            if (log_base < 0)
+                return null;
+            else
+                return x.logarithm (log_base);
+        }
+    }
+    else if (lower_name == "ln")
+        return x.ln ();
+    else if (lower_name == "sqrt") // √x
+        return x.sqrt ();
+    else if (lower_name == "abs") // |x|
+        return x.abs ();
+    else if (lower_name == "sgn") //signum function
+        return x.sgn ();
+    else if (lower_name == "arg")
+        return x.arg (root_parser.angle_units);
+    else if (lower_name == "conj")
+        return x.conjugate ();
+    else if (lower_name == "int")
+        return x.integer_component ();
+    else if (lower_name == "frac")
+        return x.fractional_component ();
+    else if (lower_name == "floor")
+        return x.floor ();
+    else if (lower_name == "ceil")
+        return x.ceiling ();
+    else if (lower_name == "round")
+        return x.round ();
+    else if (lower_name == "re")
+        return x.real_component ();
+    else if (lower_name == "im")
+        return x.imaginary_component ();
+    else if (lower_name == "sin")
+        return x.sin (root_parser.angle_units);
+    else if (lower_name == "cos")
+        return x.cos (root_parser.angle_units);
+    else if (lower_name == "tan")
+        return x.tan (root_parser.angle_units);
+    else if (lower_name == "sin⁻¹" || lower_name == "asin")
+        return x.asin (root_parser.angle_units);
+    else if (lower_name == "cos⁻¹" || lower_name == "acos")
+        return x.acos (root_parser.angle_units);
+    else if (lower_name == "tan⁻¹" || lower_name == "atan")
+        return x.atan (root_parser.angle_units);
+    else if (lower_name == "sinh")
+        return x.sinh ();
+    else if (lower_name == "cosh")
+        return x.cosh ();
+    else if (lower_name == "tanh")
+        return x.tanh ();
+    else if (lower_name == "sinh⁻¹" || lower_name == "asinh")
+        return x.asinh ();
+    else if (lower_name == "cosh⁻¹" || lower_name == "acosh")
+        return x.acosh ();
+    else if (lower_name == "tanh⁻¹" || lower_name == "atanh")
+        return x.atanh ();
+    else if (lower_name == "ones")
+        return x.ones_complement (root_parser.wordlen);
+    else if (lower_name == "twos")
+        return x.twos_complement (root_parser.wordlen);
+    return null;
+}
diff --git a/src/math-variables.vala b/src/math-variables.vala
index 0888684..14dde55 100644
--- a/src/math-variables.vala
+++ b/src/math-variables.vala
@@ -76,7 +76,30 @@ public class MathVariables : Object
         }
     }
 
-    // FIXME: Sort
+    private string[] array_sort_string (string[] array)
+    {
+        bool swapped = true;
+        int j = (array[array.length - 1] == null ? 1 : 0);
+        string tmp;
+
+        while (swapped)
+        {
+            swapped = false;
+            j++;
+            for (int i = 0; i < array.length - j; i++)
+            {
+                if (array[i] < array[i + 1])
+                {
+                    tmp = array[i];
+                    array[i] = array[i + 1];
+                    array[i + 1] = tmp;
+                    swapped = true;
+                }
+            }
+        }
+        return array;
+    }
+
     public string[] get_names ()
     {
         var names = new string[registers.size () + 1];
@@ -92,7 +115,23 @@ public class MathVariables : Object
         }
         names[i] = null;
 
-        return names;
+        return array_sort_string (names);
+    }
+
+    public string[] variables_eligible_for_autocompletion (string text)
+    {
+        string[] eligible_variables = {};
+        if (text.length <=1)
+            return eligible_variables;
+
+        string[] variables = get_names ();
+        foreach (var variable in variables)
+        {
+            if (variable.has_prefix (text))
+                eligible_variables += variable;
+        }
+
+        return eligible_variables;
     }
 
     public new void set (string name, Number value)
diff --git a/src/test-equation.vala b/src/test-equation.vala
index 6e8a933..914393b 100644
--- a/src/test-equation.vala
+++ b/src/test-equation.vala
@@ -266,7 +266,7 @@ private void test_equations ()
     test ("2z", "", ErrorCode.UNKNOWN_VARIABLE);  
     test ("z2", "", ErrorCode.UNKNOWN_VARIABLE);
     test ("z 2", "", ErrorCode.UNKNOWN_VARIABLE);
-    test ("z(2)", "", ErrorCode.UNKNOWN_VARIABLE);
+    test ("z(2)", "", ErrorCode.UNKNOWN_FUNCTION);
     test ("y²", "9", 0);
     test ("2y²", "18", 0);
     test ("x×y", "6", 0);
@@ -660,6 +660,54 @@ private void test_precedence ()
     test ("3 ^ 3!", "729", 0);
 }
 
+private void test_custom_functions ()
+{
+    number_base = 10;
+    wordlen = 32;
+    angle_units = AngleUnit.DEGREES;
+    enable_conversions = false;
+    enable_variables = true;
+
+    FunctionManager function_manager = FunctionManager.get_default_function_manager ();
+    test ("func(x;y;z)=x+y+z", "0", 0);
+    test ("func(2;3;5)", "10", 0);
+    test ("func(x;y;z)=x+y-z", "0", 0);
+    test ("func(2;3;5)", "0", 0);
+    test ("func(x;y;z)=abs(x-y)+abs(y-z)+abs(x-z)", "0", 0);
+    test ("func(1;2;3)", "4", 0);
+    test ("func(x;y;z)", "0", ErrorCode.INVALID);
+    test ("test(x;y)=func(x;y;x)+func(y;x;y)", "0", 0);
+    test ("6*test(3;5)+log10", "49", 0);
+    test ("test(test(5;6);9)", "20", 0);
+    test ("log(test(8;9))", "0.602059991", 0);
+    test ("sum(8)", "0", ErrorCode.UNKNOWN_FUNCTION);
+    test ("sum", "0", ErrorCode.UNKNOWN_VARIABLE);
+    test ("sum(x;y)=x+y", "0", 0);
+    test ("(3+2)*10 + 5^2 - sum(7;8)", "60", 0);
+    test ("dummy(abc;xyz;pqr)=abc*xyz*pqr", "0", 0);
+    test ("dummy(1;0;1)", "0", 0);
+    test ("dummy(4;5;6)", "120", 0);
+    test ("dummy(dummy(1;2;3);dummy(1;2;3);dummy(1;2;3))", "216", 0);
+    test ("dummy(10;5.687;100)", "5687", 0);
+    test ("dummy(10;5.687;1)", "56.87", 0);
+    test ("diff(x,y)=x-y", "0", ErrorCode.INVALID);
+    test ("abcd(x;y;z) = (floor((x+y+z)/z)+frac((x+y+z)/z))*z", "0", 0);
+    test ("abcd(4;5;6)", "15", 0);
+    test ("abcd(2.9;91;9.1)", "103", 0);
+    test ("log(dummy(1;5;2))", "1", 0);
+    test ("sum(sum(sum(1;1);sum(1;1));sum(sum(1;1);sum(1;1)))", "8", 0);
+    function_manager.delete ("sum");
+    test ("sum(sum(sum(1;1);sum(1;1));sum(sum(1;1);sum(1;1)))", "8", ErrorCode.UNKNOWN_FUNCTION);
+    test ("sum(x;y)=x+y+1", "0", 0);
+    test ("sum(sum(sum(1;1);sum(1;1));sum(sum(1;1);sum(1;1)))", "15", 0);
+
+    function_manager.delete ("func");
+    function_manager.delete ("test");
+    function_manager.delete ("sum");
+    function_manager.delete ("dummy");
+    function_manager.delete ("abcd");
+}
+
 public int main (string[] args)
 {
     Intl.setlocale (LocaleCategory.ALL, "C");
@@ -668,6 +716,7 @@ public int main (string[] args)
     test_equations ();
     test_base_conversion ();
     test_precedence ();
+    test_custom_functions ();
 
     if (fail_count == 0)
         stdout.printf ("Passed all %i tests\n", pass_count);


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