Title: Construcci
1Construcción de compiladores con Haskell
- José María Carmona Cejudo
- Briseida Sarasola Gutiérrez
2Índice
- Motivación
- Qué es un compilador?
- 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
3Qué 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).
4Un 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.
5Un 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.
6Esquema 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.
7Esquema 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.
8Esquema 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.
9Fase de análisis
- Tres fases
- Análisis léxico
- Análisis sintáctico
- Análisis semántico
10Fase de análisis
- Tres fases
- Análisis léxico
- 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
11Fase 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
12Fase 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.
13Aná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 -gt (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.
14Análisis léxico en Haskell
15Análisis léxico en Haskell
- Ejemplos
- funciones analizadoras simples
- éxito a -gt ReadS a
- éxito x \s -gt (x, s)
- épsilon ReadS ()
- épsilon éxito ()
- fallo ReadS a
- fallo \s -gt
- Alternativa
- infixl 5 --
- (--) ReadS a -gt ReadS a -gt ReadS a
- p1 -- p2 \s -gt p1 s p2 s
- Lectura condicional del primer carácter
- rSat (Char -gt Bool) -gt ReadS Char
- rSat p \s -gt case s of
- -gt
- xxs -gt if p x then (x,xs)
else - MAINgt rSat isUpper ABC
- (A, BC)
16Análisis léxico en Haskell
- Ejemplos
- combinación de analizadores para conseguir uno
más complejo (parser combinator) - infixl 7 gtlt
- (gtlt) ReadS a -gt ReadS b -gt ReadS (a,b)
- p1 gtlt p2 \s -gt ((x1,x2),s2) (x1,s1) lt- p1
s, - (x2,s2) lt- p2
s1 - MAINgt (rChar a gtlt rChar b) abcd
- ((a, b), cd)
17Análisis sintáctico en Haskell
- En un lenguaje funcional como Haskell, es fácil
traducir las reglas gramaticales directamente a
especificación funcional.
exp -gt term rest rest -gt exp epsilon exp term ltgt rest rest token AddOp ltgt exp ltgt epsilon
18Aná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 -gt Parser a b
- exp term ltgt many (token addOp ltgt term lt_at_ f4)
lt_at_ f5
19Aná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.
20Aná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.
21Aná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.
22Análisis semántico.
23Aná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.
24Análisis semántico
25Analizadores monádicos
- Wadler, en 1995, introdujo el uso de las mónadas
para implementar analizadores. - Usando el parser combinator gtlt que hemos visto,
tenemos tuplas anidadas, engorrosas de manipular.
- La función monádica bind (gtgt) junto con el uso
de lambda-abstracciones nos permite una notación
más manejable. - Además, podemos usar otros combinadores monádicos.
26Analizadores 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 -gt String -gt(a, Estado) aplica (AN p) ent p ent dosElementosAnaliz String dosElementosdo a lt- elemento b lt- elemento returna,b MAINgt aplica dosElementos abcdca (ab, cdca) (String, String)
27Analizadores 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 -gt p ent q ent) mzero
AN (\ent -gt )
28Analizadores 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 gt aplica unoODosElementos "abcdca" ("a","bcdca"),("ab","cdca")
(!gt) Analiz a -gt (a -gt Bool) -gt Analiz a k !gt p
do a lt- k if p a then return a else mzero
29Analizadores monádicos
- Reconocimiento de una letra, o bien de un número
letraAnalizChar letraelemento !gt
isAlpha digitoAnalizChar digitoelemento !gt
isDigit letraODigito letra ! digito.
30Analizadores monádicos
- Ejemplo reconocimiento de expresiones
term constante ( term term ) ( term / term )
31Analizadores monádicos
- Ejemplo reconocimiento de expresiones
anaConstAnalizTerm anaConstdo a lt- número return(Const a) anaSumAnalizTerm anaSumdo _ lt- literal ( u lt- term _ lt- literal v lt- term _ lt- literal ) return(uv) anaDivAnalizTerm anaDivdo _ lt- literal ( u lt- term _ lt- literal / v lt- term _ lt- literal ) return(u/v) termAnalizTerm termanaConst ! anaSum ! anaDiv
32Software específico
33Alex
- 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)
34Alex. Wrappers
- basic
- 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
35Alex. Ejemplo
- module Main (main) where
- wrapper "basic"
- digit 0-9
- alpha a-zA-Z
- tokens -
- white
- "--".
- let \s -gt Let
- in \s -gt In
- digit \s -gt Int (read s)
- \\\-\\/\(\)
- \s -gt Sym (head s)
-
- alpha alpha digit \- \' \s -gt
Var s
- -- Each action has type String -gt Token
-
- -- The token type
- data Token
- Let
- In
- Sym Char
- Var String
- Int Int
- deriving (Eq,Show)
- main do
- s lt- getContents
- print (alexScanTokens s)
36Alex. Fichero resultante
- -- The token type
- data Token
- Let
- In
- Sym Char
- Var String
- Int Int
- deriving (Eq,Show)
- main do
- s lt- getContents
- print (alexScanTokens s)
- alex_action_2 \s -gt Let
- alex_action_3 \s -gt In
- alex_action_4 \s -gt Int (read s)
- alex_action_5 \s -gt Sym (head s)
- alex_action_6 \s -gt Var s
- type AlexInput (Char,String)
- alexGetChar (_, ) Nothing
- alexGetChar (_, ccs) Just (c, (c,cs))
- alexInputPrevChar (c,_) c
- -- alexScanTokens String -gt token
- alexScanTokens str go ('\n',str)
- where go inp_at_(_,str)
- case alexScan inp 0 of
- AlexEOF -gt
- AlexError _ -gt error "lexical error"
- AlexSkip inp' len -gt go inp'
- AlexToken inp' len act -gt act (take len str)
go inp'
37Happy
- 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
38Happy
- 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
39Happy
- 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
40Happy
- 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
41Happy
- 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
42Happy. Ejemplo
- module Main where
- name calc
- tokentype Token
- 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
43Happy. Ejemplo
-
- happyError Token -gt 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
- lexer String -gt Token
- lexer
- lexer (ccs)
- isSpace c lexer cs
- isAlpha c lexVar (ccs)
- isDigit c lexNum (ccs)
- 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) -gt TokenLet lexer rest
- ("in",rest) -gt TokenIn lexer rest
44Frown
- Utiliza análisis LALR(k)
- Eficiencia
- Funcionales
45Frown
- Utiliza análisis LALR(k)
- Eficiencia
- Funcionales
46Frown
- Utiliza análisis LALR(k)
- Eficiencia
- Funcionales
47Parsec
- 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).
48Parsec. Un ejemplo
- El código
- module Main where
- import Text.ParserCombinators.Parsec
- simple Parser Char
- simple letter
- ejecuta Show a gt Parser a -gt String -gt IO ()
- ejecuta p input
- case (parse p "" input) of
- Left err -gt do putStr "error al
analizar " - print err
-
- Right x -gt print x
49Parsec. Un ejemplo
- Ejecución
- Maingt ejecuta simple ""
- Loading package parsec-1.0 ... linking ... done.
- error al analizar (line 1, column 1)
- unexpected end of input
- expecting letter
- Maingt ejecuta simple "123"
- error al analizar (line 1, column 1)
- unexpected "1"
- expecting letter
- Maingt ejecuta simple "a"
- 'a'
50Parsec. Otro ejemplo
- Código
- parens Parser ()
- parens do char '('
- parens
- char ')'
- parens
-
- ltgt return ()
51Parsec. Otro ejemplo
- Ejecución
- Maingt ejecuta parens "((()())())()"
- Reading file "C\Documents and Settings\Brise\Mis
documentos\PDA\parsec.hs" - ()
- Maingt ejecuta parens "(()"
- error al analizar (line 1, column 4)
- unexpected end of input
- expecting "(" or ")"
52Qué podemos concluir?
- Los LF respetan la estructura en fases lexing,
parsing, análisis semántico, etc.
53Qué podemos concluir?
- Los LF respetan la estructura en fases lexing,
parsing, análisis semántico, etc.
54Qué podemos concluir?
- Los LF respetan la estructura en fases lexing,
parsing, análisis semántico, etc.
lexer String -gt Token parser
Token -gt ÁrbolAbstracto semántica
ÁrbolAbstracto -gt ÁrbolEtiquetado genera
ciónDeCódigo ÁrbolEtiquetado -gt Código
máquina compilador generaciónDeCódigo .
semántica . parser . lexer
55Qué podemos concluir?
- Evaluación perezosa
- La salida del analizador léxico sería una lista
perezosa de tokens.
56Qué podemos concluir?
- Evaluación perezosa
- La salida del analizador léxico sería una lista
perezosa de tokens.
lexer String -gt Token parser
Token -gt ÁrbolAbstracto semántica
ÁrbolAbstracto -gt ÁrbolEtiquetado genera
ciónDeCódigo ÁrbolEtiquetado -gt Código
máquina compilador generaciónDeCódigo .
semántica . parser . lexer
57Qué podemos concluir?
- Tipos de datos
- Tokens tipo enumerado
- data Token Id String IntConst Int SumaOp
ProdOp PuntoYComa
58Qué 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
59Qué 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
60Qué 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
- Funciones de orden superior
- Combinadores de analizadores.
61Qué 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.
62Qué 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. - Polimorfismo
data Instr a Asignar String a (Expr a)
While (Expr a) (Instr a) If (Expr
a) (Instr a) (Instr a) ...
63Bibliografía
- 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