Articulo Haciendo un intérprete de ecuaciones matemáticas: Segunda parte

Mensajes
1,139
Oro
234,965
En la primera parte de este tutorial, más bien a modo de introducción, comenté la idea de hacer una especie de intérprete de ecuaciones matemáticas, y seguir con todo su desarrollo si esta propuesta le resultaba de interés por lo menos a alguno.

Por lo visto no hay muchos interesados en estos asuntos, tal como era fácil predecir, dado como también mencioné, no se trata de un tema de interés general, ni tampoco de un conocimiento necesario en muchos escenarios actuales, cuando casi todo está hecho y es más común arrastrar y soltar y dar unos cuantos clic para lograr algo.

Pero como por lo menos a @wire pareció gustarle la idea, y a mí también me gusta un tema como este, no vamos a dejarlo colgado así, y por eso seguiré con lo planificado, porque igual puede y más tarde le sea útil a otros más.

Por cierto, si alguien conoce un sitio donde se puedan conseguir libros antiguos sobre lenguajes de programación y temas relacionados, me gustaría lo mencionara, porque los libros de antes me parecen más instructivos, ahora como he dicho todo lo reducen a arrastrar algo con el puntero del ratón, y eso se extiende también a los manuales.

Es decir, no critico nada, el estado actual no es malo en sí, sino resultado del nivel de desarrollo alcanzado; ahora no se necesita conocer nada a profundidad, pero a mí me gusta más de la otra forma porque es más entretenido.

En todo caso, como nadie comentó nada sobre implementar esto usando otro lenguaje de programación distinto del BASIC, en la siguiente parte empezaré a escribir código en QuickBasic (o QB64) como había dicho haría, porque como recordarán, en esta parte nos tocaba hacer la definición de la gramática para representar las ecuaciones matemáticas en la notación BNF.

De todos modos no deben preocuparse, no vamos a exponer toda la gramática sino nada más la parte fundamental de esta, puesto se trata nada más de dar una idea de cómo funciona o se haría más largo y aburrido si lo hiciéramos con todos los detalles.

Por otro lado, como esto lo vamos a implementar a código, no nos hacen falta tantos detalles como a las herramientas automáticas, aun si debemos tener en mente todas estas cosas.

La línea a continuación puede ser un buen comienzo para empezar a definir cómo está compuesta una ecuación o expresión matemática.​

<expression> ::= <term> [<addop> <term>]*​

La notación nos revela como una expresión no es nada más que un término (<term>), opcionalmente (los corchetes significan opcional como cuando se usan para exponer la sintaxis de una instrucción) seguido de otros términos (un “*” significa cero o más), relacionados entre sí por medio de un operador (<addop>), en este caso un operador de suma o de resta (“+” o “-”).

Pero como se darán cuenta, ahora nos encontramos con una incógnita:

¿Qué es un término?​

<term> ::= <factor> [<mulop> <factor>]*​

La línea antes expuesta define cómo está compuesto un término (<term>), o sea, un término es un factor (<factor>), una vez más seguido opcionalmente por cero o más factores.

En cambio, esta vez los factores están relacionados por un operador (<mulop>) para la multiplicación o la división (“*” o “/”).

Nota: En este texto no tratamos un operador de elevación a potencia puesto eso será hecho por medio de una función.

Por fin, un factor a su vez podría ser una expresión completa delimitada por paréntesis como en:​

<factor> ::= (<expression>)​

La última línea mostrada nos da una idea de la naturaleza recursiva de una expresión o ecuación matemática, puesto esta se compone de términos, a su vez compuestos de factores, a su vez compuestos por una expresión entre paréntesis, y con esto seguro se han llevado la idea y nos resultará suficiente.

Pero debemos tenerlo en cuenta, un factor no se limita a esto, también podría ser un número, o una variable, entre otras cosas, por lo cual una definición más completa de factor sería (el símbolo barra vertical “|” se utiliza como un operador lógico OR):​

<factor> ::= <number> | (<expression>) | <variable>​

En todo caso, como seguro se habrán dado cuenta, la definición de factor expuesta arriba también sigue siendo básica, porque un factor también podría ser una llamada a una función, una constante etc., y todavía deberíamos seguir definiendo <number>, <variable>, puesto todos ellos son elementos compuestos por símbolos y el analizador lexicográfico tiene la necesidad de toda esa información para poder identificarlos o reconocerlos de modo correcto.

Nota: Las constantes no se tuvieron en cuenta porque vamos a representarlas como las variables, y en cuanto a las funciones las vamos a reconocer buscando en una tabla de símbolos.

En particular un número podría ser un entero o más bien ser fraccionario, y todo esto debe estar expresado para como se ha dicho sea reconocido correctamente.

El caso del número entero puede denotarse como (caso más sencillo):​

<number> ::= [<digit>]+​

donde <digit> significa un dígito de “0” a “9” y “+” significa uno a más de estos.

Pero un número válido podría ser 3.14159 correspondiente a la constante π, o incluso un número más complicado de reconocer como cuando está en notación científica.

Por su parte, una variable es un identificador, como mismo lo puede ser una palabra reservada, una función, y por tanto un analizador lexicográfico debe poder distinguir a unos de otros.

Por eso en algunos lenguajes de programación se obliga a declarar previamente las variables, o a terminar una llamada a una función con paréntesis vacios a pesar de no pasarle argumentos; todo esto no se hace así por capricho, se hace así simplemente para poder distinguir unas cosas de otras, para eliminar la posible ambigüedad, o luego el analizador lexicográfico podría confundirse.

La notación común para un identificador podría ser como esta (en algunos lenguajes están permitidos otros símbolos como “_”):​

<identifier> ::= <letter> [<letter> | <digit>]*​

Pero como seguro saben, en casi todos los lenguajes de programación comunes, un identificador como un nombre de una variable o de una función también está limitado en cuanto a su extensión.

Nota: Las palabras claves son identificadas por medio de una tabla de símbolos.

En general, como se pueden dar cuenta, hemos obviado unos cuantos detalles más con fines de simplificación, a pesar de por lo común estar representados cuando se define la gramática, donde como se ha dicho todo debe de ser definido casi hasta la mínima expresión o las herramientas de generación de código no podrían cumplir con sus objetivos.

En particular un término también podría ser negativo, y por tanto estar antecedido por un operador para manifestarlo, como podemos verlo a continuación:​

<expression> ::= <unary op> <term> [<addop> <term>]*

<term> ::= <factor> [<mulop> <factor>]*

<factor> ::= <number> | (<expression>) | <variable>​

En adición, esto mismo podemos representarlo a nivel de un factor como en:​

<expression> ::= <term> [<addop> <term>]*

<term> ::= <signed factor> [<mulop> <factor>]*

<signed factor> ::= [<addop>] <factor>

<factor> ::= <number> | (<expression>) | <variable>​

En resumen, vamos a conformarnos con la gramática básica de una expresión antes expuesta, o como pueden ver, esto podría extenderse, y eso nada más se trata de la gramática de una simple expresión matemática y no de un lenguaje completo.

Pero con esta gramática nos será suficiente, aun cuando deberemos tener en cuenta unos cuantos detalles más cuando implementemos el código del intérprete de manera pueda reconocer cada elemento y tratarlo en correspondencia, como los mencionados números en notación científica, el operador de asignación, y otros detallitos menores.

Todo esto lo iremos viendo en las restantes partes del tutorial a medida se escriba el código real del intérprete de ecuaciones matemáticas.​
 
Atrás
Arriba