Hulk es una aplicación de consola desarrollada con tecnología .NET Core 7.0 en el lenguaje C# que sirve como mini intérprete para el lenguaje de programación HULK. El proyecto cuenta con 8 clases las cuales se encargan procesar el tipo de información que sea necesaria. A continuación se explicará el funcionamiento de cada una así como el rol de cada una en el proyecto. Para ejecutar el programa en Visual Studio Code se debe introducir el siguiente comando:
dotnet run --project .\HulkSolution\ConsoleApp-EvaluadorAritmetico: Es una clase estática cuya función en el proyecto es evaluar las expresiones netamente aritméticas. Específicamente se utiliza el Algoritmo Shunting Yard que es un método para analizar(parsing) las ecuaciones matemáticas especificadas en la notación de infijo (El algoritmo fue inventado por Edsger Dijkstra, para obtener más detalles del funcionamineto del algoritmo puede ser buscado fácilmente, aunque en el proyecto hay un PDF adjunto dónde se explica el funcionamiento de dicho algoritmo). Para ello cuenta con 4 métodos, que usados conjuntamente nos devuelven el resultado de cualquier expresión artmética válida.
--Métodos de la clase EvaluadorAritmético:
-ParéntesisBalanceados: Este método tiene como función determinar si una expresión en general cuenta con paréntesis balanceados(por tanto se puede usar no solo para las expresiones aritméticas). Dicho método recibe un string ´expresión´ y devuelve un valor booleano(True en caso de tener los paréntesis balanceados y False en caso contrario). Para ellos nos apoyamos en la estructura de datos Stack(Pila) de nombre 'parétesis' donde iremos introduciendo los paréntesis izquierdos. Luego se recorre la ´expresión´ carácter a carácter para identificar si estamos en presencia de un paréntesis, en caso de que sea un paréntesis izquierdo "(" lo introducimos en la pila, en caso de que sea un paréntesis derecho ")" y que la pila no este vacía sacamos un paréntesis de la pila, en caso de que la pila este vacía retornamos falso porque existe un paréntesis derecho que no está balanceados. Si al concluir el ciclo la pila está vacía significa que cada paréntesis está balanceados y por tanto retornamos True, en caso contrario retornamos False.
-Precedencia: Este método se usa para darle la precedencia predeterminada a cada operador. Para ellos se una un switch case, a los operadores suma y resta (+,-) se le asignó precedencia 1, a los operadores multiplicación, división y resto (*,/,%) se le asignó precedencia 2, al operador potencia(^) se le asignó precedencia 3 y al operador unario de la resta (-) se le asignó precedencia 4 (este operador se usa para saber si el signo negativo pretenece a un número).
-InfijaAPosfija: Este método se utiliza para llevar una expresión infija a una posfija( las expresiones infijas son las que estamos acostumbrados a ver, Ej: 2+3, 3+4*(2-1)) y recibe un string que representa la expresión infija y devuelve un string que representa la expresión posfija. Primeramente en el método recorremos una lista de tokens(para crear esta lista se utilizó el método Tokenizador de la clase Lexer que será diseccionada más adelante) para asegurarnos que a la expresión aritmética no llegue ningún token inválido, en ese caso se lanza una "excepción". Posteriormente se crea un string vacío donde se almacenará la expresión posfija que se devuelve posteriormente, además se crea una Pila para almacenar los operadores, así como un booleano que se inicializa en true para identificar si antes de un signo negativo hay un operador o un parentésis izquierdo y así poder identificar que el signo negativo pertenece al número propiamente. El siguiente paso es recorrer la expresión infija caracter a caracter para ir identificando lo que es cada cual, si el caracter es un número o un punto decimal lo agregamos a la expresión posfija, y al booleano "previEsOperador" le asignamos false, si es un paréntesis izquierdo lo apilamos y "previoEsOperador" lo hacemos true, si es un paréntesis derecho desapilamos todos los operadores previos y los vamos agregando a la expresión posfija y eliminamos el paréntesis izquierdo correspondiente y "previoEsOperador" es false, si es un signo negativo verificamos si es un operador binario o un operador unario del propio número(para ello utilizamos la variable "previoEsOperador", si es true significa que el signo es propiamente del número, en caso contrario es el operador binario) en cualquier caso desapilamos los operadores de mayor o igual importancia y los agregarlos a la expresion posfija, apilamos el operador resta(unario o binario) y hacemos la variable "previoEsOperador" true, si es otro operador desapilamos los operadores de mayor o igual importancia y los agregamos a la expresión posfija, luego apilamos el operador actual. Luego de salir de ese ciclo verificamos si la Pila de operadores está vacía, en caso de no estarlo agregamos los operadores a la expresión posfija y la retornamos.
-EvaluarPosfija: Este método lo usaremos para evaluar una expresión posfija, recibe un string que representa la expresión y devuelve un double que representa el resultado de evaluar la expresión, a conveniencia lo primero que hacemos en este método es llamar al método InfijaAPosfija para posteriormente solo tener que llamar a este método. Luego creamos un Pila para almacenar los operandos, luego recorremos cada token de la expresión posfija. Si es un número lo apilamos, si es un operador desapilamos uno o dos operandos y realizamos la operación correspondiente y el resultado lo apilamos, para ello se utilizó un switch-case con cada una de las operaciones correspondientes. Luego se devuelve el resultado final que es lo único que quedará en la pila.
-Lexer: Es una clase estática cuya función es convertir un string en una Lista de string(que representrán nuestros "tokens"). Para ello cuenta con 5 métodos.
--Métodos de la clase Lexer:
-EsEspacio: Es un método estático que recibe un char y devuelve un booleano y como su nombre lo indica identifica si dicho caracter de entrada es un espacio o no.
-EsFinal: El método recibe dos enteros, uno representa la posición en la que nos encontramos y el otro la longitud de nuestro string de entrada, devuelve un booleano indicando si nos encontramos en la última posición o no.
-Cambio: Recibe un char que representa el caracter acutal y devuelve un booleano, su función es identificar si dicho caracter es un caracter de cambio(La siguiente Lista representa los caracteres que consideramos de cambio: *,/,%,+,-,^,",>,<,=,!,,,.,@, ,(,)).En esos casos devolvemos true y en caso contrario false.
-EsComparador: Este método se utilizar para detectar los comparadores "compuestos"(>=,<=,!=,!>,!<), para ello recibe dos argumentos de tipo string y comprueba mediante un switch-case si ambos forman alguna de las combinaciones anteriores, por tanto devuelve booleano. Se utilizar para tratar a estos operadores como un token único.
-Tokenizador: Este método es el que complementado con los anteriores crea(devuelve) una Lista de string y recibe un string. Para ellos primeramente se crea una Lista, y un string de nombre tokenactual, luego recorremos el string de entrada y hacemos el siguiente procedimiento, si el caracter acutal es un caracter de cambio y no es un espacio(' ') agregamos el tokenactual a la Lista y agregamos al caracter de cambio a la Lista, si es un espacio continuamos el ciclo y no agragamos el caracter pero sí el tokenactual, si no es un caracter de cambio lo agregamos al string tokenactual y comprobamos si es la posición final, de serlo lo agregamos a la lista de tokens. Terminado esta ciclo y con nuestra lista ya conformada realizamos otro ciclo a la Lista para los tokens que sean comparadores ´compuestos´ unificarlos en un solo token utilizando el método EsComparador. Por último retornamos la Lista.
-Excepciones: Cada una de las excepciones contenidas aquí heredan de la clase Exception de C#. Cada una de las Excepciones devuelve un Console.WriteLine con el mensaje del error(No las analizaremos aquí porque consideramos que se pueden explicar más adelante cuando estemos viendo el funcionamiento de nuestro programa).
-Expresiones: Para introducir esta clase podemos decir que este proyecto se basa en el uso de Expresiones Regulares, por tanto esta clase tiene un conjunto de propiedades, todas de tipo string y cada una de ellas es una expresión regular que representa cada una de las expresiones válidas en el lenguaje. Por mencionar algunas podemos decir que contiene una propiedad para representar las expresiones de tipo Let-In, así como una para las Condicionales, las aritméticas, los números, los strings y los booleanos por mencionar las que consideramos más importantes(cabe aclarar que nuestro programa no usa todas las expresiones regulares declaradas en esta clase, algunas están porque en algún momento de la construcción del proyecto se vieron necesarias pero posteriormente no, no se han eliminado por si el proyecto sufre cambios). Además la clase cuenta con varios métodos, cada uno de ellos devuelve un valor booleano o del tipo Match, el funcionamiento y la utilización de cada uno de ellos es la misma, se utilizan para identificar de que tipo es una expresión utilizando las expresiones regulares de las propiedades de la clase. Además de estos métodos tiene uno llamado ConcatenarString y como su nombre lo indica se usa para realizar la función re concatenar dos o más expresiones, por tanto recibe un array de tipo string y devuelve un string, por tanto para ellos se usa el método Concat de la clase String para concatenar cada una de las posiciones del array de entrada, y por último agregamos comillas dobles al inicio y al final de la expresión ya que esta expresión será tratada como un string en nuestro programa.
Dentro de la carpeta con nombre Parser se encuentra un conjunto de clases llamadas: Condicionales,EvaluadorExpresiones,Funciones,Let_In. Cada una de estas clases realiza el parsing de cada una de las expresiones que corresponden con el nombre de la clase(la clase EvaluadorExpresiones es la que junta cada una de las otras clases y por tanto en el método llamado QueEs se realiza el parsing en general de cualquier expresión de entrada). A continuación analizaremos cada una de ellas.
-Condicionales: Es una clase estática que cuenta con varias propiedades: una Lista de string llamada tokens que representa los tokens de una extrada dada, un entero llamado posIf que representa la posició de la primera expresión ´if´, un entero llamado posElse que representa la posición de la expresión ´else´ que le corresponde a la primera expresión ´if´, además cuenta con un entero llamado posTermina que representa la posición donde termina la expresión condicional en general, y por último un entero llamado posCierreB que representa la posición donde termina la expresión booleana de la condicional. Dicha clase cuenta con 4 métodos que usados conjuntamente realizan el parsing de una expresión condicional. A continuación analizaremos cada uno de ellos.
-DivideCondicional: La función de este método es dividir una expresión condicional en 3 partes: parte booleana, cuerpo del if y cuerpo del else. Para ello recibe como parámetros una Lista de string que llamaremos tokens y devuelve un array de string, primeramente creamos varias variables que serán 7 enteros, que representarán 2 contadores, la posición de if, la posición del else correspondiente, la posición donde abre el paréntesis correspondiente al booleano y la posición donde cierra el paréntesis que corresponde a ese, además de la posición donde termina el else que momentaneamente será el tamaño de la Lista de tokens de entrada pero se puede decidir donde es que termina dicho else ya que si el usuario introduce paréntesis al inicio y al final de cuerpo del else se tomará la posición del parentesis que cierra como posición donde termina el cuerpo del else.
Ej:
if(true) 3 else (2+3) *3;
Devuelve 9
if(true) 3 else ((2+3)*3);
Deveuelve 3Además tendremos un booleano que usaremos como bandera, y tres Listas que representarán la parte booleana, el cuerpo del if y el cuerpo del else, que al final rellenaremos con sus respectivos valores. Primeramente se pregunta si los paréntesis de la expresión estan balanceados utilizando el método ParéntesisBalanceados de la clase EvaluadorAritmetico en caso de que no esten balanceados se lanza una excepción de tipo ParentesisNoBalanceados, en caso de que se entre en la condicional recorremos la lista de tokens con un ciclo for usando un entero i, en caso de que tokens[i]==if y ademas si el booleano entro es true decimos que la variable posIf es igual a i y además entro es igual a false para asegurarnos que se quede con la primera posición, y además incrementamos en 1 nuestro contador, si tokens[i]==else le restamos 1 a nuestro y si nuestro contador en igual a 0 entonces significa que ese else es el que le corresponde al primer if, por tanto la variable posElse es igual a i y rompemos el ciclo. Luego hacemos nuestro contador igual a 0 y el booleano igual a true, y hacemos otro ciclo a partir de la posición donde empieza el else para determinar donde es que termina dicho else, en caso de que la lista de tokens en en posElse+1 sea igual a un parentesis izquierdo significa que el cuerpo del else está limitado, y por tanto realizamos el mismo procedimiento para quedarnos con las posiciones que le corresponde a cada uno de los parentesis, en caso contrario preguntamos si y tokens[i]==, ya que si eso es true debido al flujo de nuestro programa podemos estar seguro que esa condicional es un parámetro de alguna función y por tanto esa sería la posición donde termina el else. Luego con otro for recorremos a partir de la posicion posIf para quedarnos con la posición de cada uno de los paréntesis que delimitan la parte booleana de la condicional, esto lo hacemos usando el mismo procedimiento explicado anteriormente. Si terminado este procedimiento nuestro contador de if y else es menor que 0 significa que hay más expresiones else que if, y si es mayor que 0 significa que hay más if que else y por tanto en cualquiera de los dos casos lanzamos una excepción de tipo ErrorSintacticoElse y ErrorSinctacticoIf respectivamente. En caso que no se lance ninguna excepció rellenamos nustras Listas usando un ciclo, luego cada una de las Lista las convertimos a String usando el método Join y separando por espacio cada token, luego creamos un array de string de tamaño 3 donde la posición 0 será igual a la Lista de parte booleana llevada a String, la posición 1 será igual a la Lista cuerpoIf llevada a String y la posición 2 será igual a la Lista de cuerpoElse llevada a String y luego devolvemos dicho array.
Ej:
string[] partesCondicional = new string[3];
partesCondicional[0] = String.Join(" ", parteBooleana).Trim();
partesCondicional[1] = String.Join(" ", parteIf).Trim();
partesCondicional[2] = String.Join(" ", parteElse).Trim();-EvaluadorBooleanos: Este método se utiliza para saber el valor de booleanos del tipo >,<,>=,<=,!=,!>,!<, para ello usamos un swtich-case. El método recibe 3 parámetros de tipo string, uno representa el miembro izquierdo, otro el operador y el otro el miembro derecho, cada uno de los casos devuelve el valor de evaluar el miembro izquierdo con el miembro derecho con el operador correspondiente.
-EsBoolean: Es un método estático que devuelve un valor booleano, y se usa para identificar si una expresión es un booleano, aquí considetamos 3 casos:
--Si la expresión es la palabra true o false.
--Si es una comparación de string, aquí solo se permite el comparador ==.
--Si es una comparación de números o de expresiones aritméticas.
Para cada uno de estos casos utilizamos las expresiones regulares definidas en la clase Expresiones.
-ValorDelBooleano: Este método se utiliza para saber si un booleano es true o false, por tanto devuelve un booleano cuyo valor es el mismo valor de la expresión booleana que queremos analizar. Para este método tenemos los mismos casos anteriores, si el miembro izquierdo y el derecho son string simplementes comparamos y en caso de que sean iguales devolvemos true y si son distintos devolvemos false, para comparar números y expresiones aritméticas usamos el método EvaluadorBooleanos.
-Let_In: Esta clase se utiliza para realizar el parsing de las expresiones de tipo let-in, para ellos cuenta con 3 propiedades, ellas son: un array de string que son las palabras reservadas del lenguaje y 2 enteros que representan la posición de la exrpesión let y de la expresión in que le corresponde. Además cuenta con 7 métodos que analizaremos a continuación.
-Define: Este método se utiliza para asignarle a una variable su nombre y su valor, por tanto es void y recibe como parámetros dos strings que representan el nombre y el valor y un Dictionary<string,string> donde guardaremos las variables. Primeramente mediante un ciclo for nos aseguramos de que el nombre de la variable no coincida con ninguna palabra reservada del lenguaje, en caso de que coincida lanzamos una excepción del tipo PalabraReservada, en caso de que no sea una palabra reservada con una condicional preguntamos si nuestro diccionario contiene el nombre de dicha variable en caso positivo lanzamos una excepción del tipo VariableAsignada en caso contrario agregamos la variable con su valor al diccionario.
-DivideLet_In: El objetivo de este método es dividir una expresión lei-in en dos partes, la parte de la declaración de variables y el cuerpo del in. El funcionamiento de este método es igual que el funcionamiento del método DivideCondicional explicado anrteriormente, por ellos omitiremos su explicación.
-AsignaVariable: Este método devuelve void y recibe una lista de strings, un diccionarrio de string-string y una variable del tipo Funciones(se explicará posteriormente). La Lista representa la declaración de una variable, por ellos los que hacemos es dividir esta lista por el primer valor de igual y asi obtendremos el nombre y el valor y por tanto llamamos al método Define pasandole como parámetros estos string y el Diccionario, específicamente el string de value que pasamos es el string que devuelve el método QueEs de la clase EvaluadorExpresiones(o sea parseamos el string valor para guardar con el nombre de la variable).
-DivideVariables: Este método se usa para dividir el cuerpo del let o la declaración de variables, para ellos recibe una Lista de string y un objeto de tipo Funciones y devuelve una Lista de Listas de Strings, su funcionamiento se basa en buscar la posición de las comas que dividen a las variables, teniendo en cuenta que estas comas no pertenezcan ni a un string, ni a otra declaración de variables ni a un llamado de función, esto lo hacemos mediante un ciclo for recorriendo la lista de tokens, ya obtenidas las posiciones formamos sublistas que agregaremos posteriormente a una Lista genral.
-EsVariable: Este método se utiliza para saber si una expresión puede corresponde a un nombre de variable, para ello recibe un string y devuelve un booleano, se usa específicamente para ello una expresión regular y además se comprueba de que no sea una palabra reservada.
-GuardarVariables: Este método recibe como parámetros una Lista de Listas de string, un diccionario de string-string y un objeto de tipo Funciones. Su función es agregar al diccionario todas las variables de una expresión let-in, para ellos recorremos la Lista de Listas con un ciclo foreach y utilizando el método AsigaVariable vamos agregando valores al diccionario, por tanto devuelve void.
-EvaluadorVariables: La función de este método de sustituir el valor de las variables guardadas en el diccionario en el cuerpo del in. El método recibe un string y un Diccionario y devuelve un string que será el string de entrada luego de sustituir las variables por su valor. Para ello creaos dos variables de tipo bool que nos servirán para saber si estamos recorriendo un string o si estamos recorriendo una variable, además dos string que se inicializarán vacios y representarán la variable actual y el resultado final. Primeramente recorremos caracter a caracter el string de entrada, si es una doble comilla y no estamos recorriendo un string significa que es el comienzo de uno y por tanto hacemos la variable recorriendostring true y la agregamos al string resultado, si es una comilla y estamos recorriendo un string significa que es el final de este y hacemos la variable recorriendostring false y la agregamos al string resultado, si es una letra y no estamos recorriendo un string significa que estamos leyendo una variable por tanto hacemos la variable recorriendovariable true y lo agregamos lo agregamos al string variableactual, Si es un numero y estamos recorriendo una variable se lo agregamos al nombre de la variable actual, si no es ninguno de estos casos preguntamos si estamos recorriendo variable en caso de ser así la sustituimos por el valor del diccionario y lo agregamos al diccionario, en caso contrario simplemente lo agregamos al resultado.
-Funciones: Esta clase se utiliza para realizar el parsing de la declaración de variables así como del llamado de funciones ya declaradas. Para ello cuenta con varias propiedades: un Diccionario de string-string llamado Diccfunciones que tendrá como clave el nombre de la función y como valor el cuerpo de la misma, un Diccionario de string-string[] llamado Diccnameparametros que tendrá como clave el nombre de la función y como valor un array de string que representa los parámetros de la función, cuenta con una Lista de string que contendrá los nombres de las funciones declaradas hasta el momento, tiene dos string que son expresiones regulares para identificar una declaración de variables y un llamado de función, además de dos enteros que son las posiciones de los paréntesis que encierran los parámetros. Además cuenta con 4 métodos:
-IsFunction: Este método se utiliza para saber si una expresión es la declaración de una función, para ello se utiliza una expresión regular, el método recibe dos strings uno representa la expresión y el otro el patrón a seguir, dicho método devuelve un objeto de tipo Match.
-Function: Este método se utiliza para tomar decisiones cuando nos encontramos con una declaración de función, para ello recibe un string que representa la expresión y un objeto de tipo Match, nuestra declaración de función cuenta con tres partes(nombre,parámetros,cuerpo), para ello dividimos la expresión en tres string utilizando el método Groups de la clase Match, y además el string que le corresponde a los parámetros lo dividimos por comas utilizando el metodo Split, si el string del nombre coincide con un nombre de una funcion ya declarada o con una palabra reservada lanzamos una excepción, en otro caso actualizamos el valor de la Lista, y los dos Diccionarios de la clase, por tanto devuelve void.
-IsLLamado: Este método se utiliza para saber si una expresión es el llamado de una función, para ello se utiliza una expresión regular, el método recibe dos strings uno representa la expresión y el otro el patrón a seguir, dicho método devuelve un objeto de tipo Match.
-LLamadoFunciones: Este método recibe Lista de string y un objeto de tipo Funciones y devuelve un string. El funcionamiento a groso modo de este método es identificar a que función se llama, obtener su cuerpo y sustituir los parámetros que se pasan al método en el cuerpo de la función y devolver el valor de evaluar el cuerpo con los parámetros sustituidos. Para ello creamos uns string resultado donde iremos guardaremos el resultado a devolver y además creamos una variable booleana que la utilizaremos para saber si estamos recorriendo un string, luego recorremos la lista de tokes de atrás hacia delante para asegurar no tener conflicto con las funciones que sean llamadas como parámetros de otras funciones, si el token es una doble comilla y no estamos recorriendo un string significa que es el comienzo de uno, si es una doble comilla y estamos recorriendo un string significa que es el fin de uno, posteriormente si el token es una variable que los sabemos utilizando el método EsVariable de la clase Let_In, además la tenemos en nuestra lista de nombres de funciones y no estamos recorriendo un string significa que estamos en presencia de un posible llamado de función, realizamos un ciclo a partir del nombre del llamado hasta el paréntesis que cierra los parámetros para obtener la posición donde abre el paréntesis y donde cierra(en caso de que no se abra paréntesis despues del llamado de una función se lanza una excepción, el flujo de nuestro programa nos lo posiblilita), recortamos la lista de tokens para quedarnos solo con los parámetros, hacemos Split por , para guardar cada uno de los parámetros, si la cantidad de parámetros es mayor que 1 significa que no puede estar vacio, en caso de que alguno este vacio lanzamos una excepción, además comprobamos que los parámetros coincidan en cantidad con los que tenemos guardados en nuestro objeto Funciones en el diccionario de nombre y parámetros, en caso de no coincidir se lanza una excepción, si la función no recibe parámetros obtenemos el cuerpo de la función, removemos la lista desde la posicion de llamado hasta el cierre del paréntesis de los parámetros e insertamos en la posición del llamado el valor de evaluar el cuerpo de la función, en caso de que reciba parámetros creamos un diccionario donde guardaremos la variable y su valor, el nombre será el que esta guardado en el diccionario nombre-parametros del objeto Funciones en cada una de las posiciones correspondientes y el valor será el que pasen a la función, otenemos el cuerpo de la función, sustituimos el valor de cada parámetro por cada vez que aparece en el cuerpo y luego removemos la lista desde la posición de llamado hasta el cierre del paréntesis de los parámetros e insertamos en la posición del llamado el valor de evaluar el cuerpo de la función. En caso de que sea un posible llamado de función y no la tengamos en nuestra lista de nombre comprobamos si son alguna de las funciones elementales(sin,cos,log), en caso de serlo hacemos el mismo procedimiento pero a la hora de analizar el cuerpo simplemente usamos los metodos Sin,Cos y Log de la clase Math. Si es una variable y no estamos en un string lanzamos una excepción, en cualquier otro caso continuamos el ciclo. Finalmente la Lista resultante la unimos usando el método Join de String separando por un espacio y retornamos.
-EvaluadorExpresiones: Esta clase solo contiene una método llamado QueEs dicho método es el que parsea cualquier input de entrada, y lo hace utilizando los métodos de las otras clases explicadas anteriormente, el método recibe un string y un objeto de tipo Funciones. Primeramente comprobamos si es la declaración de una función, de ser así llamar a los métodos definidos de la clase función para realizar los procesos requeridos, que serian específicamente primeramente el método IsFunction para obtener el objeto Match y luego el método Funciones. Luego comprobar si es una expresión Let In y utilizar los métodos de la clase Let_In que evaluar expresiones de este tipo, primeramente usamos el metodo Tokenizador para obtener la lista de tokens, luego dividimos la expresion utilizando el método DivideLet_In de la clase Let_In, luego dividimos variables en una Lista de Listas de string mediante el metodo DivideVariables, luego guardamos las variables utilizando el método GuardarVariables, removemos la lista de tokens desde la posición del let hasta la posición del in(que vendría siendo la parte donde se declaran las variables y ya las tenemos guardadas), luego retornamos con el mismo método QueEs para parsear lo que quedó, y pasándole como parámetros el resultado de usar el método EvaluarVariables que devuelve un string, y el objeto Funciones pasado inicialmente. Luego comprobamos si la expresión es una Condicional, en caso de serlo convertimos el input en una Lista de string usando el método Tokenizador, luego dividimos la Lista utilizando el método DivideCondicional, que devuelve un array de string, si el array en la posición 0 es un booleano(utilizando el metodo EsBoolean de la clase Condicionales) vemos el valor de ese booleano, si es true removemos la lista desde la posición donde termina el else hasta la posiciones del else, y luego removemos desde el cierre del paréntesis de la expresión booleana hasta la posición del if, y retornamos usando el método QueEs para parsear el cuerpo del if, en caso de que el booleano sea false removemos la lista desde la posición del if hasta la posición del else y hacemos el mismo procedimiento para el retorno. Si el input es un llamado de función retornamos con el mótodo QueEs pero recibienco como parámetro de string el resultado del método LlamadoFunciones de la clase Funciones. Si es una expresión aritmética retornamos el valor devuelto por el método EvaluarPosfija de la clase EvaluadorAritmetico. Si es una concatenación retornamos el valor de usar el método Concatenar de la clase Expresiones, en caso de que sea un número, un string o un booleano simplemente retornamos su valor. En caso de no ser ninguna de estas opciones retornamos "Expresion invalida".
Podemos observar que nuestro programa siempre evalua primeramente todas las expresiones let in que existan en el input, luego pasa a todas las condicionales, y luego a todos los llamados de función, por tanto cuando se llegan a todas las expresiones luego de evaluar todos los let in no debe existir ningun tipo de variables, en ese caso podemos lanzar una excepcion.
En el program.cs simplemente intanciamos un objeto de tipo Funciones donde guardaremos las funciones declaradas, agregamos la función print dándole el nombre print, parámetro x, y su cuerpo será x. Luego entramos en un ciclo while(true), donde mostraremos en consola el mensaje "Introduce tu codigo", se guarda el string introducido, si el string es S(mayúscula) se cierra el programa, en caso de que no sea comprobamos que tenga el punto y coma, si no lo tiene lanzamos una excepción, si lo tiene lo removemos y mostramos un Console.WriteLine con el resultado del llamado del metodo QueEs.
- Si se pasa una condicional como parámetro de una función y coincide en la última posición de los parámetros se debe identificar con paréntesis el cuerpo del else, en caso de no hacerse se lanzara una excepción del tipo
ParentesisNoBalanceadosporque como vamos recortando la entrada y la condicional retorna true entonces se removerá desde la posición donde cierra el else hasta la posición del else. Ej:
>function Sum(x,y)=>x+y;
>Sum(3,if(true) 3 else (4)) retorna 6
>Sum(3,if(true) 3 else 4) Excepcion-Si en el cuerpo de una función existe existe una condicional, se debe identificar entre paréntesis el cuerpo del else. Ej:
>function Pow(x,y)=> if(y==1) x else (x*Pow(x,y-1));
>Pow(2,3)
8Ejemplos de entradas
>let x=2,y=if(true) 2+4 else 3 in print(x*y);
12
>function Sum(x,y)=>x+y;
La funcion Sum ha sido definida
>function Pow(x,y)=> if(y==1) x else (x*Pow(x,y-1));
La funcion Pow ha sido definida
>Pow(Sum(2,1),Sum(2,1));
27;
>if(let x=2 in x*2 > 3) print("Hola Mundo") else print("No debe llegar aqui");
Hola Mundo
>let x=2,y=let w=if(true) 2 else 3,r=3 in w*r in x*y;
12