summaryrefslogtreecommitdiff
path: root/src/mp-equation.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mp-equation.c')
-rw-r--r--src/mp-equation.c493
1 files changed, 493 insertions, 0 deletions
diff --git a/src/mp-equation.c b/src/mp-equation.c
new file mode 100644
index 0000000..34f698b
--- /dev/null
+++ b/src/mp-equation.c
@@ -0,0 +1,493 @@
+/* Copyright (c) 2004-2008 Sami Pietila
+ * Copyright (c) 2008-2009 Robert Ancell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <ctype.h>
+
+#include "mp-equation-private.h"
+#include "mp-equation-parser.h"
+#include "mp-equation-lexer.h"
+
+extern int _mp_equation_parse(yyscan_t yyscanner);
+
+
+static int
+variable_is_defined(MPEquationParserState *state, const char *name)
+{
+ /* FIXME: Make more generic */
+ if (strcmp(name, "e") == 0 || strcmp(name, "i") == 0 || strcmp(name, "π") == 0)
+ return 1;
+ if (state->options->variable_is_defined)
+ return state->options->variable_is_defined(name, state->options->callback_data);
+ return 0;
+}
+
+
+static int
+get_variable(MPEquationParserState *state, const char *name, MPNumber *z)
+{
+ int result = 1;
+
+ if (strcmp(name, "e") == 0)
+ mp_get_eulers(z);
+ else if (strcmp(name, "i") == 0)
+ mp_get_i(z);
+ else if (strcmp(name, "π") == 0)
+ mp_get_pi(z);
+ else if (state->options->get_variable)
+ result = state->options->get_variable(name, z, state->options->callback_data);
+ else
+ result = 0;
+
+ return result;
+}
+
+static void
+set_variable(MPEquationParserState *state, const char *name, const MPNumber *x)
+{
+ // Reserved words, e, π, mod, and, or, xor, not, abs, log, ln, sqrt, int, frac, sin, cos, ...
+ if (strcmp(name, "e") == 0 || strcmp(name, "i") == 0 || strcmp(name, "π") == 0)
+ return; // FALSE
+
+ if (state->options->set_variable)
+ state->options->set_variable(name, x, state->options->callback_data);
+}
+
+// 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
+
+
+int
+sub_atoi(const char *data)
+{
+ int i, value = 0;
+ const char *digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL};
+
+ do {
+ for(i = 0; digits[i] != NULL && strncmp(data, digits[i], strlen(digits[i])) != 0; i++);
+ if(digits[i] == NULL)
+ return -1;
+ data += strlen(digits[i]);
+ value = value * 10 + i;
+ } while(*data != '\0');
+
+ return value;
+}
+
+int
+super_atoi(const char *data)
+{
+ int i, sign = 1, value = 0;
+ const char *digits[11] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL};
+
+ if(strncmp(data, "⁻", strlen("⁻")) == 0) {
+ sign = -1;
+ data += strlen("⁻");
+ }
+
+ do {
+ for(i = 0; digits[i] != NULL && strncmp(data, digits[i], strlen(digits[i])) != 0; i++);
+ if(digits[i] == NULL)
+ return 0;
+ value = value * 10 + i;
+ data += strlen(digits[i]);
+ } while(*data != '\0');
+
+ return sign * value;
+}
+
+
+static int
+function_is_defined(MPEquationParserState *state, const char *name)
+{
+ char *c, *lower_name;
+
+ lower_name = strdup(name);
+ for (c = lower_name; *c; c++)
+ *c = tolower(*c);
+
+ /* FIXME: Make more generic */
+ if (strcmp(lower_name, "log") == 0 ||
+ (strncmp(lower_name, "log", 3) == 0 && sub_atoi(lower_name + 3) >= 0) ||
+ strcmp(lower_name, "ln") == 0 ||
+ strcmp(lower_name, "sqrt") == 0 ||
+ strcmp(lower_name, "abs") == 0 ||
+ strcmp(lower_name, "sgn") == 0 ||
+ strcmp(lower_name, "arg") == 0 ||
+ strcmp(lower_name, "conj") == 0 ||
+ strcmp(lower_name, "int") == 0 ||
+ strcmp(lower_name, "frac") == 0 ||
+ strcmp(lower_name, "floor") == 0 ||
+ strcmp(lower_name, "ceil") == 0 ||
+ strcmp(lower_name, "round") == 0 ||
+ strcmp(lower_name, "re") == 0 ||
+ strcmp(lower_name, "im") == 0 ||
+ strcmp(lower_name, "sin") == 0 || strcmp(lower_name, "cos") == 0 || strcmp(lower_name, "tan") == 0 ||
+ strcmp(lower_name, "sin⁻¹") == 0 || strcmp(lower_name, "cos⁻¹") == 0 || strcmp(lower_name, "tan⁻¹") == 0 ||
+ strcmp(lower_name, "sinh") == 0 || strcmp(lower_name, "cosh") == 0 || strcmp(lower_name, "tanh") == 0 ||
+ strcmp(lower_name, "sinh⁻¹") == 0 || strcmp(lower_name, "cosh⁻¹") == 0 || strcmp(lower_name, "tanh⁻¹") == 0 ||
+ strcmp(lower_name, "asinh") == 0 || strcmp(lower_name, "acosh") == 0 || strcmp(lower_name, "atanh") == 0 ||
+ strcmp(lower_name, "ones") == 0 ||
+ strcmp(lower_name, "twos") == 0) {
+ g_free (lower_name);
+ return 1;
+ }
+ g_free (lower_name);
+
+ if (state->options->function_is_defined)
+ return state->options->function_is_defined(name, state->options->callback_data);
+ return 0;
+}
+
+
+static int
+get_function(MPEquationParserState *state, const char *name, const MPNumber *x, MPNumber *z)
+{
+ char *c, *lower_name;
+ int result = 1;
+
+ lower_name = strdup(name);
+ for (c = lower_name; *c; c++)
+ *c = tolower(*c);
+
+ // FIXME: Re Im ?
+
+ if (strcmp(lower_name, "log") == 0)
+ mp_logarithm(10, x, z); // FIXME: Default to ln
+ else if (strncmp(lower_name, "log", 3) == 0) {
+ int base;
+
+ base = sub_atoi(lower_name + 3);
+ if (base < 0)
+ result = 0;
+ else
+ mp_logarithm(base, x, z);
+ }
+ else if (strcmp(lower_name, "ln") == 0)
+ mp_ln(x, z);
+ else if (strcmp(lower_name, "sqrt") == 0) // √x
+ mp_sqrt(x, z);
+ else if (strcmp(lower_name, "abs") == 0) // |x|
+ mp_abs(x, z);
+ else if (strcmp(lower_name, "sgn") == 0)
+ mp_sgn(x, z);
+ else if (strcmp(lower_name, "arg") == 0)
+ mp_arg(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "conj") == 0)
+ mp_conjugate(x, z);
+ else if (strcmp(lower_name, "int") == 0)
+ mp_integer_component(x, z);
+ else if (strcmp(lower_name, "frac") == 0)
+ mp_fractional_component(x, z);
+ else if (strcmp(lower_name, "floor") == 0)
+ mp_floor(x, z);
+ else if (strcmp(lower_name, "ceil") == 0)
+ mp_ceiling(x, z);
+ else if (strcmp(lower_name, "round") == 0)
+ mp_round(x, z);
+ else if (strcmp(lower_name, "re") == 0)
+ mp_real_component(x, z);
+ else if (strcmp(lower_name, "im") == 0)
+ mp_imaginary_component(x, z);
+ else if (strcmp(lower_name, "sin") == 0)
+ mp_sin(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "cos") == 0)
+ mp_cos(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "tan") == 0)
+ mp_tan(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "sin⁻¹") == 0 || strcmp(lower_name, "asin") == 0)
+ mp_asin(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "cos⁻¹") == 0 || strcmp(lower_name, "acos") == 0)
+ mp_acos(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "tan⁻¹") == 0 || strcmp(lower_name, "atan") == 0)
+ mp_atan(x, state->options->angle_units, z);
+ else if (strcmp(lower_name, "sinh") == 0)
+ mp_sinh(x, z);
+ else if (strcmp(lower_name, "cosh") == 0)
+ mp_cosh(x, z);
+ else if (strcmp(lower_name, "tanh") == 0)
+ mp_tanh(x, z);
+ else if (strcmp(lower_name, "sinh⁻¹") == 0 || strcmp(lower_name, "asinh") == 0)
+ mp_asinh(x, z);
+ else if (strcmp(lower_name, "cosh⁻¹") == 0 || strcmp(lower_name, "acosh") == 0)
+ mp_acosh(x, z);
+ else if (strcmp(lower_name, "tanh⁻¹") == 0 || strcmp(lower_name, "atanh") == 0)
+ mp_atanh(x, z);
+ else if (strcmp(lower_name, "ones") == 0)
+ mp_ones_complement(x, state->options->wordlen, z);
+ else if (strcmp(lower_name, "twos") == 0)
+ mp_twos_complement(x, state->options->wordlen, z);
+ else if (state->options->get_function)
+ result = state->options->get_function(name, x, z, state->options->callback_data);
+ else
+ result = 0;
+
+ free(lower_name);
+
+ return result;
+}
+
+
+static int
+do_convert(const char *units[][2], const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z)
+{
+ int x_index, z_index;
+ MPNumber x_factor, z_factor;
+
+ for (x_index = 0; units[x_index][0] != NULL && strcmp(units[x_index][0], x_units) != 0; x_index++);
+ if (units[x_index][0] == NULL)
+ return 0;
+ for (z_index = 0; units[z_index][0] != NULL && strcmp(units[z_index][0], z_units) != 0; z_index++);
+ if (units[z_index][0] == NULL)
+ return 0;
+
+ mp_set_from_string(units[x_index][1], 10, &x_factor);
+ mp_set_from_string(units[z_index][1], 10, &z_factor);
+ mp_multiply(x, &x_factor, z);
+ mp_divide(z, &z_factor, z);
+
+ return 1;
+}
+
+
+static int
+convert(MPEquationParserState *state, const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z)
+{
+ const char *length_units[][2] = {
+ {"parsec", "30857000000000000"},
+ {"parsecs", "30857000000000000"},
+ {"pc", "30857000000000000"},
+ {"lightyear", "9460730472580800"},
+ {"lightyears", "9460730472580800"},
+ {"ly", "9460730472580800"},
+ {"au", "149597870691"},
+ {"nm", "1852000"},
+ {"mile", "1609.344"},
+ {"miles", "1609.344"},
+ {"mi", "1609.344"},
+ {"kilometer", "1000"},
+ {"kilometers", "1000"},
+ {"km", "1000"},
+ {"kms", "1000"},
+ {"cable", "219.456"},
+ {"cables", "219.456"},
+ {"cb", "219.456"},
+ {"fathom", "1.8288"},
+ {"fathoms", "1.8288"},
+ {"ftm", "1.8288"},
+ {"meter", "1"},
+ {"meters", "1"},
+ {"m", "1"},
+ {"yard", "0.9144"},
+ {"yd", "0.9144"},
+ {"foot", "0.3048"},
+ {"feet", "0.3048"},
+ {"ft", "0.3048"},
+ {"inch", "0.0254"},
+ {"inches", "0.0254"},
+ {"centimeter", "0.01"},
+ {"centimeters", "0.01"},
+ {"cm", "0.01"},
+ {"cms", "0.01"},
+ {"millimeter", "0.001"},
+ {"millimeters", "0.001"},
+ {"mm", "0.001"},
+ {"micrometer", "0.000001"},
+ {"micrometers", "0.000001"},
+ {"um", "0.000001"},
+ {"nanometer", "0.000000001"},
+ {"nanometers", "0.000000001"},
+ {NULL, NULL}
+ };
+
+ const char *area_units[][2] = {
+ {"hectare", "10000"},
+ {"hectares", "10000"},
+ {"acre", "4046.8564224"},
+ {"acres", "4046.8564224"},
+ {"m²", "1"},
+ {"cm²", "0.001"},
+ {"mm²", "0.000001"},
+ {NULL, NULL}
+ };
+
+ const char *volume_units[][2] = {
+ {"m³", "1000"},
+ {"gallon", "3.785412"},
+ {"gallons", "3.785412"},
+ {"gal", "3.785412"},
+ {"litre", "1"},
+ {"litres", "1"},
+ {"liter", "1"},
+ {"liters", "1"},
+ {"L", "1"},
+ {"quart", "0.9463529"},
+ {"quarts", "0.9463529"},
+ {"qt", "0.9463529"},
+ {"pint", "0.4731765"},
+ {"pints", "0.4731765"},
+ {"pt", "0.4731765"},
+ {"millilitre", "0.001"},
+ {"millilitres", "0.001"},
+ {"milliliter", "0.001"},
+ {"milliliters", "0.001"},
+ {"mL", "0.001"},
+ {"cm³", "0.001"},
+ {"mm³", "0.000001"},
+ {NULL, NULL}
+ };
+
+ const char *weight_units[][2] = {
+ {"tonne", "1000"},
+ {"tonnes", "1000"},
+ {"kilograms", "1"},
+ {"kilogramme", "1"},
+ {"kilogrammes", "1"},
+ {"kg", "1"},
+ {"kgs", "1"},
+ {"pound", "0.45359237"},
+ {"pounds", "0.45359237"},
+ {"lb", "0.45359237"},
+ {"ounce", "0.002834952"},
+ {"ounces", "0.002834952"},
+ {"oz", "0.002834952"},
+ {"gram", "0.001"},
+ {"grams", "0.001"},
+ {"gramme", "0.001"},
+ {"grammes", "0.001"},
+ {"g", "0.001"},
+ {NULL, NULL}
+ };
+
+ const char *time_units[][2] = {
+ {"year", "31557600"},
+ {"years", "31557600"},
+ {"day", "86400"},
+ {"days", "86400"},
+ {"hour", "3600"},
+ {"hours", "3600"},
+ {"minute", "60"},
+ {"minutes", "60"},
+ {"second", "1"},
+ {"seconds", "1"},
+ {"s", "1"},
+ {"millisecond", "0.001"},
+ {"milliseconds", "0.001"},
+ {"ms", "0.001"},
+ {"microsecond", "0.000001"},
+ {"microseconds", "0.000001"},
+ {"us", "0.000001"},
+ {NULL, NULL}
+ };
+
+ if (do_convert(length_units, x, x_units, z_units, z) ||
+ do_convert(area_units, x, x_units, z_units, z) ||
+ do_convert(volume_units, x, x_units, z_units, z) ||
+ do_convert(weight_units, x, x_units, z_units, z) ||
+ do_convert(time_units, x, x_units, z_units, z))
+ return 1;
+
+ if (state->options->convert)
+ return state->options->convert(x, x_units, z_units, z, state->options->callback_data);
+
+ return 0;
+}
+
+
+MPErrorCode
+mp_equation_parse(const char *expression, MPEquationOptions *options, MPNumber *result, char **error_token)
+{
+ int ret;
+ MPEquationParserState state;
+ yyscan_t yyscanner;
+ YY_BUFFER_STATE buffer;
+
+ if (!(expression && result) || strlen(expression) == 0)
+ return PARSER_ERR_INVALID;
+
+ memset(&state, 0, sizeof(MPEquationParserState));
+ state.options = options;
+ state.variable_is_defined = variable_is_defined;
+ state.get_variable = get_variable;
+ state.set_variable = set_variable;
+ state.function_is_defined = function_is_defined;
+ state.get_function = get_function;
+ state.convert = convert;
+ state.error = 0;
+
+ mp_clear_error();
+
+ _mp_equation_lex_init_extra(&state, &yyscanner);
+ buffer = _mp_equation__scan_string(expression, yyscanner);
+
+ ret = _mp_equation_parse(yyscanner);
+ if (state.error_token != NULL && error_token != NULL) {
+ *error_token = state.error_token;
+ }
+
+ _mp_equation__delete_buffer(buffer, yyscanner);
+ _mp_equation_lex_destroy(yyscanner);
+
+ /* Error during parsing */
+ if (state.error)
+ return state.error;
+
+ if (mp_get_error())
+ return PARSER_ERR_MP;
+
+ /* Failed to parse */
+ if (ret)
+ return PARSER_ERR_INVALID;
+
+ mp_set_from_mp(&state.ret, result);
+
+ return PARSER_ERR_NONE;
+}
+
+
+const char *
+mp_error_code_to_string(MPErrorCode error_code)
+{
+ switch(error_code)
+ {
+ case PARSER_ERR_NONE:
+ return "PARSER_ERR_NONE";
+ case PARSER_ERR_INVALID:
+ return "PARSER_ERR_INVALID";
+ case PARSER_ERR_OVERFLOW:
+ return "PARSER_ERR_OVERFLOW";
+ case PARSER_ERR_UNKNOWN_VARIABLE:
+ return "PARSER_ERR_UNKNOWN_VARIABLE";
+ case PARSER_ERR_UNKNOWN_FUNCTION:
+ return "PARSER_ERR_UNKNOWN_FUNCTION";
+ case PARSER_ERR_UNKNOWN_CONVERSION:
+ return "PARSER_ERR_UNKNOWN_CONVERSION";
+ case PARSER_ERR_MP:
+ return "PARSER_ERR_MP";
+ default:
+ return "Unknown parser error";
+ }
+}
+
+
+int _mp_equation_error(void *yylloc, MPEquationParserState *state, char *text)
+{
+ return 0;
+}