Debo mencionar que mal interprete la consgina del trabajo practico: asumiendo que el objetvio era crear una calculadora, teniendo en cuenta los conceptos de scanner para el input, que verificase los caracteres validos; y el parser que estos esten en correcto orden, ver 12-old_autoCalc
.
No se realizan modificaciones respecto a la versión anterior.
- EOL
- NUMBER
- VAR
- ADD
- MUL
- EQ
- LET
- L_BRACKET
- R_BRACKET
Los mismos se hayan en scanner.h
bajo el enum token_t
. Fragmento:
typedef enum Token{
EPSILON,
//\n
EOL,
//[0-9]+
NUMBER,
//[a-zA-Z]
VAR,
//[ + ]
ADD,
//[ * ]
MUL,
//[=]
EQ,
//[ ( ]
L_BRACKET,
//[ ) ]
R_BRACKET,
// [let||LET]
LET,
//[ . ]
UNDEFINED
}token_t;
En esta iteración se introduce el scanner realizado automaticámente por Flex.
Dónde se definieron las siguientes reglas
%%
[ \t]
[0-9]+ { yylval.num = atoi(yytext); return NUMBER; }
[+] { return ADD; }
[*] { return MUL; }
[=] { return EQ; }
[(] { return L_BRACKET; }
[)] { return R_BRACKET; }
("let")|("LET")|("Let") { return LET; }
[a-zA-Z]+ { yylval.index = add_variable(yytext); return VAR;}
[\n] { return EOL; }
. { yyerror(*yytext);}
%%
En cuanto a la codificación, se optó por la propuesta sugerida en el libro de MUCHNIK. Donde cada transición del automáta esta representada por una funcion, para este caso un formato static void PAS()
, donde se espía al próximo token y luego se realiza, de requerirse un terminal, match(TERMINAL)
y las transiciones correspondientes.
Esta técnica utilizada es la de Análisis Sintáctico Descendente Recursivo
(ASDR), la cual se implementa por rutinas que se van invocando de forma recursiva, contruyendo así el analisis sintactico. Cada función se llama Procedimiento de Análisis Sintáctico
(PAS)
Además, me inspire en el manual de BISON
para los nombres y diseño de las transiciones.
Si bien, esto se puede simplificar, para una mayor visibilidad encontramos el siguiente automáta:
<Lines> -> | <Line> <Lines>
<Line> -> EOL | <Calc> EOL
<Calc> -> expr | <Assignment>
<Expr> -> <Terms>
<Terms> -> <Term> | <Term> ADD <Terms>
<Term> -> <Factors>
<Factors> -> <Factor> | <Factor> MUL <Factors>
<Factor> -> NUM | VAR | L_BRACKET <Expr> R_BRACKET
<Assignment> -> LET VAR EQ <Calc>
Véase el suigiente framento de parcer.c
Donde a cada transición le corresponde una función.
// <LINES>
static void lines();
// <LINE>
static void line();
// <CALC>
static int calc();
// <EXPR>
static int expr();
// <ASSIGMENT>
static int assignment();
// <TERMS>
static int terms();
// <TERM>
static int term();
// <FACTORS>
static int factors();
// NUMBER || VAR || ( EXPR )
static int factor();
Para poder obtener cada resultado se produce una reducción, lo que podemos asociar con el concepto visto en Paradigmas de la Programación
, folding, donde cada ciertos PAS reducen a un entero, por ello static int PAS()
Nótese como la precesedencia de operadores esta implícita en los PAS, donde primero se TERMS
evalúa TERMS ADD TERM
y luego FACTORS
evalúa FACTORS MUL FACTOR
.
Para la resolución de variables opté por contstruir un módulo de memoria memory.h
, el cual se encarga de gestionar una "tabla" de símbolos a través de operaciones básicas como create
,read
y update
. Por defecto, las variables no inicializadas se les otorga el valor 0
en el momento de su creación.
Las mismas se inicializan ingresando: LET <var> = <calc>
, donde <var>
representa una variable reconocida por el scanner y <calc>
una operación aritmética.