- Instalacion
- guia de uso
- Lista de comandos
- Analizador lexico
- Analizador sintactico
- Evaluador
- Caracteres
- Palabras reservadas
- Producciones
- Manejo de errores
- Generacion de la tabla y arbol - Sin hacer
- Generacion de codigo intermedio - Sin hacer
- Ejemplo de compilacion
- Instalar Node.js
- Clonar repo:
git clone https://github.com/Veronesi/NodeCompiler.git
- Instalar modulos:
cd NodeCompiler
y luegonpm i
- crear un archivo en la carpeta en
src/public
(o utilizar uno de los archivos que trae por defecto) - Ejecutar el programa:
npm run parsing example.js show
Como no esta aun terminado por completo el interprete (Evaluador) es mejor utilizar el comando parsing
, y utilzar start
solo para asignaciones numericas y condicionales simples (sin else).
Asignacion
x = y;
Ejemplo:
var1 = 4;
otraVariable = 4 + var1;
notas = cons(1,0,7,4);
Ciclo
while(condicion){ciclo};
La expresión condicion
se evalúa antes de entrar otra vez al bucle. Si esta condición se evalúa como verdadera, se ejecuta sentencia. Cuando la condición se evalúa como false, la ejecución continúa con la ejecución del programa.
La expresión ciclo
es un programa (es decir, puede ser cualquier cosa) que se ejecutara mientras la condicion
sea verdadera.
Ejemplo:
var1 = 4;
while(var1 > 0){
var1 = var1 - 1;
writeint("",var1);
};
output:
3
2
1
0
Condicional
if(condicion){sentencia1}else{sentencia2};
o if(condicion){sentencia1};
La expresión condicion
se evalúa antes de ejecutar sentencia1
. Si esta condición se evalúa como verdadera, se ejecuta sentencia1
, caso contrario se ejecutara sentencia2
.
Ejemplo:
var1 = 4;
if(var1 > 3){
var1 = var1 * 4;
}else{
var1 = var1 + 4;
};
Lectura
readint(cadena, x);
o readlist(cadena, x);
el string cadena
se mostrara en pantalla antes de que el programa le pida al usuario ingresar un numero (en caso de writeint
) o una lista numerica (en caso de writelist
). en la variable x se guardara lo que haya sido ingresado por el usuario.
Ejemplo:
readint("edad: ", edadUsuario);
if(edadUsuario >= 18){
# El usuario es mayor de edad
}else{
# El usuario no es mayor de edad
};
En este lenguaje el caracter #
representa un comentario estilo linea
Escritura
writeint(cadena, x);
o writelist(cadena, x);
el string cadena
se mostrara en pantalla junto con la variable x
, siendo este un numero en caso de writeint
o una lista numerica en caso de writelist
.
Ejemplo:
notasUsuario = cons(4, 7, 10, 8, 5)
readint("sus notas fueron: ", notasUsuario);
output:
sus notas fueron: [4, 7, 10, 8, 5]
C:\...\NodeCompiler> npm run start testif.js show
C:\...\NodeCompiler> npm run parsing testwhile.js show
C:\...\NodeCompiler> npm run parsing testelseif.js show
Usage: npm run [Command] [filename.js] [arguments]
Global Commands
scanner ............................. Genera la tabla de componentes Lexicos
parsing ............................. Genera el arbol sintactico
compile ............................. Compila un archivo en codigo fuente
start ............................... Compila y ejecuta un archivo
help ................................ Muestra informacion sobre los comandos
characters .......................... Lista los caracteres
reserved-words ...................... Lista las palabras reservadas
productions ......................... Lista las producciones
Global arguments
debug ............................... Muetra que va haciendo el compilador
save ................................ Exporta cada etapa en archivos
show ................................ Muetra informacion al finalizar cada etapa
Examples
npm run lexying myfile.js save
npm run start myfile.js
Esta fase tiene como entrada el código fuente *.js
, donde se va leyendo línea a línea, limpiando espacios en blanco y comentarios. Con la utilización de expresiones regulares, se analiza cada elemento para insertarlo en la tabla de tokens (además de verificar si el mismo es una palabra reservada o un caracter ), indicando el tipo (ID, NUMERO, OPERADOR, etc.) y en qué línea se encuentra.
Una vez finalizada la fase de análisis léxico, continua el análisis sintáctico, verificando si el orden de estos token cumplen con las posibles producciones definidas anteriormente, es decir, si "tiene sentido el orden de los mismos".
Código de entrada
A = 3;
Tabla de Tokens generada
index | name | element | type | Line |
---|---|---|---|---|
0 | 'Token' | 'A' | 'ID' | 1 |
1 | 'Token' | '=' | 'OPERADORASIGNACION' | 1 |
2 | 'Token' | '3' | 'NUMERO' | 1 |
3 | 'Token' | ';' | 'PUNTOYCOMA' | 1 |
En esta fase solo puede generarse un error al "leerse" un elemento que no sea capturado por ninguna expresión regular, por ejemplo un caracter que no esté en la lista de caracteres
var1 = $;
LexicalError: token no válido o inesperado '$' en linea 1
En esta fase se decidió dividir en 3 etapas.
1. Buscar un subárbol (sin importar que este no sea el raíz sea la producción, es decir el nodo <Programa>
ni que el árbol este completo), ya que la cantidad de posibilidades que existen de árboles que tienen como nodo raíz <Programa>
y como primer token el primer elemento de la tabla de tokens) era mucho mayor a únicamente aquellos árboles que tienen como primer nodo el primer token de la tabla.
1.1 Se toma el primer elemento de la lista de tokens y se busca todas las producciones que general al mismo (aquellas producciones que el primer token que genera sea este).
Ej.:
Primer token de la tabla: SIGNO
<
Producciones que lo generan
{
name: 'Produccion',
Produccion: '<Condicion>',
produce: [
'<ExpresionAritmetica>',
! 'SIGNO',
'<ExpresionAritmetica>'
]
},
1.2. se insertan todas las producciones en AnalisisSintactico.produccion
que generan al token.
Ej.:
<Condicion>
├──<ExpresionAritmetica> (Produccion libre)
+ ├──SIGNO '>'
└──NUMERO (Token libre)
1.3. Se toma el próximo elemento de la lista de tokens y se verifica cada producción (una a una) obtenida del paso anterior si sigue siendo un posible subárbol que genere a toda la tabla.
1.3.1. Si el token coincide con el próximo "token libre" de la producción, este se inserta, caso contrario la producción se descarta.
Ej.:
Proximo Token: NUMERO
123
<Condicion>
├──<ExpresionAritmetica> (Produccion libre)
├──SIGNO '>'
+ └──NUMERO '123' <- Se insertara en este Token libre
1.3.2. si no hay mas tokens libres, se busca la primera "produccion libre" que se encuentre despues del ultimo token no-libre, reemplazando el mimso por aquellas producciones que generen a este token (si no hay produccion que genere a este token, la produccion se descarta).
Ej.:
Proximo Token: NUMERO
123
<Asignacion>
├──ID 'A'
├──OPERADORASIGNACION '='
! └──<ExpresionAritmetica> (Produccion libre) <- Proxima produccion libre
Producciones de <ExpresionAritmetica>
que generan NUMERO
<ExpresionAritmetica>
├──NUMERO
└──<ExpresionAritmeticaFinal>
Reemplazando en la posble produccion
<Asignacion>
├──ID 'A'
├──OPERADORASIGNACION '='
! └──<ExpresionAritmetica>
+ ├──NUMERO '123'
+ └──<ExpresionAritmeticaFinal> (Produccion libre)
1.3.3. Si no hay ni token libres ni producciones libres, se busca aquellas producciones que generan al nodo raiz de esta produccion (en el caso de que nadie genere al nodo raiz, la produccion se descarta).
Ej.:
Proximo Token: PUNTOYCOMA
! <Asignacion> <- El unico que puede generar a PUNTOYCOMA
├──ID 'A'
├──OPERADORASIGNACION '='
└──<Expresion>
└──<ExpresionAritmetica>
├──NUMERO '3'
└──<ExpresionAritmeticaFinal>
└──EPSILON
Producciones que generan a <Asignacion>
<Sentencia>
└──<Asignacion>
Insertamos todas las producciones que generan a <Asignacion>
+ <Sentencia> <- No es el Nodo Raiz del lenguaje
! └──<Asignacion>
├──ID 'A'
├──OPERADORASIGNACION '='
└──<Expresion>
└──<ExpresionAritmetica>
├──NUMERO '3'
└──<ExpresionAritmeticaFinal>
└──EPSILON
Una vez que se terminaron de analizar todas las posibles producciones para este token, si aun quedan producciones sin incluir a este (en el caso que haya pasado el punto 1.3.2 o 1.3.3) se pasa al proximo token, caso contrario se vuelven a analizar estas producciones hasta que incluyan al token.
2. Cuando ya no quedan mas tokens por analizar, siguiendo la misma estrategia del punto anterior, se trata de que el nodo raiz de cada posible produccion sea generado por el Nodo Raiz, para este lenguaje es la produccion <Programa>
.
A = 3;
+ <Programa> <- Ya es Nodo Raiz
├──<Sentencia>
│ └──<Asignacion>
│ ├──ID 'A'
│ ├──OPERADORASIGNACION '='
│ └──<Expresion>
│ └──<ExpresionAritmetica>
│ ├──NUMERO '3'
│ └──<ExpresionAritmeticaFinal>
│ └──EPSILON
├──PUNTOYCOMA ';'
└──<ProgramaFin> (Produccion libre)
3. Luego se trata de completar todas las posibles producciones, es decir, que no quede ninguna "produccion libre" ni que quede un "token libre", quedando una unica produccion posible, sinedo esta la produccion que genera a la lista de tokens.
A = 3;
<Programa> <- Ya es Nodo Raiz
├──<Sentencia>
│ └──<Asignacion>
│ ├──ID 'A'
│ ├──OPERADORASIGNACION '='
│ └──<Expresion>
│ └──<ExpresionAritmetica>
│ ├──NUMERO '3'
│ └──<ExpresionAritmeticaFinal>
│ └──EPSILON
├──PUNTOYCOMA ';'
! └──<ProgramaFin>
+ └──EPSILON
En esta fase puede generarse un error por (cuando solo queda una unica posible produccion, ya que si quedan varias esta se descarta):
- cuando se quiere insertar un token y existe un "token libre" y estos no coinciden
while(i < 10){ [ <-- Elemento '[' genera un error
readint(Elem);
a = cons(Elem, A);
i = i+1;
};
Proximo Token: CORCHETE_OPEN
<Ciclo>
├──WHILE 'while'
├──PARENTESIS_OPEN '('
├──<Condicion>
│ ├──<ExpresionAritmetica>
│ │ ├──ID 'i'
│ │ └──<ExpresionAritmeticaFinal>
│ │ └──EPSILON
│ ├──SIGNO '<'
│ └──<ExpresionAritmetica>
│ ├──NUMERO '10'
│ └──<ExpresionAritmeticaFinal>
│ └──EPSILON
├──PARENTESIS_CLOSE ')'
├──LLAVE_OPEN '{'
├──<Programa>
│ ├──<Sentencia>
│ │ └──<Lectura>
│ │ └──<LecturaLista>
- │ │ ├──READLIST <- Proximo Token Libre
│ │ └──<EscrituraLectura>
│ ├──PUNTOYCOMA
│ └──<ProgramaFin>
└──LLAVE_CLOSE
SyntaxError: token inesperado '[' en linea 1
- a la hora de verificar si la produccion esta completa (etapa 3), la unica produccion posible es descartada por que queda un token libre
var1 = 45
<Programa>
├──<Sentencia>
│ └──<Asignacion>
│ ├──ID 'var1'
│ ├──OPERADORASIGNACION '='
│ └──<Expresion>
│ └──<ExpresionAritmetica>
│ ├──NUMERO '45'
│ └──<ExpresionAritmeticaFinal>
│ └──EPSILON
- ├──PUNTOYCOMA (Token libre)
└──<ProgramaFin>
SyntaxError: se esperaba un ';' en linea 1
El evaluador se encarga de recorrer todas las producciones <Programa>
que se encuentran en el árbol generado por el analizador sintáctico, ejecutándolas de forma ordenada, en donde dependiendo del nodo hijo de la producción <Sentencia>
, ejecutara una acción.
1. <Asignacion>
Al ejecutar una asignación, el evaluador le asigna a la variable (ID
) una variable temporal, la cual esta tendrá el nuevo valor de la variable. El evaluador recorrerá el subárbol <Expresion>
.
1.1 Caso Expresión Numérica: el evaluador verifica si se trata de un numero o una variable, si es un numero le asigna el valor del mismo a una nueva variable temporal, en caso de ser una variable, busca su valor en la tabla de variables. Una vez que se recorrió toda la producción <Expresion>
el evaluador recorre la tabla de variables temporales desde el último elemento en ingresarse hasta el primero, reasignando los valores (cuando encuentra un tempX
en la propiedad value
busca su valor en la tabla), cuando todas las variables temporales fueron analizadas, se busca aquella variable que tenía como valor una variable temporal y se le asigna el valor "real" de la variable temporal.
var1 = 4;
var2 = var1 + 3;
index | name | value | _eval | type |
---|---|---|---|---|
0 | 'temp1' | '4' | null | temp |
1 | 'temp2' | '4 + temp3' | null | temp |
2 | 'temp3' | 'temp4' | null | temp |
3 | 'temp4' | '3' | null | temp |
La propiedad _eval se encarga de guardar la segunda parte del valor de una variable temporal, si es que existe
index | name | value | type |
---|---|---|---|
0 | 'var1' | 4 | var |
1 | 'var2' | 7 | var |
Ejemplo de var2:
var2 = temp2
temp4 = 3
temp3 = temp4
temp3 = 3
temp2 = 4 + temp3
temp2 = 4 + 3
var2 = eval(4+3)
var2 = 7
1.2 Caso Expresion Lista: Falta programarlo
2. <Condicional>
Al ejecutar una asignación, el evaluador deberá saber si la comparación entre las dos resulta ser verdadera o falsa, para esto, el mismo crea una variable donde calculara y se guardara el valor de cada expresión para poder compararlas. (Para evitar colisionar con una creada por el usuario se le antepone el caracter $). Como el lenguaje a interpretar utiliza los mismos signos de comparación que el lenguaje compilador (Javascript) no es necesario implementar un "switch/case".
Ejemplo:
var1 = 4;
if(var1 > 3){
var2 = var1 + 3;
};
index | name | value | type |
---|---|---|---|
0 | 'var1' | 4 | var |
1 | '$c1' | 4 | var |
2 | '$c2' | 3 | var |
3 | 'var2' | 7 | var |
donde $c1 referencia a la primera componente de la comparacion y $c2 a la segunda componente.