Construcción de compiladores con Haskell

Slides:



Advertisements
Presentaciones similares
Clasificación de los compiladores
Advertisements

Compiladores e intérpretes Análisis Sintáctico II
Compiladores e intérpretes Generación de código intermedio II
Compiladores e intérpretes
Prof. Ing.Maria Rosa Damaso Rios
Cb00843 Traductores Rogelio Dávila Pérez Profesor Planta
Teoría de Autómatas y Compiladores
Facultad de Ciencias de la Computación
Fundamentos de programación
INSTITUTO TECNOLOGICO DE MINATITLAN
Unidad 1. Introducción al proceso de compilación.
Teoría de lenguajes y compiladores
1 Parsing Un parser podría ser definido como un programa que analiza una porción de texto para determinar su estructura lógica: la fase de parsing en un.
Programación monádica
Analizador Léxico. 4 scanners:
Tipo de Dato Abstracto Tipos de datos:
Procesadores de Lenguajes I
ALGORÍTMICA Dpto. Ingeniería de Sistemas y Automática
Traducción dirigida por la Sintaxis
ANALISIS SINTACTICO DESCENDENTE
Teoría de lenguajes y compiladores
METODOLOGIA DE LA PROGRAMACION
ANALISIS SINTACTICO El análisis gramatical es la tarea de determinar la sintaxis, o estructura, de un programa. Por esta razón también se le conoce como.
Teoría de lenguajes y compiladores
TRAMPAS EN EL DISEÑO DE LENGUAJES CON RELACIÓN A LOS NOMBRES - REGLAS DE ALCANCE - COMPILACIÓN POR SEPARADO CONTROL DE FLUJO - EVALUACIÓN DE EXPRESIONES.
Introducción. Agenda Clase 1 ● Preámbulo histórico. ● Razones de la Diversidad de Lenguajes. ● Razones del Éxito de un Lenguaje. ● Clasificación ● Motivación.
Programación de sistemas
TRADUCTOR DE UN PROGRAMA
Paradigmas de Programación
M.C. Meliza Contreras González
Formateador y Analizador de textos
Análisis sintáctico LR: SLR (LR simple)
Procesadores del Lenguaje
FUNDAMENTOS DE PROGRAMACION
ANALISIS SINTACTICO Parte I
Sintaxis.
Resumen de Compilación Preparado por Manuel E. Bermúdez, Ph.D. Associate Professor University of Florida Traducido por Christian Torres Universidad Ricardo.
Inteligencia artificial
Clasificación de Gramáticas y Manejo de Errores
Universidad Nacional de Jujuy Facultad de Ingeniería
Agenda Clase 16 Motivación e Historia de la Programación Funcional y la Programación Lógica. Concepto y Características de la Programación Funcional. Ventajas.
Procesadores de Lenguajes
Introducción al Análisis Sintáctico
Lectura 4: Compilación e Interpretación
INTEGRANTES LINA JIMÉNEZ LOZANO ROSA ELENA REINA CARLOS VILLADIEGO MARIANO SEPULVEDA.
Programación de Sistemas
Control de Flujo.
Tema 1. Introducción y Conceptos Básicos
Análisis Léxico Área Software de Base.
COMPILADORES DIANA ROCIO OLAYA MESA.
INFORMATICA VII (Programación e implementación de sistemas)
Programación de Sistemas FEI – 2008
Unidad 1. Introducción a los Compiladores.
Objetivo Mostrar los fundamentos de la programación a través de ejemplos y prácticas utilizadas cotidianamente en el desarrollo de aplicaciones.
UNIVERSIDAD LATINA (UNILA)
LE, EI, Profesor Ramón Castro Liceaga UNIVERSIDAD LATINA (UNILA) TRADUCTORES Y ANALIZADOR LEXICOGRÁFICO.
“PARADIGMAS Y LENGUAJES DE PROGRAMACION”
Teoría de lenguajes y compiladores
CARACTERÍSTICAS Es un lenguaje de programación estructurado de propósito general. Está estrechamente asociado al sistema operativo UNIX, ya que el propio.
ESCUELA NORMAL “PROF. DARÍO RODRÍGUEZ CRUZ” Licenciatura en educación preescolar Alumnas: *Mayra * Monserrat * Idalia *Cinthia Curso: las TIC en la educación.
Prof. Flor Narciso Departamento de Computación
PRINCIPIOS DE PROGRAMACIÓN
Lenguaje programación
El proceso de compilación
Es un tipo especial de software que nos permite *Crear *Desarrollar *Programar otras aplicaciones. Haciendo uso de sus conocimientos lógicos y lenguajes.
IV. GRAMÁTICAS DISTRIBUIDAS Y TABLAS DE SÍMBOLOS
75.41 Algoritmos y Programación II Cátedra Ing. Patricia Calvo Complejidad algorítmica.
Programación de Sistemas
Unidad 2 Lenguajes, Expresiones Regulares, Autómatas
Sintaxis y Semántica. S.Takahashi Fases en el proceso de análisis de lenguajes Lexer Parser caracteres tokensrespuesta.
Transcripción de la presentación:

Construcción de compiladores con Haskell José María Carmona Cejudo Briseida Sarasola Gutiérrez

Índice Motivación ¿Qué es un compilador? Técnicas en Haskell estándar Historia Esquema de un compilador Técnicas en Haskell estándar Análisis monádico Herramientas software Alex Happy (Frown) Parsec ¿Qué podemos concluir? Bibliografía

¿Qué es un compilador? Programa que traduce texto escrito en un lenguaje de programación (código fuente) a otro (código objeto). Código fuente escrito en un lenguaje de alto nivel (Haskell, Java, C++), que queremos pasar a un lenguaje de bajo nivel (ensamblador, lenguaje máquina).

Un poco de historia (I) En principio, se programaba en código binario. Años 40: Se crean mnemotécnicos para las operaciones binarias, usando los ordenadores para traducirlos a código máquina. Años 50: Nacen lenguajes de alto nivel, para crear programas más independientes de la máquina. Primer compilador: Fortran, 1.957. Equipo de J. Backus, de IBM.

Un poco de historia (II) Años 60: Se establecen muchos de los principios del diseño de compiladores. Aún se suelen programar en ensamblador Años 70: Se usan lenguajes de alto nivel, como Pascal y C. Otros tipos: intérpretes (realizan el proceso sentencia a sentencia). Programas resultantes más lentos, pero más fáciles de depurar.

Esquema de un compilador Dos fases Análisis: se lee el programa fuente y se estudia la estructura y el significado del mismo. Síntesis: se genera el programa objeto. Otros elementos: tabla de símbolos, rutinas de tratamiento de errores, etc.

Esquema de un compilador Dos fases Análisis: se lee el programa fuente y se estudia la estructura y el significado del mismo. Síntesis: se genera el programa objeto. Otros elementos: tabla de símbolos, rutinas de tratamiento de errores, etc.

Esquema de un compilador Dos fases Análisis: se lee el programa fuente y se estudia la estructura y el significado del mismo. Síntesis: se genera el programa objeto. Otros elementos: tabla de símbolos, rutinas de tratamiento de errores, etc.

Fase de análisis Tres fases Análisis léxico Análisis sintáctico Análisis semántico

Fase de análisis Tres fases Análisis léxico: Análisis sintáctico identificar símbolos, eliminar separadores, eliminar comentarios, crear símbolos de entrada al análisis sintáctico (tokens), descubrir errores. Análisis sintáctico Análisis semántico

Fase de análisis Tres fases Análisis léxico Análisis sintáctico: comprobar que las sentencias que componen el texto fuente son correctas en el lenguaje, creando una representación interna que corresponde a la sentencia analizada. Análisis semántico

Fase de análisis Tres fases Análisis léxico Análisis sintáctico Análisis semántico: Se ocupa de analizar si la sentencia tiene algún significado. Incluye análisis de tipos, o en general, sentencias que carecen se sentido.

Análisis léxico en Haskell Pretendemos reconocer expresiones regulares, que pueden ser reconocidas por un autómata finito determinista (AFD). Implementación de los estados del AFD f :: String -> (String, Token) Implementación de transición de A a B: la función fA llama a fB después de leer un carácter y pasarle el resto a fB.

Análisis léxico en Haskell Ejemplo:

Análisis léxico en Haskell Ejemplos funciones analizadoras simples éxito :: a -> ReadS a éxito x = \s -> [(x, s)] épsilon :: ReadS () épsilon = éxito () fallo :: ReadS a fallo = \s -> [] Alternativa: infixl 5 -+- (-+-) :: ReadS a -> ReadS a -> ReadS a p1 -+- p2 = \s -> p1 s ++ p2 s Lectura condicional del primer carácter rSat :: (Char -> Bool) -> ReadS Char rSat p = \s -> case s of [] -> [] x:xs -> if p x then [(x,xs)] else [] MAIN> rSat isUpper “ABC” [(‘A’, “BC”)]

Análisis léxico en Haskell Ejemplos combinación de analizadores para conseguir uno más complejo (parser combinator) infixl 7 &>< (&><) :: ReadS a -> ReadS b -> ReadS (a,b) p1 &>< p2 = \s -> [ ((x1,x2),s2) | (x1,s1) <- p1 s, (x2,s2) <- p2 s1 ] MAIN> (rChar ‘a’ &>< rChar ‘b’) “abcd” [((‘a’, ‘b’), “cd”)]

Análisis sintáctico en Haskell En un lenguaje funcional como Haskell, es fácil traducir las reglas gramaticales directamente a especificación funcional. exp -> term rest rest -> + exp | epsilon exp = term <*> rest rest = token AddOp <*> exp <|> epsilon

Análisis sintáctico en Haskell El paradigma funcional nos da una expresividad a la hora de representar reglas gramaticales impensable en el paradigma imperativo. Ejemplo: función many many :: Parser a b -> Parser a [b] exp = term <*> many (token addOp <*> term <@ f4) <@ f5

Análisis sintáctico en Haskell Lo que hemos visto se refiere a análisis de arriba a abajo. Realizar análisis de abajo a arriba es más complejo. Happy es una herramienta que nos facilita la creación de un analizador abajo a arriba.

Análisis semántico. Una vez construido al árbol sintáctico, los demás algoritmos se pueden expresar como recorridos en ese árbol. La programación funcional es muy potente a la hora de realizar recorridos en un árbol, como veremos.

Análisis semántico. Atributos de los nodos del árbol: Se usan para asignar un valor parcial a cada nodo del árbol, para ir calculando, por ejemplo, los valores de una expresión paso a paso. Atributo sintetizado: Para calcularlo, necesitamos calcular antes los atributos de los sucesores. Ejemplo: Inferencia de Tipos Se corresponde a un recorrido de abajo a arriba. Funciones de orden superior como foldTree son muy útiles, y nos dan una sencillez y expresividad grandes.

Análisis semántico.

Análisis semántico. Atributos heredados. Su valor ya está calculado, arriba o al mismo nivel en el árbol. Se corresponden a un recorrido de arriba a abajo. Se puede representar mediante una función recursiva (posiblemente de cola), acumulando los atributos. Veamos en el árbol anterior cuáles serían atributos heredados.

Análisis semántico

Analizadores monádicos Wadler, en 1995, introdujo el uso de las mónadas para implementar analizadores. Usando el parser combinator &>< que hemos visto, tenemos tuplas anidadas, engorrosas de manipular. La función monádica bind (>>=) junto con el uso de lambda-abstracciones nos permite una notación más manejable. Además, podemos usar otros combinadores monádicos.

Analizadores monádicos Ejemplo: secuencia Como se ha visto en clase, algo bueno de las mónadas es que permiten simular secuenciación al estilo imperativo: aplica :: Analiz a -> String ->[(a, Estado)] aplica (AN p) ent = p ent dosElementos::Analiz String dosElementos=do a <- elemento b <- elemento return[a,b] MAIN> aplica dosElementos “abcdca” [(“ab”, “cdca”)] :: [(String, String)]

Analizadores monádicos Mediante MonadPlus, podemos implementar el concepto de alternancia. Mplus toma dos analizadores, y concatena el resultado de ambos sobre la cadena entrada; mzero falla siempre. Instance MonadPlus analiz where mplus (AN p)(AN q) = AN(\ent -> p ent ++ q ent) mzero = AN (\ent -> [])

Analizadores monádicos Tomando (!+) como sinónimo de mplus, podemos construir lo siguiente: elemento !+ dosElementos, que captura un solo carácter, o dos. Otro ejemplo: filtros unoODosElementos = elemento !+ dosElementos > aplica unoODosElementos "abcdca" [("a","bcdca"),("ab","cdca")] (!>) ::Analiz a -> (a -> Bool) -> Analiz a k !> p = do a <- k if p a then return a else mzero

Analizadores monádicos Reconocimiento de una letra, o bien de un número: letra::AnalizChar letra=elemento !> isAlpha digito::AnalizChar digito=elemento !> isDigit letraODigito = letra !+ digito.

Analizadores monádicos Ejemplo: reconocimiento de expresiones: term ::= constante | ( term + term ) | ( term / term )

Analizadores monádicos Ejemplo: reconocimiento de expresiones: anaConst::AnalizTerm anaConst=do a <- número return(Const a) anaSum::AnalizTerm anaSum=do _ <- literal ’(’ u <- term _ <- literal ’+’ v <- term _ <- literal ’)’ return(u:+:v) anaDiv::AnalizTerm anaDiv=do _ <- literal ’/’ return(u:/:v) term::AnalizTerm term=anaConst !+ anaSum !+ anaDiv

Software específico Alex Happy Frown Parsec

Alex Analizador léxico (Lex). Características Basado en expresiones regulares Y en autómatas finitos deterministas (DFAs) Definir Macros Reglas Contextos Expresiones start Facilita envoltorios (wrappers)

Alex. Wrappers “basic” “posn” “monad” “gscan” El más simple: dada una cadena, devuelve una lista de Tokens. “posn” Da más funcionalidades (número de línea/columna) “monad” El más flexible Es una plantilla para construir nuestras propias mónadas “gscan” Presente por razones históricas

Alex. Ejemplo module Main (main) where %wrapper "basic" $digit = 0-9 $alpha = [a-zA-Z] tokens :- $white+ ; "--".* ; let \s -> Let in \s -> In $digit+ \s -> Int (read s) [\=\+\-\*\/\(\)] \s -> Sym (head s) $alpha [$alpha $digit \- \']* \s -> Var s -- Each action has type :: String -> Token -- The token type: data Token = Let | In | Sym Char | Var String | Int Int deriving (Eq,Show) main = do s <- getContents print (alexScanTokens s)

Alex. Fichero resultante -- The token type: data Token = Let | In | Sym Char | Var String | Int Int deriving (Eq,Show) main = do s <- getContents print (alexScanTokens s) alex_action_2 = \s -> Let alex_action_3 = \s -> In alex_action_4 = \s -> Int (read s) alex_action_5 = \s -> Sym (head s) alex_action_6 = \s -> Var s type AlexInput = (Char,String) alexGetChar (_, []) = Nothing alexGetChar (_, c:cs) = Just (c, (c,cs)) alexInputPrevChar (c,_) = c -- alexScanTokens :: String -> [token] alexScanTokens str = go ('\n',str) where go inp@(_,str) = case alexScan inp 0 of AlexEOF -> [] AlexError _ -> error "lexical error" AlexSkip inp' len -> go inp' AlexToken inp' len act -> act (take len str) : go inp'

Happy Utiliza análisis LALR(1). Trabaja en conjunción con un analizador léxico. Genera distintos tipos de código: Haskell 98 Haskell estándar con arrays Haskell con extensiones GHC Haskell GHC con arrays codificados como cadenas Flexibilidad Velocidad

Happy Utiliza análisis LALR(1). Trabaja en conjunción con un analizador léxico. Genera distintos tipos de código: Haskell 98 Haskell estándar con arrays Haskell con extensiones GHC Haskell GHC con arrays codificados como cadenas Flexibilidad Velocidad

Happy Utiliza análisis LALR(1). Trabaja en conjunción con un analizador léxico. Genera distintos tipos de código: Haskell 98 Haskell estándar con arrays Haskell con extensiones GHC Haskell GHC con arrays codificados como cadenas Flexibilidad Velocidad

Happy Utiliza análisis LALR(1). Trabaja en conjunción con un analizador léxico. Genera distintos tipos de código: Haskell 98 Haskell estándar con arrays Haskell con extensiones GHC Haskell GHC con arrays codificados como cadenas Flexibilidad Velocidad

Happy Utiliza análisis LALR(1). Trabaja en conjunción con un analizador léxico. Genera distintos tipos de código: Haskell 98 Haskell estándar con arrays Haskell con extensiones GHC Haskell GHC con arrays codificados como cadenas Flexibilidad Velocidad

Happy. Ejemplo { module Main where } %name calc %tokentype { Token } let { TokenLet } in { TokenIn } int { TokenInt $$ } var { TokenVar $$ } '=' { TokenEq } '+' { TokenPlus } '-' { TokenMinus } '(' { TokenOB } ')' { TokenCB } %% Exp : let var '=' Exp in Exp { Let $2 $4 $6 } | Exp1 { Exp1 $1 } Exp1 : Exp1 '+' Term { Plus $1 $3 } | Exp1 '-' Term { Minus $1 $3 } | Term { Term $1 } Term : int { Int $1 } | var { Var $1 } | '(' Exp ')' { Brack $2 } n : t_1 .. t_n { E }

Happy. Ejemplo { happyError :: [Token] -> a happyError _ = error "Parse error" data Exp = Let String Exp Exp | Exp1 Exp1 data Exp1 = Plus Exp1 Term | Minus Exp1 Term | Term Term data Term = Int Int | Var String | Brack Exp data Token = TokenLet | TokenIn | TokenInt Int | TokenVar String | TokenEq | TokenPlus | … deriving Show lexer :: String -> [Token] lexer [] = [] lexer (c:cs) | isSpace c = lexer cs | isAlpha c = lexVar (c:cs) | isDigit c = lexNum (c:cs) lexer ('=':cs) = TokenEq : lexer cs lexer ('+':cs) = TokenPlus : lexer cs lexer ('-':cs) = TokenMinus : lexer cs lexer ('(':cs) = TokenOB : lexer cs lexer (')':cs) = TokenCB : lexer cs lexNum cs = TokenInt (read num) : lexer rest where (num,rest) = span isDigit cs lexVar cs = case span isAlpha cs of ("let",rest) -> TokenLet : lexer rest ("in",rest) -> TokenIn : lexer rest (var,rest) -> TokenVar var : lexer rest main = getContents >>= print . calc . lexer }

Frown Utiliza análisis LALR(k) Eficiencia Funcionales

Frown Utiliza análisis LALR(k) Eficiencia Funcionales

Frown Utiliza análisis LALR(k) Eficiencia Funcionales

Parsec Es una librería de combinadores monádicos. Se trabaja directamente en Haskell. Está incluído en GHC y en Hugs. Es más eficiente con gramáticas LL(1).

Parsec. Un ejemplo El código module Main where import Text.ParserCombinators.Parsec simple :: Parser Char simple = letter ejecuta :: Show a => Parser a -> String -> IO () ejecuta p input = case (parse p "" input) of Left err -> do{ putStr "error al analizar " ; print err } Right x -> print x

Parsec. Un ejemplo Ejecución *Main> ejecuta simple "" Loading package parsec-1.0 ... linking ... done. error al analizar (line 1, column 1): unexpected end of input expecting letter *Main> ejecuta simple "123" unexpected "1" *Main> ejecuta simple "a" 'a'

Parsec. Otro ejemplo Código parens :: Parser () parens = do{ char '(' } <|> return ()

Parsec. Otro ejemplo Ejecución Main> ejecuta parens "((()())())()" Reading file "C:\Documents and Settings\Brise\Mis documentos\PDA\parsec.hs": () Main> ejecuta parens "(()" error al analizar (line 1, column 4): unexpected end of input expecting "(" or ")"

¿Qué podemos concluir? Los LF respetan la estructura en fases: lexing, parsing, análisis semántico, etc.

¿Qué podemos concluir? Los LF respetan la estructura en fases: lexing, parsing, análisis semántico, etc.

¿Qué podemos concluir? Los LF respetan la estructura en fases: lexing, parsing, análisis semántico, etc. lexer :: String -> [Token] parser :: [Token] -> ÁrbolAbstracto semántica :: ÁrbolAbstracto -> ÁrbolEtiquetado generaciónDeCódigo :: ÁrbolEtiquetado -> [Código máquina] compilador = generaciónDeCódigo . semántica . parser . lexer

¿Qué podemos concluir? Evaluación perezosa La salida del analizador léxico sería una lista perezosa de tokens.

¿Qué podemos concluir? Evaluación perezosa La salida del analizador léxico sería una lista perezosa de tokens. lexer :: String -> [Token] parser :: [Token] -> ÁrbolAbstracto semántica :: ÁrbolAbstracto -> ÁrbolEtiquetado generaciónDeCódigo :: ÁrbolEtiquetado -> [Código máquina] compilador = generaciónDeCódigo . semántica . parser . lexer

¿Qué podemos concluir? Tipos de datos Tokens: tipo enumerado data Token = Id String | IntConst Int | SumaOp | ProdOp | PuntoYComa

¿Qué podemos concluir? Tipos de datos Tokens: tipo enumerado data Token = Id String | IntConst Int | SumaOp | ProdOp | PuntoYComa Tabla de símbolos: árboles AVL o Tablas Hash

¿Qué podemos concluir? Tipos de datos Tokens: tipo enumerado data Token = Id String | IntConst Int | SumaOp | ProdOp | PuntoYComa Tabla de símbolos: árboles AVL o Tablas Hash Árboles abstractos: tipos mutuamente recursivos

¿Qué podemos concluir? Tipos de datos Funciones de orden superior Tokens: tipo enumerado data Token = Id String | IntConst Int | SumaOp | ProdOp | PuntoYComa Tabla de símbolos: árboles AVL o Tablas Hash Árboles abstractos: tipos mutuamente recursivos Funciones de orden superior Combinadores de analizadores.

¿Qué podemos concluir? Recursión La recursión y el reconocimiento de patrones que existen en Haskell son un método potente para tratar este tipo de estructuras recursivas.

¿Qué podemos concluir? Recursión Polimorfismo La recursión y el reconocimiento de patrones que existen en Haskell son un método potente para tratar este tipo de estructuras recursivas. Polimorfismo data Instr a = Asignar String a (Expr a) | While (Expr a) (Instr a) | If (Expr a) (Instr a) (Instr a) | ...

Bibliografía http://www.haskell.org Blas C. Ruiz, Francisco Gutiérrez, Paco Guerrero, José E. Gallardo. Razonando con Haskell. Thomson, Madrid 2004. Cap. 14, Analizadores. Ricardo Peña. Compile Construction in a Functional Setting. Universidad Complutense de Madrid. Jeroen Fokker. Functional Parsers. Universidad de Utrecht. Graham Hutton y Erik Meijer. Monadic Parser Combinators. Universidades de Nottingham y Utrecht. Referencias web http://www.haskell.org http://www.informatik.uni-bonn.de/~ralf/frown http://www.cs.uu.nl/~daan/parsec.html