diff options
Diffstat (limited to 'src/mp-equation.c')
-rw-r--r-- | src/mp-equation.c | 493 |
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; +} |