Biblioteca Java para transformar una fórmula matemática en un AST

Estoy buscando una biblioteca de Java que pueda analizar fórmulas matemáticas en un AST (árbol de sintaxis abstracta).

ACTUALIZACIÓN:
estoy abierto a alternativas en lenguajes que no sean Java, siempre que pueda llamar a estas herramientas/bibliotecas desde Java.
Por ejemplo, JavaScript se puede incrustar usando el motor Rhino .

Requerimientos esenciales:

  1. La capacidad de analizar fórmulas en notación infija .

  2. La capacidad de preservar variables desconocidas: no estoy buscando una calculadora.

  3. Una lista personalizable de operadores y funciones.
    También sería excelente si se pudieran eliminar las funciones ya integradas (por ejemplo, sin(x)).

Requisitos no esenciales:

  • La biblioteca puede ser de código abierto, pero no es necesario que lo sea. Una biblioteca gratuita es suficiente.
Nota: aunque mientras tanto encontré una biblioteca de JavaScript más o menos adecuada, ¡mejores alternativas (preferiblemente en Java) siguen siendo bienvenidas!

Respuestas (6)

Matemáticas.js

La parte de JavaScript

“Math.js es una extensa biblioteca matemática para JavaScript y Node.js”.
Léame del proyecto

Proporciona una función parse() .
Ejemplo utilizando el entorno NodeJS:

var math = require('mathjs')();
var ast = math.parse('xy^(1/2)');

// Fully log the object
var util = require('util');
console.log(util.inspect(ast, {showHidden: false, depth: null}));

Producción:

{ op: '^',
  fn: 'pow',
  params:
   [ { name: 'xy' },
     { op: '/',
       fn: 'divide',
       params:
        [ { valueType: 'number', value: '1' },
          { valueType: 'number', value: '2' } ] } ] }

La parte Java

Utilizo Java Nashorn VM (solo disponible en Java >= 8) para ejecutar JavaScript.

Arquitectura del programa:

User ---------------> Java
      inputs formla    |----> Nashorn ----> math.js
                       |<---------------------|
User <-----------------|

Usar el motor Nashorn es bastante simple (se omite el manejo de excepciones)

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(readerInstancePointingToMathJsLibrary);
engine.eval(readerInstancePointingToBridgeJavaScript);

El código JavaScript del puente depende en gran medida de la implementación de los nodos AST. Aprovechamos la capacidad de Nashorn para crear y transferir objetos Java en JavaScript a Java. Ejemplo:

var math = mathjs();
function convert(formula) {
    var ast = math.parse(formula);

    var javaAst = /* build your AST with Java objects */
    return javaAst;
}

Ahora podemos acceder a esa función desde Java e incluso pasar argumentos arbitrarios:

Invocable inv = (Invocable) engine;
// Expression is my AST node type in Java
expr = (Expression) inv.invokeFunction("convert", formulaFromUser);

Nota: necesitaba una forma rápida de analizar expresiones matemáticas. Siempre es preferible un analizador (ya sea escrito a mano o generado por un generador de analizador). No obstante, el código anterior muestra cómo se puede integrar fácilmente Java Nashorn.

JavaScript

Hace unos meses, utilicé Esprima para analizar dichas entradas. En realidad, Esprima analiza cualquier entrada de JavaScript (convirtiéndola en un árbol), por lo que debería funcionar para tales expresiones matemáticas.

Después de incluir Esprima, puede hacer:

esprima.parse(input);

... donde inputhay una cadena que contiene la entrada que debe analizarse (si no es válida, se generará un error).

Ejemplo

esprima.parse("1+2*3")

devuelve el siguiente objeto:

{
    "type": "Program",
    "body": [
        {
            "type": "ExpressionStatement",
            "expression": {
                "type": "BinaryExpression",
                "operator": "+",
                "left": {
                    "type": "Literal",
                    "value": 1,
                    "raw": "1"
                },
                "right": {
                    "type": "BinaryExpression",
                    "operator": "*",
                    "left": {
                        "type": "Literal",
                        "value": 2,
                        "raw": "2"
                    },
                    "right": {
                        "type": "Literal",
                        "value": 3,
                        "raw": "3"
                    }
                }
            }
        }
    ]
}

Modifiqué el código de Esprima y lo usé en un proyecto experimental para definir operadores personalizados en JavaScript. La aplicación es de código abierto en GitHub: http://ionicabizau.net/JavaScript-custom-operators/

Esprima parece ser mucho más potente y capaz de analizar funciones de lo que realmente necesito. Sin embargo, +1 y sus operadores son muy interesantes.

Java

Parece que JEP es un analizador de expresiones matemáticas.

JEP es una API de Java para analizar y evaluar expresiones matemáticas. Con esta biblioteca, puede permitir que sus usuarios ingresen una fórmula arbitraria como una cadena y la evalúen instantáneamente. JEP admite variables, constantes y funciones definidas por el usuario. Se incluyen varias funciones y constantes matemáticas comunes.

Características

  • Paquete fácil de usar para analizar expresiones matemáticas
  • Tamaño pequeño (solo 56kb como archivo jar)
  • Admite expresiones booleanas (!, &&, ||, <, >, !=, ==, >= y <=)
  • Evaluación rápida (la expresión se puede evaluar rápidamente para diferentes valores de variables)
  • Incluye funciones matemáticas comunes
  • Ampliable a través de funciones definidas por el usuario
  • Constantes predefinidas como 'pi' y 'e'
  • Compatibilidad con cadenas, números complejos y vectores
  • Compatibilidad con la multiplicación implícita (permite el uso de expresiones como "3x" en lugar de "3*x")
  • Permite elegir entre variables declaradas y no declaradas
  • Compatible con Java 1.1 (probado con Sun Java JDK 1.1.8 y Microsoft Java VM)
  • Admite caracteres Unicode (incluidos los símbolos griegos)
  • Incluye gramática JavaCC a partir de la cual se generan las clases principales

Es de código abierto en SourceForge .

Además, hay una pregunta SO sobre este tema: https://stackoverflow.com/q/4589951/1420197

El proyecto inicial parece estar descontinuado. La página del proyecto SourceForge enlaza con un nuevo sitio externo , que ofrece Jep Java como producto comercial ($550 por binarios, $950 por el código fuente).
@ComFreek Tienes razón. De todos modos, creo que el proyecto inicial debe cubrir lo que necesitas. ¡Gracias por la generosidad! :-)

Construir analizadores de fórmulas/constructores de árboles es un ejercicio bastante simple. Puede buscar una biblioteca, pero siempre terminará modificándola para producir exactamente lo que desea. En cambio, es probable que sea más fácil simplemente codificar lo que desea.

  • Este enlace StackOverflow proporciona instrucciones sobre cómo construir analizadores como este fácilmente a mano. También proporciona acceso a un segundo enlace, que muestra cómo convertir fácilmente dichos analizadores en otros que produzcan AST.
  • Puede personalizarlo fácilmente para incluir los operadores infijos que desee, los operadores de función ("sin") que desee, valores escalares y nombres de variables.
  • Puede codificar este tipo de analizadores en prácticamente cualquier idioma, incluido Java.

Si desea analizar algo considerablemente más complejo que una expresión, puede presionar este tipo de analizador para que lo haga, pero generalmente es más fácil cambiar al generador de analizador en ese caso.

Java

Symja transforma una expresión matemática en el siguiente Symja AST .

He usado javassist para convertir expresiones Java en código de bytes (que puede ser lo que finalmente esté buscando si desea evaluar la expresión). Incluso puede redefinir funciones existentes.

http://www.csg.ci.iu-tokyo.ac.jp/~chiba/javassist/

Perdóneme si no lo entendí bien, pero ¿cómo me permite Javassist analizar la entrada del usuario (cadena) en una estructura de árbol (objetos Java)?
Probablemente no. Le permite compilar su expresión en un método Java al que luego puede llamar directamente para que se evalúe. Esto se debe a que esperaba que este fuera tu objetivo final. Si esto es parte de un ejercicio para escribir un compilador, debería hacerlo a mano :)