[gnome-calculator/60-split-out-a-backend-library: 4/39] gcalc: added core methods for equation parser
- From: Daniel Espinosa Ortiz <despinosa src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-calculator/60-split-out-a-backend-library: 4/39] gcalc: added core methods for equation parser
- Date: Fri, 4 Jan 2019 16:34:34 +0000 (UTC)
commit 47fb1a908cc904fa194a70232308b9d87761510a
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 ca1559df..2d1c4fb9 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]