Generadores de Analizadores Sintácticos
#
Generadores de Analizadores SintácticosYacc
Yacc (Yet Another Compiler Compiler) esta herramienta permite especificar una gramática (libre de contexto) y genera un analizador sintáctico que reconoce oraciones validas de dicha gramática.
Yacc y Lex trabajan juntos:
Lex: Realiza el análisis lexicográfico.
Dado un stream de caracteres devuelve un stream de palabras clasificadas o tokens.
Se definen en un archivo expresiones regulares y para cada expresión regular se escribe una o un conjunto de acciones asociadas a esa expresión regular:
[a-zA-Z][a-zA-Z0-9]+ {return COMMAND}
- Lex toma un archivo de entrada, un stream de caracteres transforma en un archivo de salida, un stream de trokens
Yacc: Yet Another Compiler Compiler
- Yacc genera un parser y un analizador semántico que generan el parseo y el correspondiente análisis, sobre el stream de tokens que produce lex. A medida que se está generando el análisis sintáctico va armando el **Árbol de Parsing** y también se puede realizar cierto análisis semántico con la misma herramienta.
- Bison: Esta herramienta es la versión de GNU, que es compatible con Yacc, pero además hace otras muchas cosas.
<img src="../images/yacc1.png" alt="img" style="zoom:200%;" />
El input de yacc es una **gramática libre de contexto**, el analizador sintáctico generado va a construir un **árbol de parsing**.
<img src="../images/yacc2.png" alt="img" style="zoom:200%;" />
- mylang.y: define la gramática del lenguaje. Se puede definir tokens, tipos de tokens y eso se guarda en y.tab.h
Una vez que se tienen definidos ambos archivos el de la gramática y el del analizador léxico, se compilan para generar o el interprete o el compilador.
<img src="../images/yacc2.png" alt="img" style="zoom:200%;" />
Un Archivo YACC
La estructura de un archivo Yacc es muy similar a la estructura de los achivos lex:
Primera Parte%% producción {accion}%%Tercera Parte
Primera Parte
La primera parte del archivo de Yacc contiene:
Declaraciones en C que están delimitadas por %{%}
Definiciones específicas de Yacc
- %start
- %token
- %union (tokens de diferente tipos)
- %type (el tipo que puede tomar un token)
Producciones
La sección del medio representa la gramática.
las acciones asociadas a una producción ven entre {}
Ejemplo:
statements: statement {printf("statement");} | statement statements {printf("statements \n");} ; statement: identifier '+' identifier {printf("suma"); } statement: identifier '-' identifier {printf("resta"); }
Un aspecto interesante de las producciones es que Yacc permite acceder a los valores que están asociados con los símbolos de las producciones:
$1, $2, $3, … $N : se refieren a los valores de los símbolos asociados a las producciones.
Todos los símbolos tienen un valor asociado, sean terminales o no terminales.
La asociación por defecto es $$=$1
Entonces se puede escribir:
statement: identifier '+' identifier {$$ = $1 + $3; } statement: identifier '-' identifier {$$ = $1 - $3; }
Representan la semántica de la producción.
Manos a la Obra
En este ejemplo se desarrolla una calculadora muy básica
mi gramática:
instruccion -> NOMBRE '=' expresion | expresion expresion: -> NUMERO '+' NUMERO | NUMERO '-' NUMERO | NUMERO
Características:
- Reconocer las operaciones
- Evaluar los resultados
mi_lenguaje.y:
%{#include<stdio.h> extern int yylex(void);extern char * yytext;void yyerror(char * s); }%%token NOMBRE NUMERO%%instruccion: NOMBRE '=' expresion | expresion {printf("= %d\n",$1);} ; expresion: NUMERO '+' NUMERO {$$ = $1 + $3;} | NUMERO '-' NUMERO {$$ = $1 - $3;} | NUMERO {$$ = $1;} ;%%void yyerror (char *s){ printf("%s",s);} int main(){ yyparse(); return 0;}
mi_lenguaje.l:
%{#include<stdlib.h> #include "y.tab.h"extern int yylval;%} %% [0-9]+ { yylval=atoi(yytext); return NUMERO;}[ \t] ;\n return 0; //EOF . return yytext[0];%%