[gnome-calculator/60-split-out-a-backend-library: 12/47] gcalc: added core methods for equation parser



commit 56d1b66b0a520908fbeea9511c0724baaf8b77ce
Author: Daniel Espinosa <esodan gmail com>
Date:   Wed Dec 5 18:25:44 2018 -0600

    gcalc: added core methods for equation parser

 gcalc/gcalc-currency.vala         |  503 +++++++++
 gcalc/gcalc-equation-lexer.vala   |  716 +++++++++++++
 gcalc/gcalc-equation-parser.vala  | 2109 +++++++++++++++++++++++++++++++++++++
 gcalc/gcalc-equation.vala         |  238 +++++
 gcalc/gcalc-function-manager.vala |  387 +++++++
 gcalc/gcalc-math-function.vala    |  287 +++++
 gcalc/gcalc-number.vala           | 1372 ++++++++++++++++++++++++
 gcalc/gcalc-serializer.vala       |  526 +++++++++
 gcalc/gcalc-unit-manager.vala     |  445 ++++++++
 gcalc/meson.build                 |   15 +-
 meson.build                       |    4 +-
 11 files changed, 6599 insertions(+), 3 deletions(-)
---
diff --git a/gcalc/gcalc-currency.vala b/gcalc/gcalc-currency.vala
new file mode 100644
index 00000000..e98f8056
--- /dev/null
+++ b/gcalc/gcalc-currency.vala
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell.
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace GCalc {
+  static bool downloading_imf_rates = false;
+  static bool downloading_ecb_rates = false;
+  static bool loaded_rates = false;
+  private static CurrencyManager? default_currency_manager = null;
+
+  public class CurrencyManager : Object
+  {
+      private List<Currency> currencies;
+      public signal void updated ();
+
+      public static CurrencyManager get_default ()
+      {
+          if (default_currency_manager != null)
+              return default_currency_manager;
+
+          default_currency_manager = new CurrencyManager ();
+
+          default_currency_manager.currencies.append (new Currency ("AED", _("UAE Dirham"), "إ.د"));
+          default_currency_manager.currencies.append (new Currency ("AUD", _("Australian Dollar"), "$"));
+          default_currency_manager.currencies.append (new Currency ("BGN", _("Bulgarian Lev"), "лв"));
+          default_currency_manager.currencies.append (new Currency ("BHD", _("Bahraini Dinar"), ".ب.د"));
+          default_currency_manager.currencies.append (new Currency ("BND", _("Brunei Dollar"), "$"));
+          default_currency_manager.currencies.append (new Currency ("BRL", _("Brazilian Real"), "R$"));
+          default_currency_manager.currencies.append (new Currency ("BWP", _("Botswana Pula"), "P"));
+          default_currency_manager.currencies.append (new Currency ("CAD", _("Canadian Dollar"), "$"));
+          default_currency_manager.currencies.append (new Currency ("CFA", _("CFA Franc"), "Fr"));
+          default_currency_manager.currencies.append (new Currency ("CHF", _("Swiss Franc"), "Fr"));
+          default_currency_manager.currencies.append (new Currency ("CLP", _("Chilean Peso"), "$"));
+          default_currency_manager.currencies.append (new Currency ("CNY", _("Chinese Yuan"), "¥"));
+          default_currency_manager.currencies.append (new Currency ("COP", _("Colombian Peso"), "$"));
+          default_currency_manager.currencies.append (new Currency ("CZK", _("Czech Koruna"), "Kč"));
+          default_currency_manager.currencies.append (new Currency ("DKK", _("Danish Krone"), "kr"));
+          default_currency_manager.currencies.append (new Currency ("DZD", _("Algerian Dinar"), "ج.د"));
+          default_currency_manager.currencies.append (new Currency ("EEK", _("Estonian Kroon"), "KR"));
+          default_currency_manager.currencies.append (new Currency ("EUR", _("Euro"), "€"));
+          default_currency_manager.currencies.append (new Currency ("GBP", _("British Pound Sterling"), 
"£"));
+          default_currency_manager.currencies.append (new Currency ("HKD", _("Hong Kong Dollar"), "$"));
+          default_currency_manager.currencies.append (new Currency ("HRK", _("Croatian Kuna"), "kn"));
+          default_currency_manager.currencies.append (new Currency ("HUF", _("Hungarian Forint"), "Ft"));
+          default_currency_manager.currencies.append (new Currency ("IDR", _("Indonesian Rupiah"), "Rp"));
+          default_currency_manager.currencies.append (new Currency ("ILS", _("Israeli New Shekel"), "₪"));
+          default_currency_manager.currencies.append (new Currency ("INR", _("Indian Rupee"), "₹"));
+          default_currency_manager.currencies.append (new Currency ("IRR", _("Iranian Rial"), "﷼"));
+          default_currency_manager.currencies.append (new Currency ("ISK", _("Icelandic Krona"), "kr"));
+          default_currency_manager.currencies.append (new Currency ("JPY", _("Japanese Yen"), "¥"));
+          default_currency_manager.currencies.append (new Currency ("KRW", _("South Korean Won"), "₩"));
+          default_currency_manager.currencies.append (new Currency ("KWD", _("Kuwaiti Dinar"), "ك.د"));
+          default_currency_manager.currencies.append (new Currency ("KZT", _("Kazakhstani Tenge"), "₸"));
+          default_currency_manager.currencies.append (new Currency ("LKR", _("Sri Lankan Rupee"), "Rs"));
+          default_currency_manager.currencies.append (new Currency ("LYD", _("Libyan Dinar"), "د.ل"));
+          default_currency_manager.currencies.append (new Currency ("MUR", _("Mauritian Rupee"), "Rs"));
+          default_currency_manager.currencies.append (new Currency ("MXN", _("Mexican Peso"), "$"));
+          default_currency_manager.currencies.append (new Currency ("MYR", _("Malaysian Ringgit"), "RM"));
+          default_currency_manager.currencies.append (new Currency ("NOK", _("Norwegian Krone"), "kr"));
+          default_currency_manager.currencies.append (new Currency ("NPR", _("Nepalese Rupee"), "Rs"));
+          default_currency_manager.currencies.append (new Currency ("NZD", _("New Zealand Dollar"), "$"));
+          default_currency_manager.currencies.append (new Currency ("OMR", _("Omani Rial"), "ع.ر."));
+          default_currency_manager.currencies.append (new Currency ("PEN", _("Peruvian Nuevo Sol"), "S/."));
+          default_currency_manager.currencies.append (new Currency ("PHP", _("Philippine Peso"), "₱"));
+          default_currency_manager.currencies.append (new Currency ("PKR", _("Pakistani Rupee"), "Rs"));
+          default_currency_manager.currencies.append (new Currency ("PLN", _("Polish Zloty"), "zł"));
+          default_currency_manager.currencies.append (new Currency ("QAR", _("Qatari Riyal"), "ق.ر"));
+          default_currency_manager.currencies.append (new Currency ("RON", _("New Romanian Leu"), "L"));
+          default_currency_manager.currencies.append (new Currency ("RUB", _("Russian Rouble"), "руб."));
+          default_currency_manager.currencies.append (new Currency ("SAR", _("Saudi Riyal"), "س.ر"));
+          default_currency_manager.currencies.append (new Currency ("SEK", _("Swedish Krona"), "kr"));
+          default_currency_manager.currencies.append (new Currency ("SGD", _("Singapore Dollar"), "$"));
+          default_currency_manager.currencies.append (new Currency ("THB", _("Thai Baht"), "฿"));
+          default_currency_manager.currencies.append (new Currency ("TND", _("Tunisian Dinar"), "ت.د"));
+          default_currency_manager.currencies.append (new Currency ("TRY", _("Turkish Lira"), "₺"));
+          default_currency_manager.currencies.append (new Currency ("TTD", _("T&T Dollar (TTD)"), "$"));
+          default_currency_manager.currencies.append (new Currency ("USD", _("US Dollar"), "$"));
+          default_currency_manager.currencies.append (new Currency ("UYU", _("Uruguayan Peso"), "$"));
+          default_currency_manager.currencies.append (new Currency ("VEF", _("Venezuelan Bolívar"), "Bs F"));
+          default_currency_manager.currencies.append (new Currency ("ZAR", _("South African Rand"), "R"));
+
+          /* Start downloading the rates if they are outdated. */
+          default_currency_manager.download_rates ();
+
+          return default_currency_manager;
+      }
+
+      public List<Currency> get_currencies ()
+      {
+          var r = new List<Currency> ();
+          foreach (var c in currencies)
+              r.append (c);
+          return r;
+      }
+
+      public Currency? get_currency (string name)
+      {
+          foreach (var c in currencies)
+          {
+              if (name == c.name)
+              {
+                  var value = c.get_value ();
+                  if (value == null || value.is_negative () || value.is_zero ())
+                      return null;
+                  else
+                      return c;
+              }
+          }
+
+          return null;
+      }
+
+      private string get_imf_rate_filepath ()
+      {
+          return Path.build_filename (Environment.get_user_cache_dir (), "gnome-calculator", "rms_five.xls");
+      }
+
+      private string get_ecb_rate_filepath ()
+      {
+          return Path.build_filename (Environment.get_user_cache_dir (), "gnome-calculator", 
"eurofxref-daily.xml");
+      }
+
+      private Currency add_currency (string short_name, string source)
+      {
+          foreach (var c in currencies)
+              if (c.name == short_name)
+              {
+                  c.source = source;
+                  return c;
+              }
+
+          warning ("Currency %s is not in the currency table", short_name);
+          var c = new Currency (short_name, short_name, short_name);
+          c.source = source;
+          currencies.append (c);
+
+          return c;
+      }
+
+      /* A file needs to be redownloaded if it doesn't exist, or is too old.
+       * When an error occur, it probably won't hurt to try to download again.
+       */
+      private bool file_needs_update (string filename, double max_age)
+      {
+          if (!FileUtils.test (filename, FileTest.IS_REGULAR))
+              return true;
+
+          var buf = Posix.Stat ();
+          if (Posix.stat (filename, out buf) == -1)
+              return true;
+
+          var modify_time = buf.st_mtime;
+          var now = time_t ();
+          if (now - modify_time > max_age)
+              return true;
+
+          return false;
+      }
+
+      private void load_imf_rates ()
+      {
+          var name_map = new HashTable <string, string> (str_hash, str_equal);
+          name_map.insert ("Euro", "EUR");
+          name_map.insert ("Japanese yen", "JPY");
+          name_map.insert ("U.K. pound", "GBP");
+          name_map.insert ("U.S. dollar", "USD");
+          name_map.insert ("Algerian dinar", "DZD");
+          name_map.insert ("Australian dollar", "AUD");
+          name_map.insert ("Bahrain dinar", "BHD");
+          name_map.insert ("Botswana pula", "BWP");
+          name_map.insert ("Brazilian real", "BRL");
+          name_map.insert ("Brunei dollar", "BND");
+          name_map.insert ("Canadian dollar", "CAD");
+          name_map.insert ("Chilean peso", "CLP");
+          name_map.insert ("Chinese yuan", "CNY");
+          name_map.insert ("Colombian peso", "COP");
+          name_map.insert ("Czech koruna", "CZK");
+          name_map.insert ("Danish krone", "DKK");
+          name_map.insert ("Hungarian forint", "HUF");
+          name_map.insert ("Icelandic krona", "ISK");
+          name_map.insert ("Indian rupee", "INR");
+          name_map.insert ("Indonesian rupiah", "IDR");
+          name_map.insert ("Iranian rial", "IRR");
+          name_map.insert ("Israeli New Shekel", "ILS");
+          name_map.insert ("Kazakhstani tenge", "KZT");
+          name_map.insert ("Korean won", "KRW");
+          name_map.insert ("Kuwaiti dinar", "KWD");
+          name_map.insert ("Libyan dinar", "LYD");
+          name_map.insert ("Malaysian ringgit", "MYR");
+          name_map.insert ("Mauritian rupee", "MUR");
+          name_map.insert ("Mexican peso", "MXN");
+          name_map.insert ("Nepalese rupee", "NPR");
+          name_map.insert ("New Zealand dollar", "NZD");
+          name_map.insert ("Norwegian krone", "NOK");
+          name_map.insert ("Omani rial", "OMR");
+          name_map.insert ("Pakistani rupee", "PKR");
+          name_map.insert ("Peruvian sol", "PEN");
+          name_map.insert ("Philippine peso", "PHP");
+          name_map.insert ("Polish zloty", "PLN");
+          name_map.insert ("Qatari riyal", "QAR");
+          name_map.insert ("Russian ruble", "RUB");
+          name_map.insert ("Saudi Arabian riyal", "SAR");
+          name_map.insert ("Singapore dollar", "SGD");
+          name_map.insert ("South African rand", "ZAR");
+          name_map.insert ("Sri Lankan rupee", "LKR");
+          name_map.insert ("Swedish krona", "SEK");
+          name_map.insert ("Swiss franc", "CHF");
+          name_map.insert ("Thai baht", "THB");
+          name_map.insert ("Trinidadian dollar", "TTD");
+          name_map.insert ("Tunisian dinar", "TND");
+          name_map.insert ("U.A.E. dirham", "AED");
+          name_map.insert ("Uruguayan peso", "UYU");
+          name_map.insert ("Bolivar Fuerte", "VEF");
+
+          var filename = get_imf_rate_filepath ();
+          string data;
+          try
+          {
+              FileUtils.get_contents (filename, out data);
+          }
+          catch (Error e)
+          {
+              warning ("Failed to read exchange rates: %s", e.message);
+              return;
+          }
+
+          var lines = data.split ("\n", 0);
+
+          var in_data = false;
+          foreach (var line in lines)
+          {
+              line = line.chug ();
+
+              /* Start after first blank line, stop on next */
+              if (line == "")
+              {
+                  if (!in_data)
+                  {
+                     in_data = true;
+                     continue;
+                  }
+                  else
+                     break;
+              }
+              if (!in_data)
+                  continue;
+
+              var tokens = line.split ("\t", 0);
+              if (tokens[0] != "Currency")
+              {
+                  int value_index;
+                  for (value_index = 1; value_index < tokens.length; value_index++)
+                  {
+                      var value = tokens[value_index].chug ();
+                      if (value != "")
+                          break;
+                  }
+
+                  if (value_index < tokens.length)
+                  {
+                      var symbol = name_map.lookup (tokens[0]);
+                      if (symbol != null)
+                      {
+                          var c = get_currency (symbol);
+                          var value = mp_set_from_string (tokens[value_index]);
+                          /* Use data if we have a valid value */
+                          if (c == null && value != null)
+                          {
+                              debug ("Using IMF rate of %s for %s", tokens[value_index], symbol);
+                              c = add_currency (symbol, "imf");
+                              value = value.reciprocal ();
+                              if (c != null)
+                                  c.set_value (value);
+                          }
+                      }
+                      else
+                          warning ("Unknown currency '%s'", tokens[0]);
+                  }
+              }
+          }
+      }
+
+      private void set_ecb_rate (Xml.Node node, Currency eur_rate)
+      {
+          string? name = null, value = null;
+
+          for (var attribute = node.properties; attribute != null; attribute = attribute->next)
+          {
+              var n = (Xml.Node*) attribute;
+              if (attribute->name == "currency")
+                  name = n->get_content ();
+              else if (attribute->name == "rate")
+                  value = n->get_content ();
+          }
+
+          /* Use data if value and no rate currently defined */
+          if (name != null && value != null && get_currency (name) == null)
+          {
+              debug ("Using ECB rate of %s for %s", value, name);
+              var c = add_currency (name, "ecb");
+              var r = mp_set_from_string (value);
+              var v = eur_rate.get_value ();
+              v = v.multiply (r);
+              c.set_value (v);
+          }
+      }
+
+      private void set_ecb_fixed_rate (string name, string value, Currency eur_rate)
+      {
+          debug ("Using ECB fixed rate of %s for %s", value, name);
+          var c = add_currency (name, "ecb#fixed");
+          var r = mp_set_from_string (value);
+          var v = eur_rate.get_value ();
+          v = v.divide (r);
+          c.set_value (v);
+      }
+
+      private void load_ecb_rates ()
+      {
+          /* Scale rates to the EUR value */
+          var eur_rate = get_currency ("EUR");
+          if (eur_rate == null)
+          {
+              warning ("Cannot use ECB rates as don't have EUR rate");
+              return;
+          }
+
+          /* Set some fixed rates */
+          set_ecb_fixed_rate ("EEK", "0.06391", eur_rate);
+          set_ecb_fixed_rate ("CFA", "0.152449", eur_rate);
+
+          Xml.Parser.init ();
+          var filename = get_ecb_rate_filepath ();
+          var document = Xml.Parser.read_file (filename);
+          if (document == null)
+          {
+              warning ("Couldn't parse ECB rate file %s", filename);
+              return;
+          }
+
+          var xpath_ctx = new Xml.XPath.Context (document);
+          if (xpath_ctx == null)
+          {
+              warning ("Couldn't create XPath context");
+              return;
+          }
+
+          xpath_ctx.register_ns ("xref", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref";);
+          var xpath_obj = xpath_ctx.eval_expression ("//xref:Cube[@currency][@rate]");
+          if (xpath_obj == null)
+          {
+              warning ("Couldn't create XPath object");
+              return;
+          }
+          var len = (xpath_obj->nodesetval != null) ? xpath_obj->nodesetval->length () : 0;
+          for (var i = 0; i < len; i++)
+          {
+              var node = xpath_obj->nodesetval->item (i);
+
+              if (node->type == Xml.ElementType.ELEMENT_NODE)
+                  set_ecb_rate (node, eur_rate);
+
+              /* Avoid accessing removed elements */
+              if (node->type != Xml.ElementType.NAMESPACE_DECL)
+                  node = null;
+          }
+
+          Xml.Parser.cleanup ();
+      }
+
+      private void download_rates ()
+      {
+          /* Update rates if necessary */
+          var path = get_imf_rate_filepath ();
+          if (!downloading_imf_rates && file_needs_update (path, 60 * 60 * 24 * 7))
+          {
+              downloading_imf_rates = true;
+              debug ("Downloading rates from the IMF...");
+              download_file.begin ("https://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y";, path, 
"IMF");
+          }
+          path = get_ecb_rate_filepath ();
+          if (!downloading_ecb_rates && file_needs_update (path, 60 * 60 * 24 * 7))
+          {
+              downloading_ecb_rates = true;
+              debug ("Downloading rates from the ECB...");
+              download_file.begin ("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";, path, 
"ECB");
+          }
+      }
+
+      private bool load_rates ()
+      {
+          /* Already loaded */
+          if (loaded_rates)
+              return true;
+
+          /* In process */
+          if (downloading_imf_rates || downloading_ecb_rates)
+              return false;
+
+          /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */
+          load_imf_rates ();
+          load_ecb_rates ();
+
+          /* Check if we couldn't find out a currency */
+          foreach (var c in currencies)
+              if (c.get_value () == null || c.get_value ().is_zero ())
+                  warning ("Currency %s is not provided by IMF or ECB", c.name);
+
+          debug ("Rates loaded");
+          loaded_rates = true;
+
+          updated ();
+
+          return true;
+      }
+
+      public Number? get_value (string currency)
+      {
+          /* Make sure that the rates we're returning are up to date. (Just in case the application is 
running from a long long time) */
+          download_rates ();
+
+          if (!load_rates ())
+              return null;
+
+          var c = get_currency (currency);
+          if (c != null)
+              return c.get_value ();
+          else
+              return null;
+      }
+
+      private async void download_file (string uri, string filename, string source)
+      {
+
+          var directory = Path.get_dirname (filename);
+          DirUtils.create_with_parents (directory, 0755);
+
+          var dest = File.new_for_path (filename);
+          var session = new Soup.Session ();
+          var message = new Soup.Message ("GET", uri);
+          try
+          {
+              var bodyinput = yield session.send_async (message);
+              var output = yield dest.replace_async (null, false, FileCreateFlags.REPLACE_DESTINATION, 
Priority.DEFAULT);
+              yield output.splice_async (bodyinput,
+                                         OutputStreamSpliceFlags.CLOSE_SOURCE | 
OutputStreamSpliceFlags.CLOSE_TARGET,
+                                         Priority.DEFAULT);
+              if (source == "IMF")
+                  downloading_imf_rates = false;
+              else
+                  downloading_ecb_rates = false;
+
+              load_rates ();
+              debug ("%s rates updated", source);
+          }
+          catch (Error e)
+          {
+              warning ("Couldn't download %s currency rate file: %s", source, e.message);
+          }
+       }
+  }
+
+  public class Currency : Object
+  {
+      private Number? value;
+
+      private string _name;
+      public string name { owned get { return _name; } }
+
+      private string _display_name;
+      public string display_name { owned get { return _display_name; } }
+
+      private string _symbol;
+      public string symbol { owned get { return _symbol; } }
+
+      private string _source;
+      public string source { owned get { return _source; } owned set { _source = value; }}
+
+      public Currency (string name, string display_name, string symbol)
+      {
+          _name = name;
+          _display_name = display_name;
+          _symbol = symbol;
+      }
+
+      public void set_value (Number value)
+      {
+          this.value = value;
+      }
+
+      public Number? get_value ()
+      {
+          return value;
+      }
+  }
+}
diff --git a/gcalc/gcalc-equation-lexer.vala b/gcalc/gcalc-equation-lexer.vala
new file mode 100644
index 00000000..3b7eef20
--- /dev/null
+++ b/gcalc/gcalc-equation-lexer.vala
@@ -0,0 +1,716 @@
+/*
+ * Copyright (C) 2012 Arth Patel
+ * Copyright (C) 2012 Robert Ancell
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace GCalc {
+  /* Enum for tokens generated by pre-lexer and lexer. */
+  public enum LexerTokenType
+  {
+      UNKNOWN,              /* Unknown */
+
+      /* These are all Pre-Lexer tokens, returned by pre-lexer */
+      PL_DECIMAL,           /* Decimal separator */
+      PL_DIGIT,             /* Decimal digit */
+      PL_HEX,               /* A-F of Hex digits */
+      PL_SUPER_DIGIT,       /* Super digits */
+      PL_SUPER_MINUS,       /* Super minus */
+      PL_SUB_DIGIT,         /* Sub digits */
+      PL_FRACTION,          /* Fractions */
+      PL_DEGREE,            /* Degree */
+      PL_MINUTE,            /* Minutes */
+      PL_SECOND,            /* Seconds */
+      PL_LETTER,            /* Alphabets */
+      PL_EOS,               /* End of stream */
+      PL_SKIP,              /* Skip this symbol (whitespace or newline). */
+
+      /* These are all tokens, returned by Lexer. */
+      ADD,                /* Plus */
+      SUBTRACT,           /* Minus */
+      MULTIPLY,           /* Multiply */
+      DIVIDE,             /* Divide  */
+      MOD,                /* Modulus */
+      L_FLOOR,            /* Floor ( Left ) */
+      R_FLOOR,            /* Floor ( Right ) */
+      L_CEILING,          /* Ceiling ( Left ) */
+      R_CEILING,          /* Ceiling ( Right ) */
+      ROOT,               /* Square root */
+      ROOT_3,             /* Cube root */
+      ROOT_4,             /* Fourth root */
+      NOT,                /* Bitwise NOT */
+      AND,                /* Bitwise AND */
+      OR,                 /* Bitwise OR */
+      XOR,                /* Bitwise XOR */
+      IN,                 /* IN ( for converter ) */
+      NUMBER,             /* Number */
+      SUP_NUMBER,         /* Super Number */
+      NSUP_NUMBER,        /* Negative Super Number */
+      SUB_NUMBER,         /* Sub Number */
+      FUNCTION,           /* Function */
+      UNIT,               /* Unit of conversion */
+      VARIABLE,           /* Variable name */
+      ASSIGN,             /* = */
+      L_R_BRACKET,        /* ( */
+      R_R_BRACKET,        /* ) */
+      L_S_BRACKET,        /* [ */
+      R_S_BRACKET,        /* ] */
+      L_C_BRACKET,        /* { */
+      R_C_BRACKET,        /* } */
+      ABS,                /* | */
+      POWER,              /* ^ */
+      FACTORIAL,          /* ! */
+      PERCENTAGE,         /* % */
+      ARGUMENT_SEPARATOR  /* ; (Function argument separator) */
+  }
+
+  // FIXME: Merge into lexer
+  public class PreLexer : Object
+  {
+      public string stream; /* String being scanned */
+      public int index;      /* Current character index */
+      public int mark_index; /* Location, last marked. Useful for getting substrings as part of highlighting 
*/
+      private bool eos = false;
+
+      public PreLexer (string input)
+      {
+          stream = input;
+          index = 0;
+          mark_index = 0;
+      }
+
+      /* Roll back last scanned unichar. */
+      public void roll_back ()
+      {
+          if (eos)
+          {
+              eos = false;
+              return;
+          }
+          unichar c;
+          stream.get_prev_char (ref index, out c);
+      }
+
+      /* Set marker index. To be used for highlighting and error reporting. */
+      public void set_marker ()
+      {
+          mark_index = index;
+      }
+
+      /* Get marked substring. To be used for error reporting. */
+      public string get_marked_substring ()
+      {
+          return stream.substring (mark_index, index - mark_index);
+      }
+
+      /* Pre-Lexer tokanizer. To be called only by Lexer. */
+      public LexerTokenType get_next_token ()
+      {
+          unichar c;
+          if (!stream.get_next_char (ref index, out c))
+          {
+              // We have to flag if we ran out of chars, as roll_back from PL_EOS should have no effect
+              eos = true;
+              return LexerTokenType.PL_EOS;
+          }
+          eos = false;
+
+          if (c == ',' || c == '.')
+              return LexerTokenType.PL_DECIMAL;
+
+          if (c.isdigit ())
+              return LexerTokenType.PL_DIGIT;
+
+          if ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
+              return LexerTokenType.PL_HEX;
+
+          if (c == '⁰' || c == '¹' || c == '²' || c == '³' || c == '⁴' || c == '⁵' || c == '⁶' || c == '⁷' 
|| c == '⁸' || c == '⁹')
+              return LexerTokenType.PL_SUPER_DIGIT;
+
+          if (c == '⁻')
+              return LexerTokenType.PL_SUPER_MINUS;
+
+          if (c == '₀' || c == '₁' || c == '₂' || c == '₃' || c == '₄' || c == '₅' || c == '₆' || c == '₇' 
|| c == '₈' || c == '₉')
+              return LexerTokenType.PL_SUB_DIGIT;
+
+          if (c == '½' || c == '⅓' || c == '⅔' || c == '¼' || c == '¾' || c == '⅕' || c == '⅖' || c == '⅗' 
|| c == '⅘' || c == '⅙' || c == '⅚' || c == '⅛' || c == '⅜' || c == '⅝' || c == '⅞')
+              return LexerTokenType.PL_FRACTION;
+
+          if (c == '°')
+              return LexerTokenType.PL_DEGREE;
+
+          if (c == '\'')
+              return LexerTokenType.PL_MINUTE;
+
+          if (c == '"')
+              return LexerTokenType.PL_SECOND;
+
+          if (c.isalpha () || c == '_')
+              return LexerTokenType.PL_LETTER;
+
+          if (c == '∧')
+              return LexerTokenType.AND;
+
+          if (c == '∨')
+              return LexerTokenType.OR;
+
+          if (c == '⊻' || c == '⊕')
+              return LexerTokenType.XOR;
+
+          if (c == '¬' || c == '~')
+              return LexerTokenType.NOT;
+
+          if (c == '+')
+              return LexerTokenType.ADD;
+
+          if (c == '-' || c == '−' || c == '–')
+              return LexerTokenType.SUBTRACT;
+
+          if (c == '*' || c == '×')
+              return LexerTokenType.MULTIPLY;
+
+          if (c == '/' || c == '∕' || c == '÷')
+              return LexerTokenType.DIVIDE;
+
+          if (c == '⌊')
+              return LexerTokenType.L_FLOOR;
+
+          if (c == '⌋')
+              return LexerTokenType.R_FLOOR;
+
+          if (c == '⌈')
+              return LexerTokenType.L_CEILING;
+
+          if (c == '⌉')
+              return LexerTokenType.R_CEILING;
+
+          if (c == '√')
+              return LexerTokenType.ROOT;
+
+          if (c == '∛')
+              return LexerTokenType.ROOT_3;
+
+          if (c == '∜')
+              return LexerTokenType.ROOT_4;
+
+          if (c == '=')
+              return LexerTokenType.ASSIGN;
+
+          if (c == '(')
+              return LexerTokenType.L_R_BRACKET;
+
+          if (c == ')')
+              return LexerTokenType.R_R_BRACKET;
+
+          if (c == '[')
+              return LexerTokenType.L_S_BRACKET;
+
+          if (c == ']')
+              return LexerTokenType.R_S_BRACKET;
+
+          if (c == '{')
+              return LexerTokenType.L_C_BRACKET;
+
+          if (c == '}')
+              return LexerTokenType.R_C_BRACKET;
+
+          if (c == '|')
+              return LexerTokenType.ABS;
+
+          if (c == '^')
+              return LexerTokenType.POWER;
+
+          if (c == '!')
+              return LexerTokenType.FACTORIAL;
+
+          if (c == '%')
+              return LexerTokenType.PERCENTAGE;
+
+          if (c == ';')
+              return LexerTokenType.ARGUMENT_SEPARATOR;
+
+          if (c == ' ' || c == '\r' || c == '\t' || c == '\n')
+              return LexerTokenType.PL_SKIP;
+
+          return LexerTokenType.UNKNOWN;
+      }
+  }
+
+  /* Structure to hold single token. */
+  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. */
+  }
+
+  /* Structure to hold lexer state and all the tokens. */
+  public class Lexer : Object
+  {
+      private Parser parser;           /* Pointer to the parser parser. */
+      private PreLexer prelexer;       /* Pre-lexer  Pre-lexer is part of lexer. */
+      public List<LexerToken> tokens;  /* Pointer to the dynamic array of LexerTokens. */
+      private uint next_token;         /* Index of next, to be sent, token. */
+      private int number_base;
+
+      public Lexer (string input, Parser parser, int number_base = 10)
+      {
+          prelexer = new PreLexer (input);
+          tokens = new List<LexerToken> ();
+          next_token = 0;
+          this.parser = parser;
+          this.number_base = number_base;
+      }
+
+      public void scan ()
+      {
+          while (true)
+          {
+              var token = insert_next_token ();
+              tokens.append (token);
+              if (token.type == LexerTokenType.PL_EOS)
+                  break;
+          }
+      }
+
+      /* Get next token interface. Will be called by parser to get pointer to next token in token stream. */
+      public LexerToken get_next_token ()
+      {
+          if (next_token >= tokens.length ())
+              return tokens.nth_data (tokens.length () - 1);
+          return tokens.nth_data (next_token++);
+      }
+
+      /* Roll back one lexer token. */
+      public void roll_back ()
+      {
+          if (next_token > 0)
+              next_token--;
+      }
+
+      private bool check_if_function ()
+      {
+          var name = prelexer.get_marked_substring ();
+          return parser.function_is_defined (name);
+      }
+
+      private bool check_if_unit ()
+      {
+          int super_count = 0;
+          while (prelexer.get_next_token () == LexerTokenType.PL_SUPER_DIGIT)
+              super_count++;
+
+          prelexer.roll_back ();
+
+          var name = prelexer.get_marked_substring ();
+          if (parser.unit_is_defined (name))
+              return true;
+
+          while (super_count-- > 0)
+              prelexer.roll_back ();
+
+          name = prelexer.get_marked_substring ();
+          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;
+          var text = prelexer.get_marked_substring ();
+
+          var tmp = mp_set_from_string (text, number_base);
+          if (tmp != null)
+              return true;
+          else
+          {
+              /* Try to rollback several characters to see, if that yields any number. */
+              while (text != "")
+              {
+                  tmp = mp_set_from_string (text, number_base);
+                  if (tmp != null)
+                      return true;
+                  count++;
+                  prelexer.roll_back ();
+                  text = prelexer.get_marked_substring ();
+              }
+
+              /* Undo all rollbacks. */
+              while (count-- > 0)
+                  prelexer.get_next_token ();
+
+              return false;
+          }
+      }
+
+      /* Insert generated token to the lexer */
+      private LexerToken insert_token (LexerTokenType type)
+      {
+          var token = new LexerToken ();
+          token.text = prelexer.get_marked_substring ();
+          token.start_index = prelexer.mark_index;
+          token.end_index = prelexer.index;
+          token.type = type;
+
+          return token;
+      }
+
+      /* Generates next token from pre-lexer stream and call insert_token () to insert it at the end. */
+      private LexerToken insert_next_token ()
+      {
+          /* Mark start of next token */
+          prelexer.set_marker ();
+
+          /* Ignore whitespace */
+          var type = prelexer.get_next_token ();
+          while (type == LexerTokenType.PL_SKIP)
+          {
+              prelexer.set_marker ();
+              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 || type == LexerTokenType.ARGUMENT_SEPARATOR)
+              return insert_token (type);
+
+          /* [LexerTokenType.PL_SUPER_MINUS][LexerTokenType.PL_SUPER_DIGIT]+ */
+          if (type == LexerTokenType.PL_SUPER_MINUS)
+          {
+              if ((type = prelexer.get_next_token ()) != LexerTokenType.PL_SUPER_DIGIT)
+              {
+                  /* ERROR: expected LexerTokenType.PL_SUP_DIGIT */
+                  parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+                  return insert_token (LexerTokenType.UNKNOWN);
+              }
+
+              /* Get all LexerTokenType.PL_SUPER_DIGITs. */
+              while (prelexer.get_next_token () == LexerTokenType.PL_SUPER_DIGIT);
+              prelexer.roll_back ();
+
+              return insert_token (LexerTokenType.NSUP_NUMBER);
+          }
+
+          /* [LexerTokenType.PL_SUPER_DIGIT]+ */
+          if (type == LexerTokenType.PL_SUPER_DIGIT)
+          {
+              while (prelexer.get_next_token () == LexerTokenType.PL_SUPER_DIGIT);
+              prelexer.roll_back ();
+
+              return insert_token (LexerTokenType.SUP_NUMBER);
+          }
+
+          /* [LexerTokenType.PL_SUB_DIGIT]+ */
+          if (type == LexerTokenType.PL_SUB_DIGIT)
+          {
+              while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+              prelexer.roll_back ();
+
+              return insert_token (LexerTokenType.SUB_NUMBER);
+          }
+
+          /* [LexerTokenType.PL_FRACTION] */
+          if (type == LexerTokenType.PL_FRACTION)
+              return insert_token (LexerTokenType.NUMBER);
+
+          if (type == LexerTokenType.PL_DIGIT)
+              return insert_digit ();
+
+          if (type == LexerTokenType.PL_DECIMAL)
+              return insert_decimal ();
+
+          if (type == LexerTokenType.PL_HEX)
+              return insert_hex ();
+
+          if (type == LexerTokenType.PL_LETTER)
+              return insert_letter ();
+
+          if (type == LexerTokenType.PL_EOS)
+              return insert_token (LexerTokenType.PL_EOS);
+
+          /* ERROR: Unexpected token */
+          parser.set_error (ErrorCode.INVALID, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+
+          return insert_token (LexerTokenType.UNKNOWN);
+      }
+
+      private LexerToken insert_digit ()
+      {
+          var type = prelexer.get_next_token ();
+          while (type == LexerTokenType.PL_DIGIT)
+              type = prelexer.get_next_token ();
+
+          if (type == LexerTokenType.PL_FRACTION)
+              return insert_token (LexerTokenType.NUMBER);
+          else if (type == LexerTokenType.PL_SUB_DIGIT)
+          {
+              while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+              prelexer.roll_back ();
+              return insert_token (LexerTokenType.NUMBER);
+          }
+          else if (type == LexerTokenType.PL_DEGREE)
+          {
+              type = prelexer.get_next_token ();
+              if (type == LexerTokenType.PL_DIGIT)
+              {
+                  while ((type = prelexer.get_next_token ()) == LexerTokenType.PL_DIGIT);
+                  if (type == LexerTokenType.PL_DECIMAL)
+                      return insert_angle_num_dm ();
+
+                  else if (type == LexerTokenType.PL_MINUTE)
+                  {
+                      type = prelexer.get_next_token ();
+                      if (type == LexerTokenType.PL_DIGIT)
+                      {
+                          while ((type = prelexer.get_next_token ()) == LexerTokenType.PL_DIGIT);
+                          if (type == LexerTokenType.PL_DECIMAL)
+                              return insert_angle_num_dms ();
+                          else if (type == LexerTokenType.PL_SECOND)
+                              return insert_token (LexerTokenType.NUMBER);
+                          else
+                          {
+                              /* ERROR: expected LexerTokenType.PL_SECOND */
+                              parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), 
prelexer.mark_index, prelexer.index);
+                              return insert_token (LexerTokenType.UNKNOWN);
+                          }
+                      }
+                      else if (type == LexerTokenType.PL_DECIMAL)
+                          return insert_angle_num_dms ();
+                      else
+                      {
+                          prelexer.roll_back ();
+                          return insert_token (LexerTokenType.NUMBER);
+                      }
+                  }
+                  else
+                  {
+                      /* ERROR: expected LexerTokenType.PL_MINUTE | LexerTokenType.PL_DIGIT */
+                      parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+                      return insert_token (LexerTokenType.UNKNOWN);
+                  }
+              }
+              else if (type == LexerTokenType.PL_DECIMAL)
+                  return insert_angle_num_dm ();
+              else
+                  return insert_token (LexerTokenType.NUMBER);
+          }
+          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
+          {
+              prelexer.roll_back ();
+              return insert_token (LexerTokenType.NUMBER);
+          }
+      }
+
+      private LexerToken insert_angle_num_dm ()
+      {
+          var type = prelexer.get_next_token ();
+          if (type != LexerTokenType.PL_DIGIT)
+          {
+              /* ERROR: expected LexerTokenType.PL_DIGIT */
+              parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+              return insert_token (LexerTokenType.UNKNOWN);
+          }
+
+          while (type == LexerTokenType.PL_DIGIT);
+              type = prelexer.get_next_token ();
+
+          if (type == LexerTokenType.PL_MINUTE)
+              return insert_token (LexerTokenType.NUMBER);
+          else
+          {
+              /* ERROR: expected LexerTokenType.PL_MINUTE */
+              parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+              return insert_token (LexerTokenType.UNKNOWN);
+          }
+      }
+
+      private LexerToken insert_angle_num_dms ()
+      {
+          var type = prelexer.get_next_token ();
+          if (type != LexerTokenType.PL_DIGIT)
+          {
+              /* ERROR: expected LexerTokenType.PL_DIGIT */
+              parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+              return insert_token (LexerTokenType.UNKNOWN);
+          }
+          while ((type = prelexer.get_next_token ()) == LexerTokenType.PL_DIGIT);
+          if (type == LexerTokenType.PL_SECOND)
+              return insert_token (LexerTokenType.NUMBER);
+          else
+          {
+              /* ERROR: expected LexerTokenType.PL_SECOND */
+              parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+              return insert_token (LexerTokenType.UNKNOWN);
+          }
+      }
+
+      private LexerToken insert_decimal ()
+      {
+          var type = prelexer.get_next_token ();
+          if (type == LexerTokenType.PL_DIGIT)
+          {
+              while ((type = prelexer.get_next_token ()) == LexerTokenType.PL_DIGIT);
+              if (type == LexerTokenType.PL_DEGREE)
+                  return insert_token (LexerTokenType.NUMBER);
+              else if (type == LexerTokenType.PL_HEX)
+                  return insert_decimal_hex ();
+              else if (type == LexerTokenType.PL_SUB_DIGIT)
+              {
+                  while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+                  prelexer.roll_back ();
+                  return insert_token (LexerTokenType.NUMBER);
+              }
+              else
+              {
+                  prelexer.roll_back ();
+                  return insert_token (LexerTokenType.NUMBER);
+              }
+          }
+          else if (type == LexerTokenType.PL_HEX)
+              return insert_decimal_hex ();
+          else
+          {
+              /* ERROR: expected LexerTokenType.PL_DIGIT | LexerTokenType.PL_HEX */
+              parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+              return insert_token (LexerTokenType.UNKNOWN);
+          }
+      }
+
+      private LexerToken insert_hex ()
+      {
+          var type = prelexer.get_next_token ();
+          while (type == LexerTokenType.PL_HEX)
+              type = prelexer.get_next_token ();
+
+          if (type == LexerTokenType.PL_DIGIT)
+              return insert_hex_dec ();
+          else if (type == LexerTokenType.PL_DECIMAL)
+              return insert_decimal_hex ();
+          else if (type == LexerTokenType.PL_SUB_DIGIT)
+          {
+              while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+              prelexer.roll_back ();
+
+              if (check_if_number ())
+                  return insert_token (LexerTokenType.NUMBER);
+              else
+              {
+                  if (check_if_function ())
+                      return insert_token (LexerTokenType.FUNCTION);
+                  else if (check_if_unit ())
+                      return insert_token (LexerTokenType.UNIT);
+                  else
+                      return insert_token (LexerTokenType.VARIABLE);
+              }
+          }
+          else if (type == LexerTokenType.PL_LETTER)
+              return insert_letter ();
+          else
+          {
+              prelexer.roll_back ();
+              if (check_if_number ())
+                  return insert_token (LexerTokenType.NUMBER);
+              else
+              {
+                  if (check_if_function ())
+                      return insert_token (LexerTokenType.FUNCTION);
+                  else if (check_if_unit ())
+                      return insert_token (LexerTokenType.UNIT);
+                  else
+                      return insert_token (LexerTokenType.VARIABLE);
+              }
+          }
+      }
+
+      private LexerToken insert_hex_dec ()
+      {
+          var type = prelexer.get_next_token ();
+          while (type == LexerTokenType.PL_DIGIT || type == LexerTokenType.PL_HEX)
+              type = prelexer.get_next_token ();
+
+          if (type == LexerTokenType.PL_DECIMAL)
+              return insert_decimal_hex ();
+          else if (type == LexerTokenType.PL_SUB_DIGIT)
+          {
+              while (prelexer.get_next_token () == LexerTokenType.PL_SUB_DIGIT);
+              prelexer.roll_back ();
+              return insert_token (LexerTokenType.NUMBER);
+          }
+          else
+          {
+              if (check_if_number ())
+                  return insert_token (LexerTokenType.NUMBER);
+              /* ERROR: expected LexerTokenType.PL_DECIMAL | LexerTokenType.PL_DIGIT | LexerTokenType.PL_HEX 
*/
+              parser.set_error (ErrorCode.MP, prelexer.get_marked_substring (), prelexer.mark_index, 
prelexer.index);
+              return insert_token (LexerTokenType.UNKNOWN);
+          }
+      }
+
+      private LexerToken insert_decimal_hex ()
+      {
+          /* Make up of digits and hexadecimal characters */
+          var type = prelexer.get_next_token ();
+          while (type == LexerTokenType.PL_DIGIT || type == LexerTokenType.PL_HEX)
+              type = prelexer.get_next_token ();
+
+          /* Allow a subdigit suffix */
+          while (type == LexerTokenType.PL_SUB_DIGIT)
+              type = prelexer.get_next_token ();
+
+          prelexer.roll_back ();
+
+          return insert_token (LexerTokenType.NUMBER);
+      }
+
+      private LexerToken insert_letter ()
+      {
+          /* Get string of letters */
+          var type = prelexer.get_next_token ();
+          while (type == LexerTokenType.PL_LETTER || type == LexerTokenType.PL_HEX)
+              type = prelexer.get_next_token ();
+
+          /* Allow a subdigit suffix */
+          while (type == LexerTokenType.PL_SUB_DIGIT)
+              type = prelexer.get_next_token ();
+
+          prelexer.roll_back ();
+
+          var name = prelexer.get_marked_substring ().down ();
+          if (name == "mod")
+              return insert_token (LexerTokenType.MOD);
+          if (name == "and")
+              return insert_token (LexerTokenType.AND);
+          if (name == "or")
+              return insert_token (LexerTokenType.OR);
+          if (name == "xor")
+              return insert_token (LexerTokenType.XOR);
+          if (name == "not")
+              return insert_token (LexerTokenType.NOT);
+          // Translators: conversion keyword, used e.g. 1 EUR in USD
+          if (name == _("in"))
+              return insert_token (LexerTokenType.IN);
+          if (check_if_function ())
+              return insert_token (LexerTokenType.FUNCTION);
+          if (check_if_unit ())
+              return insert_token (LexerTokenType.UNIT);
+          else
+              return insert_token (LexerTokenType.VARIABLE);
+      }
+  }
+}
diff --git a/gcalc/gcalc-equation-parser.vala b/gcalc/gcalc-equation-parser.vala
new file mode 100644
index 00000000..93c8c2d8
--- /dev/null
+++ b/gcalc/gcalc-equation-parser.vala
@@ -0,0 +1,2109 @@
+/*
+ * Copyright (C) 2012 Arth Patel
+ * Copyright (C) 2012 Robert Ancell
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace GCalc {
+  /* Operator Associativity. */
+  public enum Associativity
+  {
+      LEFT,
+      RIGHT
+  }
+
+  /* Operator Precedence. */
+  private enum Precedence
+  {
+      UNKNOWN         = 0,
+      /* Conversion node */
+      CONVERT         = 0,
+      /* Unit for conversion */
+      UNIT            = 1,
+      /* Highest precedence of any operator in current level. Only conversion should be above this node in 
same depth level. */
+      TOP             = 2,
+      ADD_SUBTRACT    = 3,
+      MULTIPLY        = 4,
+      /* MOD and DIVIDE must have same preedence. */
+      MOD             = 5,
+      DIVIDE          = 5,
+      NOT             = 6,
+      FUNCTION        = 7,
+      BOOLEAN         = 8,
+      PERCENTAGE      = 9,
+      /* UNARY_MINUS, ROOT and POWER must have same precedence. */
+      UNARY_MINUS     = 10,
+      POWER           = 10,
+      ROOT            = 10,
+      FACTORIAL       = 11,
+      NUMBER_VARIABLE = 12,
+      /* DEPTH should be always at the bottom. It stops node jumping off the current depth level. */
+      DEPTH
+  }
+
+  /* ParseNode structure for parse tree. */
+  public class ParseNode : Object
+  {
+      public Parser parser;
+      public ParseNode? parent = null;
+      public ParseNode? left = null;
+      public ParseNode? right = null;
+      public List<LexerToken> token_list;
+      public uint precedence;
+      public Associativity associativity;
+      public string? value;
+
+      public LexerToken token()
+      {
+          assert(token_list.length() == 1);
+          return token_list.first().data;
+      }
+
+      public LexerToken first_token()
+      {
+          return token_list.first().data;
+      }
+
+      public LexerToken last_token()
+      {
+          return token_list.last().data;
+      }
+
+      public ParseNode.WithList (Parser parser, List<LexerToken> token_list, uint precedence, Associativity 
associativity, string? value = null)
+      {
+          this.parser = parser;
+          this.token_list = token_list.copy_deep((CopyFunc) Object.ref);
+          this.precedence = precedence;
+          this.associativity = associativity;
+          this.value = value;
+
+      }
+
+      public ParseNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, 
string? value = null)
+      {
+          this.parser = parser;
+          this.token_list = new List<LexerToken>();
+          token_list.insert(token, 0);
+          this.precedence = precedence;
+          this.associativity = associativity;
+          this.value = value;
+      }
+
+      public virtual Number? solve ()
+      {
+          return null;
+      }
+  }
+
+  public abstract class RNode : ParseNode
+  {
+      public RNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve ()
+      {
+          var r = right.solve ();
+          if (r == null)
+              return null;
+          var z = solve_r (r);
+
+          /* check for errors */
+          Number.check_flags ();
+          if (Number.error != null)
+          {
+              var tmpleft = right;
+              var tmpright = right;
+              while (tmpleft.left != null) tmpleft = tmpleft.left;
+              while (tmpright.right != null) tmpright = tmpright.right;
+              parser.set_error (ErrorCode.MP, Number.error, tmpleft.first_token().start_index, 
tmpright.last_token().end_index);
+              Number.error = null;
+          }
+          return z;
+      }
+
+      public abstract Number? solve_r (Number r);
+  }
+
+  public abstract class LRNode : ParseNode
+  {
+      public LRNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve ()
+      {
+          var l = left.solve ();
+          var r = right.solve ();
+          if (l == null || r == null)
+              return null;
+          var z = solve_lr (l, r);
+
+          /* check for errors */
+          Number.check_flags ();
+          if (Number.error != null)
+          {
+              var tmpleft = left;
+              var tmpright = right;
+              while (tmpleft.left != null) tmpleft = tmpleft.left;
+              while (tmpright.right != null) tmpright = tmpright.right;
+              parser.set_error (ErrorCode.MP, Number.error, tmpleft.first_token().start_index, 
tmpright.last_token().end_index);
+              Number.error = null;
+          }
+          return z;
+      }
+
+      public abstract Number solve_lr (Number left, Number r);
+  }
+
+  public class ConstantNode : ParseNode
+  {
+      public ConstantNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve ()
+      {
+          return mp_set_from_string (token().text, parser.number_base);
+      }
+  }
+
+  public class AssignNode : RNode
+  {
+      public AssignNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          parser.set_variable (left.token().text, r);
+          return r;
+      }
+  }
+
+  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)
+      {
+          base (parser, token, precedence, associativity, text);
+      }
+  }
+
+  public class VariableNode : ParseNode
+  {
+      public VariableNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve ()
+      {
+          /* If defined, then get the variable */
+          var ans = parser.get_variable (token().text);
+          if (ans != null)
+              return ans;
+
+          /* If has more than one character then assume a multiplication of variables */
+          // FIXME: Do this in the lexer
+          var value = new Number.integer (1);
+          var index = 0;
+          unichar c;
+          while (token().text.get_next_char (ref index, out c))
+          {
+              var t = parser.get_variable (c.to_string ());
+              if (t == null)
+              {
+                  parser.set_error (ErrorCode.UNKNOWN_VARIABLE, token().text, first_token().start_index, 
last_token().end_index);
+                  return null;
+              }
+              value = value.multiply (t);
+          }
+          return value;
+      }
+  }
+
+  public class VariableWithPowerNode : ParseNode
+  {
+      public VariableWithPowerNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, string text)
+      {
+          base (parser, token, precedence, associativity, text);
+      }
+
+      public override Number? solve ()
+      {
+          var pow = super_atoi (value);
+
+          value = null;
+
+          /* If defined, then get the variable */
+          var ans = parser.get_variable (token().text);
+          if (ans != null)
+              return ans.xpowy_integer (pow);
+
+          /* If has more than one character then assume a multiplication of variables */
+          // FIXME: Do in lexer
+          var value = new Number.integer (1);
+          var index = 0;
+          unichar c;
+          while (token().text.get_next_char (ref index, out c))
+          {
+              var t = parser.get_variable (c.to_string ());
+              if (t == null)
+              {
+                  parser.set_error (ErrorCode.UNKNOWN_VARIABLE, token().text, first_token().start_index, 
last_token().end_index);
+                  return null;
+              }
+
+              /* If last term do power */
+              var i = index;
+              unichar next;
+              if (!token().text.get_next_char (ref i, out next))
+                  t = t.xpowy_integer (pow);
+              value = value.multiply (t);
+          }
+
+          /* check for errors */
+          Number.check_flags ();
+          if (Number.error != null)
+          {
+              var tmpleft = left;
+              var tmpright = right;
+              while (tmpleft.left != null) tmpleft = tmpleft.left;
+              while (tmpright.right != null) tmpright = tmpright.right;
+              parser.set_error (ErrorCode.MP, Number.error, tmpleft.first_token().start_index, 
tmpright.last_token().end_index);
+              Number.error = null;
+          }
+
+          return value;
+      }
+  }
+
+  public class FunctionNameNode : NameNode
+  {
+      public FunctionNameNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, string name)
+      {
+          base (parser, token, precedence, associativity, name);
+      }
+  }
+
+  public class FunctionArgumentsNode : NameNode
+  {
+      public FunctionArgumentsNode (Parser parser, List<LexerToken> token_list, uint precedence, 
Associativity associativity, string arguments)
+      {
+          base.WithList (parser, token_list, precedence, associativity, arguments);
+      }
+  }
+
+  public class FunctionDescriptionNode : NameNode
+  {
+      public FunctionDescriptionNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, string description)
+      {
+          base (parser, token, precedence, associativity, description);
+      }
+  }
+
+  public class FunctionNode : ParseNode
+  {
+      public FunctionNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, 
string? text)
+      {
+          base (parser, token, precedence, associativity, text);
+      }
+
+      public override Number? solve ()
+      {
+          if (right == null || left == null)
+          {
+              parser.set_error (ErrorCode.UNKNOWN_FUNCTION);
+              return null;
+          }
+
+          var name = left.value;
+          if (name == null)
+          {
+              parser.set_error (ErrorCode.UNKNOWN_FUNCTION);
+              return null;
+          }
+
+          int pow = 1;
+          if (this.value != null)
+              pow = super_atoi (this.value);
+
+          if (pow < 0)
+          {
+              name = name + "⁻¹";
+              pow = -pow;
+          }
+
+          Number[] args = {};
+          if (right is FunctionArgumentsNode)
+          {
+              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;
+                  }
+              }
+          }
+          else
+          {
+              var ans = right.solve ();
+              if (ans != null)
+                  args += ans;
+              else
+              {
+                  parser.set_error (ErrorCode.UNKNOWN_FUNCTION);
+                  return null;
+              }
+          }
+
+          FunctionManager function_manager = FunctionManager.get_default_function_manager ();
+          var tmp = function_manager.evaluate_function (name, args, parser);
+
+          if (tmp != null)
+              tmp = tmp.xpowy_integer (pow);
+
+          /* check for errors */
+          Number.check_flags ();
+          if (Number.error != null)
+          {
+              parser.set_error (ErrorCode.MP, Number.error, right.first_token().start_index, 
right.last_token().end_index);
+              Number.error = null;
+          }
+
+          return tmp;
+      }
+  }
+
+  public class UnaryMinusNode : RNode
+  {
+      public UnaryMinusNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          return r.invert_sign ();
+      }
+  }
+
+  public class AbsoluteValueNode : RNode
+  {
+      public AbsoluteValueNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          return r.abs ();
+      }
+  }
+
+  public class FloorNode : RNode
+  {
+      public FloorNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          return r.floor ();
+      }
+  }
+
+  public class CeilingNode : RNode
+  {
+      public CeilingNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          return r.ceiling ();
+      }
+  }
+
+  public class FractionalComponentNode : RNode
+  {
+      public FractionalComponentNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          return r.fractional_part ();
+      }
+  }
+
+  public class RoundNode : RNode
+  {
+      public RoundNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          return r.round ();
+      }
+  }
+
+  public class PercentNode : RNode
+  {
+      public PercentNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          return r.divide_integer (100);
+      }
+  }
+
+  public class FactorialNode : RNode
+  {
+      public FactorialNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          return r.factorial ();
+      }
+  }
+
+  public class AddNode : LRNode
+  {
+      public bool do_percentage = false;
+
+      public AddNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          if (do_percentage)
+          {
+              var per = r.add (new Number.integer (100));
+              per = per.divide_integer (100);
+              return l.multiply (per);
+          }
+          else
+              return l.add (r);
+      }
+  }
+
+  public class SubtractNode : LRNode
+  {
+      public bool do_percentage = false;
+
+      public SubtractNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          if (do_percentage)
+          {
+              var per = r.add (new Number.integer (-100));
+              per = per.divide_integer (-100);
+              return l.multiply (per);
+          }
+          else
+              return l.subtract (r);
+      }
+  }
+
+  public class MultiplyNode : LRNode
+  {
+      public MultiplyNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          return l.multiply (r);
+      }
+  }
+
+  public class DivideNode : LRNode
+  {
+      public DivideNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          var z = l.divide (r);
+          if (Number.error != null)
+          {
+              uint token_start = 0;
+              uint token_end = 0;
+              var tmpleft = right;
+              var tmpright = right;
+              while (tmpleft.left != null) tmpleft = tmpleft.left;
+              while (tmpright.right != null) tmpright = tmpright.right;
+              if (tmpleft.first_token() != null) token_start = tmpleft.first_token().start_index;
+              if (tmpright.last_token() != null) token_end = tmpright.last_token().end_index;
+              parser.set_error (ErrorCode.MP, Number.error, token_start, token_end);
+              Number.error = null;
+          }
+          return z;
+      }
+  }
+
+  public class ModulusDivideNode : LRNode
+  {
+      public ModulusDivideNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve ()
+      {
+          if (left is XPowYNode)
+          {
+              var base_value = left.left.solve ();
+              var exponent = left.right.solve ();
+              var mod = right.solve ();
+              if (base_value == null || exponent == null || mod == null)
+                  return null;
+              var z = base_value.modular_exponentiation (exponent, mod);
+
+              /* check for errors */
+              Number.check_flags ();
+              if (Number.error != null)
+              {
+                  var tmpleft = left;
+                  var tmpright = right;
+                  while (tmpleft.left != null) tmpleft = tmpleft.left;
+                  while (tmpright.right != null) tmpright = tmpright.right;
+                  parser.set_error (ErrorCode.MP, Number.error, tmpleft.first_token().start_index, 
tmpright.last_token().end_index);
+                  Number.error = null;
+              }
+
+              return z;
+          }
+          else
+          {
+              var l = left.solve ();
+              var r = right.solve ();
+              if (l == null || r == null)
+                  return null;
+              var z = solve_lr (l, r);
+
+              /* check for errors */
+              Number.check_flags ();
+              if (Number.error != null)
+              {
+                  var tmpleft = left;
+                  var tmpright = right;
+                  while (tmpleft.left != null) tmpleft = tmpleft.left;
+                  while (tmpright.right != null) tmpright = tmpright.right;
+                  parser.set_error (ErrorCode.MP, Number.error, tmpleft.first_token().start_index, 
tmpright.last_token().end_index);
+                  Number.error = null;
+              }
+
+              return z;
+          }
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          return l.modulus_divide (r);
+      }
+  }
+
+  public class RootNode : RNode
+  {
+      private int n;
+      private LexerToken? token_n;
+
+      public RootNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity, int n)
+      {
+          base (parser, token, precedence, associativity);
+          this.n = n;
+          this.token_n = null;
+      }
+
+      public RootNode.WithToken (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, LexerToken token_n)
+      {
+          base (parser, token, precedence, associativity);
+          n = 0;
+          this.token_n = token_n;
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          if (n == 0 && token_n != null)
+          {
+              n = sub_atoi(token_n.text);
+          }
+          if (n == 0)
+          {
+              string error = _("The zeroth root of a number is undefined");
+              parser.set_error (ErrorCode.MP, error, token_n.start_index, token_n.end_index);
+              return null;
+          }
+          return r.root (n);
+      }
+  }
+
+  public class XPowYNode : LRNode
+  {
+      public XPowYNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          return l.xpowy (r);
+      }
+  }
+
+  /**
+   * This class is a XPowY in which the right token is an nsup number.
+   */
+  public class XPowYIntegerNode : ParseNode
+  {
+      public XPowYIntegerNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve ()
+      {
+          var val = left.solve ();
+
+          // Are we inside a nested pow?
+          if (val == null)
+          {
+              val = new Number.integer (super_atoi (left.token().text));
+          }
+
+          int64 pow;
+
+          if (right.token() != null)
+              pow = super_atoi (right.token().text);
+          else
+              pow = right.solve ().to_integer ();
+
+          if (val == null)
+              return null;
+
+          var z = val.xpowy_integer (pow);
+
+          /* check for errors */
+          Number.check_flags ();
+          if (Number.error != null)
+          {
+              var tmpleft = left;
+              var tmpright = right;
+              while (tmpleft.left != null) tmpleft = tmpleft.left;
+              while (tmpright.right != null) tmpright = tmpright.right;
+              parser.set_error (ErrorCode.MP, Number.error, tmpleft.first_token().start_index, 
tmpright.last_token().end_index);
+              Number.error = null;
+          }
+
+          return z;
+      }
+  }
+
+  public class NotNode : RNode
+  {
+      public NotNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve_r (Number r)
+      {
+          if (!mp_is_overflow (r, parser.wordlen))
+          {
+              parser.set_error (ErrorCode.OVERFLOW);
+              return new Number.integer (0);
+          }
+
+          return r.not (parser.wordlen);
+      }
+  }
+
+  public class AndNode : LRNode
+  {
+      public AndNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          return l.and (r);
+      }
+  }
+
+  public class OrNode : LRNode
+  {
+      public OrNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          return l.or (r);
+      }
+  }
+
+  public class XorNode : LRNode
+  {
+      public XorNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          return l.xor (r);
+      }
+  }
+
+  public class ConvertNode : LRNode
+  {
+      public ConvertNode (Parser parser, LexerToken? token, uint precedence, Associativity associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number solve_lr (Number l, Number r)
+      {
+          string from;
+          if (left.value != null)
+          {
+              from = left.value;
+              left.value = null;
+          }
+          else
+              from = left.token().text;
+
+          string to;
+          if (right.value != null)
+          {
+              to = right.value;
+              right.value = null;
+          }
+          else
+              to = right.token().text;
+
+          var tmp = new Number.integer (1);
+
+          var ans = parser.convert (tmp, from, to);
+          if (ans == null)
+              parser.set_error (ErrorCode.UNKNOWN_CONVERSION);
+
+          return ans;
+      }
+  }
+
+  public class ConvertBaseNode : ParseNode
+  {
+      public ConvertBaseNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity, string? value = null)
+      {
+          base (parser, token, precedence, associativity, value);
+      }
+
+      public override Number? solve ()
+      {
+          string name = value;
+
+          if (name == null && right != null)
+              name = right.token ().text;
+
+          if (name == "hex" || name == "hexadecimal")
+              parser.set_representation_base (16);
+          else if (name == "dec" || name == "decimal")
+              parser.set_representation_base (10);
+          else if (name == "oct" || name == "octal")
+              parser.set_representation_base (8);
+          else if (name == "bin" || name == "binary")
+              parser.set_representation_base (2);
+          else
+          {
+              parser.set_error (ErrorCode.UNKNOWN_CONVERSION, token().text, first_token().start_index, 
last_token().end_index);
+              return null;
+          }
+          return left.solve ();
+      }
+  }
+
+  public class ConvertNumberNode : ParseNode
+  {
+      public ConvertNumberNode (Parser parser, LexerToken? token, uint precedence, Associativity 
associativity)
+      {
+          base (parser, token, precedence, associativity);
+      }
+
+      public override Number? solve ()
+      {
+          string from;
+          if (left.value != null)
+          {
+              from = left.value;
+              left.value = null;
+          }
+          else
+              from = left.token().text;
+
+          string to;
+          if (right.value != null)
+          {
+              to = right.value;
+              right.value = null;
+          }
+          else
+              to = right.token().text;
+
+          var tmp = left.left.solve();
+          if (tmp == null)
+              return null;
+
+          var ans = parser.convert (tmp, from, to);
+          if (ans == null)
+              parser.set_error (ErrorCode.UNKNOWN_CONVERSION);
+
+          return ans;
+      }
+  }
+
+  public class Parser
+  {
+      private string input;
+      private ParseNode root;
+      private ParseNode right_most;
+      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;
+      private int error_token_start;
+      private int error_token_end;
+      private uint representation_base;
+
+      public Parser (string input, int number_base, int wordlen, AngleUnit angle_units)
+      {
+          this.input = input;
+          lexer = new Lexer (input, this, number_base);
+          root = null;
+          depth_level = 0;
+          right_most = null;
+          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 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 */
+          lexer.scan ();
+
+          /* Parse tokens */
+          var ret = statement ();
+
+          var token = lexer.get_next_token ();
+          if (token.type == LexerTokenType.ASSIGN)
+          {
+              token = lexer.get_next_token ();
+              if (token.type != LexerTokenType.PL_EOS)
+              {
+                  /* Full string is not parsed. */
+                  if (error == ErrorCode.NONE)
+                      set_error (ErrorCode.INVALID, token.text, token.start_index, token.end_index);
+
+                  error_code = error;
+                  error_token = this.error_token;
+                  error_start = error_token_start;
+                  error_end = error_token_end;
+                  return false;
+              }
+          }
+          if (token.type != LexerTokenType.PL_EOS)
+          {
+              /* Full string is not parsed. */
+              if (error == ErrorCode.NONE)
+                  set_error (ErrorCode.INVALID, token.text, token.start_index, token.end_index);
+
+              error_code = error;
+              error_token = this.error_token;
+              error_start = error_token_start;
+              error_end = error_token_end;
+              return false;
+          }
+
+          /* Input can't be parsed with grammar. */
+          if (!ret)
+          {
+              if (error == ErrorCode.NONE)
+                  set_error (ErrorCode.INVALID);
+
+              error_code = error;
+              error_token = this.error_token;
+              error_start = error_token_start;
+              error_end = error_token_end;
+              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 bool unit_is_defined (string name)
+      {
+          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;
+      }
+
+      /* 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 && this.error == ErrorCode.NONE)
+          {
+              error_code = ErrorCode.INVALID;
+              error_token = null;
+              error_start = error_token_start;
+              error_end = error_token_end;
+              return null;
+          }
+
+          representation_base = this.representation_base;
+          error_code = this.error;
+          error_token = this.error_token;
+          error_start = this.error_token_start;
+          error_end = this.error_token_end;
+          return ans;
+      }
+
+      /* Converts LexerTokenType to Precedence value. */
+      private Precedence get_precedence (LexerTokenType type)
+      {
+          /* WARNING: This function doesn't work for Unary Plus and Unary Minus. Use their precedence 
directly while inserting them in tree. */
+          if (type == LexerTokenType.ADD || type == LexerTokenType.SUBTRACT)
+              return Precedence.ADD_SUBTRACT;
+          if (type == LexerTokenType.MULTIPLY)
+              return Precedence.MULTIPLY;
+          if (type == LexerTokenType.MOD)
+              return Precedence.MOD;
+          if (type == LexerTokenType.DIVIDE)
+              return Precedence.DIVIDE;
+          if (type == LexerTokenType.NOT)
+              return Precedence.NOT;
+          if (type == LexerTokenType.ROOT || type == LexerTokenType.ROOT_3 || type == LexerTokenType.ROOT_4)
+              return Precedence.ROOT;
+          if (type == LexerTokenType.FUNCTION)
+              return Precedence.FUNCTION;
+          if (type == LexerTokenType.AND || type == LexerTokenType.OR || type == LexerTokenType.XOR)
+              return Precedence.BOOLEAN;
+          if (type == LexerTokenType.PERCENTAGE)
+              return Precedence.PERCENTAGE;
+          if (type == LexerTokenType.POWER)
+              return Precedence.POWER;
+          if (type == LexerTokenType.FACTORIAL)
+              return Precedence.FACTORIAL;
+          if (type == LexerTokenType.NUMBER || type == LexerTokenType.VARIABLE)
+              return Precedence.NUMBER_VARIABLE;
+          if (type == LexerTokenType.UNIT)
+              return Precedence.UNIT;
+          if (type == LexerTokenType.IN)
+              return Precedence.CONVERT;
+          return Precedence.TOP;
+      }
+
+      /* Return associativity of specific token type from precedence. */
+      private Associativity get_associativity_p (Precedence type)
+      {
+          if (type == Precedence.BOOLEAN || type == Precedence.DIVIDE || type == Precedence.MOD || type == 
Precedence.MULTIPLY || type == Precedence.ADD_SUBTRACT)
+              return Associativity.LEFT;
+          if (type == Precedence.POWER)
+              return Associativity.RIGHT;
+          /* For all remaining / non-associative operators, return Left Associativity. */
+          return Associativity.LEFT;
+      }
+
+      /* Return associativity of specific token by converting it to precedence first. */
+      private Associativity get_associativity (LexerToken token)
+      {
+          return get_associativity_p (get_precedence (token.type));
+      }
+
+      /* Generate precedence for a node from precedence value. Includes depth_level. */
+      private uint make_precedence_p (Precedence p)
+      {
+          return p + (depth_level * Precedence.DEPTH);
+      }
+
+      /* Generate precedence for a node from lexer token type. Includes depth_level. */
+      private uint make_precedence_t (LexerTokenType type)
+      {
+          return get_precedence (type) + (depth_level * Precedence.DEPTH);
+      }
+
+      /* Compares two nodes to decide, which will be parent and which will be child. */
+      private bool cmp_nodes (ParseNode? left, ParseNode? right)
+      {
+          /* Return values:
+           * true = right goes up (near root) in parse tree.
+           * false = left  goes up (near root) in parse tree.
+           */
+          if (left == null)
+              return false;
+          if (left.precedence > right.precedence)
+              return true;
+          else if (left.precedence < right.precedence)
+              return false;
+          else
+              return right.associativity != Associativity.RIGHT;
+      }
+
+      /* Unified interface (unary and binary nodes) to insert node into parse tree. */
+      private void insert_into_tree_all (ParseNode node, bool unary_function)
+      {
+          if (root == null)
+          {
+              root = node;
+              right_most = root;
+              return;
+          }
+          ParseNode tmp = right_most;
+          while (cmp_nodes (tmp, node))
+              tmp = tmp.parent;
+
+          if (unary_function)
+          {
+              /* If tmp is null, that means, we have to insert new node at root. */
+              if (tmp == null)
+              {
+                  node.right = root;
+                  node.right.parent = node;
+
+                  root = node;
+              }
+              else
+              {
+                  node.right = tmp.right;
+                  if (node.right != null)
+                      node.right.parent = node;
+
+                  tmp.right = node;
+                  if (tmp.right != null)
+                      tmp.right.parent = tmp;
+
+              }
+              right_most = node;
+              while (right_most.right != null)
+                  right_most = right_most.right;
+          }
+          else
+          {
+              /* If tmp is null, that means, we have to insert new node at root. */
+              if (tmp == null)
+              {
+                  node.left = root;
+                  node.left.parent = node;
+
+                  root = node;
+              }
+              else
+              {
+                  node.left = tmp.right;
+                  if (node.left != null)
+                      node.left.parent = node;
+
+                  tmp.right = node;
+                  if (tmp.right != null)
+                      tmp.right.parent = tmp;
+
+              }
+              right_most = node;
+          }
+      }
+
+      /* Insert binary node into the parse tree. */
+      private void insert_into_tree (ParseNode node)
+      {
+          insert_into_tree_all (node, false);
+      }
+
+      /* Insert unary node into the parse tree. */
+      private void insert_into_tree_unary (ParseNode node)
+      {
+          insert_into_tree_all (node, true);
+      }
+
+      /* Recursive call to free every node of parse-tree. */
+      private void destroy_all_nodes (ParseNode node)
+      {
+          if (node == null)
+              return;
+
+          destroy_all_nodes (node.left);
+          destroy_all_nodes (node.right);
+          /* Don't call free for tokens, as they are allocated and freed in lexer. */
+          /* WARNING: If node.value is freed elsewhere, please assign it null before calling 
destroy_all_nodes (). */
+      }
+
+      /* LL (*) parser. Lookahead count depends on tokens. Handle with care. :P */
+
+      /* Check if string "name" is a valid variable for given Parser. It is the same code, used to get the 
value of variable in parserfunc.c. */
+      private bool check_variable (string name)
+      {
+          /* If defined, then get the variable */
+          if (variable_is_defined (name))
+              return true;
+
+          /* If has more than one character then assume a multiplication of variables */
+          var index = 0;
+          unichar c;
+          while (name.get_next_char (ref index, out c))
+          {
+              if (!variable_is_defined (c.to_string ()))
+                  return false;
+          }
+
+          return true;
+      }
+
+      private bool statement ()
+      {
+          var token = lexer.get_next_token ();
+          if (token.type == LexerTokenType.VARIABLE || token.type == LexerTokenType.FUNCTION)
+          {
+              var token_old = token;
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.ASSIGN)
+              {
+                  insert_into_tree (new NameNode (this, token_old, make_precedence_p 
(Precedence.NUMBER_VARIABLE), get_associativity (token_old)));
+                  insert_into_tree (new AssignNode (this, token, 0, get_associativity (token)));
+
+                  if (!expression ())
+                      return false;
+
+                  return true;
+              }
+              else
+              {
+                  lexer.roll_back ();
+                  lexer.roll_back ();
+
+                  if (token.type == LexerTokenType.L_R_BRACKET)
+                  {
+                      if (function_definition ())
+                          return true;
+                  }
+
+                  if (!expression ())
+                      return false;
+
+                  return true;
+              }
+          }
+          else
+          {
+              lexer.roll_back ();
+              if (!expression ())
+                  return false;
+              return true;
+          }
+      }
+
+      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 = "";
+          List<LexerToken> token_list = new List<LexerToken> ();
+
+          while (token.type != LexerTokenType.R_R_BRACKET && token.type != LexerTokenType.PL_EOS)
+          {
+              token_list.append (token);
+              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, token_list, 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 conversion ()
+      {
+          var token = lexer.get_next_token ();
+          if (token.type == LexerTokenType.IN)
+          {
+              var token_in = token;
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.UNIT)
+              {
+                  var token_to = token;
+                  token = lexer.get_next_token ();
+                  /* We can only convert representation base, if it is next to End Of Stream */
+                  if (token.type == LexerTokenType.PL_EOS)
+                  {
+                      insert_into_tree (new ConvertBaseNode (this, token_in, make_precedence_p 
(Precedence.CONVERT), get_associativity (token_in)));
+                      insert_into_tree (new NameNode (this, token_to, make_precedence_p (Precedence.UNIT), 
get_associativity (token_to)));
+                      return true;
+                  }
+                  else
+                  {
+                      lexer.roll_back ();
+                      lexer.roll_back ();
+                      lexer.roll_back ();
+                      return false;
+                  }
+              }
+              else
+              {
+                  lexer.roll_back ();
+                  lexer.roll_back ();
+                  return false;
+              }
+          }
+          else if (token.type == LexerTokenType.UNIT)
+          {
+              var token_from = token;
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.IN)
+              {
+                  var token_in = token;
+                  token = lexer.get_next_token ();
+                  if (token.type == LexerTokenType.UNIT)
+                  {
+                      insert_into_tree (new NameNode (this, token_from, make_precedence_p (Precedence.UNIT), 
get_associativity (token_from)));
+                      insert_into_tree (new ConvertNumberNode (this, token_in, make_precedence_p 
(Precedence.CONVERT), get_associativity (token_in)));
+                      insert_into_tree (new NameNode (this, token, make_precedence_p (Precedence.UNIT), 
get_associativity (token)));
+                      return true;
+                  }
+                  else
+                  {
+                      lexer.roll_back ();
+                      lexer.roll_back ();
+                      lexer.roll_back ();
+                      return false;
+                  }
+              }
+              else
+              {
+                  lexer.roll_back ();
+                  lexer.roll_back ();
+                  return false;
+              }
+          }
+          else
+          {
+              lexer.roll_back ();
+              return false;
+          }
+      }
+
+      private bool expression ()
+      {
+          if (!expression_1 ())
+              return false;
+          if (!expression_2 ())
+              return false;
+          /* If there is a possible conversion at this level, insert it in the tree. */
+          conversion ();
+          return true;
+      }
+
+      private bool expression_1 ()
+      {
+          var token = lexer.get_next_token ();
+
+          if (token.type == LexerTokenType.PL_EOS || token.type == LexerTokenType.ASSIGN)
+          {
+              lexer.roll_back ();
+              return false;
+          }
+
+          if (token.type == LexerTokenType.L_R_BRACKET)
+          {
+              depth_level++;
+
+              if (!expression ())
+                  return false;
+
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.R_R_BRACKET)
+              {
+                  depth_level--;
+                  token = lexer.get_next_token ();
+                  lexer.roll_back ();
+
+                  if (token.type == LexerTokenType.NUMBER)
+                  {
+                      insert_into_tree (new MultiplyNode (this, null, make_precedence_p 
(Precedence.MULTIPLY), get_associativity_p (Precedence.MULTIPLY)));
+
+                      if (!expression ())
+                          return false;
+                      else
+                          return true;
+                   }
+                   else
+                       return true;
+              }
+              //Expected ")" here...
+              else
+                  return false;
+          }
+          else if (token.type == LexerTokenType.L_S_BRACKET)
+          {
+              depth_level++;
+
+              /* Give round, preference of Precedence.TOP aka 2, to keep it on the top of expression. */
+
+              insert_into_tree_unary (new RoundNode (this, token, make_precedence_p (Precedence.TOP), 
get_associativity (token)));
+
+              if (!expression ())
+                  return false;
+
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.R_S_BRACKET)
+              {
+                  depth_level--;
+                  return true;
+              }
+              else
+              //Expected "]" here...
+                  return false;
+          }
+          else if (token.type == LexerTokenType.L_C_BRACKET)
+          {
+              depth_level++;
+
+              /* Give fraction, preference of Precedence.TOP aka 2, to keep it on the top of expression. */
+
+              insert_into_tree_unary (new FractionalComponentNode (this, token, make_precedence_p 
(Precedence.TOP), get_associativity (token)));
+
+              if (!expression ())
+                  return false;
+
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.R_C_BRACKET)
+              {
+                  depth_level--;
+                  return true;
+              }
+              //Expected "}" here...
+              else
+                  return false;
+          }
+          else if (token.type == LexerTokenType.ABS)
+          {
+              depth_level++;
+
+              /* Give abs, preference of Precedence.TOP aka 2, to keep it on the top of expression. */
+
+              insert_into_tree_unary (new AbsoluteValueNode (this, token, make_precedence_p 
(Precedence.TOP), get_associativity (token)));
+
+              if (!expression ())
+                  return false;
+
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.ABS)
+              {
+                  depth_level--;
+                  return true;
+              }
+              //Expected "|" here...
+              else
+                  return false;
+          }
+          else if (token.type == LexerTokenType.NOT)
+          {
+              insert_into_tree_unary (new NotNode (this, token, make_precedence_p (Precedence.NOT), 
get_associativity (token)));
+
+              if (!expression ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.NUMBER)
+          {
+              insert_into_tree (new ConstantNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+
+              token = lexer.get_next_token ();
+              lexer.roll_back ();
+
+              if (token.type == LexerTokenType.FUNCTION || token.type == LexerTokenType.VARIABLE || 
token.type == LexerTokenType.SUB_NUMBER || token.type == LexerTokenType.ROOT || token.type == 
LexerTokenType.ROOT_3 || token.type == LexerTokenType.ROOT_4)
+              {
+                  insert_into_tree (new MultiplyNode (this, null, make_precedence_p (Precedence.MULTIPLY), 
get_associativity_p (Precedence.MULTIPLY)));
+
+                  if (!variable ())
+                      return false;
+                  else
+                      return true;
+              }
+              else
+                  return true;
+          }
+          else if (token.type == LexerTokenType.L_FLOOR)
+          {
+              depth_level++;
+              /* Give floor, preference of Precedence.TOP aka 2, to keep it on the top of expression. */
+
+              insert_into_tree_unary (new FloorNode (this, null, make_precedence_p (Precedence.TOP), 
get_associativity_p (Precedence.TOP)));
+
+              if (!expression ())
+                  return false;
+
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.R_FLOOR)
+              {
+                  depth_level--;
+                  return true;
+              }
+              //Expected ⌋ here...
+              else
+                  return false;
+          }
+          else if (token.type == LexerTokenType.L_CEILING)
+          {
+              depth_level++;
+              /* Give ceiling, preference of Precedence.TOP aka 2, to keep it on the top of expression. */
+
+              insert_into_tree_unary (new CeilingNode (this, null, make_precedence_p (Precedence.TOP), 
get_associativity_p (Precedence.TOP)));
+
+              if (!expression ())
+                  return false;
+
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.R_CEILING)
+              {
+                  depth_level--;
+                  return true;
+              }
+              //Expected ⌉ here...
+              else
+                  return false;
+          }
+          else if (token.type == LexerTokenType.SUBTRACT)
+          {
+              insert_into_tree_unary (new UnaryMinusNode (this, token, make_precedence_p 
(Precedence.UNARY_MINUS), get_associativity_p (Precedence.UNARY_MINUS)));
+
+              if (!expression_1 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.ADD)
+          {
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.NUMBER)
+              {
+                  /* Ignore ADD. It is not required. */
+                  insert_into_tree (new ConstantNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+                  return true;
+              }
+              else
+                  return false;
+          }
+          else
+          {
+              lexer.roll_back ();
+              if (!variable ())
+                  return false;
+              else
+                  return true;
+          }
+      }
+
+      private bool expression_2 ()
+      {
+          var token = lexer.get_next_token ();
+          if (token.type == LexerTokenType.L_R_BRACKET)
+          {
+              insert_into_tree (new MultiplyNode (this, null, make_precedence_p (Precedence.MULTIPLY), 
get_associativity_p (Precedence.MULTIPLY)));
+
+              depth_level++;
+              if (!expression ())
+                  return false;
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.R_R_BRACKET)
+              {
+                  depth_level--;
+
+                  if (!expression_2 ())
+                      return false;
+
+                  return true;
+              }
+              else
+                  return false;
+          }
+          else if (token.type == LexerTokenType.POWER)
+          {
+              insert_into_tree (new XPowYNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+
+              if (!expression_1 ())
+                  return false;
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.SUP_NUMBER)
+          {
+              insert_into_tree (new XPowYIntegerNode (this, null, make_precedence_p (Precedence.POWER), 
get_associativity_p (Precedence.POWER)));
+              insert_into_tree (new NameNode (this, token, make_precedence_p (Precedence.NUMBER_VARIABLE), 
get_associativity_p (Precedence.NUMBER_VARIABLE)));
+
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.NSUP_NUMBER)
+          {
+              insert_into_tree (new XPowYIntegerNode (this, null, make_precedence_p (Precedence.POWER), 
get_associativity_p (Precedence.POWER)));
+              insert_into_tree (new NameNode (this, token, make_precedence_p (Precedence.NUMBER_VARIABLE), 
get_associativity_p (Precedence.NUMBER_VARIABLE)));
+
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.FACTORIAL)
+          {
+              insert_into_tree_unary (new FactorialNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.MULTIPLY)
+          {
+              insert_into_tree (new MultiplyNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+
+              if (!expression_1 ())
+                  return false;
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.PERCENTAGE)
+          {
+              insert_into_tree_unary (new PercentNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.AND)
+          {
+              insert_into_tree (new AndNode (this, token, make_precedence_t (token.type), get_associativity 
(token)));
+
+              if (!expression_1 ())
+                  return false;
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.OR)
+          {
+              insert_into_tree (new OrNode (this, token, make_precedence_t (token.type), get_associativity 
(token)));
+
+              if (!expression_1 ())
+                  return false;
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.XOR)
+          {
+              insert_into_tree (new XorNode (this, token, make_precedence_t (token.type), get_associativity 
(token)));
+
+              if (!expression_1 ())
+                  return false;
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.DIVIDE)
+          {
+              insert_into_tree (new DivideNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+
+              if (!expression_1 ())
+                  return false;
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.MOD)
+          {
+              insert_into_tree (new ModulusDivideNode (this, token, make_precedence_t (token.type), 
get_associativity (token)));
+
+              if (!expression_1 ())
+                  return false;
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.ADD)
+          {
+              var node = new AddNode (this, token, make_precedence_t (token.type), get_associativity 
(token));
+              insert_into_tree (node);
+
+              if (!expression_1 ())
+                  return false;
+
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.PERCENTAGE)
+              {
+                  //FIXME: This condition needs to be verified for all cases.. :(
+                  if (node.right.precedence > Precedence.PERCENTAGE)
+                  {
+                      node.precedence = Precedence.PERCENTAGE;
+                      node.do_percentage = true;
+                      return true;
+                  }
+                  else
+                  {
+                      /* Assume '%' to be part of 'expression PERCENTAGE' statement. */
+                      lexer.roll_back ();
+                      if (!expression_2 ())
+                          return true;
+                  }
+              }
+              else
+                  lexer.roll_back ();
+
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.SUBTRACT)
+          {
+              var node = new SubtractNode (this, token, make_precedence_t (token.type), get_associativity 
(token));
+              insert_into_tree (node);
+
+              if (!expression_1 ())
+                  return false;
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.PERCENTAGE)
+              {
+                  //FIXME: This condition needs to be verified for all cases.. :(
+                  if (node.right.precedence > Precedence.PERCENTAGE)
+                  {
+                      node.precedence = Precedence.PERCENTAGE;
+                      node.do_percentage = true;
+                      return true;
+                  }
+                  else
+                  {
+                      /* Assume '%' to be part of 'expression PERCENTAGE' statement. */
+                      lexer.roll_back ();
+                      if (!expression_2 ())
+                          return true;
+                  }
+              }
+              else
+                  lexer.roll_back ();
+
+              if (!expression_2 ())
+                  return false;
+
+              return true;
+          }
+          else
+          {
+              lexer.roll_back ();
+              return true;
+          }
+      }
+
+      private bool variable ()
+      {
+          var token = lexer.get_next_token ();
+          if (token.type == LexerTokenType.FUNCTION)
+          {
+              lexer.roll_back ();
+              if (!function_invocation ())
+                  return false;
+              return true;
+          }
+          else if (token.type == LexerTokenType.SUB_NUMBER)
+          {
+              var token_old = token;
+              token = lexer.get_next_token ();
+              if (token.type == LexerTokenType.ROOT)
+              {
+                  insert_into_tree_unary (new RootNode.WithToken (this, token, make_precedence_t 
(token.type), get_associativity (token), token_old));
+                  if (!expression ())
+                      return false;
+
+                  return true;
+              }
+              else
+                  return false;
+          }
+          else if (token.type == LexerTokenType.ROOT)
+          {
+              insert_into_tree_unary (new RootNode (this, token, make_precedence_t (token.type), 
get_associativity (token), 2));
+
+              if (!expression ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.ROOT_3)
+          {
+              insert_into_tree_unary (new RootNode (this, token, make_precedence_t (token.type), 
get_associativity (token), 3));
+
+              if (!expression ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.ROOT_4)
+          {
+              insert_into_tree_unary (new RootNode (this, token, make_precedence_t (token.type), 
get_associativity (token), 4));
+
+              if (!expression ())
+                  return false;
+
+              return true;
+          }
+          else if (token.type == LexerTokenType.VARIABLE)
+          {
+              lexer.roll_back ();
+              //TODO: unknown function ERROR for (VARIABLE SUP_NUMBER expression).
+              if (!term ())
+                  return false;
+
+              return true;
+          }
+          else
+              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, fun_token, 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 = "";
+              List<LexerToken> token_list = new List<LexerToken>();
+
+              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;
+                  }
+                  else
+                      token_list.append(token);
+                  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, token_list, 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 ();
+
+          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_old.text))
+              {
+                  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;
+              }
+              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
+              {
+                  lexer.roll_back ();
+                  insert_into_tree (new VariableNode (this, token_old, make_precedence_t (token_old.type), 
get_associativity (token_old)));
+              }
+
+              if (!term_2 ())
+                  return false;
+
+              return true;
+          }
+          else
+              return false;
+      }
+
+      private bool term_2 ()
+      {
+          var token = lexer.get_next_token ();
+          lexer.roll_back ();
+
+          if (token.type == LexerTokenType.PL_EOS || token.type == LexerTokenType.ASSIGN)
+              return true;
+
+          if (token.type == LexerTokenType.VARIABLE)
+          {
+              /* Insert multiply in between two distinct (variable). */
+              insert_into_tree (new MultiplyNode (this, null, make_precedence_p (Precedence.MULTIPLY), 
get_associativity_p (Precedence.MULTIPLY)));
+
+              if (!term ())
+                  return false;
+
+              return true;
+          }
+          else
+              return true;
+      }
+  }
+}
diff --git a/gcalc/gcalc-equation.vala b/gcalc/gcalc-equation.vala
new file mode 100644
index 00000000..45128f9f
--- /dev/null
+++ b/gcalc/gcalc-equation.vala
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2004-2008 Sami Pietila
+ * Copyright (C) 2008-2012 Robert Ancell.
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+namespace GCalc {
+  public int sub_atoi (string data)
+  {
+      const unichar digits[] = {'₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'};
+
+      var index = 0;
+      unichar c;
+      int value = 0;
+      while (data.get_next_char (ref index, out c))
+      {
+          var is_subdigit = false;
+          for (var i = 0; i < digits.length; i++)
+          {
+              if (c == digits[i])
+              {
+                  value = value * 10 + i;
+                  is_subdigit = true;
+                  break;
+              }
+          }
+          if (!is_subdigit)
+              return -1;
+      }
+
+      return value;
+  }
+
+  public int super_atoi (string data)
+  {
+      const unichar digits[] = {'⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'};
+
+      var index = 0;
+      unichar c;
+      data.get_next_char (ref index, out c);
+      int sign = 1;
+      if (c == '⁻')
+          sign = -1;
+      else
+          index = 0;
+
+      int value = 0;
+      while (data.get_next_char (ref index, out c))
+      {
+          var is_superdigit = false;
+          for (var i = 0; i < digits.length; i++)
+          {
+              if (c == digits[i])
+              {
+                  value = value * 10 + i;
+                  is_superdigit = true;
+                  break;
+              }
+          }
+          if (!is_superdigit)
+              return 0;
+      }
+
+      return sign * value;
+  }
+
+  public string mp_error_code_to_string (ErrorCode error_code)
+  {
+      return @"$error_code".replace ("ERROR_CODE_", "ErrorCode.");
+  }
+
+  public enum ErrorCode
+  {
+      NONE,
+      INVALID,
+      OVERFLOW,
+      UNKNOWN_VARIABLE,
+      UNKNOWN_FUNCTION,
+      UNKNOWN_CONVERSION,
+      MP
+  }
+
+  public class Equation : Object
+  {
+      public int base;
+      public int wordlen;
+      public AngleUnit angle_units;
+      private string expression;
+
+      public Equation (string expression)
+      {
+          this.expression = expression;
+      }
+
+      public new Number? parse (out uint representation_base = null, out ErrorCode error_code = null, out 
string? error_token = null, out uint? error_start = null, out uint? error_end = null)
+      {
+          var parser = new EquationParser (this, expression);
+          Number.error = null;
+
+          var z = parser.parse (out representation_base, out error_code, out error_token, out error_start, 
out error_end);
+
+          /* Error during parsing */
+          if (error_code != ErrorCode.NONE)
+          {
+              return null;
+          }
+
+          if (Number.error != null)
+          {
+              error_code = ErrorCode.MP;
+              return null;
+          }
+
+          return z;
+      }
+
+      public virtual bool variable_is_defined (string name)
+      {
+          return false;
+      }
+
+      public virtual Number? get_variable (string name)
+      {
+          return null;
+      }
+
+      public virtual bool unit_is_defined (string name)
+      {
+          return false;
+      }
+
+      public virtual bool literal_base_is_defined (string name)
+      {
+          return false;
+      }
+
+      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;
+      }
+  }
+
+  private class EquationParser : Parser
+  {
+      private Equation equation;
+
+      public EquationParser (Equation equation, string expression)
+      {
+          base (expression, equation.base, equation.wordlen, equation.angle_units);
+          this.equation = equation;
+      }
+
+      protected override bool variable_is_defined (string name)
+      {
+          /* FIXME: Make more generic */
+          if (name == "e" || name == "i" || name == "π")
+              return true;
+
+          return equation.variable_is_defined (name);
+      }
+
+      protected override Number? get_variable (string name)
+      {
+          if (name == "e")
+              return new Number.eulers ();
+          else if (name == "i")
+              return new Number.i ();
+          else if (name == "π")
+              return new Number.pi ();
+          else
+              return equation.get_variable (name);
+      }
+
+      protected override void set_variable (string name, Number x)
+      {
+          // Reserved words, e, π, mod, and, or, xor, not, abs, log, ln, sqrt, int, frac, sin, cos, ...
+          if (name == "e" || name == "i" || name == "π")
+              return; // FALSE
+
+          equation.set_variable (name, x);
+      }
+
+      // FIXME: Accept "2sin" not "2 sin", i.e. let the tokenizer collect the multiple
+      // Parser then distinguishes between "sin"="s*i*n" or "sin5" = "sin 5" = "sin (5)"
+      // i.e. numbers+letters = variable or function depending on following arg
+      // letters+numbers = numbers+letters+numbers = function
+
+      protected override bool function_is_defined (string name)
+      {
+          var function_manager = FunctionManager.get_default_function_manager();
+
+          if (function_manager.is_function_defined (name))
+              return true;
+
+          return equation.function_is_defined (name);
+      }
+
+      protected override bool unit_is_defined (string name)
+      {
+          if (name == "hex" || name == "hexadecimal" || name == "dec" || name == "decimal" || name == "oct" 
|| name == "octal" || name == "bin" || name == "binary")
+              return true;
+
+          var unit_manager = UnitManager.get_default ();
+
+          if (unit_manager.unit_is_defined (name))
+              return true;
+
+          return equation.unit_is_defined (name);
+      }
+
+      protected override Number? convert (Number x, string x_units, string z_units)
+      {
+          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/gcalc/gcalc-function-manager.vala b/gcalc/gcalc-function-manager.vala
new file mode 100644
index 00000000..d2d34eab
--- /dev/null
+++ b/gcalc/gcalc-function-manager.vala
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2013 Garima Joshi
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace GCalc {
+  private FunctionManager? default_function_manager = null;
+
+  public class FunctionManager : Object
+  {
+      private string file_name;
+      private HashTable<string, MathFunction> functions;
+      private Serializer serializer;
+
+      public signal void function_added (MathFunction function);
+      public signal void function_edited (MathFunction new_function);
+      public signal void function_deleted (MathFunction function);
+
+      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);
+      }
+
+      /**
+        * Adds a function to the manager, unless the given name is already taken
+        * by a predefined function.
+        * @return If the function was successfully added.
+        */
+      private bool add (MathFunction new_function)
+      {
+          MathFunction? existing_function = get (new_function.name);
+
+          if (existing_function != null && !existing_function.is_custom_function ())
+              return false;
+
+          functions[new_function.name] = new_function;
+          if (existing_function != null)
+              function_edited (new_function);
+          else
+              function_added (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 ();
+              function_deleted (function);
+          }
+      }
+
+      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)
+      {
+          if (array.length == 0)
+              return array;
+          bool swapped = true;
+          int j = (array[array.length - 1] == null ? 1 : 0);
+          MathFunction tmp;
+
+          while (swapped)
+          {
+              swapped = false;
+              j++;
+              assert (0 <= j <= array.length);
+              for (int i = 0; i < array.length - j; i++)
+              {
+                  assert (0 <= (i+1) < array.length);
+                  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/gcalc/gcalc-math-function.vala b/gcalc/gcalc-math-function.vala
new file mode 100644
index 00000000..8db74dd0
--- /dev/null
+++ b/gcalc/gcalc-math-function.vala
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2013 Garima Joshi
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace GCalc {
+  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/gcalc/gcalc-number.vala b/gcalc/gcalc-number.vala
new file mode 100644
index 00000000..c20b0118
--- /dev/null
+++ b/gcalc/gcalc-number.vala
@@ -0,0 +1,1372 @@
+/*
+ * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved.
+ * Copyright (C) 2008-2012 Robert Ancell
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+/*  This maths library is based on the MP multi-precision floating-point
+ *  arithmetic package originally written in FORTRAN by Richard Brent,
+ *  Computer Centre, Australian National University in the 1970's.
+ *
+ *  It has been converted from FORTRAN into C using the freely available
+ *  f2c translator, available via netlib on research.att.com.
+ *
+ *  The subsequently converted C code has then been tidied up, mainly to
+ *  remove any dependencies on the libI77 and libF77 support libraries.
+ *
+ *  FOR A GENERAL DESCRIPTION OF THE PHILOSOPHY AND DESIGN OF MP,
+ *  SEE - R. P. BRENT, A FORTRAN MULTIPLE-PRECISION ARITHMETIC
+ *  PACKAGE, ACM TRANS. MATH. SOFTWARE 4 (MARCH 1978), 57-70.
+ *  SOME ADDITIONAL DETAILS ARE GIVEN IN THE SAME ISSUE, 71-81.
+ *  FOR DETAILS OF THE IMPLEMENTATION, CALLING SEQUENCES ETC. SEE
+ *  THE MP USERS GUIDE.
+ */
+
+using MPC;
+
+namespace GCalc {
+
+  private delegate int BitwiseFunc (int v1, int v2);
+
+  public enum AngleUnit
+  {
+      RADIANS,
+      DEGREES,
+      GRADIANS
+  }
+
+  /* Object for a high precision floating point number representation */
+  public class Number : Object
+  {
+      /* real and imaginary part of a Number */
+      internal static MPFR.Precision _mpfr_precision;
+
+      private Complex num = Complex (_mpfr_precision);
+
+      construct {
+        _mpfr_precision = 1000;
+        precision = new Precision.internal_precision (_mpfr_precision);
+      }
+
+      public Precision precision { get; set; }
+
+      /* Stores the error msg if an error occurs during calculation. Otherwise should be null */
+      public static string? error { get; set; default = null; }
+
+      public Number.integer (int64 real, int64 imag = 0)
+      {
+          num.set_signed_integer ((long) real, (long) imag);
+      }
+
+      public Number.unsigned_integer (uint64 real, uint64 imag = 0)
+      {
+          num.set_unsigned_integer ((ulong) real, (ulong) imag);
+      }
+
+      public Number.fraction (int64 numerator, int64 denominator)
+      {
+          if (denominator < 0)
+          {
+              numerator = -numerator;
+              denominator = -denominator;
+          }
+
+          this.integer (numerator);
+          if (denominator != 1)
+          {
+              num.divide_unsigned_integer (num, (long) denominator);
+          }
+      }
+
+      /* Helper constructor. Creates new Number from already existing MPFR.Real. */
+      internal Number.mpreal (MPFR.Real real, MPFR.Real? imag = null)
+      {
+          num.set_mpreal (real, imag);
+      }
+
+      public Number.double (double real, double imag = 0)
+      {
+          num.set_double (real, imag);
+      }
+
+      public Number.complex (Number r, Number i)
+      {
+          num.set_mpreal (r.num.get_real ().val, i.num.get_real ().val);
+      }
+
+      public Number.polar (Number r, Number theta, AngleUnit unit = AngleUnit.RADIANS)
+      {
+          var x = theta.cos (unit);
+          var y = theta.sin (unit);
+          this.complex (x.multiply (r), y.multiply (r));
+      }
+
+      public Number.eulers ()
+      {
+          num.get_real ().val.set_unsigned_integer (1);
+          /* e^1, since mpfr doesn't have a function to return e */
+          num.get_real ().val.exp (num.get_real ().val);
+          num.get_imag ().val.set_zero ();
+      }
+
+      public Number.i ()
+      {
+          num.set_signed_integer (0, 1);
+      }
+
+      public Number.pi ()
+      {
+          num.get_real ().val.const_pi ();
+          num.get_imag ().val.set_zero ();
+      }
+
+      /* Sets z to be a uniform random number in the range [0, 1] */
+      public Number.random ()
+      {
+          this.double (Random.next_double ());
+      }
+
+      public int64 to_integer ()
+      {
+          return num.get_real ().val.get_signed_integer ();
+      }
+
+      public uint64 to_unsigned_integer ()
+      {
+          return num.get_real ().val.get_unsigned_integer ();
+      }
+
+      public float to_float ()
+      {
+          return num.get_real ().val.get_float (MPFR.Round.NEAREST);
+      }
+
+      public double to_double ()
+      {
+          return num.get_real ().val.get_double (MPFR.Round.NEAREST);
+      }
+
+      /* Return true if the value is x == 0 */
+      public bool is_zero ()
+      {
+          return num.is_zero ();
+      }
+
+      /* Return true if x < 0 */
+      public bool is_negative ()
+      {
+          return num.get_real ().val.sgn () < 0;
+      }
+
+      /* Return true if x is integer */
+      public bool is_integer ()
+      {
+          if (is_complex ())
+              return false;
+
+          return num.get_real ().val.is_integer () != 0;
+      }
+
+      /* Return true if x is a positive integer */
+      public bool is_positive_integer ()
+      {
+          if (is_complex ())
+              return false;
+          else
+              return num.get_real ().val.sgn () >= 0 && is_integer ();
+      }
+
+      /* Return true if x is a natural number (an integer ≥ 0) */
+      public bool is_natural ()
+      {
+          if (is_complex ())
+              return false;
+          else
+              return num.get_real ().val.sgn () > 0 && is_integer ();
+      }
+
+      /* Return true if x has an imaginary component */
+      public bool is_complex ()
+      {
+          return !num.get_imag ().val.is_zero ();
+      }
+
+      /* Return error if overflow or underflow */
+      public static void check_flags ()
+      {
+          if (MPFR.mpfr_is_underflow () != 0)
+          {
+              /* Translators: Error displayed when underflow error occured */
+              error = _("Underflow error");
+          }
+          else if (MPFR.mpfr_is_overflow () != 0)
+          {
+              /* Translators: Error displayed when overflow error occured */
+              error = _("Overflow error");
+          }
+      }
+
+      /* Return true if x == y */
+      public bool equals (Number y)
+      {
+          return num.is_equal (y.num);
+      }
+
+      /* Returns:
+       *  0 if x == y
+       * <0 if x < y
+       * >0 if x > y
+       */
+      public int compare (Number y)
+      {
+          return num.get_real ().val.cmp (y.num.get_real ().val);
+      }
+
+      /* Sets z = sgn (x) */
+      public Number sgn ()
+      {
+          var z = new Number.integer (num.get_real ().val.sgn ());
+          return z;
+      }
+
+      /* Sets z = −x */
+      public Number invert_sign ()
+      {
+          var z = new Number ();
+          z.num.neg (num);
+          return z;
+      }
+
+      /* Sets z = |x| */
+      public Number abs ()
+      {
+        var z = new Number ();
+        z.num.get_imag ().val.set_zero ();
+        MPC.abs (z.num.get_real ().val, num);
+        return z;
+      }
+
+      /* Sets z = Arg (x) */
+      public Number arg (AngleUnit unit = AngleUnit.RADIANS)
+      {
+          if (is_zero ())
+          {
+              /* Translators: Error display when attempting to take argument of zero */
+              error = _("Argument not defined for zero");
+              return new Number.integer (0);
+          }
+          var z = new Number ();
+          z.num.get_imag ().val.set_zero ();
+          MPC.arg (z.num.get_real ().val, num);
+          mpc_from_radians (z.num, z.num, unit);
+          // MPC returns -π for the argument of negative real numbers if
+          // their imaginary part is -0 (which it is in the numbers
+          // created by test-equation), we want +π for all real negative
+          // numbers
+          if (!is_complex () && is_negative ())
+              z.num.get_real ().val.abs (z.num.get_real ().val);
+
+          return z;
+      }
+
+      /* Sets z = ‾̅x */
+      public Number conjugate ()
+      {
+          var z = new Number ();
+          z.num.conj (num);
+          return z;
+      }
+
+      /* Sets z = Re (x) */
+      public Number real_component ()
+      {
+          var z = new Number ();
+          z.num.set_mpreal (num.get_real ().val);
+          return z;
+      }
+
+      /* Sets z = Im (x) */
+      public Number imaginary_component ()
+      {
+          /* Copy imaginary component to real component */
+          var z = new Number ();
+          z.num.set_mpreal (num.get_imag ().val);
+          return z;
+      }
+
+      public Number integer_component ()
+      {
+          var z = new Number ();
+          z.num.get_imag ().val.set_zero ();
+          z.num.get_real ().val.trunc (num.get_real ().val);
+          return z;
+      }
+
+      /* Sets z = x mod 1 */
+      public Number fractional_component ()
+      {
+          var z = new Number ();
+          z.num.get_imag ().val.set_zero ();
+          z.num.get_real ().val.frac (num.get_real ().val);
+          return z;
+      }
+
+      /* Sets z = {x} */
+      public Number fractional_part ()
+      {
+          return subtract (floor ());
+      }
+
+      /* Sets z = ⌊x⌋ */
+      public Number floor ()
+      {
+          var z = new Number ();
+          z.num.get_imag ().val.set_zero ();
+          z.num.get_real ().val.floor (num.get_real ().val);
+          return z;
+      }
+
+      /* Sets z = ⌈x⌉ */
+      public Number ceiling ()
+      {
+          var z = new Number ();
+          z.num.get_imag ().val.set_zero ();
+          z.num.get_real ().val.ceil (num.get_real ().val);
+          return z;
+      }
+
+      /* Sets z = [x] */
+      public Number round ()
+      {
+          var z = new Number ();
+          z.num.get_imag ().val.set_zero ();
+          z.num.get_real ().val.round (num.get_real ().val);
+          return z;
+      }
+
+      /* Sets z = 1 ÷ x */
+      public Number reciprocal ()
+      {
+          var z = new Number ();
+          z.num.set_signed_integer (1);
+          z.num.mpreal_divide (z.num.get_real ().val, num);
+          return z;
+      }
+
+      /* Sets z = e^x */
+      public Number epowy ()
+      {
+          var z = new Number ();
+          z.num.exp (num);
+          return z;
+      }
+
+      /* Sets z = x^y */
+      public Number xpowy (Number y)
+      {
+          /* 0^-n invalid */
+          if (is_zero () && y.is_negative ())
+          {
+              /* Translators: Error displayed when attempted to raise 0 to a negative re_exponent */
+              error = _("The power of zero is undefined for a negative exponent");
+              return new Number.integer (0);
+          }
+
+          /* 0^0 is indeterminate */
+          if (is_zero () && y.is_zero ())
+          {
+              /* Translators: Error displayed when attempted to raise 0 to power of zero */
+              error = _("Zero raised to zero is undefined");
+              return new Number.integer (0);
+          }
+          if (!is_complex () && !y.is_complex () && !y.is_integer ())
+          {
+              var reciprocal = y.reciprocal ();
+              if (reciprocal.is_integer ())
+                  return root (reciprocal.to_integer ());
+          }
+
+          var z = new Number ();
+          z.num.power (num, y.num);
+          return z;
+      }
+
+      /* Sets z = x^y */
+      public Number xpowy_integer (int64 n)
+      {
+          /* 0^-n invalid */
+          if (is_zero () && n < 0)
+          {
+              /* Translators: Error displayed when attempted to raise 0 to a negative re_exponent */
+              error = _("The power of zero is undefined for a negative exponent");
+              return new Number.integer (0);
+          }
+
+          /* 0^0 is indeterminate */
+          if (is_zero () && n == 0)
+          {
+              /* Translators: Error displayed when attempted to raise 0 to power of zero */
+              error = _("Zero raised to zero is undefined");
+              return new Number.integer (0);
+          }
+          var z = new Number ();
+          z.num.power_integer (num, (long) n);
+          return z;
+      }
+
+      /* Sets z = n√x */
+      public Number root (int64 n)
+      {
+          uint64 p;
+          var z = new Number ();
+          if (n < 0)
+          {
+              z.num.unsigned_integer_divide (1, num);
+              if (n == int64.MIN)
+                  p = (uint64) int64.MAX + 1;
+              else
+                  p = -n;
+          } else if (n > 0) {
+              z.num.@set (num);
+              p = n;
+          } else {
+              error = _("The zeroth root of a number is undefined");
+              return new Number.integer (0);
+          }
+
+          if (!is_complex () && (!is_negative () || (p & 1) == 1))
+          {
+              z.num.get_real ().val.root (z.num.get_real ().val, (ulong) p);
+              z.num.get_imag().val.set_zero();
+          } else {
+              var tmp = MPFR.Real (_mpfr_precision);
+              tmp.set_unsigned_integer ((ulong) p);
+              tmp.unsigned_integer_divide (1, tmp);
+              z.num.power_mpreal (z.num, tmp);
+          }
+          return z;
+      }
+
+      /* Sets z = √x */
+      public Number sqrt ()
+      {
+          return root(2);
+      }
+
+      /* Sets z = ln x */
+      public Number ln ()
+      {
+          /* ln (0) undefined */
+          if (is_zero ())
+          {
+              /* Translators: Error displayed when attempting to take logarithm of zero */
+              error = _("Logarithm of zero is undefined");
+              return new Number.integer (0);
+          }
+
+          /* ln (-x) complex */
+          /* FIXME: Make complex numbers optional */
+          /*if (is_negative ())
+          {
+              // Translators: Error displayed attempted to take logarithm of negative value
+              mperr (_("Logarithm of negative values is undefined"));
+              return new Number.integer (0);
+          }*/
+
+          var z = new Number ();
+          z.num.log (num);
+          // MPC returns -π for the imaginary part of the log of
+          // negative real numbers if their imaginary part is -0 (which
+          // it is in the numbers created by test-equation), we want +π
+          if (!is_complex () && is_negative ())
+              z.num.get_imag ().val.abs (z.num.get_imag ().val);
+
+          return z;
+      }
+
+      /* Sets z = log_n x */
+      public Number logarithm (int64 n)
+      {
+          /* log (0) undefined */
+          if (is_zero ())
+          {
+              /* Translators: Error displayed when attempting to take logarithm of zero */
+              error = _("Logarithm of zero is undefined");
+              return new Number.integer (0);
+          }
+
+          /* logn (x) = ln (x) / ln (n) */
+          var t1 = new Number.integer (n);
+          return ln ().divide (t1.ln ());
+      }
+
+      /* Sets z = x! */
+      public Number factorial ()
+      {
+          /* 0! == 1 */
+          if (is_zero ())
+              return new Number.integer (1);
+          if (!is_natural ())
+          {
+
+               /* Factorial Not defined for Complex or for negative numbers */
+              if (is_negative () || is_complex ())
+              {
+                  /* Translators: Error displayed when attempted take the factorial of a negative or complex 
number */
+                  error = _("Factorial is only defined for non-negative real numbers");
+                  return new Number.integer (0);
+              }
+
+              var tmp = add (new Number.integer (1));
+              var tmp2 = MPFR.Real (_mpfr_precision);
+
+              /* Factorial(x) = Gamma(x+1) - This is the formula used to calculate Factorial.*/
+              tmp2.gamma (tmp.num.get_real ().val);
+
+              return new Number.mpreal (tmp2);
+          }
+
+          /* Convert to integer - if couldn't be converted then the factorial would be too big anyway */
+          var value = to_integer ();
+          var z = this;
+          for (var i = 2; i < value; i++)
+              z = z.multiply_integer (i);
+
+          return z;
+      }
+
+      /* Sets z = x + y */
+      public Number add (Number y)
+      {
+          var z = new Number ();
+          z.num.add (num, y.num);
+          return z;
+      }
+
+      /* Sets z = x − y */
+      public Number subtract (Number y)
+      {
+          var z = new Number ();
+          z.num.subtract (num, y.num);
+          return z;
+      }
+
+      /* Sets z = x × y */
+      public Number multiply (Number y)
+      {
+          var z = new Number ();
+          z.num.multiply (num, y.num);
+          return z;
+      }
+
+      /* Sets z = x × y */
+      public Number multiply_integer (int64 y)
+      {
+          var z = new Number ();
+          z.num.multiply_signed_integer (num, (long) y);
+          return z;
+      }
+
+      /* Sets z = x ÷ y */
+      public Number divide (Number y)
+      {
+          if (y.is_zero ())
+          {
+              /* Translators: Error displayed attempted to divide by zero */
+              error = _("Division by zero is undefined");
+              return new Number.integer (0);
+          }
+
+          var z = new Number ();
+          z.num.divide (num, y.num);
+          return z;
+      }
+
+      /* Sets z = x ÷ y */
+      public Number divide_integer (int64 y)
+      {
+          return divide (new Number.integer (y));
+      }
+
+      /* Sets z = x mod y */
+      public Number modulus_divide (Number y)
+      {
+          if (!is_integer () || !y.is_integer ())
+          {
+              /* Translators: Error displayed when attemping to do a modulus division on non-integer numbers 
*/
+              error = _("Modulus division is only defined for integers");
+              return new Number.integer (0);
+          }
+
+          var t1 = divide (y).floor ();
+          var t2 = t1.multiply (y);
+          var z = subtract (t2);
+
+          t1 = new Number.integer (0);
+          if ((y.compare (t1) < 0 && z.compare (t1) > 0) || (y.compare (t1) > 0 && z.compare (t1) < 0))
+              z = z.add (y);
+
+          return z;
+      }
+
+      /* Sets z = x ^ y mod p */
+      public Number modular_exponentiation (Number exp, Number mod)
+      {
+          var base_value = copy ();
+          if (exp.is_negative ())
+              base_value = base_value.reciprocal ();
+          var exp_value = exp.abs ();
+          var ans = new Number.integer (1);
+          var two = new Number.integer (2);
+          while (!exp_value.is_zero ())
+          {
+              bool is_even = exp_value.modulus_divide (two).is_zero ();
+              if (!is_even)
+              {
+                  ans = ans.multiply (base_value);
+                  ans = ans.modulus_divide (mod);
+              }
+              base_value = base_value.multiply (base_value);
+              base_value = base_value.modulus_divide (mod);
+              exp_value = exp_value.divide_integer (2).floor ();
+          }
+          return ans.modulus_divide (mod);
+      }
+
+      /* Sets z = sin x */
+      public Number sin (AngleUnit unit = AngleUnit.RADIANS)
+      {
+          var z = new Number ();
+          if (is_complex ())
+            z.num.@set (num);
+          else
+            mpc_to_radians (z.num, num, unit);
+          z.num.sin (z.num);
+          return z;
+      }
+
+      /* Sets z = cos x */
+      public Number cos (AngleUnit unit = AngleUnit.RADIANS)
+      {
+          var z = new Number ();
+          if (is_complex ())
+            z.num.@set (num);
+          else
+            mpc_to_radians (z.num, num, unit);
+          z.num.cos (z.num);
+          return z;
+      }
+
+      /* Sets z = tan x */
+      public Number tan (AngleUnit unit = AngleUnit.RADIANS)
+      {
+          /* Check for undefined values */
+          var x_radians = to_radians (unit);
+          var check = x_radians.subtract (new Number.pi ().divide_integer (2)).divide (new Number.pi ());
+
+          if (check.is_integer ())
+          {
+              /* Translators: Error displayed when tangent value is undefined */
+              error = _("Tangent is undefined for angles that are multiples of π (180°) from π∕2 (90°)");
+              return new Number.integer (0);
+          }
+
+          var z = new Number ();
+          if (is_complex ())
+            z.num.@set (num);
+          else
+            mpc_to_radians (z.num, num, unit);
+          z.num.tan (z.num);
+          return z;
+      }
+
+      /* Sets z = sin⁻¹ x */
+      public Number asin (AngleUnit unit = AngleUnit.RADIANS)
+      {
+          if (compare (new Number.integer (1)) > 0 || compare (new Number.integer (-1)) < 0)
+          {
+              /* Translators: Error displayed when inverse sine value is undefined */
+              error = _("Inverse sine is undefined for values outside [-1, 1]");
+              return new Number.integer (0);
+          }
+
+          var z = new Number ();
+          z.num.asin (num);
+          if (!z.is_complex ())
+            mpc_from_radians (z.num, z.num, unit);
+          return z;
+      }
+
+      /* Sets z = cos⁻¹ x */
+      public Number acos (AngleUnit unit = AngleUnit.RADIANS)
+      {
+          if (compare (new Number.integer (1)) > 0 || compare (new Number.integer (-1)) < 0)
+          {
+              /* Translators: Error displayed when inverse cosine value is undefined */
+              error = _("Inverse cosine is undefined for values outside [-1, 1]");
+              return new Number.integer (0);
+          }
+
+          var z = new Number ();
+          z.num.acos (num);
+          if (!z.is_complex ())
+            mpc_from_radians (z.num, z.num, unit);
+          return z;
+      }
+
+      /* Sets z = tan⁻¹ x */
+      public Number atan (AngleUnit unit = AngleUnit.RADIANS)
+      {
+          var z = new Number ();
+          z.num.atan (num);
+          if (!z.is_complex ())
+            mpc_from_radians (z.num, z.num, unit);
+          return z;
+      }
+
+      /* Sets z = sinh x */
+      public Number sinh ()
+      {
+          var z = new Number ();
+          z.num.sinh (num);
+          return z;
+      }
+
+      /* Sets z = cosh x */
+      public Number cosh ()
+      {
+          var z = new Number ();
+          z.num.cosh (num);
+          return z;
+      }
+
+      /* Sets z = tanh x */
+      public Number tanh ()
+      {
+          var z = new Number ();
+          z.num.tanh (num);
+          return z;
+      }
+
+      /* Sets z = sinh⁻¹ x */
+      public Number asinh ()
+      {
+          var z = new Number ();
+          z.num.asinh (num);
+          return z;
+      }
+
+      /* Sets z = cosh⁻¹ x */
+      public Number acosh ()
+      {
+          /* Check x >= 1 */
+          var t = new Number.integer (1);
+          if (compare (t) < 0)
+          {
+              /* Translators: Error displayed when inverse hyperbolic cosine value is undefined */
+              error = _("Inverse hyperbolic cosine is undefined for values less than one");
+              return new Number.integer (0);
+          }
+
+          var z = new Number ();
+          z.num.acosh (num);
+          return z;
+      }
+
+      /* Sets z = tanh⁻¹ x */
+      public Number atanh ()
+      {
+          /* Check -1 <= x <= 1 */
+          if (compare (new Number.integer (1)) >= 0 || compare (new Number.integer (-1)) <= 0)
+          {
+              /* Translators: Error displayed when inverse hyperbolic tangent value is undefined */
+              error = _("Inverse hyperbolic tangent is undefined for values outside [-1, 1]");
+              return new Number.integer (0);
+          }
+
+          var z = new Number ();
+          z.num.atanh (num);
+          return z;
+      }
+
+      /* Sets z = boolean AND for each bit in x and z */
+      public Number and (Number y)
+      {
+          if (!
+          is_positive_integer () || !y.is_positive_integer ())
+          {
+              /* Translators: Error displayed when boolean AND attempted on non-integer values */
+              error = _("Boolean AND is only defined for positive integers");
+          }
+
+          return bitwise (y, (v1, v2) => { return v1 & v2; }, 0);
+      }
+
+      /* Sets z = boolean OR for each bit in x and z */
+      public Number or (Number y)
+      {
+          if (!is_positive_integer () || !y.is_positive_integer ())
+          {
+              /* Translators: Error displayed when boolean OR attempted on non-integer values */
+              error = _("Boolean OR is only defined for positive integers");
+          }
+
+          return bitwise (y, (v1, v2) => { return v1 | v2; }, 0);
+      }
+
+      /* Sets z = boolean XOR for each bit in x and z */
+      public Number xor (Number y)
+      {
+          if (!is_positive_integer () || !y.is_positive_integer ())
+          {
+              /* Translators: Error displayed when boolean XOR attempted on non-integer values */
+              error = _("Boolean XOR is only defined for positive integers");
+          }
+
+          return bitwise (y, (v1, v2) => { return v1 ^ v2; }, 0);
+      }
+
+      /* Sets z = boolean NOT for each bit in x and z for word of length 'wordlen' */
+      public Number not (int wordlen)
+      {
+          if (!is_positive_integer ())
+          {
+              /* Translators: Error displayed when boolean XOR attempted on non-integer values */
+              error = _("Boolean NOT is only defined for positive integers");
+          }
+
+          return bitwise (new Number.integer (0), (v1, v2) => { return v1 ^ 0xF; }, wordlen);
+      }
+
+      /* Sets z = x masked to 'wordlen' bits */
+      public Number mask (Number x, int wordlen)
+      {
+          /* Convert to a hexadecimal string and use last characters */
+          var text = x.to_hex_string ();
+          var len = text.length;
+          var offset = wordlen / 4;
+          offset = len > offset ? (int) len - offset: 0;
+          return mp_set_from_string (text.substring (offset), 16);
+      }
+
+      /* Sets z = x shifted by 'count' bits.  Positive shift increases the value, negative decreases */
+      public Number shift (int count)
+      {
+          if (!is_integer ())
+          {
+              /* Translators: Error displayed when bit shift attempted on non-integer values */
+              error = _("Shift is only possible on integer values");
+              return new Number.integer (0);
+          }
+
+          if (count >= 0)
+          {
+              var multiplier = 1;
+              for (var i = 0; i < count; i++)
+                  multiplier *= 2;
+              return multiply_integer (multiplier);
+          }
+          else
+          {
+              var multiplier = 1;
+              for (var i = 0; i < -count; i++)
+                  multiplier *= 2;
+              return divide_integer (multiplier).floor ();
+          }
+      }
+
+      /* Sets z to be the ones complement of x for word of length 'wordlen' */
+      public Number ones_complement (int wordlen)
+      {
+          return bitwise (new Number.integer (0), (v1, v2) => { return v1 ^ v2; }, wordlen).not (wordlen);
+      }
+
+      /* Sets z to be the twos complement of x for word of length 'wordlen' */
+      public Number twos_complement (int wordlen)
+      {
+          return ones_complement (wordlen).add (new Number.integer (1));
+      }
+
+      /* Returns a list of all prime factors in x as Numbers */
+      public List<Number?> factorize ()
+      {
+          var factors = new List<Number?> ();
+
+          var value = abs ();
+
+          if (value.is_zero ())
+          {
+              factors.append (value);
+              return factors;
+          }
+
+          if (value.equals (new Number.integer (1)))
+          {
+              factors.append (this);
+              return factors;
+          }
+
+          // if value < 2^64-1, call for factorize_uint64 function which deals in integers
+
+          uint64 num = 1;
+          num = num << 63;
+          num += (num - 1);
+          var int_max = new Number.unsigned_integer (num);
+
+          if (value.compare (int_max) <= 0)
+          {
+              var factors_int64 = factorize_uint64 (value.to_unsigned_integer ());
+              if (is_negative ())
+                  factors_int64.data = factors_int64.data.invert_sign ();
+              return factors_int64;
+          }
+
+          var divisor = new Number.integer (2);
+          while (true)
+          {
+              var tmp = value.divide (divisor);
+              if (tmp.is_integer ())
+              {
+                  value = tmp;
+                  factors.append (divisor);
+              }
+              else
+                  break;
+          }
+
+          divisor = new Number.integer (3);
+          var root = value.sqrt ();
+          while (divisor.compare (root) <= 0)
+          {
+              var tmp = value.divide (divisor);
+              if (tmp.is_integer ())
+              {
+                  value = tmp;
+                  root = value.sqrt ();
+                  factors.append (divisor);
+              }
+              else
+              {
+                  tmp = divisor.add (new Number.integer (2));
+                  divisor = tmp;
+              }
+          }
+
+          if (value.compare (new Number.integer (1)) > 0)
+              factors.append (value);
+
+          if (is_negative ())
+              factors.data = factors.data.invert_sign ();
+
+          return factors;
+      }
+
+      public List<Number?> factorize_uint64 (uint64 n)
+      {
+          var factors = new List<Number?> ();
+          while (n % 2 == 0)
+          {
+              n /= 2;
+              factors.append (new Number.unsigned_integer (2));
+          }
+
+          for (uint64 divisor = 3; divisor <= n / divisor; divisor += 2)
+          {
+              while (n % divisor == 0)
+              {
+                  n /= divisor;
+                  factors.append (new Number.unsigned_integer (divisor));
+              }
+          }
+
+          if (n > 1)
+              factors.append (new Number.unsigned_integer (n));
+          return factors;
+      }
+
+      private Number copy ()
+      {
+          var z = new Number ();
+          z.num.@set (num);
+          return z;
+      }
+
+      private static void mpc_from_radians (Complex res, Complex op, AngleUnit unit)
+      {
+          int i;
+
+          switch (unit)
+          {
+              default:
+              case AngleUnit.RADIANS:
+                  if (res != op)
+                      res.@set (op);
+                  return;
+
+              case AngleUnit.DEGREES:
+                  i = 180;
+                  break;
+
+              case AngleUnit.GRADIANS:
+                  i=200;
+                  break;
+
+          }
+          var scale = MPFR.Real (_mpfr_precision);
+          scale.const_pi ();
+          scale.signed_integer_divide (i, scale);
+          res.multiply_mpreal (op, scale);
+      }
+
+      private static void mpc_to_radians (Complex res, Complex op, AngleUnit unit)
+      {
+          int i;
+
+          switch (unit)
+          {
+              default:
+              case AngleUnit.RADIANS:
+                  if (res != op)
+                      res.@set (op);
+                  return;
+
+              case AngleUnit.DEGREES:
+                  i = 180;
+                  break;
+
+              case AngleUnit.GRADIANS:
+                  i=200;
+                  break;
+          }
+          var scale = MPFR.Real (_mpfr_precision);
+          scale.const_pi ();
+          scale.divide_signed_integer (scale, i);
+          res.multiply_mpreal (op, scale);
+      }
+
+      /* Convert x to radians */
+      private Number to_radians (AngleUnit unit)
+      {
+          var z = new Number ();
+          mpc_to_radians (z.num, num, unit);
+          return z;
+      }
+
+      private Number bitwise (Number y, BitwiseFunc bitwise_operator, int wordlen)
+      {
+          var text1 = to_hex_string ();
+          var text2 = y.to_hex_string ();
+          var offset1 = text1.length - 1;
+          var offset2 = text2.length - 1;
+          var offset_out = wordlen / 4 - 1;
+          if (offset_out <= 0)
+              offset_out = offset1 > offset2 ? offset1 : offset2;
+          if (offset_out > 0 && (offset_out < offset1 || offset_out < offset2))
+          {
+              error = ("Overflow. Try a bigger word size");
+              return new Number.integer (0);
+          }
+
+          var text_out = new char[offset_out + 2];
+
+          /* Perform bitwise operator on each character from right to left */
+          for (text_out[offset_out+1] = '\0'; offset_out >= 0; offset_out--)
+          {
+              int v1 = 0, v2 = 0;
+              const char digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 
'E', 'F' };
+
+              if (offset1 >= 0)
+              {
+                  v1 = hex_to_int (text1[offset1]);
+                  offset1--;
+              }
+              if (offset2 >= 0)
+              {
+                  v2 = hex_to_int (text2[offset2]);
+                  offset2--;
+              }
+              text_out[offset_out] = digits[bitwise_operator (v1, v2)];
+          }
+
+          return mp_set_from_string ((string) text_out, 16);
+      }
+
+      private int hex_to_int (char digit)
+      {
+          if (digit >= '0' && digit <= '9')
+              return digit - '0';
+          if (digit >= 'A' && digit <= 'F')
+              return digit - 'A' + 10;
+          if (digit >= 'a' && digit <= 'f')
+              return digit - 'a' + 10;
+          return 0;
+      }
+
+      private string to_hex_string ()
+      {
+          var serializer = new Serializer (DisplayFormat.FIXED, 16, 0);
+          return serializer.to_string (this);
+      }
+      public class Precision : Object {
+        internal MPFR.Precision _precision;
+
+        construct {
+          _precision = 1000;
+        }
+
+        public long precision { get { return (long) _precision; } }
+
+        internal Precision.internal_precision (MPFR.Precision precision) {
+          _precision = precision;
+        }
+        public Precision (long precision) {
+          _precision = (MPFR.Precision) precision;
+        }
+      }
+      public class Real : Object {
+        internal MPFR.Real _value;
+        public Real (Precision precision) {
+          _value = MPFR.Real (precision._precision);
+        }
+      }
+  }
+
+  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
+
+  /* Sets z from a string representation in 'text'. */
+  public Number? mp_set_from_string (string str, int default_base = 10)
+  {
+      if (str.index_of_char ('°') >= 0)
+          return set_from_sexagesimal (str);
+
+      /* 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))
+      {
+          var value = -1;
+          for (var i = 0; i < base_digits.length; i++)
+          {
+              if (c == base_digits[i])
+              {
+                  value = i;
+                  break;
+              }
+          }
+          if (value < 0)
+              break;
+
+          end = index;
+          number_base += value * base_multiplier;
+          base_multiplier *= 10;
+      }
+
+      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 = base_prefix;
+      str.get_next_char (ref index, out c);
+      if (c == '+')
+          negate = false;
+      else if (c == '-' || c == '−')
+          negate = true;
+      else
+          str.get_prev_char (ref index, out c);
+
+      /* Convert integer part */
+      var z = new Number.integer (0);
+
+      while (str.get_next_char (ref index, out c))
+      {
+          var i = char_val (c, number_base);
+          if (i > number_base)
+              return null;
+          if (i < 0)
+          {
+              str.get_prev_char (ref index, out c);
+              break;
+          }
+
+          z = z.multiply_integer (number_base).add (new Number.integer (i));
+      }
+
+      /* Look for fraction characters, e.g. ⅚ */
+      const unichar fractions[] = {'½', '⅓', '⅔', '¼', '¾', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', 
'⅞'};
+      const int numerators[]    = { 1,   1,   2,   1,   3,   1,   2,   3,   4,   1,   5,   1,   3,   5,   7};
+      const int denominators[]  = { 2,   3,   3,   4,   4,   5,   5,   5,   5,   6,   6,   8,   8,   8,   8};
+      var has_fraction = false;
+      if (str.get_next_char (ref index, out c))
+      {
+          for (var i = 0; i < fractions.length; i++)
+          {
+              if (c == fractions[i])
+              {
+                  var fraction = new Number.fraction (numerators[i], denominators[i]);
+                  z = z.add (fraction);
+
+                  /* Must end with fraction */
+                  if (!str.get_next_char (ref index, out c))
+                      return z;
+                  else
+                      return null;
+              }
+          }
+
+          /* Check for decimal point */
+          if (c == '.')
+              has_fraction = true;
+          else
+              str.get_prev_char (ref index, out c);
+      }
+
+      /* Convert fractional part */
+      if (has_fraction)
+      {
+          var numerator = new Number.integer (0);
+          var denominator = new Number.integer (1);
+
+          while (str.get_next_char (ref index, out c))
+          {
+              var i = char_val (c, number_base);
+              if (i < 0)
+              {
+                  str.get_prev_char (ref index, out c);
+                  break;
+              }
+
+              denominator = denominator.multiply_integer (number_base);
+              numerator = numerator.multiply_integer (number_base);
+              numerator = numerator.add (new Number.integer (i));
+          }
+
+          numerator = numerator.divide (denominator);
+          z = z.add (numerator);
+      }
+
+      if (index != end)
+          return null;
+
+      if (negate)
+          z = z.invert_sign ();
+
+      return z;
+  }
+
+  private int char_val (unichar c, int number_base)
+  {
+      if (!c.isxdigit ())
+          return -1;
+
+      var value = c.xdigit_value ();
+
+      if (value >= number_base)
+          return -1;
+
+      return value;
+  }
+
+  private Number? set_from_sexagesimal (string str)
+  {
+      var degree_index = str.index_of_char ('°');
+      if (degree_index < 0)
+          return null;
+      var degrees = mp_set_from_string (str.substring (0, degree_index));
+      if (degrees == null)
+          return null;
+      var minute_start = degree_index;
+      unichar c;
+      str.get_next_char (ref minute_start, out c);
+
+      if (str[minute_start] == '\0')
+          return degrees;
+      var minute_index = str.index_of_char ('\'', minute_start);
+      if (minute_index < 0)
+          return null;
+      var minutes = mp_set_from_string (str.substring (minute_start, minute_index - minute_start));
+      if (minutes == null)
+          return null;
+      degrees = degrees.add (minutes.divide_integer (60));
+      var second_start = minute_index;
+      str.get_next_char (ref second_start, out c);
+
+      if (str[second_start] == '\0')
+          return degrees;
+      var second_index = str.index_of_char ('"', second_start);
+      if (second_index < 0)
+          return null;
+      var seconds = mp_set_from_string (str.substring (second_start, second_index - second_start));
+      if (seconds == null)
+          return null;
+      degrees = degrees.add (seconds.divide_integer (3600));
+      str.get_next_char (ref second_index, out c);
+
+      /* Skip over second marker and expect no more characters */
+      if (str[second_index] == '\0')
+          return degrees;
+      else
+          return null;
+  }
+
+  /* Returns true if x is cannot be represented in a binary word of length 'wordlen' */
+  public bool mp_is_overflow (Number x, int wordlen)
+  {
+      var t2 = new Number.integer (2).xpowy_integer (wordlen);
+      return t2.compare (x) > 0;
+  }
+}
diff --git a/gcalc/gcalc-serializer.vala b/gcalc/gcalc-serializer.vala
new file mode 100644
index 00000000..442a3368
--- /dev/null
+++ b/gcalc/gcalc-serializer.vala
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2010 Robin Sonefors
+ * Copyright (C) 2008-2012 Robert Ancell.
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace GCalc {
+  public enum DisplayFormat
+  {
+      AUTOMATIC,
+      FIXED,
+      SCIENTIFIC,
+      ENGINEERING
+  }
+
+  public class Serializer : Object
+  {
+      private int leading_digits;      /* Number of digits to show before radix */
+      private int trailing_digits;     /* Number of digits to show after radix */
+      private DisplayFormat format;    /* Number display mode. */
+      private bool show_tsep;          /* Set if the thousands separator should be shown. */
+      private bool show_zeroes;        /* Set if trailing zeroes should be shown. */
+
+      private int number_base;         /* Numeric base */
+      private uint representation_base;/* Representation base. */
+
+      private unichar radix;           /* Locale specific radix string. */
+      private unichar tsep;            /* Locale specific thousands separator. */
+      private int tsep_count;          /* Number of digits between separator. */
+
+      /* is set when an error (for example precision error while converting) occurs */
+      public string? error { get; set; default = null; }
+
+      public Serializer (DisplayFormat format, int number_base, int trailing_digits)
+      {
+          var radix_string = Posix.nl_langinfo (Posix.NLItem.RADIXCHAR);
+          if (radix_string != null && radix_string != "") {
+              var radix_utf8 = radix_string.locale_to_utf8 (-1, null, null);
+              if (radix_utf8 != null)
+                  radix = radix_utf8.get_char (0);
+              else
+                  radix = '.';
+          }
+          else
+              radix = '.';
+          var tsep_string = Posix.nl_langinfo (Posix.NLItem.THOUSEP);
+          if (tsep_string != null && tsep_string != "") {
+              var tsep_utf8 = tsep_string.locale_to_utf8 (-1, null, null);
+              if (tsep_utf8 != null)
+                  tsep = tsep_utf8.get_char (0);
+              else
+                  tsep = ' ';
+          }
+          else
+              tsep = ' ';
+          tsep_count = 3;
+
+          this.number_base = number_base;
+          this.representation_base = number_base;
+          leading_digits = 12;
+          this.trailing_digits = trailing_digits;
+          show_zeroes = false;
+          show_tsep = false;
+          this.format = format;
+      }
+
+      public string to_string (Number x)
+      {
+          /* For base conversion equation, use FIXED format. */
+          if (representation_base != number_base)
+          {
+              int n_digits = 0;
+              return cast_to_string (x, ref n_digits);
+          }
+          switch (format)
+          {
+          default:
+          case DisplayFormat.AUTOMATIC:
+              int n_digits = 0;
+              var s0 = cast_to_string (x, ref n_digits);
+              /* Decide leading digits based on number_base. Support 64 bits in programming mode. */
+              switch (get_base ())
+              {
+                  /* 64 digits for binary mode. */
+                  case 2:
+                      if (n_digits <= 64)
+                          return s0;
+                      else
+                          return cast_to_exponential_string (x, false, ref n_digits);
+                  /* 22 digis for octal mode. */
+                  case 8:
+                      if (n_digits <= 22)
+                          return s0;
+                      else
+                          return cast_to_exponential_string (x, false, ref n_digits);
+                  /* 16 digits for hexadecimal mode. */
+                  case 16:
+                      if(n_digits <= 16)
+                          return s0;
+                      else
+                          return cast_to_exponential_string (x, false, ref n_digits);
+                  /* Use default leading_digits for base 10 numbers. */
+                  case 10:
+                  default:
+                      if (n_digits <= leading_digits)
+                          return s0;
+                      else
+                          return cast_to_exponential_string (x, false, ref n_digits);
+              }
+          case DisplayFormat.FIXED:
+              int n_digits = 0;
+              return cast_to_string (x, ref n_digits);
+          case DisplayFormat.SCIENTIFIC:
+              if (representation_base == 10)
+              {
+                  int n_digits = 0;
+                  return cast_to_exponential_string (x, false, ref n_digits);
+              }
+              else
+              {
+                  int n_digits = 0;
+                  return cast_to_string (x, ref n_digits);
+              }
+          case DisplayFormat.ENGINEERING:
+              if (representation_base == 10)
+              {
+                  int n_digits = 0;
+                  return cast_to_exponential_string (x, true, ref n_digits);
+              }
+              else
+              {
+                  int n_digits = 0;
+                  return cast_to_string (x, ref n_digits);
+              }
+          }
+      }
+
+      public Number? from_string (string str)
+      {
+          // FIXME: Move mp_set_from_string into here
+          return mp_set_from_string (str, number_base);
+      }
+
+      public void set_base (int number_base)
+      {
+          this.number_base = number_base;
+      }
+
+      public int get_base ()
+      {
+          return number_base;
+      }
+
+      public void set_representation_base (uint representation_base)
+      {
+          this.representation_base = representation_base;
+      }
+
+      public uint get_representation_base ()
+      {
+          return representation_base;
+      }
+
+      public void set_radix (unichar radix)
+      {
+          this.radix = radix;
+      }
+
+      public unichar get_radix ()
+      {
+          return radix;
+      }
+
+      public void set_thousands_separator (unichar separator)
+      {
+          tsep = separator;
+      }
+
+      public unichar get_thousands_separator ()
+      {
+          return tsep;
+      }
+
+      public int get_thousands_separator_count ()
+      {
+          return tsep_count;
+      }
+
+      public void set_thousands_separator_count (int count)
+      {
+          tsep_count = count;
+      }
+
+      public void set_show_thousands_separators (bool visible)
+      {
+          show_tsep = visible;
+      }
+
+      public bool get_show_thousands_separators ()
+      {
+          return show_tsep;
+      }
+
+      public void set_show_trailing_zeroes (bool visible)
+      {
+          show_zeroes = visible;
+      }
+
+      public bool get_show_trailing_zeroes ()
+      {
+          return show_zeroes;
+      }
+
+      public int get_leading_digits ()
+      {
+          return leading_digits;
+      }
+
+      public void set_leading_digits (int leading_digits)
+      {
+          this.leading_digits = leading_digits;
+      }
+
+      public int get_trailing_digits ()
+      {
+          return trailing_digits;
+      }
+
+      public void set_trailing_digits (int trailing_digits)
+      {
+          this.trailing_digits = trailing_digits;
+      }
+
+      public DisplayFormat get_number_format ()
+      {
+          return format;
+      }
+
+      public void set_number_format (DisplayFormat format)
+      {
+          this.format = format;
+      }
+
+      private string cast_to_string (Number x, ref int n_digits)
+      {
+          var string = new StringBuilder.sized (1024);
+
+          var x_real = x.real_component ();
+          cast_to_string_real (x_real, (int) representation_base, false, ref n_digits, string);
+          if (x.is_complex ())
+          {
+              var x_im = x.imaginary_component ();
+
+              var force_sign = true;
+              if (string.str == "0")
+              {
+                  string.assign ("");
+                  force_sign = false;
+              }
+
+              var s = new StringBuilder.sized (1024);
+              int n_complex_digits = 0;
+              cast_to_string_real (x_im, (int) representation_base, force_sign, ref n_complex_digits, s);
+              if (n_complex_digits > n_digits)
+                  n_digits = n_complex_digits;
+              if (s.str == "0" || s.str == "+0" || s.str == "−0")
+              {
+                  /* Ignore */
+              }
+              else if (s.str == "1")
+              {
+                  string.append ("i");
+              }
+              else if (s.str == "+1")
+              {
+                  string.append ("+i");
+              }
+              else if (s.str == "−1")
+              {
+                  string.append ("−i");
+              }
+              else
+              {
+                  if (s.str == "+0")
+                      string.append ("+");
+                  else if (s.str != "0")
+                      string.append (s.str);
+
+                  string.append ("i");
+              }
+          }
+
+          return string.str;
+      }
+
+      private void cast_to_string_real (Number x, int number_base, bool force_sign, ref int n_digits, 
StringBuilder string)
+      {
+          const char digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 
'F'};
+
+          var number = x;
+          if (number.is_negative ())
+              number = number.abs ();
+
+          /* Add rounding factor */
+          var temp = new Number.integer (number_base);
+          temp = temp.xpowy_integer (-(trailing_digits+1));
+          temp = temp.multiply_integer (number_base);
+          temp = temp.divide_integer (2);
+          var rounded_number = number.add (temp);
+
+          /* Write out the integer component least significant digit to most */
+          temp = rounded_number.floor ();
+          var i = 0;
+          do
+          {
+              if (number_base == 10 && show_tsep && i == tsep_count)
+              {
+                  string.prepend_unichar (tsep);
+                  i = 0;
+              }
+              i++;
+
+              var t = temp.divide_integer (number_base);
+              t = t.floor ();
+              var t2 = t.multiply_integer (number_base);
+
+              var t3 = temp.subtract (t2);
+
+              var d = t3.to_integer ();
+
+              if (d < 16 && d >= 0)
+              {
+                  string.prepend_c (digits[d]);
+              }
+              else
+              {
+                  string.prepend_c ('?');
+                  error = _("Overflow: the result couldn’t be calculated");
+                  string.assign ("0");
+                  break;
+              }
+              n_digits++;
+
+              temp = t;
+          } while (!temp.is_zero ());
+
+          var last_non_zero = string.len;
+
+          string.append_unichar (radix);
+
+          /* Write out the fractional component */
+          temp = rounded_number.fractional_component ();
+          for (i = 0; i < trailing_digits; i++)
+          {
+              if (temp.is_zero ())
+                  break;
+
+              temp = temp.multiply_integer (number_base);
+              var digit = temp.floor ();
+              var d = digit.to_integer ();
+
+              string.append_c (digits[d]);
+
+              if (d != 0)
+                  last_non_zero = string.len;
+              temp = temp.subtract (digit);
+          }
+
+          /* Strip trailing zeroes */
+          if (!show_zeroes || trailing_digits == 0)
+              string.truncate (last_non_zero);
+
+          /* Add sign on non-zero values */
+          if (string.str != "0" || force_sign)
+          {
+              if (x.is_negative ())
+                  string.prepend ("−");
+              else if (force_sign)
+                  string.prepend ("+");
+          }
+
+          /* Append base suffix if not in default base */
+          if (number_base != this.number_base)
+          {
+              const string sub_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"};
+              int multiplier = 1;
+              int b = number_base;
+
+              while (number_base / multiplier != 0)
+                  multiplier *= 10;
+              while (multiplier != 1)
+              {
+                  int d;
+                  multiplier /= 10;
+                  d = b / multiplier;
+                  string.append (sub_digits[d]);
+                  b -= d * multiplier;
+              }
+          }
+      }
+
+      private int cast_to_exponential_string_real (Number x, StringBuilder string, bool eng_format, ref int 
n_digits)
+      {
+          if (x.is_negative ())
+              string.append ("−");
+
+          var mantissa = x.abs ();
+
+          var base_ = new Number.integer (number_base);
+          var base3 = base_.xpowy_integer (3);
+          var base10 = base_.xpowy_integer (10);
+          var t = new Number.integer (1);
+          var base10inv = t.divide (base10);
+
+          var exponent = 0;
+          if (!mantissa.is_zero ())
+          {
+              while (!eng_format && mantissa.compare (base10) >= 0)
+              {
+                  exponent += 10;
+                  mantissa = mantissa.multiply (base10inv);
+              }
+
+              while ((!eng_format && mantissa.compare (base_) >= 0) ||
+                      (eng_format && (mantissa.compare (base3) >= 0 || exponent % 3 != 0)))
+              {
+                  exponent += 1;
+                  mantissa = mantissa.divide (base_);
+              }
+
+              while (!eng_format && mantissa.compare (base10inv) < 0)
+              {
+                  exponent -= 10;
+                  mantissa = mantissa.multiply (base10);
+              }
+
+              t = new Number.integer (1);
+              while (mantissa.compare (t) < 0 || (eng_format && exponent % 3 != 0))
+              {
+                  exponent -= 1;
+                  mantissa = mantissa.multiply (base_);
+              }
+          }
+
+          string.append (cast_to_string (mantissa, ref n_digits));
+
+          return exponent;
+      }
+
+      private string cast_to_exponential_string (Number x, bool eng_format, ref int n_digits)
+      {
+          var string = new StringBuilder.sized (1024);
+
+          var x_real = x.real_component ();
+          var exponent = cast_to_exponential_string_real (x_real, string, eng_format, ref n_digits);
+          append_exponent (string, exponent);
+
+          if (x.is_complex ())
+          {
+              var x_im = x.imaginary_component ();
+
+              if (string.str == "0")
+                  string.assign ("");
+
+              var s = new StringBuilder.sized (1024);
+              int n_complex_digits = 0;
+              exponent = cast_to_exponential_string_real (x_im, s, eng_format, ref n_complex_digits);
+              if (n_complex_digits > n_digits)
+                  n_digits = n_complex_digits;
+              if (s.str == "0" || s.str == "+0" || s.str == "−0")
+              {
+                  /* Ignore */
+              }
+              else if (s.str == "1")
+              {
+                  string.append ("i");
+              }
+              else if (s.str == "+1")
+              {
+                  string.append ("+i");
+              }
+              else if (s.str == "−1")
+              {
+                  string.append ("−i");
+              }
+              else
+              {
+                  if (s.str == "+0")
+                      string.append ("+");
+                  else if (s.str != "0")
+                      string.append (s.str);
+
+                  string.append ("i");
+              }
+              append_exponent (string, exponent);
+          }
+
+          return string.str;
+      }
+
+      private void append_exponent (StringBuilder string, int exponent)
+      {
+          const unichar super_digits[] = {'⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'};
+
+          if (exponent == 0)
+              return;
+
+          string.append ("×10"); // FIXME: Use the current base
+          if (exponent < 0)
+          {
+              exponent = -exponent;
+              string.append_unichar ('⁻');
+          }
+
+          var super_value = "%d".printf (exponent);
+          for (var i = 0; i < super_value.length; i++)
+              string.append_unichar (super_digits[super_value[i] - '0']);
+      }
+  }
+}
diff --git a/gcalc/gcalc-unit-manager.vala b/gcalc/gcalc-unit-manager.vala
new file mode 100644
index 00000000..7b8e4bb8
--- /dev/null
+++ b/gcalc/gcalc-unit-manager.vala
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2008-2012 Robert Ancell.
+ * Copyright (C) 2018 Daniel Espinosa <esodan gmail com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+namespace GCalc {
+  private UnitManager? default_unit_manager = null;
+
+  public class UnitManager : Object
+  {
+      private List<UnitCategory> categories;
+
+      public UnitManager ()
+      {
+          categories = new List<UnitCategory> ();
+      }
+
+      public static UnitManager get_default ()
+      {
+          if (default_unit_manager != null)
+              return default_unit_manager;
+
+          default_unit_manager = new UnitManager ();
+
+          var angle_category = default_unit_manager.add_category ("angle", _("Angle"));
+          var length_category = default_unit_manager.add_category ("length", _("Length"));
+          var area_category = default_unit_manager.add_category ("area", _("Area"));
+          var volume_category = default_unit_manager.add_category ("volume", _("Volume"));
+          var weight_category = default_unit_manager.add_category ("weight", _("Mass"));
+          var duration_category = default_unit_manager.add_category ("duration", _("Duration"));
+          var temperature_category = default_unit_manager.add_category ("temperature", _("Temperature"));
+          var digitalstorage_category = default_unit_manager.add_category ("digitalstorage", _("Digital 
Storage"));
+
+          /* FIXME: Approximations of 1/(units in a circle), therefore, 360 deg != 400 grads */
+          angle_category.add_unit (new Unit ("degree", _("Degrees"), dpgettext2 (null, "unit-format", "%s 
degrees"), "π*x/180", "180x/π", dpgettext2 (null, "unit-symbols", "degree,degrees,deg")));
+          angle_category.add_unit (new Unit ("radian", _("Radians"), dpgettext2 (null, "unit-format", "%s 
radians"), "x", "x", dpgettext2 (null, "unit-symbols", "radian,radians,rad")));
+          angle_category.add_unit (new Unit ("gradian", _("Gradians"), dpgettext2 (null, "unit-format", "%s 
gradians"), "π*x/200", "200x/π", dpgettext2 (null, "unit-symbols", "gradian,gradians,grad")));
+          length_category.add_unit (new Unit ("parsec", _("Parsecs"), dpgettext2 (null, "unit-format", "%s 
pc"), "30857000000000000x", "x/30857000000000000", dpgettext2 (null, "unit-symbols", "parsec,parsecs,pc")));
+          length_category.add_unit (new Unit ("lightyear", _("Light Years"), dpgettext2 (null, 
"unit-format", "%s ly"), "9460730472580800x", "x/9460730472580800", dpgettext2 (null, "unit-symbols", 
"lightyear,lightyears,ly")));
+          length_category.add_unit (new Unit ("astronomical-unit", _("Astronomical Units"), dpgettext2 
(null, "unit-format", "%s au"), "149597870691x", "x/149597870691", dpgettext2 (null, "unit-symbols", "au")));
+          length_category.add_unit (new Unit ("nautical-mile", _("Nautical Miles"), dpgettext2 (null, 
"unit-format", "%s nmi"), "1852x", "x/1852", dpgettext2 (null, "unit-symbols", "nmi")));
+          length_category.add_unit (new Unit ("mile", _("Miles"), dpgettext2 (null, "unit-format", "%s mi"), 
"1609.344x", "x/1609.344", dpgettext2 (null, "unit-symbols", "mile,miles,mi")));
+          length_category.add_unit (new Unit ("kilometer", _("Kilometers"), dpgettext2 (null, "unit-format", 
"%s km"), "1000x", "x/1000", dpgettext2 (null, "unit-symbols", "kilometer,kilometers,km,kms")));
+          length_category.add_unit (new Unit ("cable", _("Cables"), dpgettext2 (null, "unit-format", "%s 
cb"), "219.456x", "x/219.456", dpgettext2 (null, "unit-symbols", "cable,cables,cb")));
+          length_category.add_unit (new Unit ("fathom", _("Fathoms"), dpgettext2 (null, "unit-format", "%s 
ftm"), "1.8288x", "x/1.8288", dpgettext2 (null, "unit-symbols", "fathom,fathoms,ftm")));
+          length_category.add_unit (new Unit ("meter", _("Meters"), dpgettext2 (null, "unit-format", "%s 
m"), "x", "x", dpgettext2 (null, "unit-symbols", "meter,meters,m")));
+          length_category.add_unit (new Unit ("yard", _("Yards"), dpgettext2 (null, "unit-format", "%s yd"), 
"0.9144x", "x/0.9144", dpgettext2 (null, "unit-symbols", "yard,yards,yd")));
+          length_category.add_unit (new Unit ("foot", _("Feet"), dpgettext2 (null, "unit-format", "%s ft"), 
"0.3048x", "x/0.3048", dpgettext2 (null, "unit-symbols", "foot,feet,ft")));
+          length_category.add_unit (new Unit ("inch", _("Inches"), dpgettext2 (null, "unit-format", "%s 
in"), "0.0254x", "x/0.0254", dpgettext2 (null, "unit-symbols", "inch,inches,in")));
+          length_category.add_unit (new Unit ("centimeter", _("Centimeters"), dpgettext2 (null, 
"unit-format", "%s cm"), "x/100", "100x", dpgettext2 (null, "unit-symbols", 
"centimeter,centimeters,cm,cms")));
+          length_category.add_unit (new Unit ("millimeter", _("Millimeters"), dpgettext2 (null, 
"unit-format", "%s mm"), "x/1000", "1000x", dpgettext2 (null, "unit-symbols", "millimeter,millimeters,mm")));
+          length_category.add_unit (new Unit ("micrometer", _("Micrometers"), dpgettext2 (null, 
"unit-format", "%s μm"), "x/1000000", "1000000x", dpgettext2 (null, "unit-symbols", 
"micrometer,micrometers,um")));
+          length_category.add_unit (new Unit ("nanometer", _("Nanometers"), dpgettext2 (null, "unit-format", 
"%s nm"), "x/1000000000", "1000000000x", dpgettext2 (null, "unit-symbols", "nanometer,nanometers,nm")));
+          length_category.add_unit (new Unit ("point", _("Desktop Publishing Point"), dpgettext2 (null, 
"unit-format", "%s pt"), "0.000352777778x", "x/0.000352777778", dpgettext2 (null, "unit-symbols", 
"point,pt,points,pts")));
+          area_category.add_unit (new Unit ("hectare", _("Hectares"), dpgettext2 (null, "unit-format", "%s 
ha"), "10000x", "x/10000", dpgettext2 (null, "unit-symbols", "hectare,hectares,ha")));
+          area_category.add_unit (new Unit ("acre", _("Acres"), dpgettext2 (null, "unit-format", "%s 
acres"), "4046.8564224x", "x/4046.8564224", dpgettext2 (null, "unit-symbols", "acre,acres")));
+          area_category.add_unit (new Unit ("square-meter", _("Square Meters"), dpgettext2 (null, 
"unit-format", "%s m²"), "x", "x", dpgettext2 (null, "unit-symbols", "m²")));
+          area_category.add_unit (new Unit ("square-centimeter", _("Square Centimeters"), dpgettext2 (null, 
"unit-format", "%s cm²"), "0.0001x", "10000x", dpgettext2 (null, "unit-symbols", "cm²")));
+          area_category.add_unit (new Unit ("square-millimeter", _("Square Millimeters"), dpgettext2 (null, 
"unit-format", "%s mm²"), "0.000001x", "1000000x", dpgettext2 (null, "unit-symbols", "mm²")));
+          volume_category.add_unit (new Unit ("cubic-meter", _("Cubic Meters"), dpgettext2 (null, 
"unit-format", "%s m³"), "1000x", "x/1000", dpgettext2 (null, "unit-symbols", "m³")));
+          volume_category.add_unit (new Unit ("gallon", _("Gallons"), dpgettext2 (null, "unit-format", "%s 
gal"), "3.785412x", "x/3.785412", dpgettext2 (null, "unit-symbols", "gallon,gallons,gal")));
+          volume_category.add_unit (new Unit ("litre", _("Liters"), dpgettext2 (null, "unit-format", "%s 
L"), "x", "x", dpgettext2 (null, "unit-symbols", "litre,litres,liter,liters,L")));
+          volume_category.add_unit (new Unit ("quart", _("Quarts"), dpgettext2 (null, "unit-format", "%s 
qt"), "0.9463529x", "x/0.9463529", dpgettext2 (null, "unit-symbols", "quart,quarts,qt")));
+          volume_category.add_unit (new Unit ("pint", _("Pints"), dpgettext2 (null, "unit-format", "%s pt"), 
"0.4731765x", "x/0.4731765", dpgettext2 (null, "unit-symbols", "pint,pints,pt")));
+          volume_category.add_unit (new Unit ("millilitre", _("Milliliters"), dpgettext2 (null, 
"unit-format", "%s mL"), "0.001x", "1000x", dpgettext2 (null, "unit-symbols", 
"millilitre,millilitres,milliliter,milliliters,mL,cm³")));
+          volume_category.add_unit (new Unit ("microlitre", _("Microliters"), dpgettext2 (null, 
"unit-format", "%s μL"), "0.000001x", "1000000x", dpgettext2 (null, "unit-symbols", "mm³,μL,uL")));
+          weight_category.add_unit (new Unit ("tonne", _("Tonnes"), dpgettext2 (null, "unit-format", "%s 
T"), "1000x", "x/1000", dpgettext2 (null, "unit-symbols", "tonne,tonnes")));
+          weight_category.add_unit (new Unit ("kilograms", _("Kilograms"), dpgettext2 (null, "unit-format", 
"%s kg"), "x", "x", dpgettext2 (null, "unit-symbols", "kilogram,kilograms,kilogramme,kilogrammes,kg,kgs")));
+          weight_category.add_unit (new Unit ("pound", _("Pounds"), dpgettext2 (null, "unit-format", "%s 
lb"), "0.45359237x", "x/0.45359237", dpgettext2 (null, "unit-symbols", "pound,pounds,lb")));
+          weight_category.add_unit (new Unit ("ounce", _("Ounces"), dpgettext2 (null, "unit-format", "%s 
oz"), "0.02834952x", "x/0.02834952", dpgettext2 (null, "unit-symbols", "ounce,ounces,oz")));
+          weight_category.add_unit (new Unit ("gram", _("Grams"), dpgettext2 (null, "unit-format", "%s g"), 
"0.001x", "1000x", dpgettext2 (null, "unit-symbols", "gram,grams,gramme,grammes,g")));
+          weight_category.add_unit (new Unit ("stone", _("Stone"), dpgettext2 (null, "unit-format", "%s 
st"), "6.350293x", "x/6.350293", dpgettext2 (null, "unit-symbols", "stone,st,stones")));
+          duration_category.add_unit (new Unit ("year", _("Years"), dpgettext2 (null, "unit-format", "%s 
years"), "31557600x", "x/31557600", dpgettext2 (null, "unit-symbols", "year,years")));
+          duration_category.add_unit (new Unit ("day", _("Days"), dpgettext2 (null, "unit-format", "%s 
days"), "86400x", "x/86400", dpgettext2 (null, "unit-symbols", "day,days")));
+          duration_category.add_unit (new Unit ("hour", _("Hours"), dpgettext2 (null, "unit-format", "%s 
hours"), "3600x", "x/3600", dpgettext2 (null, "unit-symbols", "hour,hours")));
+          duration_category.add_unit (new Unit ("minute", _("Minutes"), dpgettext2 (null, "unit-format", "%s 
minutes"), "60x", "x/60", dpgettext2 (null, "unit-symbols", "minute,minutes")));
+          duration_category.add_unit (new Unit ("second", _("Seconds"), dpgettext2 (null, "unit-format", "%s 
s"), "x", "x", dpgettext2 (null, "unit-symbols", "second,seconds,s")));
+          duration_category.add_unit (new Unit ("millisecond", _("Milliseconds"), dpgettext2 (null, 
"unit-format", "%s ms"), "0.001x", "1000x", dpgettext2 (null, "unit-symbols", 
"millisecond,milliseconds,ms")));
+          duration_category.add_unit (new Unit ("microsecond", _("Microseconds"), dpgettext2 (null, 
"unit-format", "%s μs"), "0.000001x", "1000000x", dpgettext2 (null, "unit-symbols", 
"microsecond,microseconds,us,μs")));
+          temperature_category.add_unit (new Unit ("degree-celcius", _("Celsius"), dpgettext2 (null, 
"unit-format", "%s ˚C"), "x+273.15", "x-273.15", dpgettext2 (null, "unit-symbols", 
"degC,˚C,C,c,Celsius,celsius")));
+          temperature_category.add_unit (new Unit ("degree-farenheit", _("Fahrenheit"), dpgettext2 (null, 
"unit-format", "%s ˚F"), "(x+459.67)*5/9", "x*9/5-459.67", dpgettext2 (null, "unit-symbols", 
"degF,˚F,F,f,Fahrenheit,fahrenheit")));
+          temperature_category.add_unit (new Unit ("degree-kelvin", _("Kelvin"), dpgettext2 (null, 
"unit-format", "%s K"), "x", "x", dpgettext2 (null, "unit-symbols", "k,K,Kelvin,kelvin")));
+          temperature_category.add_unit (new Unit ("degree-rankine", _("Rankine"), dpgettext2 (null, 
"unit-format", "%s ˚R"), "x*5/9", "x*9/5", dpgettext2 (null, "unit-symbols", 
"degR,˚R,˚Ra,r,R,Rankine,rankine")));
+          /* We use IEC prefix for digital storage units. i.e. 1 kB = 1 KiloByte = 1000 bytes, and 1 KiB = 1 
kibiByte = 1024 bytes */
+          digitalstorage_category.add_unit (new Unit ("bit", _("Bits"), dpgettext2 (null, "unit-format", "%s 
b"), "x/8", "8x", dpgettext2 (null, "unit-symbols", "bit,bits,b")));
+          digitalstorage_category.add_unit (new Unit ("byte", _("Bytes"), dpgettext2 (null, "unit-format", 
"%s B"), "x", "x", dpgettext2 (null, "unit-symbols", "byte,bytes,B")));
+          digitalstorage_category.add_unit (new Unit ("nibble", _("Nibbles"), dpgettext2 (null, 
"unit-format", "%s nibble"), "x/2", "2x", dpgettext2 (null, "unit-symbols", "nibble,nibbles")));
+          /* The SI symbol for kilo is k, however we also allow "KB" and "Kb", as they are widely used and 
accepted. */
+          digitalstorage_category.add_unit (new Unit ("kilobit", _("Kilobits"), dpgettext2 (null, 
"unit-format", "%s kb"), "1000x/8", "8x/1000", dpgettext2 (null, "unit-symbols", "kilobit,kilobits,kb,Kb")));
+          digitalstorage_category.add_unit (new Unit ("kilobyte", _("Kilobytes"), dpgettext2 (null, 
"unit-format", "%s kB"), "1000x", "x/1000", dpgettext2 (null, "unit-symbols", "kilobyte,kilobytes,kB,KB")));
+          digitalstorage_category.add_unit (new Unit ("kibibit", _("Kibibits"), dpgettext2 (null, 
"unit-format", "%s Kib"), "1024x/8", "8x/1024", dpgettext2 (null, "unit-symbols", "kibibit,kibibits,Kib")));
+          digitalstorage_category.add_unit (new Unit ("kibibyte", _("Kibibytes"), dpgettext2 (null, 
"unit-format", "%s KiB"), "1024x", "x/1024", dpgettext2 (null, "unit-symbols", "kibibyte,kibibytes,KiB")));
+          digitalstorage_category.add_unit (new Unit ("megabit", _("Megabits"), dpgettext2 (null, 
"unit-format", "%s Mb"), "1000000x/8", "8x/1000000", dpgettext2 (null, "unit-symbols", 
"megabit,megabits,Mb")));
+          digitalstorage_category.add_unit (new Unit ("megabyte", _("Megabytes"), dpgettext2 (null, 
"unit-format", "%s MB"), "1000000x", "x/1000000", dpgettext2 (null, "unit-symbols", 
"megabyte,megabytes,MB")));
+          digitalstorage_category.add_unit (new Unit ("mebibit", _("Mebibits"), dpgettext2 (null, 
"unit-format", "%s Mib"), "1048576x/8", "8x/1048576", dpgettext2 (null, "unit-symbols", 
"mebibit,mebibits,Mib")));
+          digitalstorage_category.add_unit (new Unit ("mebibyte", _("Mebibytes"), dpgettext2 (null, 
"unit-format", "%s MiB"), "1048576x", "x/1048576", dpgettext2 (null, "unit-symbols", 
"mebibyte,mebibytes,MiB")));
+          digitalstorage_category.add_unit (new Unit ("gigabit", _("Gigabits"), dpgettext2 (null, 
"unit-format", "%s Gb"), "1000000000x/8", "8x/1000000000", dpgettext2 (null, "unit-symbols", 
"gigabit,gigabits,Gb")));
+          digitalstorage_category.add_unit (new Unit ("gigabyte", _("Gigabytes"), dpgettext2 (null, 
"unit-format", "%s GB"), "1000000000x", "x/1000000000", dpgettext2 (null, "unit-symbols", 
"gigabyte,gigabytes,GB")));
+          digitalstorage_category.add_unit (new Unit ("gibibit", _("Gibibits"), dpgettext2 (null, 
"unit-format", "%s Gib"), "1073741824x/8", "8x/1073741824", dpgettext2 (null, "unit-symbols", 
"gibibit,gibibits,Gib")));
+          digitalstorage_category.add_unit (new Unit ("gibibyte", _("Gibibytes"), dpgettext2 (null, 
"unit-format", "%s GiB"), "1073741824x", "x/1073741824", dpgettext2 (null, "unit-symbols", 
"gibibyte,gibibytes,GiB")));
+          digitalstorage_category.add_unit (new Unit ("terabit", _("Terabits"), dpgettext2 (null, 
"unit-format", "%s Tb"), "1000000000000x/8", "8x/1000000000000", dpgettext2 (null, "unit-symbols", 
"terabit,terabits,Tb")));
+          digitalstorage_category.add_unit (new Unit ("terabyte", _("Terabytes"), dpgettext2 (null, 
"unit-format", "%s TB"), "1000000000000x", "x/1000000000000", dpgettext2 (null, "unit-symbols", 
"terabyte,terabytes,TB")));
+          digitalstorage_category.add_unit (new Unit ("tebibit", _("Tebibits"), dpgettext2 (null, 
"unit-format", "%s Tib"), "1099511627776x/8", "8x/1099511627776", dpgettext2 (null, "unit-symbols", 
"tebibit,tebibits,Tib")));
+          digitalstorage_category.add_unit (new Unit ("tebibyte", _("Tebibytes"), dpgettext2 (null, 
"unit-format", "%s TiB"), "1099511627776x", "x/1099511627776", dpgettext2 (null, "unit-symbols", 
"tebibyte,tebibytes,TiB")));
+          digitalstorage_category.add_unit (new Unit ("petabit", _("Petabits"), dpgettext2 (null, 
"unit-format", "%s Pb"), "1000000000000000x/8", "8x/1000000000000000", dpgettext2 (null, "unit-symbols", 
"petabit,petabits,Pb")));
+          digitalstorage_category.add_unit (new Unit ("petabyte", _("Petabytes"), dpgettext2 (null, 
"unit-format", "%s PB"), "1000000000000000x", "x/1000000000000000", dpgettext2 (null, "unit-symbols", 
"petabyte,petabytes,PB")));
+          digitalstorage_category.add_unit (new Unit ("pebibit", _("Pebibits"), dpgettext2 (null, 
"unit-format", "%s Pib"), "1125899906842624x/8", "8x/1125899906842624", dpgettext2 (null, "unit-symbols", 
"pebibit,pebibits,Pib")));
+          digitalstorage_category.add_unit (new Unit ("pebibyte", _("Pebibytes"), dpgettext2 (null, 
"unit-format", "%s PiB"), "1125899906842624x", "x/1125899906842624", dpgettext2 (null, "unit-symbols", 
"pebibyte,pebibytes,PiB")));
+          digitalstorage_category.add_unit (new Unit ("exabit", _("Exabits"), dpgettext2 (null, 
"unit-format", "%s Eb"), "1000000000000000000x/8", "8x/1000000000000000000", dpgettext2 (null, 
"unit-symbols", "exabit,exabits,Eb")));
+          digitalstorage_category.add_unit (new Unit ("exabyte", _("Exabytes"), dpgettext2 (null, 
"unit-format", "%s EB"), "1000000000000000000x", "x/1000000000000000000", dpgettext2 (null, "unit-symbols", 
"exabyte,exabytes,EB")));
+          digitalstorage_category.add_unit (new Unit ("exbibit", _("Exbibits"), dpgettext2 (null, 
"unit-format", "%s Eib"), "1152921504606846976x/8", "8x/1152921504606846976", dpgettext2 (null, 
"unit-symbols", "exbibit,exbibits,Eib")));
+          digitalstorage_category.add_unit (new Unit ("exbibyte", _("Exbibytes"), dpgettext2 (null, 
"unit-format", "%s EiB"), "1152921504606846976x", "x/1152921504606846976", dpgettext2 (null, "unit-symbols", 
"exbibyte,exbibytes,EiB")));
+          digitalstorage_category.add_unit (new Unit ("zettabit", _("Zettabits"), dpgettext2 (null, 
"unit-format", "%s Eb"), "1000000000000000000000x/8", "8x/1000000000000000000000", dpgettext2 (null, 
"unit-symbols", "zettabit,zettabits,Zb")));
+          digitalstorage_category.add_unit (new Unit ("zettabyte", _("Zettabytes"), dpgettext2 (null, 
"unit-format", "%s EB"), "1000000000000000000000x", "x/1000000000000000000000", dpgettext2 (null, 
"unit-symbols", "zettabyte,zettabytes,ZB")));
+          digitalstorage_category.add_unit (new Unit ("zebibit", _("Zebibits"), dpgettext2 (null, 
"unit-format", "%s Zib"), "1180591620717411303424x/8", "8x/1180591620717411303424", dpgettext2 (null, 
"unit-symbols", "zebibit,zebibits,Zib")));
+          digitalstorage_category.add_unit (new Unit ("zebibyte", _("Zebibytes"), dpgettext2 (null, 
"unit-format", "%s ZiB"), "1180591620717411303424x", "x/1180591620717411303424", dpgettext2 (null, 
"unit-symbols", "zebibyte,zebibytes,ZiB")));
+          digitalstorage_category.add_unit (new Unit ("yottabit", _("Yottabits"), dpgettext2 (null, 
"unit-format", "%s Yb"), "1000000000000000000000000x/8", "8x/1000000000000000000000000", dpgettext2 (null, 
"unit-symbols", "yottabit,yottabits,Yb")));
+          digitalstorage_category.add_unit (new Unit ("yottabyte", _("Yottabytes"), dpgettext2 (null, 
"unit-format", "%s YB"), "1000000000000000000000000x", "x/1000000000000000000000000", dpgettext2 (null, 
"unit-symbols", "yottabyte,yottabytes,YB")));
+          digitalstorage_category.add_unit (new Unit ("yobibit", _("Yobibits"), dpgettext2 (null, 
"unit-format", "%s Yib"), "1208925819614629174706176x/8", "8x/1208925819614629174706176", dpgettext2 (null, 
"unit-symbols", "yobibit,yobibits,Yib")));
+          digitalstorage_category.add_unit (new Unit ("yobibyte", _("Yobibytes"), dpgettext2 (null, 
"unit-format", "%s YiB"), "1208925819614629174706176x", "x/1208925819614629174706176", dpgettext2 (null, 
"unit-symbols", "yobibyte,yobibytes,YiB")));
+
+          var currency_category = default_unit_manager.add_category ("currency", _("Currency"));
+          var currencies = CurrencyManager.get_default ().get_currencies ();
+          currencies.sort ((a, b) => { return a.display_name.collate (b.display_name); });
+          foreach (var currency in currencies)
+          {
+              /* Translators: result of currency conversion, %s is the symbol, %%s is the placeholder for 
amount, i.e.: USD100 */
+              var format = _("%s%%s").printf (currency.symbol);
+              var unit = new Unit (currency.name, currency.display_name, format, null, null, currency.name);
+              currency_category.add_unit ( unit);
+          }
+
+          return default_unit_manager;
+      }
+
+      public UnitCategory add_category (string name, string display_name)
+      {
+          var category = new UnitCategory (name, display_name);
+          categories.append (category);
+          return category;
+      }
+
+      public List<UnitCategory> get_categories ()
+      {
+          var r = new List<UnitCategory> ();
+          foreach (var c in categories)
+              r.append (c);
+          return r;
+      }
+
+      public UnitCategory? get_category (string category)
+      {
+          foreach (var c in categories)
+              if (c.name == category)
+                  return c;
+
+          return null;
+      }
+
+      public Unit? get_unit_by_name (string name)
+      {
+          int count = 0;
+          Unit? return_unit = null;
+          foreach (var c in categories)
+          {
+              var u = c.get_unit_by_name (name);
+              if (u != null)
+              {
+                  return_unit = u;
+                  count++;
+              }
+          }
+          if (count > 1)
+              return null;
+          else if (count == 1)
+              return return_unit;
+
+          foreach (var c in categories)
+          {
+              var u = c.get_unit_by_name (name, false);
+              if (u != null)
+              {
+                  return_unit = u;
+                  count++;
+              }
+          }
+          if (count == 1)
+              return return_unit;
+          return null;
+      }
+
+      public Unit? get_unit_by_symbol (string symbol)
+      {
+          int count = 0;
+          Unit? return_unit = null;
+          foreach (var c in categories)
+          {
+              var u = c.get_unit_by_symbol (symbol);
+              if (u != null)
+              {
+                  return_unit = u;
+                  count++;
+              }
+          }
+          if (count > 1)
+              return null;
+          else if (count == 1)
+              return return_unit;
+
+          foreach (var c in categories)
+          {
+              var u = c.get_unit_by_symbol (symbol, false);
+              if (u != null)
+              {
+                  return_unit = u;
+                  count++;
+              }
+          }
+          if (count == 1)
+              return return_unit;
+          return null;
+      }
+
+      public bool unit_is_defined (string name)
+      {
+          var unit = get_unit_by_symbol (name);
+          if (unit != null)
+              return true;
+          else
+              return false;
+      }
+
+      public Number? convert_by_symbol (Number x, string x_symbol, string z_symbol)
+      {
+          foreach (var c in categories)
+          {
+              var x_units = c.get_unit_by_symbol (x_symbol);
+              if (x_units == null)
+                  x_units = c.get_unit_by_symbol (x_symbol, false);
+              var z_units = c.get_unit_by_symbol (z_symbol);
+              if (z_units == null)
+                  z_units = c.get_unit_by_symbol (z_symbol, false);
+              if (x_units != null && z_units != null)
+                  return c.convert (x, x_units, z_units);
+          }
+
+          return null;
+      }
+  }
+
+  public class UnitCategory : Object
+  {
+      private List<Unit> units;
+
+      private string _name;
+      public string name { owned get { return _name; } }
+
+      private string _display_name;
+      public string display_name { owned get { return _display_name; } }
+
+      public UnitCategory (string name, string display_name)
+      {
+          _name = name;
+          _display_name = display_name;
+          units = new List<Unit> ();
+      }
+
+      public void add_unit (Unit unit)
+      {
+          units.append (unit);
+      }
+
+      public Unit? get_unit_by_name (string name, bool case_sensitive = true)
+      {
+          int count = 0;
+          Unit? return_unit = null;
+          foreach (var unit in units)
+              if ((case_sensitive && unit.name == name) || (!case_sensitive && unit.name.down() == name.down 
()))
+              {
+                  return_unit = unit;
+                  count++;
+              }
+          if (count == 1)
+              return return_unit;
+          return null;
+      }
+
+      public Unit? get_unit_by_symbol (string symbol, bool case_sensitive = true)
+      {
+          int count = 0;
+          Unit? return_unit = null;
+          foreach (var unit in units)
+              if (unit.matches_symbol (symbol))
+              {
+                  return_unit = unit;
+                  count++;
+              }
+          if (count > 1)
+              return null;
+          else if (count == 1)
+              return return_unit;
+
+          foreach (var unit in units)
+              if (unit.matches_symbol (symbol, false))
+              {
+                  return_unit = unit;
+                  count++;
+              }
+          if (count == 1)
+              return return_unit;
+          return null;
+      }
+
+      public unowned List<Unit> get_units ()
+      {
+          return units;
+      }
+
+      public Number? convert (Number x, Unit x_units, Unit z_units)
+      {
+          var t = x_units.convert_from (x);
+          if (t == null)
+              return null;
+          return z_units.convert_to (t);
+      }
+  }
+
+  public class Unit : Object
+  {
+      private string _name;
+      public string name { owned get { return _name; } }
+
+      private string _display_name;
+      public string display_name { owned get { return _display_name; } }
+
+      private string _format;
+      private List<string> _symbols;
+      private string? from_function;
+      private string? to_function;
+      private Serializer serializer;
+
+      public Unit (string name, string display_name, string format, string? from_function, string? 
to_function, string symbols)
+      {
+          serializer = new Serializer (DisplayFormat.AUTOMATIC, 10, 2);
+          serializer.set_leading_digits (6);
+
+          _name = name;
+          _display_name = display_name;
+          this._format = format;
+          this.from_function = from_function;
+          this.to_function = to_function;
+          _symbols = new List<string> ();
+          var symbol_names = symbols.split (",", 0);
+          foreach (var symbol_name in symbol_names)
+              _symbols.append (symbol_name);
+      }
+
+      public bool matches_symbol (string symbol, bool case_sensitive = true)
+      {
+          foreach (var s in _symbols)
+              if ((case_sensitive && s == symbol) || (!case_sensitive && s.down () == symbol.down ()))
+                  return true;
+
+          return false;
+      }
+
+      public unowned List<string> get_symbols ()
+      {
+          return _symbols;
+      }
+
+      public Number? convert_from (Number x)
+      {
+          if (from_function != null)
+              return solve_function (from_function, x);
+          else
+          {
+              // FIXME: Hack to make currency work
+              var r = CurrencyManager.get_default ().get_value (name);
+              if (r == null)
+                  return null;
+              return x.divide (r);
+          }
+      }
+
+      public Number? convert_to (Number x)
+      {
+          if (to_function != null)
+              return solve_function (to_function, x);
+          else
+          {
+              // FIXME: Hack to make currency work
+              var r = CurrencyManager.get_default ().get_value (name);
+              if (r == null)
+                  return null;
+              return x.multiply (r);
+          }
+      }
+
+      public string format (Number x)
+      {
+          var number_text = serializer.to_string (x);
+          return _format.printf (number_text);
+      }
+
+      private Number? solve_function (string function, Number x)
+      {
+          var equation = new UnitSolveEquation (function, x);
+          equation.base = 10;
+          equation.wordlen = 32;
+          var z = equation.parse ();
+          if (z == null)
+              warning ("Failed to convert value: %s", function);
+
+          return z;
+      }
+  }
+
+  private class UnitSolveEquation : Equation
+  {
+      private Number x;
+
+      public UnitSolveEquation (string function, Number x)
+      {
+          base (function);
+          this.x = x;
+      }
+
+      public override bool variable_is_defined (string name)
+      {
+          return true;
+      }
+
+      public override Number? get_variable (string name)
+      {
+          return x;
+      }
+  }
+}
diff --git a/gcalc/meson.build b/gcalc/meson.build
index 64ab6b71..36d53f18 100644
--- a/gcalc/meson.build
+++ b/gcalc/meson.build
@@ -40,7 +40,16 @@ configure_file(output : 'config.h',
 
 sources = files([
        'gcalc-solver.vala',
-       'gcalc-result.vala'
+       'gcalc-result.vala',
+       'gcalc-currency.vala',
+       'gcalc-equation.vala',
+       'gcalc-equation-parser.vala',
+       'gcalc-equation-lexer.vala',
+       'gcalc-function-manager.vala',
+       'gcalc-math-function.vala',
+       'gcalc-number.vala',
+       'gcalc-serializer.vala',
+       'gcalc-unit-manager.vala'
 ])
 
 
@@ -52,6 +61,9 @@ deps = [
        namespaceinfo_dep,
        inc_libh_dep,
        inc_rooth_dep,
+       posix,
+       libxml,
+       libsoup
 ]
 
 
@@ -94,6 +106,7 @@ lib = library(VERSIONED_PROJECT_NAME,
        dependencies : deps,
        vala_args: [
                '--vapidir='+vapi_dir,
+               '--pkg=mpc',
                '--pkg=mpfr'
        ],
        c_args : [
diff --git a/meson.build b/meson.build
index 397bbf4c..d032ba08 100644
--- a/meson.build
+++ b/meson.build
@@ -28,6 +28,8 @@ gio = dependency('gio-2.0', version: '>= ' + glib_min_version)
 glib = dependency('glib-2.0', version: '>= ' + glib_min_version)
 gmodule_export = dependency('gmodule-export-2.0')
 gobject = dependency('gobject-2.0', version: '>= ' + glib_min_version)
+libxml = dependency('libxml-2.0')
+libsoup = dependency('libsoup-2.4', version: '>= 2.42')
 
 # Libraries
 cc = meson.get_compiler('c')
@@ -65,8 +67,6 @@ meson.add_install_script('meson_post_install.py')
 subdir('gcalc')
 if not get_option ('disable-ui')
 gtk = dependency('gtk+-3.0', version: '>= 3.19.3')
-libsoup = dependency('libsoup-2.4', version: '>= 2.42')
-libxml = dependency('libxml-2.0')
 gtksourceview = dependency('gtksourceview-4', version: '>= 4.0.2')
 subdir('data')
 subdir('lib')



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