javadoc JMEScript Manual de Usuario JME Homepage

introduccion
version
entrada
tipos
 ■ realdoble
 ■ complejo
 ■ enterogrande
 ■ realgrande
 ■ booleano
 ■ texto
 ■ vector
 ■ diccionario
var_const
thread
 ■ expthread
operadores
operadores_usuario
funciones
funciones_usuario
constantes
entrada_tostring
repl
csv
 ■ import_csv
 ■ export_csv
html_table
dependencies
considerations


Introducción a JME


Java Math Expression 'JME' es una biblioteca freeware ejecutable (incluye una consola REPL) para el lenguaje Java que implementa un potente evaluador matemático de expresiones matemáticas introducidas en tiempo de ejecución en forma de cadena (Math Parser en inglés).

La utilización de la biblioteca JME es sencilla y el lenguaje matemático que utiliza es muy similar al lenguaje matemático usual y al utilizado en muchos programas de cálculo y los propios lenguajes de programación.

JME realiza cálculos numéricos, no es una biblioteca de cálculo simbólico (CAS), por lo que no simplificará expresiones o resolverá ecuaciones algebraicamente (si algunas numéricas). En lugar de eso devolverá como resultado los tipos de datos del lenguaje.

El lenguaje JME es el lenguaje implementado por la biblioteca JME. Es un lenguaje case insensitive, por lo que todas las variables, constantes, funciones y caracteres en general no diferencian mayúsculas-minúsculas (excepto para el tipo Texto y ciertos operadores). Los tokens del lenguaje están formados por distintas clases de números, operadores, funciones, identificadores, paréntesis, booleanos, vectores y cadenas. JME no es thread-safe, por lo que el programador debe encargarse de la programación multihilo.

La clase jme.Expresion es la clase principal de la biblioteca JME, encargada de crear, almacenar y evaluar las expresiones.


Versión de Java VM


JME mantiene la compatibilidad con Java 6 para soportar portabilidad con Android y está testado en Java 7, 8. JME podría migrar a Java 8+ dependiendo de la portabilidad en Android.


Entrada de expresiones


Las expresiones JME se introducen mediante una cadena al constructor de la clase jme.Expresion:

// inicialización de una expresión (parseo)
try
{
    Expresion exp = new Expresion( "2*sqrt(3^2+4^2)" );
}
catch ( ExpresionException e ) { /*...*/ }

ExpresionException es la excepción base de todas las excepciones JME y hereda de RuntimeException, por lo que no es obligatorio capturarla pero si recomendable.

El método Expresion#evaluar evalúa la expresión y devuelve un objeto de tipo Terminal, que es la clase base de todos los tipos de datos JME que se tratarán en la próxima sección.

// evaluación
try
{
    Expresion exp = new Expresion( "2*sqrt(3^2+4^2)" );
    System.out.println( exp.evaluar() );  // (llamada implícita a toString del resultado)
}
catch ( ExpresionException e ) { /*...*/ }
10

Los espacios y tabuladores son ignorados, en general, en las expresiones JME, exceptuando el tipo Texto y pueden separarse factores (mediante multiplicación implícita) con espacios "3+4 6" → 27.


Tipos de datos


JME implementa 8 tipos de datos; RealDoble, Complejo, EnteroGrande, RealGrande, VectorEvaluado, Diccionario, Booleano y Texto. Además de dos pseudo-tipos; matrices y conjuntos. El diagrama de clases de la jerarquía de tipos es el siguiente:




Como puede verse en el diagrama, todos los tipos de datos heredan de la clase Terminal, que es la clase base de todos los tipos. Esta clase abstracta define métodos de comprobación de tipo y un método estático Terminal#castToJME que permite convertir un tipo u objeto cualquiera a tipo JME con las siguientes conversiones:

Además especifica el método Terminal#castToJava para cada tipo de dato JME, que convierte éste a objeto Java con las siguientes conversiones:


Todos los terminales implementan Cloneable y todas las clases JME Serializable.

Los 4 tipos de números heredan de la clase Numero, que especifica métodos de conversión a tipos Java y Complejo.


RealDoble


El tipo RealDoble es el wrapper JME para el tipo double de Java. Puede instanciarse un RealDoble en Java mediante un constructor que recibe un valor double:

RealDoble rd = new RealDoble(Math.PI);  // puede obtenerse también mediante la constante RealDoble.PI

La distintas formas de entrada de un valor RealDoble en una expresión JME pueden encontrarse en el manual de usuario#realdoble:

1E15(1+pi)

RealDoble: 4.141592653589793E15


E/S y conversiones de RealDoble
Método Tipo de retorno Conversión Posible pérdida de precisión
entrada String Devuelve la representación usual de un double, pero eliminando el punto cero final en enteros (2.0 → 2). Los valores especiales se introducen como inf,-inf y nan NO
toString String Devuelve la representación usual de un double, pero eliminando el punto cero final en enteros (2.0 → 2) NO
doble double Devuelve el propio valor que representa el RealDoble NO
re double Igual que doble() NO
im double Devuelve 0. NO (obviamente se pierde la parte real, pero la imaginaria es exacta)
complejo Complejo Devuelve un complejo con parte real igual al RealDoble e imaginaria 0. NO
longint long Devuelve la parte entera del RealDoble como entero largo SI
ent int Devuelve la parte entera del RealDoble como entero SI
biginteger BigInteger Devuelve la parte entera del RealDoble como BigInteger (valores no finitos no pueden convertirse a BigInteger) SI (si es entero NO)
bigdecimal BigDecimal Devuelve el RealDoble como BigDecimal (valores no finitos no pueden convertirse a BigDecimal) NO

Complejo


El tipo Complejo implementa números complejos a partir de dos double de Java. Puede instanciarse un Complejo en Java mediante un constructor que recibe los valores double de la parte real e imaginaria, o solo parte imaginaria para imaginarios puros:

Complejo z1 = new Complejo(-1,2);  // -1+2i
Complejo z2 = new Complejo(-3);  // -3i

El token î y la constante ui representan a la unidad imaginaria. La salida se muestra como I.

La distintas formas de entrada de un valor Complejo en una expresión JME pueden encontrarse en el manual de usuario#complejo:

cmplj(2,3)^polar(1,pi/2)+3-2ui

Complejo: 3.106419568690621-1.6411846545785433I


E/S y conversiones de Complejo
Método Tipo de retorno Conversión Posible pérdida de precisión
entrada String Representación en la forma a+bî del complejo NO
toString String Representación en la forma a+bI del complejo NO
doble double Igual que re() NO (obviamente se pierde la parte imaginaria, pero la real es exacta)
re double Devuelve la parte real del complejo NO (obviamente se pierde la parte imaginaria, pero la real es exacta)
im double Devuelve la parte imaginaria del complejo NO (obviamente se pierde la parte real, pero la imaginaria es exacta)
complejo Complejo Devuelve el propio complejo NO
longint long Devuelve la parte entera de la parte real como entero largo SI
ent int Devuelve la parte entera de la parte real como entero SI
biginteger BigInteger Devuelve la parte entera de la parte real como BigInteger (valores no finitos no pueden convertirse a BigInteger) SI
bigdecimal BigDecimal Devuelve la parte real como BigDecimal (valores no finitos no pueden convertirse a BigDecimal) NO (obviamente se pierde la parte imaginaria, pero la real es exacta)

EnteroGrande


El tipo EnteroGrande es el wrapper JME para la clase BigInteger de Java. Puede instanciarse un EnteroGrande en Java mediante una cadena, un BigInteger o un entero:

EnteroGrande n1 = new EnteroGrande( "123456789012345678901234567890" );
EnteroGrande n2 = new EnteroGrande( BigInteger.valueOf(3L) );
EnteroGrande n3 = new EnteroGrande(3);
EnteroGrande n4 = new EnteroGrande(3L);

La distintas formas de entrada de un valor EnteroGrande en una expresión JME pueden encontrarse en el manual de usuario#enterogrande:

3b^50*enterogrande(80)!

EnteroGrande: 51379569193711263672749930673071572699596143108424836522265297582329006950023338319748268146554922091327716819073018804305920000000000000000000


E/S y conversiones de EnteroGrande
Método Tipo de retorno Conversión Posible pérdida de precisión
entrada String Representación del entero más un sufijo b al final NO
toString String Representación del entero NO
doble double Devuelve un valor double a partir del EnteroGrande SI
re double Igual que doble() SI
im double 0. NO (obviamente se pierde la parte real, pero la imaginaria es exacta)
complejo Complejo Devuelve complejo con parte real double a partir del EnteroGrande e imaginaria igual a 0. SI
longint long Convierte a long SI
ent int Convierte a int SI
biginteger BigInteger Devuelve el propio valor que representa el EnteroGrande NO
bigdecimal BigDecimal Devuelve el valor como BigDecimal con escala 0 NO

RealGrande


El tipo RealGrande es el wrapper JME para la clase BigDecimal de Java. Puede instanciarse un RealGrande en Java mediante una cadena, un BigDecimal o un flotante:

RealGrande x1 = new RealGrande( "1.2345678901234567890123456789" );
RealGrande x2 = new RealGrande( BigDecimal.valueOf(Math.PI) );
RealGrande x3 = new RealGrande(3);
RealGrande x4 = new RealGrande(3L);
RealGrande x5 = new RealGrande(3.1f);
RealGrande x6 = new RealGrande(3.2d);

La distintas formas de entrada de un valor RealGrande en una expresión JME pueden encontrarse en el manual de usuario#realgrande:

100d!/realgrande(5+95)¡

RealGrande: 2.718281828459045235360287471352662


E/S y conversiones de RealGrande
Método Tipo de retorno Conversión Posible pérdida de precisión
entrada String Representación del número más un sufijo d al final NO
toString String Representación del número NO
doble double Convierte a double SI
re double Igual que doble() SI
im double 0. NO (obviamente se pierde la parte real, pero la imaginaria es exacta)
complejo Complejo Devuelve complejo con parte real double a partir del RealGrande e imaginaria igual a 0. SI
longint long Devuelve la parte entera convertida a entero largo SI
ent int Devuelve la parte entera convertida a entero SI
biginteger BigInteger Devuelve la parte entera como BigInteger SI (si es entero NO)
bigdecimal BigDecimal Devuelve el propio valor NO

Booleano


El tipo Booleano es el wrapper JME para el tipo boolean de Java. No puede instanciarse, sino que se emplea mediante el doubleton Booleano.VERDADERO y Booleano.FALSO o Booleano#booleano(boolean):

Booleano b1 = Booleano.VERDADERO;
Booleano b2 = Booleano.FALSO;
Booleano b3 = Booleano.booleano(true);
Booleano b4 = Booleano.booleano(false);

La distintas formas de entrada de un valor Booleano en una expresión JME pueden encontrarse en el manual de usuario#booleano:

[true,false,verdadero,falso,booleano('mentira'),booleano('SI')]

VectorEvaluado: [verdadero,falso,verdadero,falso,falso,verdadero]


E/S y conversiones de Booleano
Método Tipo de retorno Conversión
entrada String Si el flag de inglés está desactivado "verdadero"|"falso", si no "true"|"false"
toString String Igual que entrada()
booleano boolean Devuelve el valor del Booleano

EL método estático Booleano#setIngles(true) hace que la salida de Booleano sea en inglés; true|false (la entrada puede ser en español/inglés siempre).


Texto


El tipo Texto es el wrapper JME para la clase String de Java. Puede instanciarse mediante un constructor que recibe la cadena a envolver:

Texto t = new Texto( "Hola Mundo" );
Texto t0 = Texto.VACIO;  // cadena vacía

La distintas formas de entrada de un valor Texto en una expresión JME pueden encontrarse en el manual de usuario#texto:

'Foo Mc\'Bar '+stexto(pi)

Texto: 'Foo Mc'Bar 3.141592653589793'

Tanto las comillas simples como la barra "\" deben escaparse con "\".


E/S y conversiones de Texto
Método Tipo de retorno Conversión
entrada String Devuelve la propia cadena con comillas simples a inicio y fin y antepone carácter '\' a comillas simples y barras internas
toString String Devuelve la propia cadena con comillas simples a inicio y fin
textoPlano String Devuelve texto original (sin comillas)

EL método estático Booleano#setIngles(true) hace que la salida de Booleano sea en inglés; true|false (la entrada puede ser en español/inglés siempre).


Vector / VectorEvaluado


La clase Vector representa a una lista de Token JME. Los vectores JME pueden contener expresiones, por tanto no son datos finales, sino que deben evaluarse para obtener un VectorEvaluado, que es una lista de Terminal, y es el tipo devuelto al obtener un vector como resultado de evaluar una expresión. La clase Vector es usualmente usada internamente. Los vectores y diccionarios son los únicos tipos JME mutables. Puede instanciarse un VectorEvaluado mediante el constructor por defecto o un array de terminales:

VectorEvaluado v1 = new VectorEvaluado();
v1.nuevoComponente( RealDoble.DOS );
v1.nuevoComponente( Booleano.FALSO );

VectorEvaluado v2 = new VectorEvaluado( RealDoble.DOS, Booleano.FALSO );

System.out.println( "     v1: " + v1 );
System.out.println( "     v2: " + v2 );
System.out.println( "  v2[1]: " + v2.getComponente(1) );
System.out.println( "dim(v1): " + v1.dimension() );

v1: [2,falso] v2: [2,falso] v2[1]: falso dim(v1): 2


La distintas formas de entrada de un valor vectorial en una expresión JME pueden encontrarse en el manual de usuario#vector:

[1,4b,5,ui,[],[2,3,4]]

VectorEvaluado: [1,4,5,I,[],[2,3,4]]


E/S y conversiones de VectorEvaluado
Método Tipo de retorno Conversión
entrada String Devuelve la entrada de los elementos del vector entre corchetes separados por comas
toString String Devuelve la salida de los elementos del vector entre corchetes separados por comas
toArray Terminal[] Devuelve los elementos del vector en un array de terminales

El tipo Vector permite la introducción de matrices y conjuntos, que aunque no son tipos JME existen funciones que permiten la manipulación de vectores como matrices o conjuntos.
Puede verse la forma de entrada en manual de usuario#matriz y manual de usuario#conjunto.


Diccionario


La clase Diccionario representa un mapa de pares de terminales JME. Los pares o entradas están formados por dos terminales; una clave única y un valor. Puede instanciarse un Diccionario mediante el constructor por defecto, un array de terminales con un número par de elementos, un mapa de terminales, una cadena JSON con el método Diccionario#fromJSON o una cadena XML con el método Diccionario#fromXML:

Diccionario d1 = new Diccionario();  // diccionario vacío
Diccionario d2 = new Diccionario( new LinkedHashMap<Terminal,Terminal>() );  // diccionario vacío
d2.getMap().put( new Texto("libname"), new Texto("JME") );
Diccionario d3 = new Diccionario( new Texto("a"), RealDoble._INF,
                                  new Texto("b"), RealDoble.NAN,
                                  new Texto("c"), RealDoble._UNO );
Diccionario d4 = Diccionario.fromJSON( "{x:100,y:-1,z:0,state:false}" );
Diccionario d5 = Diccionario.fromXML( "<x>100</x><y>-1</y><z>0</z><state>false</state>" );
		
System.out.println( String.format( "d1: %s\nd2: %s\nd3: %s\nd4: %s\nd5: %s", d1, d2, d3, d4, d5 ) );

d1: {} d2: {'libname'='JME'} d3: {'a'=-Infinity, 'b'=NaN, 'c'=-1} d4: {'x'=100, 'y'=-1, 'z'=0, 'state'=falso} d5: {'x'=100, 'y'=-1, 'z'=0, 'state'=falso}


La distintas formas de entrada de un diccionario en una expresión JME pueden encontrarse en el manual de usuario#diccionario:

dic('x',100,'y',nan,'z',inf)

Diccionario: {'x'=100, 'y'=NaN, 'z'=Infinity}

dic(rango(10))

Diccionario: {0=1, 2=3, 4=5, 6=7, 8=9}


E/S de Diccionario
Método Tipo de retorno Conversión
entrada String Devuelve la entrada del diccionario usando la función dic en forma vectorial; dic(c1,v1,c2,v2,...)
toString String Devuelve la salida de los pares clave/valor en forma de mapa Java {c1=v1, c2=v2,...}

El método Diccionario#getMap devuelve el mapa que respalda el tipo Diccionario y permite modificar el diccionario (los diccionarios son mutables).


Variables y constantes


Las variables y constantes en JME son identificadores que pueden sustituirse en la expresión por cualquier tipo de dato JME o por otra subexpresión. Su sintaxis consta siempre de una letra/guión-bajo inicial y cero o más cifras, letras, o guión bajo; <letra>|_[<letra>|<cifra>|_]*.

JME es case insensitive, por lo que las variables X y x o valor y VaLoR, son iguales.

La diferencia entre las variables y constantes en JME, es que las variables se almacenan en un mapa y pueden modificarse y eliminarse para una misma expresión cuantas veces se quiera para evaluar la expresión varias veces con distintos valores. Las constantes no se pueden modificar una vez establecidas, y no se almacenan en un mapa sino que modifican directamente los identificadores de la expresión, siendo por tanto ligeramente más eficientes a la hora de evaluar.

Se debe tener en cuenta que no se deben dar nombres de función a variables o constantes.

Los métodos para introducir variables y constantes son respectivamente Expresion#setVariable y Expresion#setConstante. Ambos métodos están sobrecargados para añadir fácilmente distintos datos JME y Java:

void setVariable( String nombre, Token valor ) : establece la variable nombre con el valor de un determinado Token de JME. Este objeto Token puede ser cualquier dato JME u otra expresión:

Expresion exp = new Expresion( "x^2-x-1" );
exp.setVariable( "x", new RealDoble(3) );
System.out.println( exp.evaluar() );
5

Expresion exp = new Expresion( "p->q" ); // p implica q
exp.setVariable( "p", Booleano.VERDADERO );
exp.setVariable( "q", Booleano.FALSO );
System.out.println( exp.evaluar() );
falso

Expresion exp = new Expresion( "sub^2" );
exp.setVariable( "sub", new Expresion( "x+2" ) );
exp.setVariable( "x", new Complejo( 2, 3 ) );
System.out.println( exp.evaluar() );
7+23.999999999999996I

void setVariable( String nombre, double valor ) : forma más cómoda de introducir una variable RealDoble. Es equivalente a setVariable( nombre, new RealDoble(valor) ):

Expresion exp = new Expresion( "x+1" );
exp.setVariable( "x", 1/2. );
System.out.println( exp.evaluar() );
1.5

void setVariable( String nombre, double re, double im ) : forma más cómoda de introducir una variable compleja. Es equivalente a setVariable( nombre, new Complejo(re,im) ):

Expresion exp = new Expresion( "cos(z)" );
exp.setVariable( "z", 1, -1 );
System.out.println( exp.evaluar() );
0.8337300251311491+0.9888977057628651I

void setVariable( String nombre, boolean bool ) : forma más cómoda de introducir una variable Booleano. Es equivalente a setVariable( nombre, bool ? Booleano.VERDADERO:Booleano.FALSO ):

Expresion exp = new Expresion( "true->p" );
exp.setVariable( "p", false );
System.out.println( exp.evaluar() );
falso

void setVariable( String nombre, BigInteger bigint ) : forma más cómoda de introducir una variable EnteroGrande. Es equivalente a setVariable( nombre, new EnteroGrande( bigint ) ):

Expresion exp = new Expresion( "x!" );
exp.setVariable( "x", new BigInteger( "100" ) );
System.out.println( exp.evaluar() );
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

void setVariable( String nombre, BigDecimal bigdec ): forma más cómoda de introducir una variable RealGrande. Es equivalente a setVariable( nombre, new RealGrande( bigdec ) ):

Expresion exp = new Expresion( "2/x" );
exp.setVariable( "x", new BigDecimal( "1E1000" ) );
System.out.println( exp.evaluar() );
2E-1000

void setVariable( String nombre, Token… elementos ) : forma de introducir una variable vectorial a partir de un array:

Expresion exp = new Expresion( "v+[0,1,2]" );
exp.setVariable( "v", RealDoble.CERO, RealDoble.DOS, new RealDoble(1.5) );
System.out.println( exp.evaluar() );
[0,3,3.5]

void setVariable( String nombre, String expresion ) : introduce una subexpresión en una variable a partir de una cadena. Nota: no comprueba excepciones en la creación de la subexpresión:

Expresion exp = new Expresion( "sub/mod(sub)" ); // vector normalizado (=unit(sub))
exp.setVariable( "sub", "[1,2,3]" );
System.out.println( exp.evaluar() );
[0.2672612419124244,0.5345224838248488,0.8017837257372732]

void setVariableTexto( String nombre, String cadena ) : introduce una cadena de texto en una variable. Es equivalente a setVariable( nombre, new Texto( cadena ) ):

Expresion exp = new Expresion( "s$A$" );
exp.setVariableTexto( "s", "Hola Mundo" );
System.out.println( exp.evaluar() );
'HOLA MUNDO'

Los métodos Expresion#setConstante se utilizan de la misma forma que los métodos setVariable. Todas las variables o constantes de una expresión deben ser establecidas para poder evaluar, salvo que sea una variable local de una función (como el sumatorio, la integral, etc). De lo contrario se obtendrá una excepción.

Los métodos Expresion#borrarVariable y Expresion#borrarVariables permiten eliminar una o todas las variables de una expresión respectivamente:

exp1.borrarVariable( "miVar" );
exp2.borrarVariables();

También puede obtenerse el mapa de variables que tienen su valor establecido en la expresión mediante el método Expresion#getVariables, y establecerse mediante un mapa mediante el método Expresion#setVariables.

Si se quieren obtener los identificadores de la expresión que pueden sustituirse por variables o constantes, se puede usar el método Expresion#getIdentificadores, o Expresion#getIdentificadoresOrden, que devuelve un vector de identificadores en orden lexicográfico. El método Expresion#getIdentificadoresDeclarados, devuelve según un parámetro booleano, los identificadores para los cuales existe un valor declarado o los que no:

Expresion exp = new Expresion( "z*sum(n^2,n,1,10)-m" );
exp.setVariable( "z", -1 );
System.out.println( "                       identificadores: " + Arrays.toString( exp.getIdentificadores() ) ); 
System.out.println( "              identificadores en orden: " + Arrays.toString( exp.getIdentificadoresOrden() ) ); 
System.out.println( "   identificadores con valor declarado: " + Arrays.toString( exp.getIdentificadoresDeclarados(true) ) ); 
System.out.println( "identificadores con valor no declarado: " + Arrays.toString( exp.getIdentificadoresDeclarados(false) ) );

identificadores: [z, m, n] identificadores en orden: [m, n, z] identificadores con valor declarado: [z] identificadores con valor no declarado: [m, n] ('n' es local a 'sum' y no necesita iniciarse)

Puede sustituirse el nombre de una variable por otra en una expresión usando Expresion#setConstante:

exp = new Expresion( "m[j,sum(sqr(k),k,1,3)]" );
exp.setConstante( "m", new Identificador("a") )
   .setConstante( "j", new Identificador("b") )
   .setConstante( "k", new Identificador("c") );
exp.setVariable( "a", 2 )
   .setVariable( "b", 3 );
System.out.println( "entrada: " + exp.entrada() );
System.out.println( "  valor: " + exp.evaluar() );

entrada: a*[b,sum(sqr(c),c,1,3)] valor: [6,28]


Ejecución en hilo


Algunas expresiones pueden necesitar de una gran cantidad de tiempo para evaluarse (en algunos casos años), como un factorial extremadamente grande, o una operación pesada sobre muchos elementos. Por eso es preferible usar un hilo para evaluar las expresiones, que se detendrán en un hilo interrumpido con Thread#interrupt.


Clase ExpresionThread


Puede utilizarse cualquier tipo de hilo para evaluar una expresión JME (en Java 8+, por ejemplo, un FutureTask sería una buena opción), pero la clase jme.ExpresionThread permite evaluar fácilmente una expresión, incluyendo la posibilidad de un temporizador.

ExpresonThread incluye dos constructores, inicializando a partir de una expresión parseada. El segundo permite añadir el tiempo máximo aproximado de evaluación:

/**
 * Crea un hilo para evaluar la expresión dada
 * 
 * @param exp expresión parseada
 */
public ExpresionThread( @NotNull Expresion exp )

/**
 * Crea un hilo para evaluar la expresión dada con límite de tiempo
 * (en milisegundos)
 * 
 * @param exp expresión parseada
 * @param maxTiempo límite aproximado en milisegundos de evaluación
 */
public ExpresionThread( @NotNull Expresion exp, long maxTiempo )

El método ExpresionThread#getExpresion permite obtener la instancia de la expresión, y ExpresionThread#getException la excepción producida en caso de error o interrupción.

El método ExpresionThread#alTerminar es una rutina vacía en la clase base que ejecuta al terminar la evaluación de la expresión haya excepción o no. Puede reescribirse para ejecutar cualquier código que se desee al terminar (avisar al hilo principal, imprimir un mensaje,...).

El método ExpresionThread#startAndJoin, lanza el hilo del script y realiza un 'join' del hilo padre a la espera del resultado y puede capturar una excepción de tipo Throwable (generalmente una ExpresionException) en caso de error o interrupción.


Ejemplo de evaluación de expresión con temporizador en 2 segundos:

ExpresionThread expThread = new ExpresionThread(
	new Expresion( "enterogrande(n)#" ).setVariable( "n", 1000 ),
	2000 );

try
{
    Terminal resultado = expThread.startAndJoin();
    System.out.println( "Resultado: " + resultado );
}
catch ( Throwable e )
{
    System.err.println( e.getMessage() );
}
Ejecución con n=1000:

Resultado: 19590340644999083431262508198206381046123972390589368223882605328968666316379870661851951648789482321596229559115436019149189529725215266728292282990852649023362731392404017939142010958261393634959471483757196721672243410067118516227661133135192488848989914892157188308679896875137439519338903968094905549750386407106033836586660683539201011635917900039904495065203299749542985993134669814805318474080581207891125910


Ejecución con n=1E6:

<<<OperacionException>>> en operador [ # ]: Tiempo expirado --> (EnteroGrande:1000000b)#


Operadores


Los operadores de JME puede ser unarios (actúan sobre un operando) o binarios (actúan sobre dos). Todos los operadores de JME constan de uno a tres caracteres a lo sumo. Puede encontrarse la descripción actualizada de todos los operadores JME en manual de usuario#operadores

System.out.println( new Expresion( "[4!+m#+n^5,v;#;[2,4],s$A$+'!!!']" )
			.setVariable( "m", 5 )
			.setVariable( "n", 2 )
			.setVariable( "v", "[5,23,55,2,-1]" )
			.setVariableTexto( "s", "Hola Mundo" )
			.evaluar() );

[86,[23,55,2],'HOLA MUNDO!!!']


Operadores definidos por el usuario


Pueden definirse nuevos operadores mediante el método Expresion#nuevaOperacion. Este método esta sobrecargado permitiendo varias formas de introducir la definición del operador.

Todos los operadores definidos por el usuario tienen tres caracteres (excepto en el caso de carácter nulo '\0', que permite definir un operador ::). El primer y tercer carácter siempre es ':', por lo que todos tendrán la forma :<carácter>:, siendo <carácter> cualquier carácter unicode.


Primera forma:

Método Expresion.nuevaOperacion( Operador op ): Este método recibe como parámetros un objeto de la clase OperadorBinarioDefinidaPorUsuario u OperadorUnarioDefinidoPorUsuario, que heredan de la clase abstracta Operador. Estas clases reciben como parámetros del constructor el carácter de la nueva operación, uno o dos nombres de variable, la prioridad como entero y la expresión JME que representa al operador y opcionalmente una descripción del operador:

Expresion.nuevaOperacion( new OperadorBinarioDefinidoPorUsuario(
				'\0', "x", "y", 1000, new Expresion( "sqrt(sqr(x)+sqr(y))" ) ) );
System.out.println( Expresion.evaluar( "3::4" ) );

5



Segunda forma:

Método Expresion.nuevaOperacion( String declaracion ): En esta segunda forma se introduce la declaración del operador en una cadena con la sintaxis

<def_operador_binario> ::= <var1>:<simbolo>:<var2>(<prioridad>):=<expresion_jme>

<def_operador_unario> ::= <var>:<simbolo>:(<prioridad>):=<expresion_jme> | :<simbolo>:<var>(<prioridad>):=<expresion_jme>

Expresion.nuevaOperacion( "a:.:b(1000):=rango(a,b+1)" );
Expresion.nuevOperador( "v:<:(900):=revertir(v)" );
System.out.println( Expresion.evaluar( "10:.:15:<:" ) );

[15,14,13,12,11,10]

Las variables en la definición del operador son abstractas, únicamente definen la expresión del operador.
El método Expresion#quitarOperadoresUsuario permite eliminar todos los operadores de usuario. Expresion#quitarOperador permite eliminar el operador especificado (de usuario o no).


Funciones


Las funciones predefinidas en JME se escriben mediante un nombre de función y parámetros separados por comas encerrados entre paréntesis como en la mayoría de lenguajes de programación y el propio lenguaje matemático. Aunque en realidad las funciones JME sólo aceptan un parámetro, pero pueden utilizarse vectores para el paso de parámetros, y el analizador léxico se encarga de hacer transparente la entrada de éste vector si así se desea:

System.out.println( new Expresion( "max(5,6,76b,32,2.3d,0)" ).evaluar() );
System.out.println( new Expresion( "max([5,6,76b,32,2.3d,0])" ).evaluar() );

76 76

Las dos formas de llamar a la función max (máximo) son equivalentes. Puede encontrarse la descripción actualizada de todas las funciones JME en manual de usuario#funciones


Funciones definidas por el usuario


Se pueden definir nuevas funciones a partir de una expresión JME y el método estático Expresion#nuevaFuncion. Este método está sobrecargado para permitir dos formas equivalentes de introducir la función;


Primera forma:

Método Expresion.nuevaFuncion( FuncionDefinidaPorUsuario fdpu ): Este método recibe como parámetros un objeto de la clase FuncionDefinidaPorUsuario, que hereda de la clase abstracta Funcion. Esta clase recibe como parámetros del constructor el nombre de la nueva función, un vector de variables o parámetros formales, la expresión JME que representa a la función y opcionalmente una descripción de la función:

Expresion.nuevaFuncion( new FuncionDefinidaPorUsuario( 
			"col", new String[] { "mat", "index" }, 
			new Expresion( "_catch_(trasp(mat);#;index,format('Error al acceder a la columna %2$s de la matriz %1$s',[mat,index]))",
			"Obtener columna de matriz" ) ) );
Expresion exp = new Expresion( "col(m,i)" );
System.out.println( exp
                    .setVariable( "m", "[[1,2,3],[4,5,6]]" )
                    .setVariable( "i", 3 )
                    .evaluar() );
System.out.println( exp
                    .setVariable( "i", 0 )
                    .evaluar() );

[3,6] 'Error al acceder a la columna 0 de la matriz [[1,2,3],[4,5,6]]'



Segunda forma:

Método Expresion.nuevaFuncion( String declaracion ): En esta segunda forma se introduce la declaración de la función en una cadena con la sintaxis

<def_funcion> ::= <nombre_funcion>(<variable>[,<variable>]*):=<expresion_jme>

Expresion.nuevaFuncion( "col(mat,index):=_catch_(trasp(mat);#;index,format('Error al acceder a la columna %2$s de la matriz %1$s',[mat,index]))",
                        "Obtener columna de matriz" );
Expresion exp = new Expresion( "col(m,i)" );
System.out.println( exp
                    .setVariable( "m", "[[1,2,3],[4,5,6]]" )
                    .setVariable( "i", 3 )
                    .evaluar() );
System.out.println( exp
                    .setVariable( "i", 0 )
                    .evaluar() );

[3,6] 'Error al acceder a la columna 0 de la matriz [[1,2,3],[4,5,6]]'

El segundo parámetro, la descripción, es opcional. Las variables o parámetros formales de la expresión usada como nueva función son abstractas, únicamente definen la expresión de la función.
La función puede redefinirse cuantas veces se quiera llamando de nuevo al método Expresion#nuevaFuncion con una función con el mismo nombre, o puede eliminarse con el método estático Expresion#quitarFuncion (ejemplo: Expresion.quitarFuncion( "col" ) ). El método Expresion#quitarFuncionesUsuario elimina todas las funciones definidas por el usuario del mapa.


Constantes predefinidas y definidas por el usuario


JME incorpora varias constantes predefinidas que actúan como constantes globales a todas las expresiones JME. Además se pueden incorporar nuevas constantes al mapa de constantes globales que se encuentra en la clase Expresion, y que puede obtenerse mediante el método estático Expresion#getConstantes. Las constantes predefinidas y de usuario son siempre objetos Terminal o derivadas.

Puede encontrarse la descripción actualizada de todas las constantes predefinidas JME en manual de usuario#constantes

Además de estas constantes también existen otras como parámetros predefinidos para ciertas funciones, como _simpson_ para indicar el método de integración numérica de Simpson (ejemplo: int(phi*x+1,x,-pi,pi,1000,_simpson_). El valor de estas constantes es irrelevante, excepto para la función para la que están definidas.

El usuario puede introducir nuevas constantes globales mediante el método Expresion#nuevaConstante( String nombre, Terminal valor ). También pueden eliminarse mediante Expresion#quitarConstante.

Expresion.nuevaConstante( "rt11", new RealDoble( Math.sqrt(11) ) );
System.out.println( new Expresion( "rt11*cos(rt11)" ).evaluar() );

-3.2659498915472995


Los métodos #entrada y #toString


Los métodos #entrada y #toString pertenecen a todos los token de JME (toString a todos los objetos Java). En la sección de tipos de datos ya se introdujeron estos dos métodos para los terminales JME, pero pertenecen a todos los token e incluso a la clase Expresion.

El método #entrada, representa la forma de entrada del token en JME, es decir, la cadena que será reconocible por el analizador léxico para la introducción de ese token. El método #toString representa la forma en que se muestra el token a la salida. En muchas ocasiones ambas cadenas coinciden. En otras, son diferentes. Por ejemplo, un entero grande debe introducirse con una 'b' final, pero se muestra sin ella a la salida (entrada: 1234567890b, salida: 1234567890).

Una expresión completa también tiene método de entrada y salida:

Expresion exp = new Expresion( "3b+î-[3,4];#;2" );
System.out.println( " entrada: " + exp.entrada() );
System.out.println( "toString: " + exp );

entrada: 3b+î-[3,4];#;2 toString: 3+I-[3,4] sub 2

Si se quiere editar una expresión JME para volver a introducirla debe usarse #entrada, ya que será reconocida por el analizador léxico, mientras que #toString en general no.

Puede crearse una expresión a partir de otra usando la entrada:

Expresion exp = new Expresion( "3b+î-[3,4];#;2" );
System.out.println( " entrada: " + exp.entrada() );
System.out.println( "toString: " + exp );

entrada: cos(3b+î-[3,4];#;2) toString: cos(3+I-[3,4] sub 2)


Modo REPL


La biblioteca JME se distibuye en un jar ejecutable que contiene un terminal REPL (Read/Eval/Print/Loop) o modo consola de comandos que permite introducir y evaluar expresiones JME y comandos propios del modo REPL.

Algunas funciones:




Captura de operaciones del REPL:



Uso del jar como calculadora desde terminal sin entrar en modo REPL:


Importar/Exportar CSV


La clase Vector incluye un método estático para la importación desde CSV a un vector/matriz JME y un método para la conversión de cualquier vector/matriz JME a CSV. Estas operaciones también se pueden realizar desde el modo REPL.


Importar


El método estático Vector#fromCSV recibe una cadena con contenido CSV (obtenido por ejemplo leyendo un archivo de texto CSV). Cada línea del texto CSV es convertido en fila de la matriz, y cada valor de la fila en un elemento de la matriz. El segundo parámetro del método indica si debe ignorarse la primera fila del texto CSV (los encabezados de tablas de datos de un archivo CSV no son en general legibles en JME).

El texto debe ser CSV estricto, con valores separados por comas y expresiones JME válidas. Por el contrario, la exportación descrita en la siguiente subsección permite formatos CSV no estrictos (que no pueden ser importados a JME posteriormente). Si el contenido CSV no puede convertirse a Vector JME el método lanza una ExpresionException.

System.out.println( Vector.fromCSV( "12,sqrt(3),exp(2)\n"
                                    + "_e,1+3ui,0\n"
                                    + "-2,[],nan\n"
                                    + "falso,falso,inf\n",
                                    false ).evaluar().toStringMatriz() );

| 12 1.7320508075688772 7.38905609893065 | | 2.718281828459045 1+3I 0 | | -2 [] NaN | | falso falso Infinity |


Usando el REPL de JME:

>>> import csv archivo=foo.csv varname=csv_vector csv_vector -> [[[1,1,1]],[[1,1,2]],[[1,1,3]],[[1,2,1]],[[1,2,2]],[[1,2,3]],[[1,3,1]], [[1,3,2]],[[1,3,3]],[[2,1,1]],[[2,1,2]],[[2,1,3]],[[2,2,1]],[[2,2,2]],[[2,2,3]], [[2,3,1]],[[2,3,2]],[[2,3,3]],[[3,1,1]],[[3,1,2]],[[3,1,3]],[[3,2,1]],[[3,2,2]], [[3,2,3]],[[3,3,1]],[[3,3,2]],[[3,3,3]]] >>> csv_vector ==> VectorEvaluado: | [1,1,1] | | [1,1,2] | | [1,1,3] | | [1,2,1] | | [1,2,2] | | [1,2,3] | | [1,3,1] | | [1,3,2] | | [1,3,3] | | [2,1,1] | | [2,1,2] | … | [3,3,3] |


Exportar


El método Vector#toCSV tiene la siguiente declaración:

/**
 * Convierte un vector a formato CSV, siendo cada elemento una fila, y si
 * el elemento es otro vector, cada elemento suyo una columna de datos
 * @param filter filtro a aplicar a los tokens del vector. OUTPUT_FILTER, INPUT_FILTER o un FlterToken personalizado
 * @param separador delimitador entre datos
 * @param quote carácteres para envolver texto literal
 * @param numLinea incluir primera columna como nºs de línea
 * @param encabezados array de encabezados en la primera fila
 * @return texto CSV con los datos del vector
 */
public String toCSV( @NotNull final FilterToken filter,
                     @NotNull final String separador,
                     final char quote,
                     boolean numLinea, 
                     @Nullable String... encabezados )

El parámetro filter acepta un objeto que implemente la interfaz funcional Util.FilterToken, cuyo método FilterToken#filter recibe un token JME y devuelve la cadena que el usuario defina para ese token. Las contantes Vector.OUTPUT_FILTER y Vector.INPUT_FILTER devuelven la cadena de salida/entrada estándar del token respectivamente.

El parámetro separador establece el separador entre items de la salida CSV. En CSV estricto, este separador debe ser una coma “,”.

El parámetro quote recibe un carácter para texto literal que generalmente es el entrecomillado.

El booleano numLinea especifica que se añada una primera columna contadora de líneas.

El array de encabezados permite añadir una primera fila de texto para cabeceras de columna. Si se introduce (String[]) null no se añadirá fila de encabezados.


Ejemplo 1; Tabla de verdad con encabezados:

System.out.println( ((Vector) new Expresion( "tverdad((p&&r)->q,p,q,r)" ).evaluar())
                    .toCSV( Vector.OUTPUT_FILTER, 
                            ",", 
                            '\"', 
                            true, 
                            "nº", "p", "q", "r", "(p&&r)->q" ) );

"nº","p","q","r","(p&&r)->q" 1,falso,falso,falso,verdadero 2,falso,falso,verdadero,verdadero 3,falso,verdadero,falso,verdadero 4,falso,verdadero,verdadero,verdadero 5,verdadero,falso,falso,verdadero 6,verdadero,falso,verdadero,falso 7,verdadero,verdadero,falso,verdadero 8,verdadero,verdadero,verdadero,verdadero


Usando el REPL de JME:

>>> export csv archivo=foo.csv jme=tverdad((p&&r)->q,p,q,r) separador="," comillas=""" nums=si filtro=output encabezados="nº","p","q","r","(p&&r)->q" >>> exec type foo.csv ('type' en Windows, use 'cat' en Linux) "nº","p","q","r","(p&&r)->q" 1,falso,falso,falso,verdadero 2,falso,falso,verdadero,verdadero 3,falso,verdadero,falso,verdadero 4,falso,verdadero,verdadero,verdadero 5,verdadero,falso,falso,verdadero 6,verdadero,falso,verdadero,falso 7,verdadero,verdadero,falso,verdadero 8,verdadero,verdadero,verdadero,verdadero >>>


Ejemplo 2; Secuencias de longitud 3 del array [1,2,3] separadas por punto y coma:

System.out.println( ((Vector) new Expresion( "[1,2,3];s;3" ).evaluar())
                    .toCSV( Vector.OUTPUT_FILTER, 
                            ";", 
                            '\"', 
                            false, 
                            (String[]) null ) );

1;1;1 1;1;2 1;1;3 1;2;1 1;2;2 1;2;3 1;3;1 1;3;2 1;3;3 2;1;1 2;1;2 2;1;3 2;2;1 2;2;2 … 3;2;3 3;3;1 3;3;2 3;3;3


Usando el REPL de JME:

>>> export csv archivo=foo.csv jme=[1,2,3];s;3 separador=";" >>> exec type foo.csv ('type' en Windows, use 'cat' en Linux) 1;1;1 1;1;2 1;1;3 1;2;1 1;2;2 1;2;3 1;3;1 … 3;3;3 >>>


Exportar a HTML <table>


El método Vector#toHtmlTable transforma un vector/matriz en una etiqueta table de HTML con el contenido del vector/matriz. Esta operación también se puede realizar desde el modo REPL. La declaración del método es como sigue:

/**
 * Convierte un vector a formato tabla de HTML, siendo cada elemento una
 * fila, y si el elemento es otro vector, cada elemento suyo una columna de datos
 * @param filter filtro a aplicar a los tokens del vector. OUTPUT_FILTER, INPUT_FILTER o un FlterToken personalizado
 * @param salto carácter a añadir como salto de línea tras cada etiqueta HTML ("" para nada)
 * @param numLinea incluir primera columna como nºs de línea
 * @param encabezados array de encabezados en la primera fila
 * @return texto HTML con los datos del vector
 */
public String toHtmlTable( @NotNull final FilterToken filter, 
                           String salto,
                           boolean numLinea, 
                           @Nullable String... encabezados )

El parámetro filter acepta un objeto que implemente la interfaz funcional Util.FilterToken, cuyo método filter recibe un token JME y devuelve la cadena que el usuario defina para ese token. Las contantes Vector.OUTPUT_FILTER y Vector.INPUT_FILTER devuelven la cadena de salida/entrada estándar del token respectivamente.

El parámetro salto añade una cadena de texto al final de cada etiqueta, que puede ser el salto de línea o la cadena vacía típicamente.

El booleano numLinea especifica que se añada una primera columna contadora de líneas.

El array de encabezados permite añadir una primera fila de texto para cabeceras de columna. Si se introduce (String[]) null no se añadirá fila de encabezados.


Ejemplo; Tabla de verdad con encabezados:

System.out.println( ((Vector) new Expresion( "tverdad((p&&r)->q,p,q,r)" ).evaluar())
                    .toHtmlTable( Vector.OUTPUT_FILTER,  /* filtro */
                                  "",  /* salto */ 
                                  true, /* números de línea */
                                  "nº", "p", "q", "r", "(p&&r)->q" ) /* encabezados */ );
<table><tr><th>nº</th><th>p</th><th>q</th><th>r</th><th>(p&&r)->q</th></tr><tr><td>1</td><td>falso</td><td>falso</td><td>falso</td><td>verdadero</td></tr><tr><td>2</td><td>falso</td><td>falso</td><td>verdadero</td><td>verdadero</td></tr><tr><td>3</td><td>falso</td><td>verdadero</td><td>falso</td><td>verdadero</td></tr><tr><td>4</td><td>falso</td><td>verdadero</td><td>verdadero</td><td>verdadero</td></tr><tr><td>5</td><td>verdadero</td><td>falso</td><td>falso</td><td>verdadero</td></tr><tr><td>6</td><td>verdadero</td><td>falso</td><td>verdadero</td><td>falso</td></tr><tr><td>7</td><td>verdadero</td><td>verdadero</td><td>falso</td><td>verdadero</td></tr><tr><td>8</td><td>verdadero</td><td>verdadero</td><td>verdadero</td><td>verdadero</td></tr></table>

En navegador sin formato:

pqr(p&&r)->q
1falsofalsofalsoverdadero
2falsofalsoverdaderoverdadero
3falsoverdaderofalsoverdadero
4falsoverdaderoverdaderoverdadero
5verdaderofalsofalsoverdadero
6verdaderofalsoverdaderofalso
7verdaderoverdaderofalsoverdadero
8verdaderoverdaderoverdaderoverdadero

Usando el REPL de JME con salto de línea:

>>> export html jme=tverdad((p&&r)->q,p,q,r) salto=si nums=si encabezados="nº","p","q","r","(p&&r)->q"

<table>
<tr>
<th>nº</th>
<th>p</th>
<th>q</th>
<th>r</th>
<th>(p&&r)->q</th>
</tr>
<tr>
<td>1</td>
<td>falso</td>
<td>falso</td>
<td>falso</td>
<td>verdadero</td>
</tr>
<tr>
<td>2</td>
<td>falso</td>
<td>falso</td>
<td>verdadero</td>
<td>verdadero</td>
</tr>
<tr>
<td>3</td>
<td>falso</td>
<td>verdadero</td>
<td>falso</td>
<td>verdadero</td>
</tr>
<tr>
<td>4</td>
<td>falso</td>
<td>verdadero</td>
<td>verdadero</td>
<td>verdadero</td>
</tr>
<tr>
<td>5</td>
<td>verdadero</td>
<td>falso</td>
<td>falso</td>
<td>verdadero</td>
</tr>
<tr>
<td>6</td>
<td>verdadero</td>
<td>falso</td>
<td>verdadero</td>
<td>falso</td>
</tr>
<tr>
<td>7</td>
<td>verdadero</td>
<td>verdadero</td>
<td>falso</td>
<td>verdadero</td>
</tr>
<tr>
<td>8</td>
<td>verdadero</td>
<td>verdadero</td>
<td>verdadero</td>
<td>verdadero</td>
</tr>
</table>

>>>


Dependencias de la biblioteca


A partir de la versión 0.4.4, JME utiliza para el parseo de objetos JME desde/hacia objetos JSON y XML de la biblioteca JSON-java, incluida en el jar ejecutable.


A partir de la versión 0.4.7, JME utiliza para la evaluación de funciones que dependen de las funciones especiales tipo Gamma y Beta, de los archivos fuente Gamma.java, Beta.java y ContinuedFraction.java de Apache Common Math. Estos tres archivos están incluidos y modificados en el paquete jme.extlibs.


Consideraciones sobre JME


Algunas consideraciones en el uso de JME: