307

Libro Compiladores

Embed Size (px)

DESCRIPTION

Este libro trata sobre los compiladores , las fases de analisis que llevan acabo

Citation preview

  • JAVA A TOPE: TRADUCTORES Y COMPILADORES CON LEX/YACC, JFLEX/CUP Y JAVACC.EDICIN ELECTRNICA

    AUTORES: SERGIO GLVEZ ROJASM IGUEL NGEL MORA MATA

    ILUSTRACINDE PORTADA: JOS M IGUEL GLVEZ ROJAS

    Sun, el logotipo de Sun, Sun M icrosystems y Java son marcas o marcas registradas de Sun M icrosystemsInc. en los EE.UU. y otros pases. El personaje de Duke es una marca de Sun M icrosystems Inc.PCLex y PCYacc son productos de Abraxas Software Inc.JFlex est liberado con licencia GPL.Cup est protegido por las licencias de cdigo abierto, siendo compatible con la licencia GPL.JavaCC est sujeto a la licencia BSD (Berkeley Software Distribution) de cdigo abierto.

    2005 por Sergio Glvez Rojas. Versin corregida Octubre 2005.DEPSITO LEGAL: MA-185-2005ISBN: 84-689-1037-6

  • Java a tope:

    CompiladoresTraductores y Compiladores

    con Lex/Yacc, JFlex/cup y JavaCC

    Sergio Glvez RojasDoctor Ingeniero en Informtica

    Miguel ngel Mora MataIngeniero en Informtica

    Dpto. de Lenguajes y Ciencias de la ComputacinE.T.S. de Ingeniera Informtica

    Universidad de Mlaga

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    i

    ndice

    Prlogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix

    Captulo 1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1 Visin general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Concepto de traductor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

    1.2.1 Tipos de traductores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2.1.1 Traductores del idioma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.2.1.2 Compiladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.2.1.3 Intrpretes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.2.1.4 Preprocesadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2.1.5 Intrpretes de comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2.1.6 Ensambladores y macroensambladores . . . . . . . . . . . . . . . . . 51.2.1.7 Conversores fuente-fuente . . . . . . . . . . . . . . . . . . . . . . . . . . 61.2.1.8 Compilador cruzado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

    1.2.2 Conceptos bsicos relacionados con la traduccin . . . . . . . . . . . 61.2.2.1 Compilacin, enlace y carga. . . . . . . . . . . . . . . . . . . . . . . . . 61.2.2.2 Pasadas de compilacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91.2.2.3 Compilacin incremental . . . . . . . . . . . . . . . . . . . . . . . . . . . 91.2.2.4 Autocompilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.2.2.5 Metacompilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.2.2.6 Descompilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

    1.3 Estructura de un traductor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121.3.1 Construccin sistemtica de compiladores . . . . . . . . . . . . . . . . . 131.3.2 La tabla de smbolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

    1.4 Ejemplo de compilacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161.4.1 Preprocesamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171.4.2 Etapa de anlisis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

    1.4.2.1 Fase de anlisis lexicogrfico . . . . . . . . . . . . . . . . . . . . . . . 171.4.2.2 Fase de anlisis sintctico . . . . . . . . . . . . . . . . . . . . . . . . . . 18

    1.4.2.2.1 Compilacin dirigida por sintaxis . . . . . . . . . . . . . 191.4.2.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

    1.4.3 Etapa de sntesis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201.4.3.1 Fase de generacin de cdigo intermedio . . . . . . . . . . . . . . 201.4.3.2 Fase de optimizacin de cdigo . . . . . . . . . . . . . . . . . . . . . 211.4.3.3 Fase de generacin de cdigo mquina . . . . . . . . . . . . . . . . 21

  • ndice

    ii

    Captulo 2 Anlisis lexicogrfico . . . . . . . . . . . . . . . . . . . . . . . . 232.1 Visin general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232.2 Concepto de analizador lxico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

    2.2.1 Funciones del analizador lxico . . . . . . . . . . . . . . . . . . . . . . . . . 242.2.2 Necesidad del analizador lxico . . . . . . . . . . . . . . . . . . . . . . . . . 25

    2.2.2.1 Simplificacin del diseo . . . . . . . . . . . . . . . . . . . . . . . . . . 252.2.2.2 Eficiencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272.2.2.3 Portabilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272.2.2.4 Patrones complejos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

    2.3 Token, patrn y lexema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282.3.1 Aproximaciones para construir un analizador lexicogrfico . . . 30

    2.4 El generador de analizadores lexicogrficos: PCLex . . . . . . . . . . . . . . . . 312.4.1 Visin general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312.4.2 Creacin de un analizador lxico . . . . . . . . . . . . . . . . . . . . . . . . 322.4.3 El lenguaje Lex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

    2.4.3.1 Premisas de Lex para reconocer lexemas . . . . . . . . . . . . . . 342.4.3.2 Caracteres especiales de Lex . . . . . . . . . . . . . . . . . . . . . . . . 352.4.3.3 Caracteres de sensibilidad al contexto . . . . . . . . . . . . . . . . 362.4.3.4 Estado lxicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372.4.3.5 rea de definiciones y rea de funciones . . . . . . . . . . . . . . 372.4.3.6 Funciones y variables suministradas por PCLex . . . . . . . . 39

    2.5 El generador de analizadores lexicogrficos JFlex . . . . . . . . . . . . . . . . . . 422.5.1 Ejemplo preliminar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432.5.2 rea de opciones y declaraciones . . . . . . . . . . . . . . . . . . . . . . . 44

    2.5.2.1 Opciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452.5.2.1.1 Opciones de clase . . . . . . . . . . . . . . . . . . . . . . . . . 452.5.2.1.2 Opciones de la funcin de anlisis . . . . . . . . . . . . 452.5.2.1.3 Opciones de fin de fichero . . . . . . . . . . . . . . . . . . 462.5.2.1.4 Opciones de juego de caracteres . . . . . . . . . . . . . . 462.5.2.1.5 Opciones de contadores . . . . . . . . . . . . . . . . . . . . 46

    2.5.2.2 Declaraciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472.5.2.2.1 Declaraciones de estados lxicos. . . . . . . . . . . . . . 472.5.2.2.2 Declaraciones de reglas. . . . . . . . . . . . . . . . . . . . . 47

    2.5.3 rea de reglas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472.5.4 Funciones y variables de la clase Yylex . . . . . . . . . . . . . . . . . . . 49

    Captulo 3 Anlisis sintctico . . . . . . . . . . . . . . . . . . . . . . . . . . . 513.1 Visin general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513.2 Concepto de analizador sintctico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513.3 Manejo de errores sintcticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    iii

    3.3.1 Ignorar el problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 533.3.2 Recuperacin a nivel de frase . . . . . . . . . . . . . . . . . . . . . . . . . . . 543.3.3 Reglas de produccin adicionales . . . . . . . . . . . . . . . . . . . . . . . 543.3.4 Correccin Global . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

    3.4 Gramtica utilizada por un analizador sintctico . . . . . . . . . . . . . . . . . . . 543.4.1 Derivaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553.4.2 rbol sintctico de una sentencia de un lenguaje . . . . . . . . . . . 56

    3.5 Tipos de anlisis sintctico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593.5.1 Anlisis descendente con retroceso . . . . . . . . . . . . . . . . . . . . . . 593.5.2 Anlisis descendente con funciones recursivas . . . . . . . . . . . . . 63

    3.5.2.1 Diagramas de sintaxis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633.5.2.2 Potencia de los diagramas de sintaxis . . . . . . . . . . . . . . . . . 633.5.2.3 Correspondencia con flujos de ejecucin . . . . . . . . . . . . . . 643.5.2.4 Ejemplo completo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 663.5.2.5 Conclusiones sobre el anlisis descendente con funciones

    recursivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 683.5.3 Anlisis descendente de gramticas LL(1) . . . . . . . . . . . . . . . . . 693.5.4 Generalidades del anlisis ascendente . . . . . . . . . . . . . . . . . . . . 72

    3.5.4.1 Operaciones en un analizador ascendente . . . . . . . . . . . . . . 733.5.5 Anlisis ascendente con retroceso . . . . . . . . . . . . . . . . . . . . . . . 743.5.6 Anlisis ascendente de gramticas LR(1) . . . . . . . . . . . . . . . . . . 76

    3.5.6.1 Consideraciones sobre el anlisis LR(1) . . . . . . . . . . . . . . . 823.5.6.1.1 Recursin a derecha o a izquierda . . . . . . . . . . . . 823.5.6.1.2 Conflictos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

    3.5.6.2 Conclusiones sobre el anlisis LR(1) . . . . . . . . . . . . . . . . . 84

    Captulo 4 Gramticas atribuidas . . . . . . . . . . . . . . . . . . . . . . . 874.1 Anlisis semntico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

    4.1.1 Atributos y acciones semnticas . . . . . . . . . . . . . . . . . . . . . . . . . 874.1.2 Ejecucin de una accin semntica . . . . . . . . . . . . . . . . . . . . . . 90

    4.2 Traduccin dirigida por sintaxis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 924.2.1 Definicin dirigida por sintaxis . . . . . . . . . . . . . . . . . . . . . . . . . 934.2.2 Esquema formal de una definicin dirigida por sintaxis . . . . . . 94

    4.2.2.1 Atributos sintetizados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 954.2.2.2 Atributos heredados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 964.2.2.3 Grafo de dependencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . 984.2.2.4 Orden de evaluacin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1004.2.2.5 Gramtica L-atribuida . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1014.2.2.6 Gramtica S-atribuida . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

    4.2.3 Esquemas de traduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1034.2.4 Anlisis LALR con atributos . . . . . . . . . . . . . . . . . . . . . . . . . . 104

  • ndice

    iv

    4.2.4.1 Conflictos reducir/reducir . . . . . . . . . . . . . . . . . . . . . . . . . 1044.2.4.2 Conflictos desplazar/reducir . . . . . . . . . . . . . . . . . . . . . . . 105

    4.3 El generador de analizadores sintcticos PCYacc . . . . . . . . . . . . . . . . . 1064.3.1 Formato de un programa Yacc . . . . . . . . . . . . . . . . . . . . . . . . . 107

    4.3.1.1 rea de definiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1074.3.1.2 Creacin del analizador lxico . . . . . . . . . . . . . . . . . . . . . 1084.3.1.3 rea de reglas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1094.3.1.4 rea de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

    4.3.2 Gestin de atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1114.3.2.1 Acciones intermedias . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

    4.3.3 Ambigedad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1154.3.4 Tratamiento de errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1184.3.5 Ejemplo final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121

    4.4 El generador de analizadores sintcticos Cup . . . . . . . . . . . . . . . . . . . . 1224.4.1 Diferencias principales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1224.4.2 Sintaxis completa. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

    4.4.2.1 Especificacin del paquete e importaciones. . . . . . . . . . . . 1264.4.2.2 Cdigo de usuario. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1264.4.2.3 Listas de smbolos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1264.4.2.4 Asociatividad y precedencia. . . . . . . . . . . . . . . . . . . . . . . . 1274.4.2.5 Gramtica. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

    4.4.3 Ejecucin de Cup. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1284.4.4 Comunicacin con JFlex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130

    Captulo 5 JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1315.1 Introduccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

    5.1.1 Caractersticas generales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1315.1.2 Ejemplo preliminar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

    5.2 Estructura de un programa en JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . . 1345.2.1 Opciones. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1365.2.2 rea de tokens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

    5.2.2.1 Caracteres especiales para patrones JavaCC . . . . . . . . . . . 1405.2.2.2 Elementos accesibles en una accin lxica . . . . . . . . . . . . 1415.2.2.3 La clase Token y el token manager . . . . . . . . . . . . . . . . . . 143

    5.2.3 rea de funciones BNF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1455.3 Gestin de atributos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1495.4 Gestin de errores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

    5.4.1 Versiones antiguas de JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . 1515.4.2 Versiones modernas de JavaCC . . . . . . . . . . . . . . . . . . . . . . . . 152

    5.5 Ejemplo final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    v

    Captulo 6 Tabla de smbolos . . . . . . . . . . . . . . . . . . . . . . . . . . 1576.1 Visin general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1576.2 Informacin sobre los identificadores de usuario . . . . . . . . . . . . . . . . . . 1576.3 Consideraciones sobre la tabla de smbolos . . . . . . . . . . . . . . . . . . . . . . 1596.4 Ejemplo: una calculadora con variables . . . . . . . . . . . . . . . . . . . . . . . . . 161

    6.4.1 Interfaz de la tabla de smbolos . . . . . . . . . . . . . . . . . . . . . . . . 1626.4.2 Solucin con Lex/Yacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1626.4.3 Solucin con JFlex/Cup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1666.4.4 Solucin con JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

    Captulo 7 Gestin de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1737.1 Visin general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1737.2 Compatibilidad nominal, estructural y funcional . . . . . . . . . . . . . . . . . . 1747.3 Gestin de tipos primitivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

    7.3.1 Gramtica de partida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1777.3.2 Pasos de construccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178

    7.3.2.1 Propuesta de un ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . 1797.3.2.2 Definicin de la tabla de smbolos . . . . . . . . . . . . . . . . . . 1797.3.2.3 Asignacin de atributos . . . . . . . . . . . . . . . . . . . . . . . . . . 180

    7.3.2.3.1 Atributos de terminales . . . . . . . . . . . . . . . . . . . . 1807.3.2.3.2 Atributos de no terminales . . . . . . . . . . . . . . . . . 181

    7.3.2.4 Acciones semnticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1827.3.3 Solucin con Lex/Yacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1847.3.4 Solucin con JFlex/Cup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1897.3.5 Solucin con JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193

    7.4 Gestin de tipos complejos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1977.4.1 Objetivos y propuesta de un ejemplo . . . . . . . . . . . . . . . . . . . . 1987.4.2 Pasos de construccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199

    7.4.2.1 Gramtica de partida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1997.4.2.2 Gestin de atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2017.4.2.3 Implementacin de una pila de tipos . . . . . . . . . . . . . . . . . 2027.4.2.4 Acciones semnticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204

    7.4.3 Solucin con Lex/Yacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2077.4.4 Solucin con JFlex/Cup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2127.4.5 Solucin con JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217

    Captulo 8 Generacin de cdigo . . . . . . . . . . . . . . . . . . . . . . . 2218.1 Visin general . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2218.2 Cdigo de tercetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2238.3 Una calculadora simple compilada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225

    8.3.1 Pasos de construccin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225

  • ndice

    vi

    8.3.1.1 Propuesta de un ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . 2258.3.1.2 Gramtica de partida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2258.3.1.3 Consideraciones importantes sobre el traductor . . . . . . . . 2278.3.1.4 Atributos necesarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2298.3.1.5 Acciones semnticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230

    8.3.2 Solucin con Lex/Yacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2308.3.3 Solucin con JFlex/Cup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2328.3.4 Solucin con JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234

    8.4 Generacin de cdigo en sentencias de control . . . . . . . . . . . . . . . . . . . 2368.4.1 Gramtica de partida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2368.4.2 Ejemplos preliminares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2418.4.3 Gestin de condiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244

    8.4.3.1 Evaluacin de condiciones usando cortocircuito . . . . . . . 2458.4.3.1.1 Condiciones simples . . . . . . . . . . . . . . . . . . . . . . 2458.4.3.1.2 Condiciones compuestas . . . . . . . . . . . . . . . . . . . 247

    8.4.4 Gestin de sentencias de control de flujo . . . . . . . . . . . . . . . . . 2528.4.4.1 Sentencia IF-THEN-ELSE . . . . . . . . . . . . . . . . . . . . . . . . 2538.4.4.2 Sentencia WHILE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2558.4.4.3 Sentencia REPEAT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2568.4.4.4 Sentencia CASE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259

    8.4.5 Solucin con Lex/Yacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2638.4.6 Solucin con JFlex/Cup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2688.4.7 Solucin con JavaCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274

    Captulo 9 Gestin de la memoria en tiempo de ejecucin . 2819.1 Organizacin de la memoria durante la ejecucin . . . . . . . . . . . . . . . . . 2819.2 Zona de cdigo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282

    9.2.1 Overlays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2829.3 Zona de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283

    9.3.1 Zona de Datos de Tamao Fijo . . . . . . . . . . . . . . . . . . . . . . . . 2849.3.2 Pila (Stack) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2869.3.3 Montn (heap) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    vii

    Prlogo

    La construccin de un compilador es una de las tareas ms gratas con lasque un informtico puede encontrarse a lo largo de su carrera profesional.Aunque no resulta sensato pensar que una labor tal pueda formar parte

    de la actividad cotidiana de la mayora de estos profesionales, s es cierto que, concierta frecuencia, suele aparecer la necesidad de analizar un fichero de texto quecontiene informacin distribuida segn algn patrn reconocible: ficheros XML,ficheros de inicializacin .ini, ficheros con comandos del sistema operativo, los propiosprogramas fuente, etc.

    Es ms, el concepto de anlisis de un texto puede ser til para ahorrar trabajoen situaciones que estn fuera del mbito profesional informtico, como por ejemplola generacin de ndices analticos en archivos de texto escritos con procesadores queno admiten esta opcin.

    Pero a pesar de la indudable utilidad de los conceptos generales relacionadoscon la teora y prctica de los anlisis de textos, el crear un compilador introduce alinformtico en un nuevo mundo en el que el control sobre el ordenador es absoluto yle lleva al paroxismo de la omnipotencia sobre la mquina. Y es que resultaimprescindible un pleno conocimiento de todos los conceptos intrnsecos al ordenador:dispositivos de E/S, cdigo mquina utilizado por el microprocesador, comunicacina bajo nivel entre ste y el resto de sus perifricos, distribucin de la memoria,mecanismos de uso de memoria virtual, etc.

    No obstante, el lector no debe asustarse ante el poder que este libro le va aconceder, ni tampoco ante los grandes conocimientos que exige el control de ese poder.Un compilador es una programa que da mucho juego en el sentido de que puedeapoyarse sobre otras herramientas ya existentes, lo que facilita enormemente elaprendizaje de su construccin y permite su estudio de forma gradual y pausada.

    En el volumen que el lector tiene en sus manos se ana una gran cantidad deinformacin orientada a la construccin de compiladores, pero en la que se prestaespecial atencin a las herramientas destinadas a facilitar la labor de reconocimientode textos que siguen una determinada gramtica y lxico. Es por ello que los ejemplospropuestos se resuelven desde una perspectiva ambivalente: de un lado mediante Lexy Yacc, ya que por motivos histricos constituyen el pilar principal de apoyo alreconocimiento lxico y sintctico; y de otro lado mediante JavaCC, lo que nosintroduce tanto en la utilizacin del lenguaje Java como medio para construircompiladores, como en los mecanismos basados en notacin BNF para dicha

  • Prlogo

    viii

    construccin. Con el objetivo de centrar nuestra atencin en el lenguaje Java, losejemplos tambin son resueltos mediante las herramientas FLEX y Cup que constituyenversiones adaptadas de Lex y Yacc para generar cdigo Java orientado a objetos.

    Hemos escogido JavaCC frente a otras opciones como SableCC, CoCo/Java,etc. (vase la pgina http://catalog.compilertools.net/java.html) por dos motivosprincipales. Primero, JavaCC ha sido apoyado por Sun Microsystems hasta hace bienpoco (hoy por hoy Sun Microsystems no apoya a ninguna de estas utilidades, ya quese hallan lo suficientemente maduras como para proseguir solas su camino). Y segundo,se trata de una de las herramientas ms potentes basadas en una notacinsustancialmente diferente a la empleada por Lex y Yacc, lo cual enriquecer nuestrapercepcin de este excitante mundo: la construccin de compiladores.

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    1

    Captulo 1

    Introduccin

    1.1 Visin general

    Uno de los principales mecanismos de comunicacin entre un ordenador y unapersona viene dado por el envo y recepcin de mensajes de tipo textual: el usuarioescribe una orden mediante el teclado, y el ordenador la ejecuta devolviendo comoresultado un mensaje informativo sobre las acciones llevadas a cabo.

    Aunque la evolucin de los ordenadores se encuentra dirigida actualmentehacia el empleo de novedosas y ergonmicas interfaces de usuario (como el ratn, laspantallas tctiles, las tabletas grficas, etc.), podemos decir que casi todas las accionesque el usuario realiza sobre estas interfaces se traducen antes o despus a secuenciasde comandos que son ejecutadas como si hubieran sido introducidas por teclado. Porotro lado, y desde el punto de vista del profesional de la Informtica, el trabajo que sterealiza sobre el ordenador se encuentra plagado de situaciones en las que se produceuna comunicacin textual directa con la mquina: utilizacin de un intrprete decomandos (shell), construccin de ficheros de trabajo por lotes, programacin mediantediversos lenguajes, etc. Incluso los procesadores de texto como WordPerfect y MSWord almacenan los documentos escritos por el usuario mediante una codificacintextual estructurada que, cada vez que se abre el documento, es reconocida, recorriday presentada en pantalla.

    Por todo esto, ningn informtico que se precie puede esquivar la indudablenecesidad de conocer los entresijos de la herramienta que utiliza durante su trabajodiario y sobre la que descansa la interaccin hombre-mquina: el traductor.

    Existe una gran cantidad de situaciones en las que puede ser muy til conocercmo funcionan las distintas partes de un compilador, especialmente aqulla que seencarga de trocear los textos fuentes y convertirlos en frases sintcticamente vlidas.Por ejemplo, una situacin de aparente complejidad puede presentrsenos si se poseeun documento de MS Word que procede de una fusin con una base de datos y sequiere, a partir de l, obtener la B.D. original. Cmo solucionar el problema? Puesbasndose en que la estructura del documento est formada por bloques que se repiten;la solucin podra ser:

    Convertir el documento a formato texto puro. Procesar dicho texto con un traductor para eliminar los caracteres superfluos

    y dar como resultado otro texto en el que cada campo de la tabla de la B.D.

  • Introduccin

    2

    est entre comillas. El texto anterior se importa con cualquier SGBD.

    Otras aplicaciones de la construccin de traductores pueden ser la creacin depreprocesadores para lenguajes que no lo tienen (por ejemplo, para trabajar fcilmentecon SQL en C, se puede hacer un preprocesador para introducir SQL inmerso), oincluso la conversin del carcter ASCII 10 (LF) en de HTML para pasar textoa la web.

    En este captulo, se introduce la construccin de un compilador y se describensus componentes, el entorno en el que estos trabajan y algunas herramientas de softwareque facilitan su construccin.

    1.2 Concepto de traductor.

    Un traductor se define como un programa que traduce o convierte desdeun texto o programa escrito en un lenguaje fuente hasta un texto o programaequivalente escrito en un lenguaje destino produciendo, si cabe, mensajes deerror. Los traductores engloban tanto a los compiladores (en los que el lenguajedestino suele ser cdigo mquina) como a los intrpretes (en los que el lenguaje destinoest constituido por las acciones atmicas que puede ejecutar el intrprete). La figura1.1 muestra el esquema bsico que compone a un compilador/intrprete.

    Es importante destacar la velocidad con la que hoy en da se puede construirun compilador. En la dcada de 1950, se consider a los traductores como programasnotablemente difciles de escribir. El primer compilador de Fortran (FormulaTranslator), por ejemplo, necesit para su implementacin el equivalente a 18 aos detrabajo individual (realmente no se tard tanto puesto que el trabajo se desarroll enequipo). Hasta que la teora de autmatas y lenguajes formales no se aplic a la creacinde traductores, su desarrollo ha estado plagado de problemas y errores. Sin embargo,hoy da un compilador bsico puede ser el proyecto fin de carrera de cualquierestudiante universitario de Informtica.

    1.2.1 Tipos de traductores

    Desde los orgenes de la computacin, ha existido un abismo entre la formaen que las personas expresan sus necesidades y la forma en que un ordenador es capaz

    Figura 1.1 Esquema preliminar de un traductor

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    3

    de interpretar instrucciones. Los traductores han intentado salvar este abismo parafacilitarle el trabajo a los humanos, lo que ha llevado a aplicar la teora de autmatasa diferentes campos y reas concretas de la informtica, dando lugar a los distintos tiposde traductores que veremos a continuacin.

    1.2.1.1 Traductores del idioma

    Traducen de un idioma dado a otro, como por ejemplo del ingls al espaol.Este tipo de traductores posee multitud de problemas, a saber:

    Necesidad de inteligencia artificial y problema de las frases hechas. Elproblema de la inteligencia artificial es que tiene mucho de artificial y pocode inteligencia, por lo que en la actualidad resulta casi imposible traducirfrases con un sentido profundo. Como ancdota, durante la guerra fra, en unintento por realizar traducciones automticas del ruso al ingls y viceversa, sepuso a prueba un prototipo introduciendo el texto en ingls: El espritu esfuerte pero la carne es dbil cuya traduccin al ruso se pas de nuevo alingls para ver si coincida con el original. Cual fue la sorpresa de losdesarrolladores cuando lo que se obtuvo fue: El vino est bueno pero la carneest podrida (en ingls spirit significa tanto espritu como alcohol ). Otrosejemplos difciles de traducir lo constituyen las frases hechas como: Piel degallina, por si las moscas, molar mazo, etc.

    Difcil formalizacin en la especificacin del significado de las palabras. Cambio del sentido de las palabras segn el contexto. Ej: por decir aquello,

    se llev una galleta. En general, los resultados ms satisfactorios en la traduccin del lenguaje

    natural se han producido sobre subconjuntos restringidos del lenguaje. Y anms, sobre subconjuntos en los que hay muy poco margen de ambigedad enla interpretacin de los textos: discursos jurdicos, documentacin tcnica,etc.

    1.2.1.2 Compiladores

    Es aquel traductor que tiene como entrada una sentencia en lenguaje formaly como salida tiene un fichero ejecutable, es decir, realiza una traduccin de un cdigode alto nivel a cdigo mquina (tambin se entiende por compilador aquel programa queproporciona un fichero objeto en lugar del ejecutable final).

    1.2.1.3 Intrpretes

    Es como un compilador, solo que la salida es una ejecucin. El programa deentrada se reconoce y ejecuta a la vez. No se produce un resultado fsico (cdigomquina) sino lgico (una ejecucin). Hay lenguajes que slo pueden ser interpretados,como p.ej. SNOBOL (StriNg Oriented SimBOlyc Language), LISP (LISt Processing),algunas versiones de BASIC (Beginners All-purpose Symbolic Instruction Code), etc.

  • Introduccin

    4

    Figura 1.2 Esquema de traduccin/ejecucin de un programa interpretado

    Su principal ventaja es que permiten una fcil depuracin. Entre losinconvenientes podemos citar, en primer lugar, la lentitud de ejecucin , ya que alejecutar a la vez que se traduce no puede aplicarse un alto grado de optimizacin; porejemplo, si el programa entra en un bucle y la optimizacin no est muy afinada, lasmismas instrucciones se interpretarn y ejecutarn una y otra vez, enlenteciendo laejecucin del programa. Otro inconveniente es que durante la ejecucin, el intrpretedebe residir en memoria, por lo que consumen ms recursos.

    Adems de que la traduccin optimiza el programa acercndolo a la mquina,los lenguajes interpretados tienen la caracterstica de que permiten construir programasque se pueden modificar a s mismos.

    Algunos lenguajes intentan aunar las ventajas de los compiladores y de losintrpretes y evitar sus desventajas; son los lenguajes pseudointerpretados. En estos,el programa fuente pasa por un pseudocompilador que genera un pseudoejecutable. Paraejecutar este pseudoejecutable se le hace pasar por un motor de ejecucin que lointerpreta de manera relativamente eficiente. Esto tiene la ventaja de la portabilidad, yaque el pseudoejecutable es independiente de la mquina en que vaya a ejecutarse, ybasta con que en dicha mquina se disponga del motor de ejecucin apropiado parapoder interpretar cualquier pseudoejecutable. El ejemplo actual ms conocido loconstituye el lenguaje Java; tambin son pseudointerpretadas algunas versiones dePascal y de COBOL (COmmon Bussiness Oriented Language). La figura 1.2 muestralos pasos a seguir en estos lenguajes para obtener una ejecucin.

    1.2.1.4 Preprocesadores

    Permiten modificar el programa fuente antes de la verdadera compilacin.Hacen uso de macroinstrucciones y directivas de compilacin. Por ejemplo, en lenguajeC, el preprocesador sustituye la directiva #include Uno.c por el cdigo completo quecontiene el fichero Uno.c, de manera que cuando el compilador comienza suejecucin se encuentra con el cdigo ya insertado en el programa fuente (la figura 1.3ilustra esta situacin). Algunas otras directivas de preprocesamiento permiten compilartrozos de cdigo opcionales (lenguajes C y Clipper): #fi, #ifdef, #define, #ifndef, etc.Los preprocesadores suelen actuar de manera transparente para el programador,pudiendo incluso considerarse que son una fase preliminar del compilador.

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    5

    1.2.1.5 Intrpretes de comandos

    Un intrprete de comandos traduce sentencias simples a invocaciones aprogramas de una biblioteca. Se utilizan especialmente en los sistemas operativos (lashell de Unix es un intrprete de comandos). Los programas invocados pueden residiren el kernel (ncleo) del sistema o estar almacenados en algn dispositivo externo comorutinas ejecutables que se traen a memoria bajo demanda.

    Por ejemplo, si bajo MS-DOS se teclea el comando copy se ejecutar lafuncin de copia de ficheros del sistema operativo, que se encuentra residente enmemoria.

    1.2.1.6 Ensambladores y macroensambladores

    Son los pioneros de los compiladores, ya que en los albores de la informtica,los programas se escriban directamente en cdigo mquina, y el primer paso hacia loslenguajes de alto nivel lo constituyen los ensambladores. En lenguaje ensamblador seestablece una relacin biunvoca entre cada instruccin y una palabra mnemotcnica,de manera que el usuario escribe los programas haciendo uso de los mnemotcnicos,y el ensamblador se encarga de traducirlo a cdigo mquina puro. De esta manera, losensambladores suelen producir directamente cdigo ejecutable en lugar de producirficheros objeto.

    Un ensamblador es un compilador sencillo, en el que el lenguaje fuente tieneuna estructura tan sencilla que permite la traduccin de cada sentencia fuente a unanica instruccin en cdigo mquina. Al lenguaje que admite este compilador tambinse le llama lenguaje ensamblador. En definitiva, existe una correspondencia uno a uno

    Figura 1.3 Funcionamiento de la directiva de preprocesamiento #include en lenguaje C

  • Introduccin

    6

    entre las instrucciones ensamblador y las instrucciones mquina. Ej: Instruccin ensamblador: LD HL, #0100Cdigo mquina generado: 65h.00h.01h

    Por otro lado, existen ensambladores avanzados que permiten definirmacroinstrucciones que se pueden traducir a varias instrucciones mquina. A estosprogramas se les llama macroensambladores, y suponen el siguiente paso hacia loslenguajes de alto nivel. Desde un punto de vista formal, un macroensamblador puedeentenderse como un ensamblador con un preprocesador previo.

    1.2.1.7 Conversores fuente-fuente

    Permiten traducir desde un lenguaje de alto nivel a otro lenguaje de alto nivel,con lo que se consigue una mayor portabilidad en los programas de alto nivel.

    Por ejemplo, si un ordenador slo dispone de un compilador de Pascal, yqueremos ejecutar un programa escrito para otra mquina en COBOL, entonces unconversor de COBOL a Pascal solucionar el problema. No obstante el programa fuenteresultado puede requerir retoques manuales debido a diversos motivos:

    En situaciones en que el lenguaje destino carece de importantes caractersticasque el lenguaje origen s tiene. Por ejemplo un conversor de Java a C,necesitara modificaciones ya que C no tiene recolector de basura.

    En situaciones en que la traduccin no es inteligente y los programas destinoson altamente ineficientes.

    1.2.1.8 Compilador cruzado

    Es un compilador que genera cdigo para ser ejecutado en otra mquina. Seutilizan en la fase de desarrollo de nuevos ordenadores. De esta manera es posible, p.ej.,construir el sistema operativo de un nuevo ordenador recurriendo a un lenguaje de altonivel, e incluso antes de que dicho nuevo ordenador disponga siquiera de uncompilador.

    Ntese tambin que, para facilitar el desarrollo de software de un nuevoordenador, uno de los primeros programas que se deben desarrollar para ste es,precisamente, un compilador de algn lenguaje de alto nivel.

    1.2.2 Conceptos bsicos relacionados con la traduccin

    Vamos a estudiar a continuacin diversa terminologa relacionada con elproceso de compilacin y de construccin de compiladores.

    1.2.2.1 Compilacin, enlace y carga.

    Estas son las tres fases bsicas que hay que seguir para que un ordenador

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    7

    Figura 1.4 Entrada y salida de un compilador real

    ejecute la interpretacin de un texto escrito mediante la utilizacin de un lenguaje dealto nivel. Aunque este libro se centrar exclusivamente en la primera fase, vamos a veren este punto algunas cuestiones relativas al proceso completo.

    Por regla general, el compilador no produce directamente un ficheroejecutable, sino que el cdigo generado se estructura en mdulos que se almacenan enun fichero objeto. Los ficheros objeto poseen informacin relativa tanto al cdigomquina como a una tabla de smbolos que almacena la estructura de las variables ytipos utilizados por el programa fuente. La figura 1.4 muestra el resultado real queproduce un compilador.

    Pero, por qu no se genera directamente un fichero ejecutable? Sencillamente,para permitir la compilacin separada, de manera que varios programadores puedandesarrollar simultneamente las partes de un programa ms grande y, lo que es msimportante, puedan compilarlos independientemente y realizar una depuracin enparalelo. Una vez que cada una de estas partes ha generado su correspondiente ficheroobjeto, estos deben fusionarse para generar un solo ejecutable.

    Como se ha comentado, un fichero objeto posee una estructura de mdulostambin llamados registros. Estos registros tienen longitudes diferentes dependiendode su tipo y cometido. Ciertos tipos de estos registros almacenan cdigo mquina, otrosposeen informacin sobre las variables globales, y otros incluyen informacin sobre losobjetos externos (p. ej, variables que se supone que estn declaradas en otro ficheros.El lenguaje C permite explcitamente esta situacin mediante el modificador extern).

    Durante la fase de enlace, el enlazador o linker resuelve las referenciascruzadas, (as se llama a la utilizacin de objetos externos), que pueden estar declaradosen otros ficheros objeto, o en libreras (ficheros con extensin lib o dll), englobaen un nico bloque los distintos registros que almacenan cdigo mquina, estructurael bloque de memoria destinado a almacenar las variables en tiempo de ejecucin ygenera el ejecutable final incorporando algunas rutinas adicionales procedentes delibreras, como por ejemplo las que implementan funciones matemticas o de e/sbsicas. La figura 1.5 ilustra este mecanismo de funcionamiento.

    De esta manera, el bloque de cdigo mquina contenido en el ficheroejecutable es un cdigo reubicable, es decir, un cdigo que en su momento se podrejecutar en diferentes posiciones de memoria, segn la situacin de la misma en elmomento de la ejecucin. Segn el modelo de estructuracin de la memoria del

  • Introduccin

    8

    microprocesador, este cdigo se estructura de diferentes formas. Lo ms usual es queel fichero ejecutable est dividido en segmentos: de cdigo, de datos, de pila de datos,etc.

    Cuando el enlazador construye el fichero ejecutable, asume que cada segmentova a ser colocado en la direccin 0 de la memoria. Como el programa va a estar divididoen segmentos, las direcciones a que hacen referencia las instrucciones dentro de cadasegmento (instrucciones de cambio de control de flujo, de acceso a datos, etc.), no setratan como absolutas, sino que son direcciones relativas a partir de la direccin baseen que sea colocado cada segmento en el momento de la ejecucin. El cargador cargael fichero .exe, coloca sus diferentes segmentos en memoria (donde el sistema operativole diga que hay memoria libre para ello) y asigna los registros base a sus posicionescorrectas, de manera que las direcciones relativas funcionen correctamente. La figura1.6 ilustra el trabajo que realiza un cargador de programas.

    Cada vez que una instruccin mquina hace referencia a una direccin dememoria (partiendo de la direccin 0), el microprocesador se encarga automticamentede sumar a dicha direccin la direccin absoluta de inicio de su segmento. Por ejemplopara acceder a la variable x almacenada en la direccin 1Fh, que se encuentra en el

    Figura 1.5 Funcionamiento de un enlazador

    Figura 1.6 Labor realizada por el cargador. El cargador suele ser parte del sistema operativo

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    9

    segmento de datos ubicado en la direccin 8A34h, la instruccin mquina harreferencia a 1Fh, pero el microprocesador la traducir por 8A34h+1Fh dando lugar aun acceso a la direccin 8A53h: dir absoluta del segmento en memoria + dir relativa dex en el segmento = dir absoluta de x en memoria.

    1.2.2.2 Pasadas de compilacin

    Es el nmero de veces que un compilador debe leer el programa fuente paragenerar el cdigo. Hay algunas situaciones en las que, para realizar la compilacin, noes suficiente con leer el fichero fuente una sola vez. Por ejemplo, en situaciones en lasque existe recursin indirecta (una funcin A llama a otra B y la B llama a la A).Cuando se lee el cuerpo de A, no se sabe si B est declarada ms adelante o se le haolvidado al programador; o si lo est, si los parmetros reales coinciden en nmero ytipo con los formales o no. Es ms, aunque todo estuviera correcto, an no se hagenerado el cdigo para B, luego no es posible generar el cdigo mquinacorrespondiente a la invocacin de B puesto que no se sabe su direccin de comienzoen el segmento de cdigo. Por todo esto, en una pasada posterior hay que controlar loserrores y rellenar los datos que faltan.

    Diferentes compiladores y lenguajes solucionan este problema de manerasdistintas. Una solucin consiste en hacer dos o ms pasadas de compilacin, pero elloconsume demasiado tiempo puesto que las operaciones de e/s son, hoy por hoy, uno delos puntos fundamentales en la falta de eficiencia de los programas (incluidos loscompiladores). Otra solucin pasa por hacer una sola pasada de compilacin ymodificar el lenguaje obligando a hacer las declaraciones de funciones recursivasindirectas antes de su definicin. De esta manera, lenguajes como Pascal o Modula-2utilizan la palabra reservada FORWARD para ello: FORWARD precede la declaracinde B, a continuacin se define A y por ltimo se define B. Lenguajes como C tambinpermiten el no tener que declarar una funcin si sta carece de parmetros y devuelveun entero.

    Otros lenguajes dan por implcito el FORWARD, y si an no se hanencontrado aqullo a que se hace referencia, continan, esperando que el linkerresuelva el problema, o emita el mensaje de error.

    Actualmente, cuando un lenguaje necesita hacer varias pasadas decompilacin, suele colocar en memoria una representacin abstracta del fichero fuente,de manera que las pasadas de compilacin se realizan sobre dicha representacin enlugar de sobre el fichero de entrada, lo que soluciona el problema de la ineficienciadebido a operaciones de e/s.

    1.2.2.3 Compilacin incremental

    Cuando se desarrolla un programa fuente, ste se recompila varias veces hastaobtener una versin definitiva libre de errores. Pues bien, en una compilacin

  • Introduccin

    10

    incremental slo se recompilan las modificaciones realizadas desde la ltimacompilacin. Lo ideal es que slo se recompilen aquellas partes que contenan loserrores o que, en general, hayan sido modificadas, y que el cdigo generado se reinsertecon cuidado en el fichero objeto generado en la ltima compilacin. Sin embargo estoes muy difcil de conseguir y no suele ahorrar tiempo de compilacin ms que en casosmuy concretos.

    La compilacin incremental se puede llevar a cabo con distintos grados deafinacin. Por ejemplo, si se olvida un ; en una sentencia, se podra generar un ficheroobjeto transitorio parcial. Si se corrige el error y se aade el ; que falta y se recompila,un compilador incremental puede funcionar a varios niveles:

    A nivel de carcter: se recompila el ; y se inserta en el fichero objeto lasentencia que faltaba.

    A nivel de sentencia: si el ; faltaba en la lnea 100 slo se compila la lnea100 y se actualiza el fichero objeto.

    A nivel de bloque: si el ; faltaba en un procedimiento o bloque slo secompila ese bloque y se actualiza el fichero objeto.

    A nivel de fichero fuente: si la aplicacin completa consta de 15 ficherosfuente, y solo se modifica 1( al que le faltaba el ;), slo se compila aqul alque se le ha aadido el ;, se genera por completo su fichero objeto y luegose enlazan todos juntos para obtener el ejecutable.

    Lo ideal es que se hiciese eficientemente a nivel de sentencia, pero lo normales encontrarlo a nivel de fichero. La mayora de los compiladores actuales realizan unacompilacin incremental a este nivel.

    Cuando un compilador no admite compilacin incremental, suelen suministraruna herramienta externa (como RMAKE en caso de Clipper, MAKE en algunasversiones de C) en la que el programador indica las dependencias entre ficheros, demanera que si se recompila uno, se recompilan todos los que dependen de aqul. Estasherramientas suelen estar diseadas con un propsito ms general y tambin permitenenlaces condicionales.

    1.2.2.4 Autocompilador

    Es un compilador escrito en el mismo lenguaje que compila (o parecido).Normalmente, cuando se extiende entre muchas mquinas diferentes el uso de uncompilador, y ste se desea mejorar, el nuevo compilador se escribe utilizando ellenguaje del antiguo, de manera que pueda ser compilado por todas esas mquinasdiferentes, y d como resultado un compilador ms potente de ese mismo lenguaje.

    1.2.2.5 Metacompilador

    Este es uno de los conceptos ms importantes con los que vamos a trabajar.Un metacompilador es un compilador de compiladores. Se trata de un programa que

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    11

    acepta como entrada la descripcin de un lenguaje y produce el compilador de dicholenguaje. Hoy por hoy no existen metacompiladores completos, pero s parciales en losque se acepta como entrada una gramtica de un lenguaje y se genera un autmata quereconoce cualquier sentencia del lenguaje . A este autmata podemos aadirle cdigopara completar el resto del compilador. Ejemplos de metacompiladores son: Lex,YACC, FLex, Bison, JavaCC, JLex, Cup, PCCTS, MEDISE, etc.

    Los metacompiladores se suelen dividir entre los que pueden trabajar congramticas de contexto libre y los que trabajan con gramticas regulares. Los primerosse dedican a reconocer la sintaxis del lenguaje y los segundos trocean los ficherosfuente y lo dividen en palabras.

    PCLex es un metacompilador que genera la parte del compilador destinada areconocer las palabras reservadas. PCYACC es otro metacompilador que genera la partedel compilador que informa sobre si una sentencia del lenguaje es vlida o no. JavaCCes un metacompilador que ana el reconocimiento de palabras reservadas y laaceptacin o rechazo de sentencias de un lenguaje. PCLex y PCYACC generan cdigoC y admiten descripciones de un lenguaje mediante gramticas formales, mientras queJavaCC produce cdigo Java y admite descripciones de lenguajes expresadas ennotacin BNF (Backus-Naur Form). Las diferencias entre ellas se irn estudiando entemas posteriores.

    1.2.2.6 Descompilador

    Un descompilador realiza una labor de traduccin inversa, esto es, pasa de uncdigo mquina (programa de salida) al equivalente escrito en el lenguaje que lo gener(programa fuente). Cada descompilador trabaja con un lenguaje de alto nivel concreto.

    La descompilacin suele ser una labor casi imposible, porque al cdigomquina generado casi siempre se le aplica una optimizacin en una fase posterior, demanera que un mismo cdigo mquina ha podido ser generado a partir de varios cdigosfuente. Por esto, slo existen descompiladores de aquellos lenguajes en los que existeuna relacin biyectiva entre el cdigo destino y el cdigo fuente, como sucede con losdesensambladores, en los que a cada instruccin mquina le corresponde una y slo unainstruccin ensamblador.

    Los descompiladores se utilizan especialmente cuando el cdigo mquina hasido generado con opciones de depuracin, y contiene informacin adicional de ayudaal descubrimiento de errores (puntos de ruptura, seguimiento de trazas, opciones devisualizacin de variables, etc.).

    Tambin se emplean cuando el compilador no genera cdigo mquina puro,sino pseudocdigo para ser ejecutado a travs de un pseudointrprete. En estos casossuele existir una relacin biyectiva entre las instrucciones del pseudocdigo y lasconstrucciones sintcticas del lenguaje fuente, lo que permite reconstruir un programa

  • Introduccin

    12

    de alto nivel a partir del de un bloque de pseudocdigo.

    1.3 Estructura de un traductor

    Un traductor divide su labor en dos etapas: una que analiza la entrada y generaestructuras intermedias y otra que sintetiza la salida a partir de dichas estructuras. Portanto, el esquema de un traductor pasa de ser el de la figura 1.1, a ser el de la figura 1.7.

    Bsicamente los objetivos de la etapa de anlisis son: a) controlar lacorreccin del programa fuente, y b) generar las estructuras necesarias para comenzarla etapa de sntesis.

    Para llevar esto a cabo, la etapa de anlisis consta de las siguientes fases:

    O Anlisis lexicogrfico. Divide el programa fuente en los componentes bsicosdel lenguaje a compilar. Cada componente bsico es una subsecuencia decaracteres del programa fuente, y pertenece a una categora gramatical:nmeros, identificadores de usuario (variables, constantes, tipos, nombres deprocedimientos, ...), palabras reservadas, signos de puntuacin, etc.

    O Anlisis sintctico. Comprueba que la estructura de los componentes bsicossea correcta segn las reglas gramaticales del lenguaje que se compila.

    O Anlisis semntico. Comprueba que el programa fuente respeta las directricesdel lenguaje que se compila (todo lo relacionado con el significado): chequeode tipos, rangos de valores, existencia de variables, etc.

    Cualquiera de estas tres fases puede emitir mensajes de error derivados defallos cometidos por el programador en la redaccin de los textos fuente. Mientras mserrores controle un compilador, menos problemas dar un programa en tiempo deejecucin. Por ejemplo, el lenguaje C no controla los lmites de un array, lo queprovoca que en tiempo de ejecucin puedan producirse comportamientos del programade difcil explicacin.

    La etapa de sntesis construye el programa objeto deseado (equivalentesemnticamente al fuente) a partir de las estructuras generadas por la etapa de anlisis.Para ello se compone de tres fases fundamentales:

    O Generacin de cdigo intermedio. Genera un cdigo independiente de la

    Figura 1.7 Esquema por etapas de un traductor

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    13

    Figura 1.8 Construccin de tres compiladores de C reutilizando un front-end

    mquina muy parecido al ensamblador. No se genera cdigo mquinadirectamente porque as es ms fcil hacer pseudocompiladores y adems sefacilita la optimizacin de cdigo independientemente del microprocesador.

    O Generacin del cdigo mquina. Crea un bloque de cdigo mquinaejecutable, as como los bloques necesarios destinados a contener los datos.

    O Fase de optimizacin. La optimizacin puede realizarse sobre el cdigointermedio (de forma independiente de las caractersticas concretas delmicroprocesador), sobre el cdigo mquina, o sobre ambos. Y puede ser unaaislada de las dos anteriores, o estar integrada con ellas.

    1.3.1 Construccin sistemtica de compiladores

    Con frecuencia, las fases anteriores se agrupan en una etapa inicial (front-end) y una etapa final (back- end). La etapa inicial comprende aquellas fases, o partesde fases, que dependen exclusivamente del lenguaje fuente y que son independientesde la mquina para la cual se va a generar el cdigo. En la etapa inicial se integran losanlisis lxicos y sintcticos, el anlisis semntico y la generacin de cdigointermedio. La etapa inicial tambin puede hacer cierta optimizacin de cdigo eincluye adems, el manejo de errores correspondiente a cada una de esas fases.

    La etapa final incluye aquellas fases del compilador que dependen de lamquina destino y que, en general, no dependen del lenguaje fuente sino slo dellenguaje intermedio. En esta etapa, se encuentran aspectos de la fase de generacin decdigo, adems de su optimizacin, junto con el manejo de errores necesario y el accesoa las estructuras intermedias que haga falta.

  • Introduccin

    14

    Figura 1.9 Creacin de tres compiladores (Pascal, C y COBOL) para una misma mquinaIntel

    Figura 1.10 La combinacin de cada front-end con un back-end da lugar a un compiladordistinto: tres de Pascal, tres de C y tres de COBOL. El esfuerzo se ha reducidoconsiderablemente.

    Se ha convertido en una prctica comn el tomar la etapa inicial de uncompilador y rehacer su etapa final asociada para producir un compilador para el mismolenguaje fuente en una mquina distinta. Tambin resulta tentador crear compiladorespara varios lenguajes distintos y generar el mismo lenguaje intermedio para, por ltimo,usar una etapa final comn para todos ellos, y obtener as varios compiladores para unamquina. Para ilustrar esta prctica y su inversa, la figura 1.8 muestra una situacin enla que se quiere crear un compilador del lenguaje C para tres mquinas diferentes: Dec-Alpha (Unix), Motorola (Mac OS) e Intel (MS-DOS). Cada bloque de lneas punteadasagrupa un front-end con un back-end dando lugar a un compilador completo.

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    15

    De manera inversa se podran construir tres compiladores de Pascal, C yCOBOL para una misma mquina, p.ej. Intel, como se ilustra en la figura 1.9.

    Por ltimo, la creacin de compiladores de Pascal, C y COBOL para lasmquinas Dec-Alpha, Motorola e Intel, pasara por la combinacin de los mtodosanteriores, tal como ilustra la figura 1.10.

    1.3.2 La tabla de smbolos

    Una funcin esencial de un compilador es registrar los identificadores deusuario (nombres de variables, de funciones, de tipos, etc.) utilizados en el programafuente y reunir informacin sobre los distintos atributos de cada identificador. Estosatributos pueden proporcionar informacin sobre la memoria asignada a unidentificador, la direccin de memoria en que se almacenar en tiempo de ejecucin, sutipo, su mbito (la parte del programa donde es visible), etc.

    Pues bien, la tabla de smbolos es una estructura de datos que poseeinformacin sobre los identificadores definidos por el usuario, ya sean constantes,variables, tipos u otros. Dado que puede contener informacin de diversa ndole, debehacerse de forma que su estructura no sea uniforme, esto es, no se guarda la mismainformacin sobre una variable del programa que sobre un tipo definido por el usuario.Hace funciones de diccionario de datos y su estructura puede ser una tabla hash, unrbol binario de bsqueda, etc., con tal de que las operaciones de acceso sean lobastante eficientes.

    Tanto la etapa de anlisis como la de sntesis accede a esta estructura, por loque se halla muy acoplada al resto de fases del compilador. Por ello conviene dotar ala tabla de smbolos de una interfaz lo suficientemente genrica como para permitir elcambio de las estructuras internas de almacenamiento sin que estas fases deban serretocadas. Esto es as porque suele ser usual hacer un primer prototipo de un

    Figura 1.11 Esquema por etapas definitivo de un traductor

  • Introduccin

    16

    compilador con una tabla de smbolos fcil de construir (y por tanto, ineficiente), ycuando el compilador ya ha sido finalizado, entonces se procede a sustituir la tabla desmbolos por otra ms eficiente en funcin de las necesidades que hayan ido surgiendoa lo largo de la etapa de desarrollo anterior. Siguiendo este criterio, el esquema generaldefinitivo de un traductor se detalla en la figura 1.11. La figura 1.12 ilustra el esquemapor fases, donde cada etapa ha sido sustituida por las fases que la componen y se hahecho mencin explcita del preprocesador.

    1.4 Ejemplo de compilacin

    Vamos a estudiar a continuacin las diferentes tareas que lleva a cabo cadafase de un compilador hasta llegar a generar el cdigo asociado a una sentencia de C.La sentencia con la que se va a trabajar es la siguiente:

    #define PORCENTAJE 8comision = fijo + valor * PORCENTAJE;

    Figura 1.12 Esquema completo de un compilador por fases conpreprocesador.

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    17

    Para no complicar demasiado el ejemplo, asumiremos que las variablesreferenciadas han sido previamente declaradas de tipo int, e inicializadas a los valoresdeseados.

    Comenzaremos con el preprocesamiento hasta llegar, finalmente, a la fase degeneracin de cdigo mquina en un microprocesador cualquiera.

    1.4.1 Preprocesamiento

    Como ya hemos visto, el cdigo fuente de una aplicacin se puede dividir enmdulos almacenados en archivos distintos. La tarea de reunir el programa fuente amenudo se confa a un programa distinto, llamado preprocesador. El preprocesadortambin puede expandir abreviaturas, llamadas macros, a proposiciones del lenguajefuente. En nuestro ejemplo, la constante PORCENTAJE se sustituye por su valor,dando lugar al texto:

    comision = fijo + valor * 8;que pasa a ser la fuente que entrar al compilador.

    1.4.2 Etapa de anlisis

    En esta etapa se controla que el texto fuente sea correcto en todos los sentidos,y se generan las estructuras necesarias para la generacin de cdigo.

    1.4.2.1 Fase de anlisis lexicogrfico

    En esta fase, la cadena de caracteres que constituye el programa fuente se leede izquierda a derecha y se agrupa en componentes lxicos, que son secuencias decaracteres que tienen un significado atmico; adems el analizador lxico trabaja conla tabla de smbolos introduciendo en sta los nombres de las variables.

    En nuestro ejemplo los caracteres de la proposicin de asignacin comision= fijo + valor * 8 ;

    se agruparan en los componentes lxicos siguientes:1.- El identificador comision.2.- El smbolo de asignacin =.3.- El identificador fijo.4.- El signo de suma +.5.- El identificador valor.6.- El signo de multiplicacin *.7.- El nmero 8.8.- El smbolo de fin de sentencia ;.

    La figura 1.13 ilustra cmo cada componente lxico se traduce a su categoragramatical, y se le asocia alguna informacin, que puede ser un puntero a la tabla desmbolos donde se describe el identificador, o incluso un valor directo, como ocurre enel caso del literal 8. As, cada componente se convierte en un par (categora, atributo),

  • Introduccin

    18

    y se actualiza la tabla de smbolos. Esta secuencia de pares se le pasa a la siguiente fasede anlisis.

    Ntese como los espacios en blanco que separan los caracteres de estoscomponentes lxicos normalmente se eliminan durante el anlisis lxico, siempre ycuando la definicin del lenguaje a compilar as lo aconseje, como ocurre en C. Lomismo pasa con los tabuladores innecesarios y con los retornos de carro. Loscomentarios, ya estn anidados o no, tambin son eliminados.

    1.4.2.2 Fase de anlisis sintctico

    Trabaja con una gramtica de contexto libre y genera el rbol sintctico quereconoce su sentencia de entrada. En nuestro caso las categoras gramaticales delanlisis lxico son los terminales de la gramtica. Para el ejemplo que nos ocupapodemos partir de la gramtica:

    S expr expr

    | expr

    Figura 1.13 Transformacin realizada por el analizador lexicogrfico

    Figura 1.14 rbol sintctico de la sentencia de entrada.Aunque slo se han representado las categorasgramaticales, recurdese que cada una lleva o puedellevar asociado un atributo.

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    19

    | expr|

    de manera que el anlisis sintctico intenta generar un rbol sintctico que encaje conla sentencia de entrada. Para nuestro ejemplo, dicho rbol sintctico existe y es el dela figura 1.14. El rbol puede representarse tal y como aparece en esta figura, o bieninvertido.

    1.4.2.2.1 Compilacin dirigida por sintaxis

    Se ha representado una situacin ideal en la que la fase lexicogrfica acta porseparado y, slo una vez que ha acabado, le suministra a la sintctica su resultado desalida. Aunque proseguiremos en nuestro ejemplo con esta clara distincin entre fases,es importante destacar que el analizador sintctico tiene el control en todo momento,y el lxico por trozos, a peticin del sintctico. En otras palabras, el sintctico vaconstruyendo su rbol poco a poco (de izquierda a derecha), y cada vez que necesita unnuevo componente lxico para continuar dicha construccin, se lo solicita allexicogrfico; ste lee nuevos caracteres del fichero de entrada hasta conformar unnuevo componente y, una vez obtenido, se lo suministra al sintctico, quien continala construccin del rbol hasta que vuelve a necesitar otro componente, momento en quese reinicia el proceso. Este mecanismo finaliza cuando se ha obtenido el rbol y ya nohay ms componentes en el fichero de entrada, o bien cuando es imposible construir elrbol.

    Esto es tan slo el principio de lo que se denomina compilacin dirigida porsintaxis (ver figura 1.15): es aqul mecanismo de compilacin en el que el control lolleva el analizador sintctico, y todas las dems fases estn sometidas a l.

    1.4.2.3 Fase de anlisis semntico

    Esta fase revisa el rbol sintctico junto con los atributos y la tabla desmbolos para tratar de encontrar errores semnticos. Para todo esto se analizan losoperadores y operandos de expresiones y proposiciones. Finalmente rene la

    Figura 1.15 Anlisis dirigido por sintaxis

  • Introduccin

    20

    informacin necesaria sobre los tipos de datos para la fase posterior de generacin decdigo.

    El componente ms importante del anlisis semntico es la verificacin detipos. Aqu, el compilador verifica si los operandos de cada operador son compatiblessegn la especificacin del lenguaje fuente. Si suponemos que nuestro lenguaje solotrabaja con nmeros reales, la salida de esta fase sera su mismo rbol, excepto porqueel atributo de , que era el entero 8 a la entrada, ahora pasara a ser el real 8,0.Adems se ha debido controlar que las variables implicadas en la sentencia, a saber,comision, fijo y valor son compatibles con el tipo numrico de la constante 8,0.

    1.4.3 Etapa de sntesis

    En la etapa anterior se ha controlado que el programa de entrada es correcto.Por tanto, el compilador ya se encuentra en disposicin de generar el cdigo mquinaequivalente semnticamente al programa fuente. Para ello se parte de las estructurasgeneradas en dicha etapa anterior: rbol sintctico y tabla de smbolos.

    1.4.3.1 Fase de generacin de cdigo intermedio

    Despus de la etapa de anlisis, se suele generar una representacin intermediaexplcita del programa fuente. Dicha representacin intermedia se puede considerarcomo un programa para una mquina abstracta.

    Cualquier representacin intermedia debe tener dos propiedades importantes;debe ser fcil de generar y fcil de traducir al cdigo mquina destino. As, unarepresentacin intermedia puede tener diversas formas. En el presente ejemplo setrabajar con una forma intermedia llamada cdigo de tres direcciones, que es muyparecida a un lenguaje ensamblador para un microprocesador que carece de registrosy slo es capaz de trabajar con direcciones de memoria y literales. En el cdigo de tresdirecciones cada instruccin tiene como mximo tres operandos. Siguiendo el ejemplopropuesto, se generara el siguiente cdigo de tres direcciones:

    t1 = 8.0t2 = valor * t1t3 = fijo + t2comision = t3

    De este ejemplo se pueden destacar varias propiedades del cdigo intermedioescogido:

    Cada instruccin de tres direcciones tiene a lo sumo un operador, adems dela asignacin.

    El compilador debe generar un nombre temporal para guardar los valoresintermedios calculados por cada instruccin: t1, t2 y t3.

    Algunas instrucciones tienen menos de tres operandos, como la primera y laltima instrucciones del ejemplo.

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    21

    1.4.3.2 Fase de optimizacin de cdigo

    Esta fase trata de mejorar el cdigo intermedio, de modo que en la siguientefase resulte un cdigo de mquina ms rpido de ejecutar. Algunas optimizaciones sontriviales. En nuestro ejemplo hay una forma mejor de realizar el clculo de la comisin,y pasa por realizar sustituciones triviales en la segunda y cuarta instrucciones,obtenindose:

    t2 = valor * 8.0comision= fijo + t2El compilador puede deducir que todas las apariciones de la variable t1

    pueden sustituirse por la constante 8,0, ya que a t1 se le asigna un valor que ya nocambia, de modo que la primera instruccin se puede eliminar. Algo parecido sucedecon la variable t3, que se utiliza slo una vez, para transmitir su valor a comision enuna asignacin directa, luego resulta seguro sustituir comision por t3, a raz de lo cualse elimina otra de las lneas del cdigo intermedio.

    1.4.3.3 Fase de generacin de cdigo mquina

    La fase final de un compilador es la generacin de cdigo objeto, que por logeneral consiste en cdigo mquina reubicable o cdigo ensamblador. Cada una de lasvariables usadas por el programa se traduce a una direccin de memoria (esto tambinse ha podido hacer en la fase de generacin de cdigo intermedio). Despus, cada unade las instrucciones intermedias se traduce a una secuencia de instrucciones de mquinaque ejecuta la misma tarea. Un aspecto decisivo es la asignacin de variables a registros.

    Siguiendo el mismo ejemplo, y utilizando los registros R1 y R2 de unmicroprocesador hipottico, la traduccin del cdigo optimizado podra ser:

    MOVE [1Ah], R1MULT #8.0, R1MOVE [15h], R2ADD R1, R2MOVE R2, [10h]El primer y segundo operandos de cada instruccin especifican una fuente y

    un destino, respectivamente. Este cdigo traslada el contendido de la direccin [1Ah]al registro R1, despus lo multiplica por la constante real 8.0. La tercera instruccinpasa el contenido de la direccin [15h] al registro R2. La cuarta instruccin le suma elvalor previamente calculado en el registro R1. Por ltimo el valor del registro R2 sepasa a la direccin [10h]. Como el lector puede suponer, la variable comision sealmacena en la direccin [10h], fijo en [15h] y valor en [1Ah].

  • Introduccin

    22

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    23

    Captulo 2

    Anlisis lexicogrfico

    2.1 Visin general

    Este captulo estudia la primera fase de un compilador, es decir su anlisislexicogrfico, tambin denominado abreviadamente anlisis lxico. Las tcnicasutilizadas para construir analizadores lxicos tambin se pueden aplicar a otras reascomo, por ejemplo, a lenguajes de consulta y sistemas de recuperacin de informacin.En cada aplicacin, el problema de fondo es la especificacin y diseo de programasque ejecuten las acciones activadas por palabras que siguen ciertos patrones dentro delas cadenas a reconocer. Como la programacin dirigida por patrones est ampliamenteextendida y resulta de indudable utilidad, existen numerosos metalenguajes quepermiten establecer pares de la forma patrn-accin, de manera que la accin se ejecutacada vez que el sistema se encuentra una serie de caracteres cuya estructura coincidecon la del patrn. En concreto, vamos a estudiar Lex con el objetivo de especificar losanalizadores lxicos. En este lenguaje, los patrones se especifican por medio deexpresiones regulares, y el metacompilador de Lex genera un reconocedor de lasexpresiones regulares mediante un autmata finito (determinista evidentemente)eficiente.

    Por otro lado, una herramienta software que automatiza la construccin deanalizadores lxicos permite que personas con diferentes conocimientos utilicen laconcordancia de patrones en sus propias reas de aplicacin, ya que, a la hora de laverdad, no es necesario tener profundos conocimientos de informtica para aplicardichas herramientas.

    2.2 Concepto de analizador lxico

    Se encarga de buscar los componentes lxicos o palabras que componen elprograma fuente, segn unas reglas o patrones.

    La entrada del analizador lxico podemos definirla como una secuencia decaracteres, que pueda hallarse codificada segn cualquier estndar: ASCII (AmericanStandard Code for Information Interchange), EBCDIC (Extended Binary CodedDecimal Interchange Code), Unicode, etc. El analizador lxico divide esta secuenciaen palabras con significado propio y despus las convierte a una secuencia determinales desde el punto de vista del analizador sintctico. Dicha secuencia es el puntode partida para que el analizador sintctico construya el rbol sintctico que reconoce

  • Anlisis lexicogrfico

    24

    Figura 2.1 Entradas y salidas de las dos primeras fases de la etapa de anlisis.La frase Secuencia de Terminales hace referencia a la gramtica del sintctico;pero tambin es posible considerar que dicha secuencia es de no terminales siusamos el punto de vista del lexicogrfico.

    la/s sentencia/s de entrada, tal y como puede verse en la figura 2.1.

    El analizador lxico reconoce las palabras en funcin de una gramtica regularde manera que el alfabeto G de dicha gramtica son los distintos caracteres del juegode caracteres del ordenador sobre el que se trabaja (que forman el conjunto de smbolosterminales), mientras que sus no terminales son las categoras lxicas en que se integranlas distintas secuencias de caracteres. Cada no terminal o categora lxica de lagramtica regular del anlisis lxico es considerado como un terminal de la gramticade contexto libre con la que trabaja el analizador sintctico, de manera que la salida dealto nivel (no terminales) de la fase lxica supone la entrada de bajo nivel (terminales)de la fase sintctica. En el caso de Lex, por ejemplo, la gramtica regular se expresamediante expresiones regulares.

    2.2.1 Funciones del analizador lxico

    El analizador lxico es la primera fase de un compilador. Su principal funcinconsiste en leer los caracteres de entrada y elaborar como salida una secuencia de

    Figura 2.2 La fase de anlisis lxico se halla bajo el control del anlisis sintctico.Normalmente se implementa como una funcin de ste

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    25

    componentes lxicos que utiliza el analizador sintctico para hacer el anlisis. Estainteraccin suele aplicarse convirtiendo al analizador lxico en una subrutina ocorrutina del analizador sintctico. Recibida la orden Dame el siguiente componentelxicodel analizador sintctico, el lxico lee los caracteres de entrada hasta que puedaidentificar el siguiente componente lxico, el cual devuelve al sintctico segn elformato convenido (ver figura 2.2).

    Adems de esta funcin principal, el analizador lxico tambin realiza otrasde gran importancia, a saber:

    Eliminar los comentarios del programa. Eliminar espacios en blanco, tabuladores, retorno de carro, etc, y en general,

    todo aquello que carezca de significado segn la sintaxis del lenguaje. Reconocer los identificadores de usuario, nmeros, palabras reservadas del

    lenguaje, etc., y tratarlos correctamente con respecto a la tabla de smbolos(solo en los casos en que este analizador deba tratar con dicha estructura).

    Llevar la cuenta del nmero de lnea por la que va leyendo, por si se producealgn error, dar informacin acerca de dnde se ha producido.

    Avisar de errores lxicos. Por ejemplo, si el carcter @ no pertenece allenguaje, se debe emitir un error.

    Tambin puede hacer funciones de preprocesador.

    2.2.2 Necesidad del analizador lxico

    Un buen profesional debe ser capaz de cuestionar y plantearse todas lasdecisiones de diseo que se tomen, y un asunto importante es el porqu se separa elanlisis lxico del sintctico si, al fin y al cabo, el control lo va a llevar el segundo. Enotras palabras, por qu no se delega todo el procesamiento del programa fuente slo enel anlisis sintctico, cosa perfectamente posible (aunque no plausible como veremosa continuacin), ya que el sintctico trabaja con gramticas de contexto libre y stasengloban a las regulares. A continuacin estudiaremos algunas razones de estaseparacin.

    2.2.2.1 Simplificacin del diseo

    Un diseo sencillo es quizs la ventaja ms importante. Separar el anlisislxico del anlisis sintctico a menudo permite simplificar una, otra o ambas fases.Normalmente aadir un analizador lxico permite simplificar notablemente elanalizador sintctico. An ms, la simplificacin obtenida se hace especialmentepatente cuando es necesario realizar modificaciones o extensiones al lenguajeinicialmente ideado; en otras palabras, se facilita el mantenimiento del compilador amedida que el lenguaje evoluciona.

  • Anlisis lexicogrfico

    26

    Figura 2.3 Pasos en la construccin progresiva de un compilador

    La figura 2.3 ilustra una situacin en la que, mediante los patronescorrespondientes, el analizador lxico reconoce nmeros enteros y operadoresaritmticos. A la hora de construir una primera versin del analizador sintctico,podemos asumir que dos expresiones pueden ir conectadas con cualquiera de dichosoperadores, por lo que se puede optar por agruparlos todos bajo la categora lxicaOPARIT (Operadores ARITmticos). En una fase posterior, puede resultar necesariodisgregar dicha categora en tantas otras como operadores semnticamente diferenteshaya: OPARIT desaparece y aparecen MAS, MENOS, MULT y DIV. Unamodificacin tal resulta trivial si se han separado adecuadamente ambos analizadores,ya que consiste en sustituir el patrn agrupado (-|+|*|/) por los patronesdisgregados -, +, * y /.

    Si el sintctico tuviera la gramtica del paso 1, el lexicogrfico sera:el patrn ( 0 | 1 | 2 | ... | 9) retorna la categora NUM +el patrn (+ | - | * | /) retorna la categora OPARIT

    En cambio, si el sintctico adopta el paso 2, el lexicogrfico sera:el patrn ( 0 | 1 | 2 | ... | 9) retorna la categora NUM +el patrn + retorna la categora MASel patrn - retorna la categora MENOSel patrn * retorna la categora MULTel patrn / retorna la categora DIV

    Tambin se puede pensar en eliminar el lexicogrfico, incorporando sugramtica en la del sintctico, de manera que ste vera incrementado su nmero dereglas con las siguientes:

    NUM 6 0| 1

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    27

    | 2| 3...| NUM NUM

    sin embargo, los autmatas destinados a reconocer los componentes lxicos y al rbolsintctico son radicalmente diferentes, tanto en su concepcin como en suimplementacin lo que, de nuevo, nos lleva a establecer una divisin entre estosanlisis.

    A modo de conclusin, se puede decir que es muy recomendable trabajar condos gramticas, una que se encarga del anlisis lxico y otra que se encarga del anlisissintctico. Dnde se pone el lmite entre lo que reconoce una y otra gramtica?, quse considera un componente bsico?, cuntas categoras gramaticales se establecen?Si se crean muchas categoras gramaticales se estar complicando la gramtica delsintctico, como ocurre p.ej. en el paso 2. En general, deben seguirse un par de reglasbsicas para mantener la complejidad en unos niveles admisibles. La primera es que lainformacin asociada a cada categora lxica debe ser la necesaria y suficiente, lo quequedar ms claro en captulos posteriores, cuando se conozca el concepto de atributo.La segunda es que, por regla general, las gramticas que se planteen (regular y decontexto libre) no deben verse forzadas, en el sentido de que los distintos conceptosque componen el lenguaje de programacin a compilar deben formar parte de una o deotra de forma natural; p.ej., el reconocimiento que deba hacerse carcter a carcter (sinque stos tengan un significado semntico por s solos) debe formar parte del anlisislxico.

    2.2.2.2 Eficiencia

    La divisin entre anlisis lxico y sintctico tambin mejora la eficiencia delcompilador. Un analizador lxico independiente permite construir un procesadorespecializado y potencialmente ms eficiente para las funciones explicadas en elepgrafe 2.2.1. Gran parte del tiempo de compilacin se invierte en leer el programafuente y dividirlo en componentes lxicos. Con tcnicas especializadas de manejo debuffers para la lectura de caracteres de entrada y procesamiento de patrones se puedemejorar significativamente el rendimiento de un compilador.

    2.2.2.3 Portabilidad

    Se mejora la portabilidad del compilador, ya que las peculiaridades delalfabeto de partida, del juego de caracteres base y otras anomalas propias de losdispositivos de entrada pueden limitarse al analizador lxico. La representacin desmbolos especiales o no estndares, como 8 en Pascal, se pueden aislar en elanalizador lxico.

    Por otro lado, algunos lenguajes, como APL (A Program Language) se

  • Anlisis lexicogrfico

    28

    benefician sobremanera del tratamiento de los caracteres que forman el programa deentrada mediante un analizador aislado. El Diccionario de Informtica de la OxfordUniversity Press define este lenguaje de la siguiente forma:

    ... Su caracterstica principal es que proporciona un conjunto muy grandede operadores importantes para tratar las rdenes multidimensionales junto con lacapacidad del usuario de definir sus propios operadores. Los operadoresincorporados se encuentran representados, principalmente, por caracteres solos queutilizan un conjunto de caracteres especiales. De este modo, los programas APL sonmuy concisos y, con frecuencia, impenetrables.

    2.2.2.4 Patrones complejos

    Otra razn por la que se separan los dos anlisis es para que el analizadorlxico se centre en el reconocimiento de componentes bsicos complejos. Por ejemploen Fortran, existe el siguiente par de proposiciones muy similares sintcticamente, perode significado bien distinto:

    DO5I = 2.5 (Asignacin del valor 2.5 a la variable DO5I)DO 5 I = 2, 5 (Bucle que se repite para I = 2, 3, 4 y 5)

    En este lenguaje los espacios en blancos no son significativos fuera de loscomentarios y de un cierto tipo de cadenas (para ahorrar espacio de almacenamiento,en una poca de la Informtica en la que ste era un bien escaso), de modo quesupngase que todos los espacios en blanco eliminables se suprimen antes de comenzarel anlisis lxico. En tal caso, las proposiciones anteriores apareceran ante elanalizador lxico como:

    DO5I=2.5DO5I=2,5

    El analizador lxico no sabe si DO es una palabra reservada o es el prefijo delnombre de una variable hasta que se lee la coma. Ha sido necesario examinar la cadenade entrada mucho ms all de la propia palabra a reconocer haciendo lo que sedenomina lookahead (o prebsqueda). La complejidad de este procesamiento hacerecomendable aislarlo en una fase independiente del anlisis sintctico.

    En cualquier caso, en lenguajes como Fortran primero se dise el lenguajey luego el compilador, lo que conllev problemas como el que se acaba de plantear. Hoyda los lenguajes se disean teniendo en mente las herramientas de que se dispone parala construccin de su compilador y se evitan este tipo de situaciones.

    2.3 Token, patrn y lexema

    Desde un punto de vista muy general, podemos abstraer el programa queimplementa un anlisis lxicogrfico mediante una estructura como:

    1 1(Expresin regular) {accin a ejecutar}

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    29

    2 2(Expresin regular) {accin a ejecutar}3 3(Expresin regular) {accin a ejecutar}

    ... ...n n(Expresin regular) {accin a ejecutar}

    donde cada accin a ejecutar es un fragmento de programa que describe cul ha de serla accin del analizador lxico cuando la secuencia de entrada coincida con laexpresin regular. Normalmente esta accin suele finalizar con la devolucin de unacategora lxica.

    Todo esto nos lleva a los siguientes conceptos de fundamental importancia alo largo de nuestro estudio:

    O Patrn: es una expresin regular.

    O Token: es la categora lxica asociada a un patrn. Cada token se convierte enun nmero o cdigo identificador nico. En algunos casos, cada nmero tieneasociada informacin adicional necesaria para las fases posteriores de la etapade anlisis. El concepto de token coincide directamente con el concepto determinal desde el punto de vista de la gramtica utilizada por el analizadorsintctico.

    O Lexema: Es cada secuencia de caracteres concreta que encaja con un patrn.P.ej: 8", 23" y 50" son algunos lexemas que encajan con el patrn(0'|1'|2'| ... |9') . El nmero de lexemas que puede encajar con un patrn+puede ser finito o infinito, p.ej. en el patrn WHILE slo encaja ellexema WHILE.

    Una vez detectado que un grupo de caracteres coincide con un patrn, seconsidera que se ha detectado un lexema. A continuacin se le asocia el nmero de sucategora lxica, y dicho nmero o token se le pasa al sintctico junto con informacinadicional, si fuera necesario. En la figura 1.13 puede verse cmo determinadascategoras llevan informacin asociada, y otras no.

    Por ejemplo, si se necesita construir un analizador lxico que reconozca losnmeros enteros, los nmeros reales y los identificadores de usuario en minsculas, sepuede proponer una estructura como:

    Expresin Regular Terminal asociado(0 ... 9) NUM_ENT+(0 ... 9) . (0 ... 9) NUM_REAL* +(a ... z) (a ... z 0 ... 9) ID*

    Asociado a la categora gramatical de nmero entero se tiene el tokenNUM_ENT que puede equivaler, p.ej. al nmero 280; asociado a la categoragramatical nmero real se tiene el token NUM_REAL que equivale al nmero 281; yla categora gramatical identificador de usuario tiene el token ID que equivale al nmero

  • Anlisis lexicogrfico

    30

    282. As, la estructura expresin regular-accin sera la siguiente (supuesto que lasacciones las expresamos en C o Java):

    (0 ... 9) { return 280;}+(0 ... 9) . (0 ... 9) { return 281;}* +(a ... z) (a ... z 0...9) { return 282;}* { }

    De esta manera, un analizador lxico que obedeciera a esta estructura, sidurante su ejecucin se encuentra con la cadena:

    95.7 99 cont intentar leer el lexema ms grande de forma que, aunque el texto 95" encaja con elprimer patrn, el punto y los dgitos que le siguen .7" hacen que el analizador decidareconocer 95.7" como un todo, en lugar de reconocer de manera independiente 95"por un lado y .7" por otro; as se retorna el token NUM_REAL. Resulta evidente queun comportamiento distinto al expuesto sera una fuente de problemas. A continuacinel patrn y la accin asociada permiten ignorar los espacios en blanco. El 99"coincide con el patrn de NUM_ENT, y la palabra cont con ID.

    Para facilitar la comprensin de las acciones asociadas a los patrones, en vezde trabajar con los nmeros 280, 281 y 282 se definen mnemotcnicos.

    # define NUM_ENT 280# define NUM_REAL 281# define ID 282( \t \n)(0 ... 9) {return NUM_ENT;}+(0 ... 9) . (0 ... 9) {return NUM_REAL;}* +(a ... z) (a ... z 0 ... 9) {return ID;}*

    En esta nueva versin, los lexemas que entran por el patrn ( \t \n) no tienenaccin asociada, por lo que, por defecto, se ejecuta la accin nula o vaca.

    El asociar un nmero (token) a cada categora gramatical se suele emplearmucho en los metacompiladores basados en lenguajes puramente imperativos, comopueda ser C o Pascal. Los metacompiladores ms modernos basados en programacinorientada a objetos tambin asocian un cdigo a cada categora gramatical, pero en lugarde retornar dicho cdigo, retornan objetos con una estructura ms compleja.

    2.3.1 Aproximaciones para construir un analizadorlexicogrfico

    Hay tres mecanismos bsicos para construir un analizador lexicogrfico:

    Ad hoc. Consiste en la codificacin de un programa reconocedor que no siguelos formalismos propios de la teora de autmatas. Este tipo de construccioneses muy propensa a errores y difcil de mantener.

  • Java a tope: Traductores y compiladores con Lex/Yacc, JFlex/Cup y JavaCC

    31

    Mediante la implementacin manual de los autmatas finitos. Este mecanismoconsiste en construir los patrones necesarios para cada categora lxica,construir sus automtas finitos individuales, fusionarlos por opcionalidad y,finalmente, implementar los autmatas resultantes. Aunque la construccin deanalizadores mediante este mtodo es sistemtica y no propensa a errores,cualquier actualizacin de los patrones reconocedores implica la modificacindel cdigo que los implementa, por lo que el mantenimiento se hace muycostoso.

    Mediante un metacompilador. En este caso, se utiliza un programa especialque tiene como entrada pares de la forma (expresin regular, accin). Elmetacompilador genera todos los autmatas finitos, los convierte a autmatafinito determinista, y lo implementa en C. El programa C as generado secompila y se genera un ejecutable que es el anlizador lxico de nuestrolenguaje. Por supuesto, existen metacompiladores que generan cdigo Java,Pascal, etc. en lugar de C.

    Dado que hoy da existen numerosas herramientas para construir analizadoreslxicos a partir de notaciones de propsito especial basadas en expresiones regulares,nos basaremos en ellas para proseguir nuestro estudio dado que, adems, losanalizadores resultantes suelen ser bastante eficientes tanto en tiempo como enmemoria. Comenzaremos con un generador de analizadores lxicos escritos en C, ycontinuaremos con otro que genera cdigo Java.

    2.4 El generador de analizadores lexicogrficos:PCLex

    En esta seccin se describe la herramienta actualmente ms extendida, llamadaLex, para la especificacin de analizadores lxicos en general, aunque en nuestro casonos centraremos en el analizador lxico de un compilador. La herramienta equivalentepara entorno DOS se denomina PCLex, y la especificacin de su entrada, lenguaje Lex.El estudio de una herramienta existente permitir mostrar cmo, utilizando expresionesregulares, se puede combinar la especificacin de patrones con acciones, para p.ej.,realizar inserciones en una tabla de smbolos.

    2.4.1 Visin general

    Como ya sabemos, las reglas de reconocimiento son de la forma:1 1p {accin }2 2p {accin }

    ... ...n np {accin }

    i idonde p es una expresin regular y accin es un fragmento de progr