generador de analizador sintáctico yacc y javacc

23
Tecnológico Nacional de México Instituto Tecnológico de Pachuca Ingeniería en Sistemas Computacionales Materia: Lenguaje Y Autómatas I Alumno: Miguel Castillo López No. De control: 14200855 Unidad 6: Investigación sobre el generador de analizador sintáctico YACC y JavaCC

Upload: castillo-miguel

Post on 17-Sep-2015

57 views

Category:

Documents


1 download

DESCRIPTION

Generador de analizador sintáctico YACC y JavaCC

TRANSCRIPT

Tecnolgico Nacional de MxicoInstituto Tecnolgico de Pachuca

Ingeniera en Sistemas Computacionales

Materia: Lenguaje Y Autmatas I

Alumno: Miguel Castillo LpezNo. De control: 14200855

Unidad 6: Investigacin sobre el generador de analizador sintctico YACC y JavaCC

GENERADOR DE ANALIZADOR SINTCTICO YACC

Yacc es un programa para generar analizadores sintcticos. Las siglas del nombre significan Yet Another Compiler-Compiler, es decir, "Otro generador de compiladores ms". Genera un analizador sintctico (la parte de un compilador que comprueba que la estructura del cdigo fuente se ajusta a la especificacin sintctica del lenguaje) basado en una gramtica analtica escrita en una notacin similar a la BNF. Yacc genera el cdigo para el analizador sintctico en el Lenguaje de programacin C.

Fue desarrollado por Stephen C. Johnson en AT&T para el sistema operativo Unix. Despus se escribieron programas compatibles, por ejemplo Berkeley Yacc, GNU visn, MKS yacc y Abraxas yacc (una versin actualizada de la versin original de AT&T que tambin es software libre como parte del proyecto de OpenSolaris de Sun). Cada una ofrece mejoras leves y caractersticas adicionales sobre el Yacc original, pero el concepto ha seguido siendo igual. Yacc tambin se ha reescrito para otros lenguajes, incluyendo Ratfor, EFL, ML, Ada, Java, y Limbo.

Puesto que el analizador sintctico generado por Yacc requiere un analizador lxico, se utiliza a menudo conjuntamente con un generador de analizador lxico, en la mayora de los casos lex o Flex, alternativa del software libre. El estndar de IEEE POSIX P1003.2 define la funcionalidad y los requisitos a Lex y Yacc.

La versin Yacc de AT&T se convirti en software libre;

VERSIONES

Versin Yacc de AT&T ML-Yacc, una versin de yacc para el lenguaje Standard ML. Yecc, una versin de yacc para Erlang.

MODO DE USO

Construccin de un traductor usando Yacc:

Partes de las que consta un programa fuente en Yacc:

{declaraciones}%%{reglas}%%{rutinas de apoyo en C}

-Declaraciones

1) Declaraciones ordinarias en C, delimitadas por %{ y %}.2) Declaraciones de los componentes lxicos de la gramtica (esto se explica ms adelante).

Podra estar vaca

-Reglas de traduccin

Cada una de ellas consta de una produccin de la gramtica y la accin semntica asociada.

Es decir,

| | ... |

pasara a ser en Yacc:

: {accin semntica 1} | {accin semntica 2} ... | {accin semntica n} ;

Un carcter simple entre comillas c se considera como el smbolo terminal c. Las cadenas sin comillas de letras y dgitos no declaradas como componentes lxicos se consideran no terminales. El primer lado izquierdo se considera como smbolo inicial por defecto, o bien se declara : %start smbolo Una accin semntica es una secuencia de proposiciones en C, dnde $$ se refiere al valor del atributo asociado con el no terminal del lado izquierdo, mientras que $i se refiere al valor asociado con el i-simo smbolo gramatical del lado derecho. Se ejecuta siempre que se reduzca por la produccin asociada. Por defecto es {$$=$1;}. Una accin semntica no tiene por qu venir al final de su regla. Yacc permite que una accin sea escrita en mitad de una regla. Esta regla devuelve un valor, accesible de forma normal por las acciones que estn a su derecha, que pueden acceder a los valores devueltos por los smbolos a su izquierda

Ejemplo: A:B {$$=1;} C {x=$2; y=$3;} El efecto es poner x a 1 e y al valor devuelto por C.

Las acciones que no terminan una regla son manejadas por Yacc como si fueran un nuevo nombre de no-terminal, con una nueva regla para l con el String vaco en su parte derecha, y, como accin de esa regla, ella misma.

Es decir, el ejemplo anterior lo maneja como si se hubiera escrito as:

$ACT : /* empty */ {$$=1;}; A : B $ACT C {x=$2; y=$3;};

NOTA: Puede haber conflictos cuando ocurre una accin interior en una regla antes de que el parser pueda estar seguro de qu regla est siendo reducida.En muchas aplicaciones, la salida no viene dada directamente con las acciones; en su lugar, una estructura de datos, tal como un rbol de anlisis, se construye en memoria y se aplican transformaciones en l antes de que la salida sea generada. Los rboles de anlisis son particularmente fciles de construir, si se tienen rutinas para realizar y mantener la estructura de rbol deseada. Ejemplo.- Tenemos una funcin C que se llama nodo, de forma que

nodo(L,n1,n2)

crea un nodo con etiqueta L y descendientes n1 y n2, y devuelve el ndice del nuevo nodo. El rbol de anlisis podra ser realizado suministrando acciones en la especificacin como

expr : expr '+' expr {$$=nodo('+',$1,$3);}

Pueden definirse tambin otras variables para ser usadas por las acciones. Tanto las declaraciones como las definiciones deben aparecer en la seccin de declaraciones, entre %{ y %}. Tienen mbito global. Deben evitarse los nombres de variables que empiecen por yy, pues los nombres de variables internas de Yacc comienzan de esta forma todos.

EJEMPLOS

Mini calculadora

A continuacin, vamos a analizar un ejemplo sencillo de una verdadera especificacin de yacc, que es la gramtica para una calculadora sencilla que permite hacer operaciones como suma, resta, multiplicacin, divisin y exponente.

%{#include %}

%union{ double dval;}

%token NUMBER %token PLUS MINUS TIMES DIVIDE POWER%token LEFT_PARENTHESIS RIGHT_PARENTHESIS%token END

%left PLUS MINUS%left TIMES DIVIDE%left NEG%right POWER

%type Expression%start Input

%%

Input:Line| Input Line ;

Line:END | Expression END { printf("Result: %f\n",$1); } ;

Expression:NUMBER { $$=$1; }

| Expression PLUS Expression { $$=$1+$3; } | Expression MINUS Expression { $$=$1-$3; } | Expression TIMES Expression { $$=$1*$3; } | Expression DIVIDE Expression { $$=$1/$3; } | MINUS Expression %prec NEG { $$=-$2; } | Expression POWER Expression { $$=pow($1,$3); } | LEFT_PARENTHESIS Expression RIGHT_PARENTHESIS { $$=$2; } ;

%%int yyerror(char *s) { printf("%s\n",s);}

int main(void) { yyparse();}

Definiciones

En esta primera seccin, al igual que en lex, incluimos las libreras que usaremos en el programa, definiciones de los tokens, tipos de datos y precedencia de la gramtica.

%union

Esta definicin, se traduce a una union de C que a su vez dar el tipo de dato a una variable global de nombre yylval que ser de donde yacc tomara los datos a procesar, en la union se definen miembros cuyos correspondientes tipos de datos sern usados para dar el tipo de dato a los tokens como se explicara en la siguiente seccin. %union se traduce de la siguiente forma :

En yacc :

%union{ double dval;}

En C :

typedef union{ double dval;} YYSTYPE;

Con esta definicin, yacc declara algunas uniones de este tipo, de las cuales la mas importante es :

YYSTYPE yylval;que ser usada en la especificacin de lex, del mismo programa para asignarle valor a los tokens que yacc usara para realizar operaciones. Esta estructura puede llegar a ser muy compleja, y para saber de que tipo es cada token devuelto por yylex(), se usan las definiciones %token y %type.

%token y %type

%token sirve para definir los tokens que hay, y si es necesario, el tipo de dato que usan, todos los tokens son tomados como smbolos terminales, lo cual veremos mejor reflejado en la seccin de reglas, estos tambin tienen el objetivo de servir como etiquetas que yylex() regresa a yacc para identificar el token que se ha ledo recientemente.Su uso es como sigue :

%token [] ETIQUETA1 [ETIQUETA2 ... ETIQUETAn]

Donde todo lo que esta entre [ y ] es opcional.

: Indica el miembro al que sern mapeados los tokens en la union yylval dentro de lex.

ETIQUETAS : Estos son los nombres con los que se identificaran los tokens mismos, que sern traducidos en C como nmeros en instrucciones #define del preprocesador de C.

%type es anlogo a %token, solo que este define el tipo de dato para smbolos no terminales de nuestra gramtica, la nica diferencia es que el tipo de dato a usar es obligatorio.En nuestro ejemplo :

%token NUMBER %token PLUS MINUS TIMES DIVIDE POWER%token LEFT_PARENTHESIS RIGHT_PARENTHESIS%token END

...

%type ExpresinLa primera lnea indica que el token NUMERO ser del tipo de miembro de dval, es decir, un doubl.Las siguientes tres lneas, son para definir algunos tokens mas que sern usados en la gramtica, pero no necesitan un tipo de dato ni un miembro en yylval asociado.En la ultima lnea definimos el tipo de dato que usara nuestro no terminal Expresin.%left y %right

El siguiente paso, es definir el tipo de precedencia de nuestros tokens operadores, en este punto tenemos dos factores, la precedencia por si misma, y la agrupacin de los operadores.Precedencia

La precedencia es asignada en orden inverso al que aparecen, es decir, el ultimo operador declarado, tiene mayor precedencia que el anterior y as sucesivamente.Asociatividad

%left y %right indican si el operador se agrupa a la derecha o a la izquierda, por ejemplo, en el caso de POWER (Exponente) debe asociarse a la derecha, por que buscamos que se resuelva de ese modo, de derecha a izquierda, por ejemplo :Buscamos que

4^3^5^2^9

sea evaluado as :

4^(3^(5^(2^9)))

Por lo contrario, las sumas y restas queremos resolverlas de izquierda a derecha:Buscamos que

4-3+5+2-9

sea evaluado as :

(((4-3)+5)+2)-9

Usar este tipo de declaraciones es importante para disminuir la posibilidad de ambigedades en el lenguaje generado.

%start

En algunos casos es conveniente indicarle a yacc cual es el smbolo (no terminal) inicial a la hora de hacer el parseo, es decir, el smbolo que se trata de reducir, si esta opcin no es especificada, yacc toma al primer smbolo de la seccin de reglas como smbolo inicial. En nuestro ejemplo, se presentan ambos casos, nuestro smbolo inicial "Input" se encuentra al inicio del archivo y tambin esta declarado como smbolo inicial.

%start Input

Reglas

En esta parte finalmente llegamos a la definicin de la gramtica, ac podemos observar que cada smbolo se define con su nombre, seguido de dos puntos ":" seguidos de varios smbolos que conformaran su composicin gramatical que en caso de tener varias opciones, son separados por "|" (or) indicando que se tienen varias opciones para reducir ese smbolo y para terminar cada regla, un ";".

Ejemplo :Si tomamos la gramtica que definimos al principio de esta seccin :

Numero + Numero - Numero

Y la transformamos a una regla de yacc, se vera como esto:

Expresion: NUMERO '+' Expresion{$$ = $1 + $3;}| NUMERO '-' Expresion{$$ = $1 - $3;}| NUMERO{$$ = $1;};

en el ejemplo ya transformado a una regla gramatical de yacc, podemos ver que ya se especifica que hacer con los smbolos de la gramtica una vez que son resueltos en la parte de cdigo de C. En este ejemplo, Expresion es el smbolo no terminal que se esta definiendo de manera recursiva, el valor que tomara una vez resuelto es el valor asignado a la variable $$, que traducida a C es una variable mapeada al smbolo no terminal, $1, $2 y $3 son variables que son mapeadas al valor de los smbolos de cada lnea de cada regla, estas son numeradas de izquierda a derecha.

Ejemplo :En este segmento de regla :

Expresion: NUMERO '+' Expresion

Expresion equivale a $$NUMERO equivale a $1'+' equivale a $2yExpresion (la parte recursiva) equivale a $3

todo esto claro, en la parte de acciones en C para cada lnea de la regla en yacc.En el ejemplo tambin podemos encontrar el uso de %prec que sirve para cambiar la precedencia de un operador dependiendo del contexto en el ejemplo, le estamos dando una ms alta precedencia a la operacin "menos" cuando esta en un contexto unario, que a la mayora de los operadores excepto el POWER (exponente) :...| MINUS Expression %prec NEG { $$=-$2; }...Reduccin

Yacc reduce sus reglas generando un parse tree (no literalmente), y va resolviendo cada regla completa tan pronto como puede, lo cual nos trae un detalle de diseo de gramaticas en yacc, y es la diferencia entre especificar la recursividad por la derecha o por la izquierda, para expresiones muy sencillas que generen un parse tree pequeo no hay ningn problema pero para casos donde la reduccin es compleja, puede desbordar la pila ya que cuando la recursin es derecha, para resolverla, tiene que guardar los datos de la izquierda, y si estos son demasiados, no puede manejarlos. Por lo contrario, cuando la recursin es izquierda, no tiene que guardar datos que no va a utilizar por que recorre el rbol de izquierda a derecha y resuelve las reglas tan pronto como puede. En el ejemplo anterior tenemos la recursin por la derecha, un anlogo recurrente por la izquierda seri como este :

Expresion: Expresion '+' NUMERO {$$ = $1 + $3;}| Expresion '-' NUMERO {$$ = $1 - $3;}| NUMERO{$$ = $1;};

Especificacin de Lex para el ejemplo

Para que el Ejemplo pueda funcionar, al igual que cualquier otro programa en yacc, necesita un tokenizer, y a continuacin tenemos su tokenizer correspondiente escrito en lex.

%{#include "y.tab.h"#include #include %}

white [ \t]+digit [0-9]integer {digit}+

%%

{white} { /* Ignoramos espacios en blanco */ }"exit"|"quit"|"bye"{printf("Terminando programa\n");exit(0);}{integer}{ yylval.dval=atof(yytext); return(NUMBER); }

"+" return(PLUS);"-" return(MINUS);"*" return(TIMES);"/" return(DIVIDE);"^" return(POWER);"(" return(LEFT_PARENTHESIS);")" return(RIGHT_PARENTHESIS);"\n" return(END);

%%

Acerca de la seccin de definiciones de este lexer, lo nico relevante que podemos mencionar es la lnea de C que dice :

#include "y.tab.h"

esta lnea incluye al archivo y.tab.h que contiene algunas de las definiciones de yacc que lex necesita para poder interactuar con el, entre las mas importantes se encuentran definidas todas las etiquetas de los tokens, como PLUS, MINUS, NUMBER, etctera. Estas constantes son los valores que yylex() regresara a yyparse() (la funcin del parser de yacc) para identificar el tipo de token que recin se ha ledo. En la seccin de reglas, en la parte del cdigo, podemos ver como al final de cada regla, se hace un return especificando la etiqueta que fue declarada como %token o como %left/%rigth en la especificacin yacc.Para compilar y correr este ejemplo en sistemas UNIX o similares :

$ lex ejem1.1.l$ yacc -d ejem1.1.y$ cc -o ejem1.1 lex.yy.c y.tab.c -ly -ll -lm$ ejem1.125*5-5Result: 120.0000005^2*2Result: 50.0000005^(2*2)Result: 625.000000byeTerminando programa$Subrutinas

En esta ultima seccin, es posible reimplementar, siguiendo la misma idea de lex, algunas funciones que pueden ser tiles en algn momento dado o declarar nuevas funciones para usar dentro de nuestro cdigo o nuestras reglas, no hay mucho que reimplementat a este nivel (yacc) a menos que sea con propsitos realmente especficos. Las funciones mas comnmente implementadas son main() e yyerror(), la primera se usa para personalizar el programa con mensajes antes o despus del parser, o para llamarlo varias veces en el cdigo y la segunda la ocupa yyparse() cada vez ue encuentra un error de sintaxis, en este ejemplo, se incluyen ambas con su contenido mnimo.

GENERADOR DE ANALIZADOR SINTCTICO JAVACC

JavaCC (Java Compiler Compiler) es un generador de analizadores sintcticos de cdigo abierto para el lenguaje de programacin Java. JavaCC es similar a Yacc en que genera un parser para una gramtica presentada en notacin BNF, con la diferencia de que la salida es en cdigo Java. A diferencia de Yacc, JavaCC genera analizadores descendentes (top-down), lo que lo limita a la clase de gramticas LL(K) (en particular, la recursin desde izquierda no se puede usar). El constructor de rboles que lo acompaa, JJTree, construye rboles de abajo hacia arriba (bottom-up).JavaCC est licenciado bajo una licencia BSD.En 1996, Sun Microsystems liber un parser llamado Jack. Los desarrolladores responsables de Jack crearon su propia compaa llamada Metamata y cambiaron el nombre Jack a JavaCC. Metamata se convirti en WebGain. Despus de que WebGain finalizara sus operaciones, JavaCC se traslad a su ubicacin actual.

VERSIONES

todas las modificaciones que han tenido lugar desdeLA LIBERACIN DE LA VERSIN 0.5 DE OCTUBRE DE 1996 .

Durante la transicin de 0,5 a 6,0 , se han producidoLAS SIGUIENTES VERSIONES INTERMEDIOS :

0.6. - 100.6. - 90.6. - 80.6 ( Beta1 )0.6 ( Beta 2 )0.60.6.10.7pre10.7pre20.7pre30.7pre40.7pre50.7pre60.7pre70.70.7.10.8pre10.8pre21.01.22.02.13.03.13.24.04.14.26.0

MODO DE USO

ESTRUCTURA GENERAL DE UN PROGRAMA EN JAVACCCualquier cdigo escrito para JavaCC obedece a la siguiente estructura:

El fichero de entrada comienza con una lista de opciones, la cual es opcional.Seguidamente nos encontramos la unidad de compilacin java la cual se encuentra encerrada entre PARSER_BEGIN(nombre) y PARSER_END(nombre). Despus nos encontramos con una lista de reglas de produccin (cada una de estas partes son claramente distinguibles en el ejemplo).

El nombre que sigue a PARSER_BEGIN y a PARSER_END debe ser el mismo, yste identifica al analizador sintctico que va a ser generado con posterioridad. Por ejemplo si nuestro nombre es Gramtica, se generan los siguientes archivos: Gramatica.java: El analizador sintctico. GramaticaTokenManager.java: gestor de tokens para el analizador lxico. GramaticaConstants.java: El analizador lxico no maneja los tokens por el nombre que especificamos nosotros en el cdigo, sino que a cada uno de ellos le asigna un nmero. En este archivo se encuentra cada uno de los tokens junto con el nmero que le ha sido asignado por el analizador lxico.

Adems de stos, tambin se generan otros ficheros (Token.java y ParseException.java, ASCII_CharStream.java, TokenMgrError.java), pero estos ficheros son los mismos para cualquier gramtica y pueden conservarse de una para otra.

Entre PARSER_BEGIN y PARSER_END, se encuentra una unidad de compilacin Java (el contenido completo de un archivo escrito en Java). sta puede ser cualquier cdigo Java siempre que contenga una declaracin de clase cuyo nombre sea el mismo que el analizador sintctico que va a ser generado.(Gramtica en el ejemplo anterior). Esta parte del archivo de gramtica tiene esta forma:

JavaCC no realiza un chequeo minucioso de la unidad de compilacin por lo que es posible que un fichero de gramtica pase el test de JavaCC y genere los archivos correspondientes y luego produzca errores al ser compilados.El archivo correspondiente al analizador sintctico que es generado por JavaCCcontiene la unidad de compilacin Java en su totalidad, adems del cdigo generado por el analizador sintctico, el cual se incluye al final de su clase. En el caso del ejemplo la siguiente forma:

El cdigo del analizador sintctico incluye la declaracin de un mtodo pblico correspondiente a cada no terminal de la gramtica. La realizacin del anlisis sintctico a partir de un no terminal se consigue invocando al mtodo correspondiente a ese no terminal.

EJEMPLOS

Construir un analizador lxico con JavaCC.

En la programacin, un analizador lxico es la parte de un compilador o analizador (parser) que trata de descomponer el lenguaje proporcionado como entrada en tokens.Un token es la unidad mnima con significado. Tokens habituales son los identificadores, enteros, flotantes, constantes, etc.Para el desarrollo, utilizaremos una herramienta increblemente til, JavaCC. Mediante expresiones regulares podemos definir cmodamente los tokens de nuestro lenguaje.

Caso prctico, construir analizador lxico para un determinado lenguaje de programacin:

Las especificaciones de nuestro lenguaje son:Tokens:Constantes: Cadenas: Caracteres entrecomillados, ejemplo: "cadena" Enteros: Nmeros positivos, ejemplo: 234 o 0 Lgicas: TRUE y FALSEIdentificadores: Todos los identificadores son una secuencia de letras (a-zA-Z) y nmeros que obligatoriamente deben comenzar con una letra (y no un nmero).Los identificadores que se refieran a cadenas terminarn en "$".Palabras reservadas:En el lenguaje hay palabras reservadas que darn vida al lenguaje, estas sern: "not, if, end, let, call, then, case, else, input, print, select y static".Adems el lenguaje ser "case insensitive" o lo que es lo mismo, un identificador llamado "id" apuntar al mismo lugar que "Id", "iD" o "ID".De igual forma ser para las palabras reservadas.

Cdigo JavaCC (exparser.jj):

options { IGNORE_CASE = true;}PARSER_BEGIN(ExampleParser)

public class ExampleParser { //Ejecucin del analizador public static void main ( String args [ ] ) {

//Inicializacin del analizador ExampleParser parser;

if(args.length == 0){ System.out.println ("ExampleParser: Leyendo de la entrada estandar ..."); parser = new ExampleParser(System.in); } else if(args.length == 1){ System.out.println ("ExampleParser: Leyendo de fichero " + args[0] + " ..." ); try { parser = new ExampleParser(new java.io.FileInputStream(args[0])); } catch(java.io.FileNotFoundException e) { System.out.println ("ExampleParser: El fichero " + args[0] + " no ha sido encontrado."); return; } } else { System.out.println ("ExampleParser: Debes utilizarlo de una de las siguientes formas:"); System.out.println (" java ExampleParser < fichero"); System.out.println ("Or"); System.out.println (" java ExampleParser fichero"); return ; } try { compilador.Start(); System.out.println ("ExampleParser: La entrada ha sido leida con xito."); } catch(ParseException e){ System.out.println ("ExampleParser: Ha ocurrido un error durante el anlisis."); System.out.println (e.getMessage()); } catch(TokenMgrError e){ System.out.println ("ExampleParser: Ha ocurrido un error."); System.out.println (e.getMessage()); } } }PARSER_END(ExampleParser)

//ESTRUCTURAS Y CARACTERES DE ESCAPESKIP : { " "| "\t"| "\n"| "\r"| }//TOKENS ESTTICOSTOKEN : { | | | }//PALABRAS RESERVADASTOKEN : { | | | | | | | | | | | | }//TOKEN IDENTIFICADORTOKEN : { | }

//UNIDAD PRINCIPALvoid Start () : {}{ ( INTEGER_CONSTANT | STRING_CONSTANT | LOGIC_CONSTANT | NOT | IF | END | SUB | LET | CALL | THEN | CASE | ELSE | INPUT | PRINT | SELECT | STATIC | IDENTIFIER )* }

Para compilar este fichero, se debe hacer con "javacc" y posteriormente con "javac":

javacc exparser.jjjavac *.java

Para ejecutar el programa:

java ExampleParser fichero

CONCLUSIN

El analizador sintctico ya se a YACC o JavaCC recibe los tokens o palabras generadas por el analizador lxico y determina si la secuencia u orden que presentan es correcta y permitida por el lenguaje. La salida que generar ser el rbol sintctico. Gracias a estas excelentes herramientas, nosotros como programadores no tenemos que reinventar la rueda cuando de crear un analizador sintctico se trate, las numerosas opciones que nos brindan los generadores hacen que esta tarea sea mas fcil.

REFERENCIAShttp://es.wikipedia.org/wiki/Yacchttp://www.lcc.uma.es/~galvez/theme/IntroduccionAJavaCC.pdfhttp://kiwwito.com/construir-un-analizador-lexico-con-javacc