lunes, 14 de noviembre de 2016

Analisis Lexico

  
Análisis Léxico 

Analizador sintáctico PLY (Python Lex Yacc) de Python.

Creación y generalidades:El metacompilador hecho en lenguaje Python PLY es originario de la universidad de Chicago, por el profesor de ciencias de la computación David Beazly, esta api presenta una integrativa de Lex y Yacc que son dos apis que permiten la creación de analizadores léxicos, ambos escritos en lenguaje C.

Fue creado como una herramienta de instrucción primeramente para usuarios iniciados, pero la herramienta demostró ser robusta y por ello también es usada por usuarios experimentados, esto tampoco implica la creación de árboles sintácticos y la generación de herramientas avanzadas, si no tomado como una herramienta de análisis.

En general PLY presenta dentro de sí dos scripts que no son más que Lex y Yacc escritos en Python (Lex.py y Yacc.py), de manera que se permite la integración de ambos en programas en los que se desee desarrollar análisis léxico de otros scripts o instrucciones, estas instrucciones se evalúan en una colección de tokens suministrados y reglas de expresiones regulares, Yacc.py se utiliza para reconocer la sintaxis del lenguaje que se ha especificado en la forma de una gramática libre de contexto.

Las dos herramientas están destinadas a trabajar juntos. En concreto, lex.py proporciona una interfaz externa en forma de un símbolo () función que devuelve el siguiente token válido del flujo de entrada. Yacc.py llama a esto varias veces para recuperar los símbolos e invocar las reglas gramaticales.

Yacc está dentro de Unix por eso al usar un sistema basado en Unix (alguna distribución de Linux) encontraremos el programa Yacc, sin embargo Yacc.py tiene algunas diferencias con este, la principal entre yacc.py y Unix Yacc es que yacc.py no implica un proceso de generación de código independiente, a diferencia tradicional lex / yacc que requieren un archivo de entrada especial que se convierte en un archivo fuente separado, las especificaciones dadas para ejercer son programas Python válidos. Esto significa que no hay archivos de origen adicionales ni hay un paso especial construcción del compilador (por ejemplo, correr yacc para generar código Python para el compilador).

Lex:
lex.py se utiliza para dividir una cadena de entrada. Por ejemplo, supongamos que usted está escribiendo un lenguaje de programación y un usuario se suministra la siguiente cadena de entrada:
x = 3 + 42 * (s - t)
            Lex separa en tokens la cadena de texto ingresada.
'X', '=', '3', '+', '42', '*', '(', 's', '-', 't', ')'

Los créditos son por lo general dan nombres para indicar cuáles son. Por ejemplo:

"ID", "iguales", "Número", "más", "Número", "tiempos",
'LPAREN', 'ID', 'menos', 'ID', 'RPAREN'

Más específicamente, la entrada se divide en pares de tipos y valores de tokens. Por ejemplo:

( 'ID', 'x'), ( 'es igual a', '='), ( 'Cantidad', '3'),
( 'PLUS', '+'), ( 'Cantidad', '42), (' TIMES ',' * '),
( 'LPAREN', '('), ( 'ID', 's'), ( 'MENOS', '-'),
( 'ID', 't'), ( 'RPAREN', ')'
           
(Writing Parsers and Compilers with PLY, David Beazley, 2007)

            Estructura del Script del analizador personalizado.
            PLY nos permite crear un analizador personalizado, en el cual podemos crear una serie de definiciones que nos permitirán evaluar las expresiones que ingresemos en un script o en la consola de comandos, el cual recibirá una evaluación certera o negada, en cada uno de sus tokens, en este caso serán presentados una serie de ejemplos y directrices necesarias para la construcción de un analizador léxico personalizado, no contemplan el todo de la api a usar, en el caso que se requiera toda la información, se puede dirigir a la documentación oficial de dicha interfaz de programación de aplicaciones (API).

Se debe proporcionar una lista de tokens: en dicha lista se deben definir un grupo de nombres simbólicos que se pueden producir por el analizador léxico, esta lista también es usada por el Yacc.py. Ejemplo:
tokens = (
   'NÚMERO',
   'MÁS',
   'MENOS',
   'VECES',
   'DIVIDIR',
   'LPAREN',
   'RPAREN',
)

Se agrega una definición a los tokens: se crean fichas, o definiciones de cada uno de los tokens, estableciendo una expresión regular para cada uno, en esta api se debe identificar con un prefijo ‘t_’ que indica que es una definición de token, el calor siguiente debe coincidir exactamente con un valor suministado en la lista de tokens. Ejemplo.

t_MAS = r’\ +’
Palabras reservadas: Para manejar las palabras reservadas, se debe escribir una sola regla para que coincida con un identificador y hacer una búsqueda de nombre especial en una función. Ejemplo.
reserved = {
   'if' : 'IF',
   'then' : 'THEN',
   'else' : 'ELSE',
   'while' : 'WHILE',
   ...
}

tokens = ['LPAREN','RPAREN',...,'ID'] + list(reserved.values())
 
def t_ID(t):
    r'[a-zA-Z_][a-zA-Z_0-9]*'
    t.type = reserved.get(t.value,'ID')  
    return t

Comentarios: Para descartar una ficha, tal como un comentario, sólo se tiene que definir una regla de token que no devuelve nada. Ejemplo:

def T_COMMENT (t):
    r '\ #. *'
    pass
    # No hay valor de retorno. Token descartados
Numeros de línea e información posicional: Para actualizar esta información, se debe escribir una regla especial. En el ejemplo, el t_newline () regla muestra cómo hacer esto.
def t_newline(t):
    r'\n+'
    t.lexer.lineno += len(t.value)
 
               La palabra reservada lineno, toma el número de las líneas y esta información se le asigna más el valor de la línea actual, en resultado un incremento progresivo de las líneas de código escritas.
 
            Caracteres literales: Los caracteres literales se pueden especificar mediante la definición de una variable literal en su módulo léxico. Por ejemplo:
               literals = [ '+','-','*','/' ]
 
O alternativamente

               literals = "+ - * /"

Un carácter literal es simplemente un único carácter que se devuelve tal cual cuando se encuentran por el analizador léxico. Los literales se comprueban después de todas las reglas de expresiones regulares definidas. Por lo tanto, si una norma comienza con uno de los caracteres literales, siempre tendrá prioridad.

Errores de manipulación: se debe agregar una función que se utiliza para controlar los errores léxicos, que se producen cuando se detectan caracteres no válidos. Su definición se da de la siguiente manera:

def t_error(t):
    print("Caracter ilegal '%s'" % t.value[0])
    t.lexer.skip(1)

Fin de archivo: la función de fin de archivo denota que no se ingresó más contenido para ser analizado o que el script que se proporcionó llego a su fin. Ejemplo:

def t_eof(t):
    more = raw_input('... ')
    if more:
        self.lexer.input(more)
        return self.lexer.token()
    return None




               Construccion y utilización del léxico: para la construcción del analizador, se usa la función lex.lex()
 
               Lexer = lex.lex()
               
               Esta función inspecciona las reglas de expresiones regulares, fuera del contexto de llamada y construye el analizador léxico, una vez este esta construido se utilizan dos métodos para controlarlo.
 
               Lexer.input(datos): restablece el analizador y almacena una nueva cadena de entrada.
 
               Lexer.token(): devuelve el siguiente token, devuelve una respuesta de éxito o fracaso en el análisis de la entrada.
 
               (Documentación de PLY Python; Teaching Compilers with Python, Matthieu Amiguet, 2010)
 
 
2.    Analizador léxico para lenguaje de expresiones matemáticas básicas.

Se eligió para esta asignación el lenguaje de programación Python, debido a su sintaxis bastante simple y a su dinámica de código, en esta ocasión se presenta la estructura del analizador léxico personalizado de expresiones matemáticas simples.
·         Importacion de la API contenedora de Lex y Yacc, en este caso solo usaremos lex debido a que solo se pide un analizador léxico, hacemos la abreviación “as lex” para usar la api en el script con el nombre de “lex”


·         Paso siguiente, se agrega la lista de tokens.



·         Luego se agregan las expresiones regulares para cada token declarado en la lista anterior que sea una expresión simple.



·         Se agregan las expresiones regulares de los tokens que representan una definición mayor, en este caso para el token “NUMBER” que representa una expresión numérica o un número entero.



·         Se agrega la definición de saltos de línea lo cual nos permite evaluar varias expresiones.



·         Se agrega posteriormente la definición de caracteres ignorados, esto nos permite incluir espacios y tabulaciones



·         Gestión de errores, dada por la función que permite mostrar caracteres ilegales no presentes en la lista inicial de tokens.



·         Finalizando las definiciones tenemos la construcción del analizador, creando así un objeto analizador.


                        Todo el script presentado, estará adjunto a este documento, el paso siguiente es la construcción de expresiones que puedan ser leídas por el analizador, estas nos permitirán ver la precisión de dicho lexema y su contenido.
Para la evaluación de las expresiones matemáticas y los ejemplos, creamos otro script en el cual incluimos la llamada de un archivo .txt y la integración del analizador antes creado, los comentarios nos permiten entender más el script.


3.    5 Ejemplos, en prueba del analizador.
Se ejecutaron varios ejemplos guardados en el archivo textlex.txt, los cuales se presentan a continuación
·         Expresión: x = (1 + 5) * 20 / 5
Esta expresión como es de notarse tiene correspondencia con tokens, y por ende no arroja errores léxicos.



Es notorio mostrar la secuencia de verificación que nos va indicando, a que token pertenece cada elemento ingresado, cuando llega al final de la expresión no existen errores, recordemos y tengamos en cuenta que el resultado será aceptación del léxico, y no un resultado matemático ponderado, así ajustándose a los requerimientos pedidos.

·         Expresión: y = 25 + (5 / 6) - 100 + w
En esta expresión podemos notar un elemento en una posición que podría causar un poco de incertidumbre, el elemento w no debería permitir la operación, pero recordemos que es un análisis léxico, es saber si la expresión tiene correspondencia con lo definido, y por ende está bien definido y no contiene errores.



·         Expresión: y = 45 - (30/2) * 12
En este tercer caso también se presenta una expresión válida para nuestro analizador y como vemos el resultado es válido en la consola, en donde recibimos el resultado.



·         Expresión: z = {23}{23}
En este caso la expresión no tiene sentido matemático, pero no es por esto que sea errónea para el analizador, si no que no tiene correspondencia con los tokens definidos.



Y en el resultado podemos observar que nos devuelve los 4 caracteres ilegales que no tienen correspondencia.

·         Expresión: w= 32 # 5
En este caso esta expresión no es válida, los números si lo son pero el elemento “#” no está dentro de los tokens.



Como vemos en la evaluación es devuelto como carácter ilegal.



Referencias Bibliográficas.

Writing Parsers and Compilers with PLY, David Beazley, 2007
Documentación de PLY Python.

Teaching Compilers with Python, Matthieu Amiguet, 2010.

Análisis Sintáctico

Análisis Sintáctico


Análisis Sintáctico.

El análisis sintáctico es un análisis a nivel de sentencias, y es mucho más complejo que el análisis léxico. Su función es tomar el programa fuente en forma de tokens, que recibe del analizador léxico, y determinar la estructura de las sentencias del programa. Este proceso es similar a determinar la estructura de una frase en un idioma de habla humana, determinando quien es el sujeto, predicado, el verbo y los complementos. (Sergio Gálvez Rojas, Traductores, Compiladores e Intérpretes, 2009).

Manejo de errores sintácticos.

   Si un compilador tuviera que procesar sólo programas correctos, su diseño e implantación se simplificarían mucho. Pero los programadores a menudo escriben programas incorrectos, y un buen compilador debería ayudar al programador a identificar y localizar errores. Es más, considerar desde el principio el manejo de errores puede simplificar la estructura de un compilador y mejorar su respuesta a los errores.
Los errores en la programación pueden ser de los siguientes tipos:
• Léxicos, producidos al escribir mal un identificador, una palabra clave o un operador.
• Sintácticos, por una expresión aritmética o paréntesis no equilibrados.
• Semánticos, como un operador aplicado a un operando incompatible.
• Lógicos, puede ser una llamada infinitamente recursiva.
El manejo de errores de sintaxis es el más complicado desde el punto de vista de la creación de compiladores. Nos interesa que cuando el compilador encuentre un error, se recupere y siga buscando errores. Por lo tanto el manejador de errores de un analizador sintáctico debe tener como objetivos:
• Indicar los errores de forma clara y precisa. Aclarar el tipo de error y su localización.
• Recuperarse del error, para poder seguir examinando la entrada.
• No ralentizar significativamente la compilación.
Un buen compilador debe hacerse siempre teniendo también en mente los errores que se pueden producir; con ello se consigue:
• Simplificar la estructura del compilador.
• Mejorar la respuesta ante los errores.
(Sergio Gálvez Rojas, Traductores, Compiladores e Intérpretes, 2009).

Gramáticas aceptadas por un analizador sintáctico.

Nosotros nos centraremos en el análisis sintáctico para lenguajes basados en gramáticas formales, ya que de otra forma se hace muy difícil la comprensión del compilador, y se pueden corregir, quizás más fácilmente, errores de muy difícil localización, como es la ambigüedad en el reconocimiento de ciertas sentencias.
La gramática que acepta el analizador sintáctico es una gramática de contexto libre, ya vista en el tema Jerarquía de Chomsky el cual nos define esta gramática como una cuádrupla con elementos:
• G (N, T, P, S)
N = No terminales.
T = Terminales.
P = Reglas de Producción.
S = Axioma Inicial
Ejemplo : Se considera la gramática que reconoce las operaciones aritméticas.
E  -> E + T
| T
 T -> T * F
| F
 F -> ID
| NUM
 | ( E )
En el que: N = {E, T, F} están a la izquierda de la regla.
 T = {ID, NUM, ( ,) ,+ ,*}
 P = Son las siete reglas de producción.
 S = Axioma inicial. Podría ser cualquiera, en este caso es E.
También se incluyen la derivaciones por la izquierda vito en el tema anterior y las asignaciones anteriores.

Árbol Sintáctico.

Es una representación que se utiliza para describir el proceso de derivación de una sentencia, recordemos las derivaciones son el proceso de descomposición y resolución de los autómata de pila, es el intercambio del valor de la derecha por el valor correspondiente, mejor explicado en la asignación Jerarquía de Chomsky.
            En cierto sentido un árbol sintáctico es la representación de las derivaciones de modo gráfico y los pasos para llegar a una respuesta específica o un valor deseado.

Proceso de Análisis Sintáctico.

El proceso de análisis sintáctico parte justo después de realizar el análisis léxico, es el paso siguiente y permite un análisis más apegado a un lenguaje que el anterior, el proceso fundamental del análisis sintáctico parte de los siguientes puntos.
·         Comprobar si la cadena de componentes léxicos proporcionada por el analizador léxico puede ser generada por la gramática que define el lenguaje fuente (Gramática libre de contexto).

·         Construir el árbol de análisis sintáctico que define la estructura jerárquica de un programa y obtener la serie de derivaciones para generar la cadena de componentes léxicos. El árbol sintáctico se utilizara como representación intermedia en la generación de código.

·         Informar de los errores sintácticos de forma precisa y significativa y debería estar dotado de un mecanismo de recuperación de errores para continuar con el análisis.

Analizador sintactico 

     Comprueba que las sentencias que componen el texto fuente son correctas en el lenguaje, creando una representación interna que corresponde a la sentencia analizada. De esta manera se garantiza que solo serán procesadas las sentencias que pertenezcan al lenguaje fuente. Durante el análisis sintáctico, así como en las demás etapas se irán mostrando los errores que se encuentran. En otros términos se puede definir un analizador sintáctico como el encargado del orden de los tokens que implica agrupar los componentes léxicos del programa fuente en frases gramaticales que el compilador utiliza para sintetizar la salida.

Nota: Ejemplo de Error Sintáctico, una expresión aritmética con mayor número de paréntesis  de apertura que de cierre.

Ejemplo #1: Arbol de análisis sintáctico (Describe la estructura sintáctica de la ENTRADA).

posición:= incial+velocidad*60



En la expresión inicial+velocidad*60, la frase velocidad * 60 es una unidad lógica, porque las convenciones usuales de las expresiones aritmeticas indican que la multiplicación se hace antes que la suma. Puesto que la expresión inicial+ velocidad va seguida de un * , no se agrupa en una sola frase independiente.


Ejemplo #2: Árbol de análisis sintáctico (Representación interna más común de la estructura sintáctica es la que da el árbol sintáctico de la siguiente figura).



Un árbol sintáctico es una representación compacta del árbol de análisis sintáctico en el que los operadores aparecen como nodos interiores y los operando de un operador son los hijos del nodo para ese operador.

Función del Analizador Sintáctico:

Analizar  sintácticamente una cadena de tokens no es más que encontrar  para ella el árbol sintáctico o de derivación que tiene como raíz  el axioma de la gramática, y como nodos terminales la sucesión ordenada de símbolos que componen la cadena analizada. En caso de no existir este árbol sintáctico, la cadena no pertenecerá  al lenguaje  y el analizador sintáctico habrá de emitir el correspondiente mensaje de error. Existen dos formas de analizar sintácticamente una cadena:

  Análisis  Descendiente: Se parte del axioma inicial de la gramática y se va descendiendo utilizando las derivaciones izquierdas, hasta llegar a construir la cadena analizada. ANTLR  genera árboles sintácticos descendientes, escrito en java y genera código en java o c++. Tiene como ventaja que es buena integración de los analizadores léxicos y sintácticos y como desventaja, genera analizadores menos eficientes que los generadores YACC.

  Análisis Ascendente: Se va construyendo el árbol desde sus nodos terminales. Es decir, se construye desde los símbolos de la cadena hasta llegar al axioma de la gramática. Simultáneamente  a la fase de análisis sintáctico, además de reconocer las secuencias de tokens y analizar su estructura, pueden realizarse una serie de tareas adicionales como:
Recopilar información de los distintos tokens y almancenarla en la tabla de símbolos.
Realizar algún tipo de análisis semántico, tal como la comprobación de tipos.
Generar Código Intermedio.
Avisar de los errores que se detecten.
JAVACC inicialmente se llamó JACK es similar al ANTLR y genera árboles ascendentes. Tiene como ventaja que es buena integración en los analizadores léxicos y sintácticos. Genera analizadores sintácticos y por lo siguiente árboles sintácticos y como desventaja, analizadores menos eficientes.


Ejemplo: Construir una Calculadora. (Analizador sintactico)

Especificaciones sintácticas:

El lenguaje a reconocer viene definido por la siguiente gramática:

Calculadora:   id = Expresion ;
Expresión:      num |
                       Expresion +Expresion | Expresion -Expresion |
                       Expresion * Expresion | Expresion /Expresion |
                       (Expresion )| sen (Expresion )| cos (Expresion)

Mostrar Código Ejemplo:

Para una pequeña calculadora que permite trabajar con numeros enteros y reales con las operaciones básicas de suma, resta, producto, division y trigonometricas como el seno y el coseno.


Fichero lexico

#include <stdio.h>
#include <stdlib.h>
int nlines=0;
%}
DIGITO [0-9]
ID [a-zA-Z][a-zA-Z0-9_]*
%%
{DIGITO}+  {printf("Encontrado TKN_NUM_ENTERO:%d",atoi(yytext));}{DIGITO}+"."{DIGITO}+                 {printf("Encontrado TKN_NUM_REA%f",atof(yytext));}
"="              {printf("Encontrado TKN_ASIGN: %s",yytext);}
";"               {printf("Encontrado TKN_PTOCOMA: %s",yytext);}
"*"              {printf("Encontrado TKN_MULT: %s",yytext);}
"/"              {printf("Encontrado TKN_DIV: %s",yytext);}
"+"             {printf("Encontrado TKN_MAS: %s",yytext);}
"-"             {printf("Encontrado TKN_MENOS: %s",yytext);}
"("             {printf("Encontrado TKN_PAA: %s",yytext);}
")"             {printf("Encontrado TKN_PAC: %s",yytext);}
"cos"         {printf("Encontrado TKN_COS: %s",yytext);}
"sen"         {printf("Encontrado TKN_SEN: %s",yytext);}
{ID}           {printf("Encontrado TKN_ID: %s",yytext);}
"\n"           {nlines++;}
.
%%
void main(int argc,char **argv)
{
if (argc>1)
   yyin=fopen(argv[1],"rt");
else
   yyin=stdin;
   yylex();
printf("\nNumerolineas analizadas: %d\n", nlines);
}
/* para compilar
flex lexico.l
cc lex.yy.c -o milex -lfl -lm
*/

Fichero lexico (Version a enlazar con Bison):

#include <stdio.h>
#include <stdlib.h>
#include "sintactico.tab.h"
int nlines=0;
%}
DIGITO [0-9]
ID [a-zA-Z][a-zA-Z0-9_]*
%%
{DIGITO}+("."{DIGITO}+)? {//printf("Encontrado TKN_NUM: %f\n",atof(yytext));
yylval.real=atof(yytext);
return(TKN_NUM);}
"="          {//printf("Encontrado TKN_ASIGN: %s\n",yytext);
             return(TKN_ASIGN);}
";"          {//printf("Encontrado TKN_PTOCOMA: %s\n",yytext);
             return(TKN_PTOCOMA);}
"*"          {//printf("Encontrado TKN_MULT: %s\n",yytext);
             return(TKN_MULT);}
"/"         {//printf("Encontrado TKN_DIV: %s\n",yytext);
            return(TKN_DIV);}
"+"         {//printf("Encontrado TKN_MAS: %s\n",yytext);
            return(TKN_MAS);}
"-"        {//printf("Encontrado TKN_MENOS: %s\n",yytext);
           return(TKN_MENOS);}
"("       {//printf("Encontrado TKN_PAA: %s\n",yytext);
          return(TKN_PAA);}
")"       {//printf("Encontrado TKN_PAC: %s\n",yytext);
          return(TKN_PAC);}
"cos"     {//printf("Encontrado TKN_COS: %s\n",yytext);
          return(TKN_COS);}
"sen"     {//printf("Encontrado TKN_SEN: %s\n",yytext);
          return(TKN_SEN);}
{ID}      {//printf("Encontrado TKN_ID: %s\n",yytext);
          return(TKN_ID);}
"\n"      {nlines++;}
%
/********
Para el lexico solo
void main(int argc,char **argv)
{
if (argc>1)
yyin=fopen(argv[1],"rt");
else
yyin=stdin;
yylex();
printf("\nNumero lineas analizadas: %d\n", nlines);
}
*******/
/* para compilar flex lexico.lcc lex.yy.c -o milex -lfl -lm*/

Fichero Sintactico.y (Bison)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
extern int yylex(void);
extern char *yytext;
extern int nlines;
extern FILE *yyin;
void yyerror(char *s);
%}

%union
{
float real;
}

%start Calculadora
%token <real> TKN_NUM
%token TKN_ASIGN
%token TKN_PTOCOMA
%token TKN_MULT
%token TKN_DIV
%token TKN_MAS
%token TKN_MENOS
%token TKN_PAA
%token TKN_PAC
%token TKN_COS
%token TKN_SEN
%token <real
> TKN_ID
%type Calculadora
%type <real> Expresion
%left TKN_MAS TKN_MENOS
%left TKN_MULT TKN_DIV
%%

Calculadora : TKN_ID { printf("El valor de %s es: ", yytext);}
TKN_ASIGN Expresion TKN_PTOCOMA { printf("%5.2f\n", $4); } ;
Expresion :
TKN_NUM {$$=$1;}|
Expresion TKN_MAS Expresion {$$=$1+$3;}|
Expresion TKN_MENOS Expresion {$$=$1-$3;}|
Expresion TKN_MULT Expresion {$$=$1*$3;}|
Expresion TKN_DIV Expresion {$$=$1/$3;} |
TKN_PAA
Expresion TKN_PAC {$$=$2;}|
TKN_COS TKN_PAA Expresion TKN_PAC {$$=cos($3);}|
TKN_SEN TKN_PAA Expresion TKN_PAC {$$=sin($3);};
%%
void yyerror(char *s)
{
printf("Error %s",s);
}
int main(int argc,char **argv)
{
if (argc>1)
yyin=fopen(argv[1],"rt");
else
yyin=stdin;
yyparse();
printf("FIN del Analisis. Entrada CORRECTA\n");
printf("Numero lineas analizadas: %d\n", nlines);
return 0;
}
/* para compilar bison -d sintactico.yflex lexico.lcc lex.yy.c sintactico.tab.c -o analizador -lfl -lm*/



     Para el lenguaje creado simulador de calculadora se toma las siguientes características o sintaxis:

X es la variable que guardara el resultado
= es el operador que asignara el resultado de la operación a la variable X
Luego se procede a colocar 2 cifras a operar.
Y por ultimo el operando +, -, *, /. De la operación que se desea realizar finalizado por un ;
De lo contrario el programa se cerrara.



Ejemplo 1: SUMA





Ejemplo 2: RESTA










Ejemplo 3: DIVISION



Ejemplo 4: MULTIPLICACION




Ejemplo 5: ASIGNACION DE VALOR



En el caso de haber un error el programa se cerrara sin mostrar ningún detalle.


Referencia Bibliográfica


Vanegas, C. A. (2013). Compiladores: un enfoque. Vínculos, 1(2), 59-66.

Palacios, R. H., & González, D. H. (2014). Herramientas para el diseño de compiladores. CIENCIA HUASTECA, 1(2).

Sethi, R., & Ullman, J. D. (1998). Compiladores: principios, técnicas y herramientas. Pearson Educación.