En este post te mostraré cómo evaluar expresiones matemáticas a partir de una cadena. Es decir, hacer que se evalúe una ecuación, fórmula, etcétera.

De este modo podemos hacer que, por ejemplo, un usuario introduzca una fórmula, introduzca sus valores y se resuelva.

Además de evaluar expresiones aritméticas también podremos proporcionar valores a las variables dentro de la misma.

Vamos a usar tinyexpr, un pequeño analizador para expresiones matemáticas. Si bien el código aquí está destinado a ser usado con C, puede compilar, con ciertos ajustes, para C++.

Descargando librería

Este analizador es muy pequeño (su nombre lo dice) así que basta con incluir al archivo principal y el encabezado; esto es, tinyexpr.h y tinyexpr.c.

Ambos archivos están en el repositorio oficial, pero dejo los enlaces aquí:

  1. https://github.com/codeplea/tinyexpr/blob/master/tinyexpr.h
  2. https://github.com/codeplea/tinyexpr/blob/master/tinyexpr.c

Entramos al enlace y los descargamos, ya sea haciendo click en Raw y luego guardando el archivo, o copiando y pegando.

Incluyendo tinyexpr en proyecto

Ahora al compilar solo debes incluir estos dos archivos; depende de tu compilador (por eso es que recomiendo usar gcc).

Si usas GCC simplemente coloca ambos archivos en el directorio en donde está tu código principal, y en tu código principal usa include:

// Se debe incluir con "" y no con <>, para que busque de manera relativa
#include "tinyexpr.h"
#include "tinyexpr.c"

Luego, compila normalmente con:

gcc main.c

Eso suponiendo que tu archivo se llama main.c. Ahora bien, si usas clang hay que cambiar un poco las cosas.

En tu encabezado solo incluye a tinyexpr.h pero al compilar incluye tinyexpr.c; algo así:

clang main.c tinyexpr.c

Y si estás en Linux con clang usa algo como:

clang-7 -pthread -lm -o main main.c tinyexpr.c

Dicho eso, comencemos con los ejemplos.

Evaluando una simple expresión

Hagamos nuestro “hola mundo” usando esta librería. Vamos a usar la función te_interp que toma dos parámetros: la expresión y un apuntador a una variable en donde colocará el error si es que lo hay.

Vamos a manejar el error en el último ejemplo, por ahora lo vamos a dejar así. Esta función devuelve un double, que es el resultado de la expresión:

/*
  ____          _____               _ _           _
 |  _ \        |  __ \             (_) |         | |
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |
        |___/                               |___/

    Blog:       https://parzibyte.me/blog
    Ayuda:      https://parzibyte.me/blog/contrataciones-ayuda/
    Contacto:   https://parzibyte.me/blog/contacto/

    Copyright (c) 2020 Luis Cabrera Benito
    Licenciado bajo la licencia MIT

    El texto de arriba debe ser incluido en cualquier redistribución
*/
#include <stdio.h>
// Se debe incluir con "" y no con <>, para que busque de manera relativa
#include "tinyexpr.h"
#include "tinyexpr.c"

int main() {
  /*
      Ahora tenemos a las funciones:
      te_interp
      te_compile
      te_eval
      te_free
  */  char *expresion = "2^3 * 5";
  double resultado = te_interp(expresion, 0);
  printf("%s es: %f", expresion, resultado);
}

Al ejecutar el programa, la salida es:

2^3 * 5 es: 40.000000

Como ves estamos elevando el 2 a la potencia 3 y luego multiplicando por 5, así que es 8 multiplicado por 5 que al final da 40, por lo que estamos evaluando la expresión matemática a partir de la cadena.

Por ahora no estamos enviando ninguna variable; solo estamos evaluando, si lo vemos así, números literales.

Variables dentro de la expresión

No siempre vamos a querer resolver una expresión matemática en C usando números literales; habrá ocasiones en las que necesitamos pasarle variables.

Para ello debemos definir las variables, después crear un arreglo de tipo te_variable y finalmente creamos una expresión con te_compile. Aunque suene complejo, no lo es.

Comenzamos definiendo lo necesario:

  char *ecuacion = "x * y * z * pow(x, y)";
  // Es importante usar el tipo double, y no float
  double x, y, z;

  /*
  Juntamos las variables del programa con las de la expresión. Nota
  que son punteros, así, podemos modificar las variables más tarde pero
  la expresión ya estará compilada
*/  te_variable variables[] = {
      {"x", &x},
      {"y", &y},
      {"z", &z},
  };
  int numeroVariables =
      3; // Esto se define manualmente; es la cantidad de variables

Vamos a resolver la expresión x * y * z * pow(x, y); si te fijas, se puede usar pow o el operador ^ para elevar un número. Dentro de la expresión se utilizará a x, y y z por eso es que creamos un arreglo y le pasamos el nombre de la variable, y el apuntador a la variable.

Más abajo definimos el número de variables, que son 3 en este caso, pues la función requiere que le digamos cuántas variables son (por ello de los arreglos, apuntadores a arreglos, funciones, etcétera)

Ahora compilamos la expresión:

te_expr *expresion = te_compile(ecuacion, variables, numeroVariables, 0);
if (!expresion) {
  printf("Error en la expresion");
  return EXIT_FAILURE;
}

Eso nos devolverá un apuntador a una variable de tipo te_expr, más tarde debemos liberar la memoria ocupada por la misma usando te_free. Lo importante por el momento es verificar que no se haya regresado un apuntador nulo, lo cual indica que hay un error en la expresión.

Llegados a este punto podemos invocar a te_eval pasándole la expresión, y se resolverá de acuerdo al valor de las variables.

Ya no será necesario compilar la expresión de nuevo, pues como pasamos el apuntador a la variable, siempre que evaluemos la expresión se tomará el valor actual.

/*
  Llegados a este punto estamos seguros de que la expresión es válida

  Nota: no es necesario escanear, podrías asignar tú mismo los valores, por
  ejemplo: x = 1;
*/printf("Vamos a resolver %s\n", ecuacion);
printf("Ingresa el valor de x:\n");
scanf("%lf", &x);
printf("Ingresa el valor de y:\n");
scanf("%lf", &y);
printf("Ingresa el valor de z:\n");
scanf("%lf", &z);

// La evaluamos e imprimimos el valor
double resultado = te_eval(expresion);
printf("Resultado: %f", resultado);
// Liberamos el puntero, o mejor dicho, la memoria ocupada por la expresión
te_free(expresion);

En este caso estoy solicitando el valor de las variables por teclado usando scanf; no es estrictamente necesario hacer esto, pues puedes definir las variables como tú quieras.

El código completo es:

/*
  ____          _____               _ _           _
 |  _ \        |  __ \             (_) |         | |
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |
        |___/                               |___/

    Blog:       https://parzibyte.me/blog
    Ayuda:      https://parzibyte.me/blog/contrataciones-ayuda/
    Contacto:   https://parzibyte.me/blog/contacto/

    Copyright (c) 2020 Luis Cabrera Benito
    Licenciado bajo la licencia MIT

    El texto de arriba debe ser incluido en cualquier redistribución
*/#include <stdio.h>
#include <stdlib.h>
// Se debe incluir con "" y no con <>, para que busque de manera relativa
#include "tinyexpr.h"
#include "tinyexpr.c"

int main() {
  /*
      Ahora tenemos a las funciones:
      te_interp
      te_compile
      te_eval
      te_free
  */  char *ecuacion = "x * y * z * pow(x, y)";
  // Es importante usar el tipo double, y no float
  double x, y, z;

  /*
  Juntamos las variables del programa con las de la expresión. Nota
  que son punteros, así, podemos modificar las variables más tarde pero
  la expresión ya estará compilada
*/  te_variable variables[] = {
      {"x", &x},
      {"y", &y},
      {"z", &z},
  };
  int numeroVariables =
      3; // Esto se define manualmente; es la cantidad de variables

  // Compilamos la expresión, para usarla más tarde. Aquí mismo comprobamos que
  // no haya errores en la misma
  te_expr *expresion = te_compile(ecuacion, variables, numeroVariables, 0);
  if (!expresion) {
    printf("Error en la expresion");
    return EXIT_FAILURE;
  }
  /*
    Llegados a este punto estamos seguros de que la expresión es válida

    Nota: no es necesario escanear, podrías asignar tú mismo los valores, por
    ejemplo: x = 1;
  */  printf("Vamos a resolver %s\n", ecuacion);
  printf("Ingresa el valor de x:\n");
  scanf("%lf", &x);
  printf("Ingresa el valor de y:\n");
  scanf("%lf", &y);
  printf("Ingresa el valor de z:\n");
  scanf("%lf", &z);

  // La evaluamos e imprimimos el valor
  double resultado = te_eval(expresion);
  printf("Resultado: %f", resultado);
  // Liberamos el puntero, o mejor dicho, la memoria ocupada por la expresión
  te_free(expresion);
}

Si lo ejecutamos veremos que la expresión se resuelve de manera correcta:

Evaluar expresión matemática en C pasando valor de las variables

Manejo de errores

Se puede pasar el apuntador a una variable que tendrá el error. Ese error tendrá la posición en la que se encontró al mismo.

Si el error es 0 entonces no hubo error, vamos a probar con una expresión que intencionalmente no está correcta:

/*
  ____          _____               _ _           _
 |  _ \        |  __ \             (_) |         | |
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |
        |___/                               |___/

    Blog:       https://parzibyte.me/blog
    Ayuda:      https://parzibyte.me/blog/contrataciones-ayuda/
    Contacto:   https://parzibyte.me/blog/contacto/

    Copyright (c) 2020 Luis Cabrera Benito
    Licenciado bajo la licencia MIT

    El texto de arriba debe ser incluido en cualquier redistribución
*/
#include <stdio.h>
#include <stdlib.h>
// Se debe incluir con "" y no con <>, para que busque de manera relativa
#include "tinyexpr.h"
#include "tinyexpr.c"

int main() {
  /*
      En la siguiente expresión hay un error
      intencional
  */  char *expresion = "2^3 * ";
  int err = 0;
  double resultado = te_interp(expresion, &err);
  if (err != 0) {
    printf("Hay un error en la expresión cerca de la columna %d\n", err);
    return EXIT_FAILURE;
  }
  printf("%s es: %f", expresion, resultado);
}

Si lo ejecutamos, el programa indica que hay un error en la columna 6; y es porque seguramente esperaba un operando o expresión después del operador, pero no encontró nada.

De este modo manejamos errores y prevenimos comportamientos inesperados.

Compilar en Visual Studio 2010

Un lector del blog me envió un mensaje indicando cómo compilarlo en Visual Studio 2010. Me envió lo siguiente y me comentó lo siguiente:

El archivo tinyExpr.c no es “C” estándar, no se puede compilar con el VisualStudio. Lo he renombrado por tinyExpr.cpp. Hay declaraciones de variables
en mitad de un bloque de instrucciones. El compilador del VisualStudio no lo permite. Se soluciona renombrando el archivo a .cpp

Nota: yo no lo he probado en Visual Studio 2010 ya que prefiero compilar con GCC actualizado, así que no puedo confirmar si funciona.

// SPDX-License-Identifier: Zlib
/*
 * TINYEXPR - Tiny recursive descent parser and evaluation engine in C
 *
 * Copyright (c) 2015-2020 Lewis Van Winkle
 *
 * http://CodePlea.com
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgement in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */
/* COMPILE TIME OPTIONS */
/* Exponentiation associativity:
For a^b^c = (a^b)^c and -a^b = (-a)^b do nothing.
For a^b^c = a^(b^c) and -a^b = -(a^b) uncomment the next line.*//* #define TE_POW_FROM_RIGHT */
/* Logarithms
For log = base 10 log do nothing
For log = natural log uncomment the next line. *//* #define TE_NAT_LOG */
#include "tinyexpr.h"
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h>

#ifndef NAN
#define NAN 99e99 //(0.0/0.0)
#endif

#ifndef INFINITY
#define INFINITY 99e99 //(1.0/0.0)
#endif

typedef double (*te_fun2)(double, double);

enum
{
    TOK_NULL = TE_CLOSURE7 + 1,
    TOK_ERROR,
    TOK_END,
    TOK_SEP,
    TOK_OPEN,
    TOK_CLOSE,
    TOK_NUMBER,
    TOK_VARIABLE,
    TOK_INFIX
};

enum
{
    TE_CONSTANT = 1
};

typedef struct state
{
    const char *start;
    const char *next;
    int type;
    union
    {
        double value;
        const double *bound;
        const void *function;
    };
    void *context;

    const te_variable *lookup;
    int lookup_len;
} state;

#define TYPE_MASK(TYPE) ((TYPE)&0x0000001F)

#define IS_PURE(TYPE) (((TYPE)&TE_FLAG_PURE) != 0)
#define IS_FUNCTION(TYPE) (((TYPE)&TE_FUNCTION0) != 0)
#define IS_CLOSURE(TYPE) (((TYPE)&TE_CLOSURE0) != 0)
#define ARITY(TYPE) (((TYPE) & (TE_FUNCTION0 | TE_CLOSURE0)) ? ((TYPE)&0x00000007) : 0)
//#define NEW_EXPR(type, ...) new_expr((type), (const te_expr*[]){__VA_ARGS__})
//#define NEW_EXPR(type, ...) new_expr((type), (const te_expr**)(__VA_ARGS__))
// No funciona este ultimo define (no es lo mismo ** que * []) y el anterior da error de compilacion
// Se crean las funciones NEW_EXPR1 y NEW_EXPR2

static te_expr *new_expr(const int type, const te_expr *parameters[])
{
    const int arity = ARITY(type);
    const int psize = sizeof(void *) * arity;
    const int size = (sizeof(te_expr) - sizeof(void *)) + psize + (IS_CLOSURE(type) ? sizeof(void *) : 0);
    te_expr *ret = (te_expr *)malloc(size);
    memset(ret, 0, size);
    if (arity && parameters)
    {
        memcpy(ret->parameters, parameters, psize);
    }
    ret->type = type;
    ret->bound = 0;
    return ret;
}

static te_expr *NEW_EXPR1(const int type, const te_expr *p1)
{
    const te_expr *parameters[1];

    parameters[0] = p1;
    return new_expr(type, parameters);
}

static te_expr *NEW_EXPR2(const int type, const te_expr *p1, const te_expr *p2)
{
    const te_expr *parameters[2];

    parameters[0] = p1;
    parameters[1] = p2;
    return new_expr(type, parameters);
}

void te_free_parameters(te_expr *n)
{
    if (!n)
        return;
    switch (TYPE_MASK(n->type))
    {
    case TE_FUNCTION7:
    case TE_CLOSURE7:
        te_free((te_expr *)n->parameters[6]); /* Falls through. */    case TE_FUNCTION6:
    case TE_CLOSURE6:
        te_free((te_expr *)n->parameters[5]); /* Falls through. */    case TE_FUNCTION5:
    case TE_CLOSURE5:
        te_free((te_expr *)n->parameters[4]); /* Falls through. */    case TE_FUNCTION4:
    case TE_CLOSURE4:
        te_free((te_expr *)n->parameters[3]); /* Falls through. */    case TE_FUNCTION3:
    case TE_CLOSURE3:
        te_free((te_expr *)n->parameters[2]); /* Falls through. */    case TE_FUNCTION2:
    case TE_CLOSURE2:
        te_free((te_expr *)n->parameters[1]); /* Falls through. */    case TE_FUNCTION1:
    case TE_CLOSURE1:
        te_free((te_expr *)n->parameters[0]);
    }
}

void te_free(te_expr *n)
{
    if (!n)
        return;
    te_free_parameters(n);
    free(n);
}

static double add(double a, double b) { return a + b; }
static double sub(double a, double b) { return a - b; }
static double mul(double a, double b) { return a * b; }
static double divide(double a, double b) { return a / b; }
static double negate(double a) { return -a; }
static double comma(double a, double b)
{
    (void)a;
    return b;
}

static double tEfmod(double a, double b) { return fmod(a, b); }
static double tEfabs(double a) { return fabs(a); }
static double tEacos(double a) { return acos(a); }
static double tEasin(double a) { return asin(a); }
static double tEatan(double a) { return atan(a); }
static double tEatan2(double a, double b) { return atan2(a, b); }
static double tEceil(double a) { return ceil(a); }
static double tEcos(double a) { return cos(a); }
static double tEcosh(double a) { return cosh(a); }
static double tEexp(double a) { return exp(a); }
static double tEfloor(double a) { return floor(a); }
static double tElog(double a) { return log(a); }

static double tElog10(double a) { return log10(a); }
static double tEpow(double a, double b) { return pow(a, b); }
static double tEsin(double a) { return sin(a); }
static double tEsinh(double a) { return sinh(a); }
static double tEsqrt(double a) { return sqrt(a); }
static double tEtan(double a) { return tan(a); }
static double tEtanh(double a) { return tanh(a); }

static double tEpi(void) { return 3.14159265358979323846; }
static double tEe(void) { return 2.71828182845904523536; }

static double tEfac(double a)
{ /* simplest version of fac */    if (a < 0.0)
        return NAN;
    if (a > UINT_MAX)
        return INFINITY;
    unsigned int ua = (unsigned int)(a);
    unsigned long int result = 1, i;
    for (i = 1; i <= ua; i++)
    {
        if (i > ULONG_MAX / result)
            return INFINITY;
        result *= i;
    }
    return (double)result;
}
static double tEncr(double n, double r)
{
    if (n < 0.0 || r < 0.0 || n < r)
        return NAN;
    if (n > UINT_MAX || r > UINT_MAX)
        return INFINITY;
    unsigned long int un = (unsigned int)(n), ur = (unsigned int)(r), i;
    unsigned long int result = 1;
    if (ur > un / 2)
        ur = un - ur;
    for (i = 1; i <= ur; i++)
    {
        if (result > ULONG_MAX / (un - ur + i))
            return INFINITY;
        result *= un - ur + i;
        result /= i;
    }
    return result;
}
static double tEnpr(double n, double r) { return tEncr(n, r) * tEfac(r); }

static const te_variable functions[] = {
    /* must be in alphabetical order */    {"abs", tEfabs, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"acos", tEacos, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"asin", tEasin, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"atan", tEatan, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"atan2", tEatan2, TE_FUNCTION2 | TE_FLAG_PURE, 0},
    {"ceil", tEceil, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"cos", tEcos, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"cosh", tEcosh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"e", tEe, TE_FUNCTION0 | TE_FLAG_PURE, 0},
    {"exp", tEexp, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"fac", tEfac, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"floor", tEfloor, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"ln", tElog, TE_FUNCTION1 | TE_FLAG_PURE, 0},
#ifdef TE_NAT_LOG
    {"log", tElog, TE_FUNCTION1 | TE_FLAG_PURE, 0},
#else
    {"log", tElog10, TE_FUNCTION1 | TE_FLAG_PURE, 0},
#endif
    {"log10", tElog10, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"ncr", tEncr, TE_FUNCTION2 | TE_FLAG_PURE, 0},
    {"npr", tEnpr, TE_FUNCTION2 | TE_FLAG_PURE, 0},
    {"pi", tEpi, TE_FUNCTION0 | TE_FLAG_PURE, 0},
    {"pow", tEpow, TE_FUNCTION2 | TE_FLAG_PURE, 0},
    {"sin", tEsin, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"sinh", tEsinh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"sqrt", tEsqrt, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"tan", tEtan, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {"tanh", tEtanh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
    {0, 0, 0, 0}};

static const te_variable *find_builtin(const char *name, int len)
{
    int imin = 0;
    int imax = sizeof(functions) / sizeof(te_variable) - 2;

    /*Binary search.*/    while (imax >= imin)
    {
        const int i = (imin + ((imax - imin) / 2));
        int c = strncmp(name, functions[i].name, len);
        if (!c)
            c = '\0' - functions[i].name[len];
        if (c == 0)
        {
            return functions + i;
        }
        else if (c > 0)
        {
            imin = i + 1;
        }
        else
        {
            imax = i - 1;
        }
    }

    return 0;
}

static const te_variable *find_lookup(const state *s, const char *name, int len)
{
    int iters;
    const te_variable *var;
    if (!s->lookup)
        return 0;

    for (var = s->lookup, iters = s->lookup_len; iters; ++var, --iters)
    {
        if (strncmp(name, var->name, len) == 0 && var->name[len] == '\0')
        {
            return var;
        }
    }
    return 0;
}

void next_token(state *s)
{
    s->type = TOK_NULL;

    do
    {

        if (!*s->next)
        {
            s->type = TOK_END;
            return;
        }

        /* Try reading a number. */        if ((s->next[0] >= '0' && s->next[0] <= '9') || s->next[0] == '.')
        {
            s->value = strtod(s->next, (char **)&s->next);
            s->type = TOK_NUMBER;
        }
        else
        {
            /* Look for a variable or builtin function call. */            if (isalpha(s->next[0]))
            {
                const char *start;
                start = s->next;
                while (isalpha(s->next[0]) || isdigit(s->next[0]) || (s->next[0] == '_'))
                    s->next++;

                const te_variable *var = find_lookup(s, start, s->next - start);
                if (!var)
                    var = find_builtin(start, s->next - start);

                if (!var)
                {
                    s->type = TOK_ERROR;
                }
                else
                {
                    switch (TYPE_MASK(var->type))
                    {
                    case TE_VARIABLE:
                        s->type = TOK_VARIABLE;
                        s->bound = (double *)var->address;
                        break;

                    case TE_CLOSURE0:
                    case TE_CLOSURE1:
                    case TE_CLOSURE2:
                    case TE_CLOSURE3: /* Falls through. */                    case TE_CLOSURE4:
                    case TE_CLOSURE5:
                    case TE_CLOSURE6:
                    case TE_CLOSURE7:              /* Falls through. */                        s->context = var->context; /* Falls through. */
                    case TE_FUNCTION0:
                    case TE_FUNCTION1:
                    case TE_FUNCTION2:
                    case TE_FUNCTION3: /* Falls through. */                    case TE_FUNCTION4:
                    case TE_FUNCTION5:
                    case TE_FUNCTION6:
                    case TE_FUNCTION7: /* Falls through. */                        s->type = var->type;
                        s->function = var->address;
                        break;
                    }
                }
            }
            else
            {
                /* Look for an operator or special character. */                switch (s->next++[0])
                {
                case '+':
                    s->type = TOK_INFIX;
                    s->function = add;
                    break;
                case '-':
                    s->type = TOK_INFIX;
                    s->function = sub;
                    break;
                case '*':
                    s->type = TOK_INFIX;
                    s->function = mul;
                    break;
                case '/':
                    s->type = TOK_INFIX;
                    s->function = divide;
                    break;
                case '^':
                    s->type = TOK_INFIX;
                    s->function = tEpow;
                    break;
                case '%':
                    s->type = TOK_INFIX;
                    s->function = tEfmod;
                    break;
                case '(':
                    s->type = TOK_OPEN;
                    break;
                case ')':
                    s->type = TOK_CLOSE;
                    break;
                case ',':
                    s->type = TOK_SEP;
                    break;
                case ' ':
                case '\t':
                case '\n':
                case '\r':
                    break;
                default:
                    s->type = TOK_ERROR;
                    break;
                }
            }
        }
    } while (s->type == TOK_NULL);
}

static te_expr *list(state *s);
static te_expr *expr(state *s);
static te_expr *power(state *s);

static te_expr *base(state *s)
{
    /* <base>      =    <constant> | <variable> | <function-0> {"(" ")"} | <function-1> <power> | <function-X> "(" <expr> {"," <expr>} ")" | "(" <list> ")" */    te_expr *ret;
    int arity;

    switch (TYPE_MASK(s->type))
    {
    case TOK_NUMBER:
        ret = new_expr(TE_CONSTANT, 0);
        ret->value = s->value;
        next_token(s);
        break;

    case TOK_VARIABLE:
        ret = new_expr(TE_VARIABLE, 0);
        ret->bound = s->bound;
        next_token(s);
        break;

    case TE_FUNCTION0:
    case TE_CLOSURE0:
        ret = new_expr(s->type, 0);
        ret->function = s->function;
        if (IS_CLOSURE(s->type))
            ret->parameters[0] = s->context;
        next_token(s);
        if (s->type == TOK_OPEN)
        {
            next_token(s);
            if (s->type != TOK_CLOSE)
            {
                s->type = TOK_ERROR;
            }
            else
            {
                next_token(s);
            }
        }
        break;

    case TE_FUNCTION1:
    case TE_CLOSURE1:
        ret = new_expr(s->type, 0);
        ret->function = s->function;
        if (IS_CLOSURE(s->type))
            ret->parameters[1] = s->context;
        next_token(s);
        ret->parameters[0] = power(s);
        break;

    case TE_FUNCTION2:
    case TE_FUNCTION3:
    case TE_FUNCTION4:
    case TE_FUNCTION5:
    case TE_FUNCTION6:
    case TE_FUNCTION7:
    case TE_CLOSURE2:
    case TE_CLOSURE3:
    case TE_CLOSURE4:
    case TE_CLOSURE5:
    case TE_CLOSURE6:
    case TE_CLOSURE7:
        arity = ARITY(s->type);

        ret = new_expr(s->type, 0);
        ret->function = s->function;
        if (IS_CLOSURE(s->type))
            ret->parameters[arity] = s->context;
        next_token(s);

        if (s->type != TOK_OPEN)
        {
            s->type = TOK_ERROR;
        }
        else
        {
            int i;
            for (i = 0; i < arity; i++)
            {
                next_token(s);
                ret->parameters[i] = expr(s);
                if (s->type != TOK_SEP)
                {
                    break;
                }
            }
            if (s->type != TOK_CLOSE || i != arity - 1)
            {
                s->type = TOK_ERROR;
            }
            else
            {
                next_token(s);
            }
        }

        break;

    case TOK_OPEN:
        next_token(s);
        ret = list(s);
        if (s->type != TOK_CLOSE)
        {
            s->type = TOK_ERROR;
        }
        else
        {
            next_token(s);
        }
        break;

    default:
        ret = new_expr(0, 0);
        s->type = TOK_ERROR;
        ret->value = NAN;
        break;
    }

    return ret;
}

static te_expr *power(state *s)
{
    /* <power>     =    {("-" | "+")} <base> */    int sign = 1;
    while (s->type == TOK_INFIX && (s->function == add || s->function == sub))
    {
        if (s->function == sub)
            sign = -sign;
        next_token(s);
    }

    te_expr *ret;

    if (sign == 1)
    {
        ret = base(s);
    }
    else
    {
        // ret = NEW_EXPR (TE_FUNCTION1 | TE_FLAG_PURE, base(s));
        ret = NEW_EXPR1(TE_FUNCTION1 | TE_FLAG_PURE, base(s));
        ret->function = negate;
    }

    return ret;
}

#ifdef TE_POW_FROM_RIGHT
static te_expr *factor(state *s)
{
    /* <factor>    =    <power> {"^" <power>} */    te_expr *ret = power(s);

    int neg = 0;

    if (ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate)
    {
        te_expr *se = ret->parameters[0];
        free(ret);
        ret = se;
        neg = 1;
    }

    te_expr *insertion = 0;

    while (s->type == TOK_INFIX && (s->function == tEpow))
    {
        te_fun2 t = s->function;
        next_token(s);

        if (insertion)
        {
            /* Make exponentiation go right-to-left. */            te_expr *insert = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, insertion->parameters[1], power(s));
            insert->function = t;
            insertion->parameters[1] = insert;
            insertion = insert;
        }
        else
        {
            ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s));
            ret->function = t;
            insertion = ret;
        }
    }

    if (neg)
    {
        ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, ret);
        ret->function = negate;
    }

    return ret;
}
#else
static te_expr *factor(state *s)
{
    /* <factor>    =    <power> {"^" <power>} */    te_expr *ret = power(s);

    while (s->type == TOK_INFIX && (s->function == tEpow))
    {
        te_fun2 t = (te_fun2)s->function;
        next_token(s);
        // ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s));
        ret = NEW_EXPR2(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s));
        ret->function = t;
    }

    return ret;
}
#endif

static te_expr *term(state *s)
{
    /* <term>      =    <factor> {("*" | "/" | "%") <factor>} */    te_expr *ret = factor(s);

    while (s->type == TOK_INFIX && (s->function == mul || s->function == divide || s->function == tEfmod))
    {
        te_fun2 t = (te_fun2)s->function;
        next_token(s);
        // ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, factor(s));
        ret = NEW_EXPR2(TE_FUNCTION2 | TE_FLAG_PURE, ret, factor(s));
        ret->function = t;
    }

    return ret;
}

static te_expr *expr(state *s)
{
    /* <expr>      =    <term> {("+" | "-") <term>} */    te_expr *ret = term(s);

    while (s->type == TOK_INFIX && (s->function == add || s->function == sub))
    {
        te_fun2 t = (te_fun2)s->function;
        next_token(s);
        // ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, term(s));
        ret = NEW_EXPR2(TE_FUNCTION2 | TE_FLAG_PURE, ret, term(s));
        ret->function = t;
    }

    return ret;
}

static te_expr *list(state *s)
{
    /* <list>      =    <expr> {"," <expr>} */    te_expr *ret = expr(s);

    while (s->type == TOK_SEP)
    {
        next_token(s);
        // ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, expr(s));
        ret = NEW_EXPR2(TE_FUNCTION2 | TE_FLAG_PURE, ret, expr(s));
        ret->function = comma;
    }

    return ret;
}

#define TE_FUN(...) ((double (*)(__VA_ARGS__))n->function)
#define M(e) te_eval((const te_expr *)n->parameters[e])

double te_eval(const te_expr *n)
{
    if (!n)
        return NAN;

    switch (TYPE_MASK(n->type))
    {
    case TE_CONSTANT:
        return n->value;
    case TE_VARIABLE:
        return *n->bound;

    case TE_FUNCTION0:
    case TE_FUNCTION1:
    case TE_FUNCTION2:
    case TE_FUNCTION3:
    case TE_FUNCTION4:
    case TE_FUNCTION5:
    case TE_FUNCTION6:
    case TE_FUNCTION7:
        switch (ARITY(n->type))
        {
        case 0:
            return TE_FUN(void)();
        case 1:
            return TE_FUN(double)(M(0));
        case 2:
            return TE_FUN(double, double)(M(0), M(1));
        case 3:
            return TE_FUN(double, double, double)(M(0), M(1), M(2));
        case 4:
            return TE_FUN(double, double, double, double)(M(0), M(1), M(2), M(3));
        case 5:
            return TE_FUN(double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4));
        case 6:
            return TE_FUN(double, double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4), M(5));
        case 7:
            return TE_FUN(double, double, double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4), M(5), M(6));
        default:
            return NAN;
        }

    case TE_CLOSURE0:
    case TE_CLOSURE1:
    case TE_CLOSURE2:
    case TE_CLOSURE3:
    case TE_CLOSURE4:
    case TE_CLOSURE5:
    case TE_CLOSURE6:
    case TE_CLOSURE7:
        switch (ARITY(n->type))
        {
        case 0:
            return TE_FUN(void *)(n->parameters[0]);
        case 1:
            return TE_FUN(void *, double)(n->parameters[1], M(0));
        case 2:
            return TE_FUN(void *, double, double)(n->parameters[2], M(0), M(1));
        case 3:
            return TE_FUN(void *, double, double, double)(n->parameters[3], M(0), M(1), M(2));
        case 4:
            return TE_FUN(void *, double, double, double, double)(n->parameters[4], M(0), M(1), M(2), M(3));
        case 5:
            return TE_FUN(void *, double, double, double, double, double)(n->parameters[5], M(0), M(1), M(2), M(3), M(4));
        case 6:
            return TE_FUN(void *, double, double, double, double, double, double)(n->parameters[6], M(0), M(1), M(2), M(3), M(4), M(5));
        case 7:
            return TE_FUN(void *, double, double, double, double, double, double, double)(n->parameters[7], M(0), M(1), M(2), M(3), M(4), M(5), M(6));
        default:
            return NAN;
        }

    default:
        return NAN;
    }
}

#undef TE_FUN
#undef M

static void optimize(te_expr *n)
{
    /* Evaluates as much as possible. */    if (n->type == TE_CONSTANT)
        return;
    if (n->type == TE_VARIABLE)
        return;

    /* Only optimize out functions flagged as pure. */    if (IS_PURE(n->type))
    {
        const int arity = ARITY(n->type);
        int known = 1;
        int i;
        for (i = 0; i < arity; ++i)
        {
            optimize((te_expr *)n->parameters[i]);
            if (((te_expr *)(n->parameters[i]))->type != TE_CONSTANT)
            {
                known = 0;
            }
        }
        if (known)
        {
            const double value = te_eval(n);
            te_free_parameters(n);
            n->type = TE_CONSTANT;
            n->value = value;
        }
    }
}

te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, int *error)
{
    state s;
    s.start = s.next = expression;
    s.lookup = variables;
    s.lookup_len = var_count;

    next_token(&s);
    te_expr *root = list(&s);

    if (s.type != TOK_END)
    {
        te_free(root);
        if (error)
        {
            *error = (s.next - s.start);
            if (*error == 0)
                *error = 1;
        }
        return 0;
    }
    else
    {
        optimize(root);
        if (error)
            *error = 0;
        return root;
    }
}

double te_interp(const char *expression, int *error)
{
    te_expr *n = te_compile(expression, 0, 0, error);
    double ret;
    if (n)
    {
        ret = te_eval(n);
        te_free(n);
    }
    else
    {
        ret = NAN;
    }
    return ret;
}

static void pn(const te_expr *n, int depth)
{
    int i, arity;
    printf("%*s", depth, "");

    switch (TYPE_MASK(n->type))
    {
    case TE_CONSTANT:
        printf("%f\n", n->value);
        break;
    case TE_VARIABLE:
        printf("bound %p\n", n->bound);
        break;

    case TE_FUNCTION0:
    case TE_FUNCTION1:
    case TE_FUNCTION2:
    case TE_FUNCTION3:
    case TE_FUNCTION4:
    case TE_FUNCTION5:
    case TE_FUNCTION6:
    case TE_FUNCTION7:
    case TE_CLOSURE0:
    case TE_CLOSURE1:
    case TE_CLOSURE2:
    case TE_CLOSURE3:
    case TE_CLOSURE4:
    case TE_CLOSURE5:
    case TE_CLOSURE6:
    case TE_CLOSURE7:
        arity = ARITY(n->type);
        printf("f%d", arity);
        for (i = 0; i < arity; i++)
        {
            printf(" %p", n->parameters[i]);
        }
        printf("\n");
        for (i = 0; i < arity; i++)
        {
            pn((const te_expr *)n->parameters[i], depth + 1);
        }
        break;
    }
}

void te_print(const te_expr *n)
{
    pn(n, 0);
}

Conclusión

Este analizador matemático en C soporta varias cosas. Por ejemplo, soporta la invocación a pow; operaciones comunes y además otras funciones como fac (para el factorial), ncr para el número de combinaciones, entre otros.

También define algunas constantes como Pi o e. Recuerda que puedes darle un vistazo en el repositorio de GitHub.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • Buenas, la verdad necesito mas informacion sobre como puedo usar tinyexpr en devc++
    Mil gracias.

Entradas recientes

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

3 días hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

3 días hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

3 días hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

3 días hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

3 días hace

Solución: Apache – Server unable to read htaccess file

Ayer estaba editando unos archivos que son servidos con el servidor Apache y al visitarlos…

3 días hace

Esta web usa cookies.