Upload
others
View
4
Download
0
Embed Size (px)
Citation preview
Compiladores (CC3001)Aula 9: Análise de tipos
Pedro Vasconcelos
DCC/FCUP
2020
Esta aula
Sistemas de Tipos
Análise de TiposExpressõesDeclarações e comandosFunções
Extras
Re-lembrar: fases dum compilador
texto do programa↓
Análise lexical↓
sequência de tokens
↓Análise sintática
↓árvore sintática abstrata
↓Análise semântica
↓AST & tabela de símbolos
Geração de código↓
código intermédio
Seleção de instruções↓
código assembly simbólico↓
Alocação de registos↓
código assembly concreto↓
Assembler & linker↓
código executável
Sistemas de Tipos
Um sistema de tipos é um conjunto de regras lógicas da linguagem que todos osprogramas devem respeitar.
Exemplos de regras:
I +, -, *, / só operam sobre números
I a condição de if deve ser um boleano
I numa atribuição var = expr a variável deve ser declarada com um tipocompatível com o da a expressão
Diferentes Sistemas de Tipos
Podemos classi�car em dois eixos separados:
Estáticos veri�cação ocorre antes da execução do programa
vs.
Dinâmicos veri�cação ocorre durante a execução do programa
Fortes operações com erros de tipos são proibidas
vs.
Fracos operações com erros de tipos são permitidas
Diferentes Sistemas de Tipos (cont.)
Exemplos:
I Haskell, SML, Java: tipos estáticos e fortes
I Python, Scheme: tipos dinâmicos e fortes
I C: tipos estáticos e fracos
I Estas classi�cações são graduais e não absolutase.g. o sistema de tipos de Java é mais forte que o de C mas mais fraco que o de Haskell
I A distinção estático/dinâmico deixa de fazer sentido para código máquina
Diferentes Sistemas de Tipos (cont.)
(Imagem: Introduction to Compiler Design, Torben Mogensen.)
Porquê análise de tipos?
I Efetuar a análise de tipos durante a compilação (estática):I evita veri�cações durante a execução do programaI permite gerar código mais e�ciente
I Em qualquer caso: ajudam o programador a detetar erros lógicos nos programas
Atributos
I A análise de tipos pode ser feita como uma (ou mais) travesia da AST
I Como as ASTs são estruturas recursivas, a travesia será implementada comofunções recursivas
I As traversias constroem atributos; exemplos:I o tipo de cada sub-expressão;I a tabela que associa cada identi�cador ao seu tipo (ambiente)
I Atributos sintetizados: calculados das folhas da AST para a raiz
I Atributos herdados: passados da raiz da AST para as folhas
I Atributos sintetizados numa parte da AST podem ser herdados noutra(e.g. a tabela de símbolos é sintetizada nas declarações e herdada nas expressões)
Expressões
I Uma linguagem com variáveis, expressões aritméticas e lógicas (comparações)
I Dois tipos: int (números inteiros) e bool (valores lógicos)
Exemplos de expressões:
1+2*3
(1+2)<3
1+x*3 (válida se x for int)(1+x)<y (válida se x for int e y for int)
Um erro de tipos (não podemos somar inteiros com valores lógicos):
1+(2<3)
Atributos sintetizados e herdados
Determinamos o tipo da expressão a partir dos tipos das sub-expressões (atributosintetizado).
<
+
1 2
3
boolxatributo sintetizado
Passamos um tabela de símbolos com os tipos da variáveis (atributo herdado).
<
+
1 x
y
atributo herdado{x 7→ int, y 7→ int}y
Implementação
data Expr = Num Int �- 1, 2, 3
| Var Ident �- x, y, z
| Add Expr Expr �- e1 + e2
| LessThan Expr Expr �- e1 < e2
data Type = TyInt | TyBool
type TypeEnv = Map Ident Type �- ambiente (tabela de símbolos)
checkExpr :: TypeEnv -> Expr -> Type
�- sintetizar o tipo de uma expressão
�- argumentos: o ambiente (TypeEnv) e a expressão (Expr)
�- resultado: é um tipo (ou erro)
�- a definição é por análise de casos
Atributos sintetizados vs. herdados
O tipo da expressão é propagado das folhas para a raiz (atributo sintetizado).
checkExp env (Num n) = TyInt...
checkExpr env (Add e1 e2)
= let t1 = checkExpr env e1
t2 = checkExpr env e2
in if t1==TyInt && t2==TyInt then TyInt
else error "type error in +"
Atributos sintetizados vs. herdados (cont.)
O ambiente (tabela de símbolos) é propagado da raiz para as folhas (atributo herdado).
checkExpr env (Add e1 e2)
= let t1 = checkExpr env e1
t2 = checkExpr env e2
in if t1==TyInt && t2==TyInt then TyInt
else error "type error in +"
Atributos sintetizados vs. herdados (cont.)
O ambiente será determinada na chamada de topo (raiz da AST):
> checkExpr (Map.fromList [("x",TyInt)]) (Add (Var "x") (Num 1))
TyInt
Esta expressão teria um erro de tipo num outro ambiente:
> checkExp (Map.fromList [("x", TyBool)] (Add (Var "x") (Num 1)))
type error in +
(Ver demonstração.)
De�nição completa
checkExpr env (Num n) = TyInt
checkExpr env (Var x) = case Map.lookup x env of
Nothing -> error "undeclared var"
Just t -> t
checkExpr env (Add e1 e2)
= let t1 = checkExpr env e1
t2 = checkExpr env e2
in if t1==TyInt && t2==TyInt then TyInt
else error "type error in +"
checkExpr env (LessThan e1 e2)
= let t1 = checkExpr env e1
t2 = checkExpr env e2
in if t1==TyInt && t2==TyInt then TyBool
else error "type error in <"
Declarações e comandos
I Vamos acrescentar de�nições para declações de variáveis e comandos
I Acrescentamos novos de�nições na sintaxe abstrata e funções para fazer a análisede tipos
Declarações e comandos (cont.)
data Stm = Assign Ident Expr �- var := expr
| If Expr Stm Stm �- if expr then stm1 else stm2
| While Expr Stm �- ciclo while
| Block [Decl] [Stm] �- bloco: declarações e comandos
| Skip �- comando vazio
deriving Show
type Decl = (Ident,Type) �- (ident,tipo)
Exemplos
if (x<y) x=y; else x=0;
If (LessThan (Var "x") (Var "y"))
(Assign "x" (Var "y"))
(Assign "x" (Num 0))
Exemplos (cont.)
n = 1;
while(n < 10)
n = n+1;
Block []
[ Assign "n" (Num 1)
, While (LessThan (Var "n") (Num 10))
(Assign "n" (Add (Var "n") (Num 1)))
]
Exemplos (cont.)
if (x<y) {
int temp;
temp = x;
x = y;
y = temp;
}
If (LessThan (Var "x") (Var "y"))
(Block [("temp",TyInt)]
[ Assign "temp" (Var "x")
, Assign "x" (Var "y")
, Assign "y" (Var "temp")
])
Skip
Regras de tipos para comandos
Atribuição (var := expr) a variável e a expressão devem ter o mesmo tipo
Condição (if exp then stm1 else stm2)
1. a expressão deve ter tipo bool
2. os dois comandos devem estar corretamente tipados
Ciclo (while exp stm)
1. a expressão deve ter tipo bool
2. o corpo deve estar corretamente tipado
Bloco extendemos o ambiente com declarações e veri�camos se todos oscomandos estão bem tipados
Skip está sempre bem tipado
Análise de tipos para comandos
checkStm :: TypeEnv -> Stm -> Bool
I Tal como para expressões vamos passar um ambiente como atributo herdado
I O resultado de analizar dum comando será um boleanoI True se o comando respeita as regras de tiposI ou lança um erro1 com uma mensagem apropriada
I Nos blocos: necessitamos de extender o ambiente
(Ver demonstração.)
1Em vez de False.
Analisar if/else
checkStm env (If cond stm1 stm2)
= let t0 = checkExpr env cond
in if t0 == TyBool then checkStm env stm1 && checkStm env stm2
else error "type error: condition must be bool"
Analisar blocos
checkStm env (Block decls stms)
= let env' = extendEnv env decls
in checkAll env' stms
-- funções auxiliares: extender um ambiente
extendEnv :: TypeEnv -> [Decl] -> TypeEnv
extendEnv env [] = env
extendEnv env ((v,t):rest) = extendEnv (Map.insert v t env) rest
-- verificar tipos de uma lista de comandos
checkAll env [] = True
checkAll env (first:rest) = checkStm env first && checkAll env rest
Análise de tipos para funções
I Vamos extender a linguagem com declarações e chamada de funções
I Simpli�cação: de�nições com uma só expressão (e não comandos)
I Um programa pode ter:I declarações de uma ou mais funções;I seguindo de um comando (pode ser um bloco).
Análise de tipos para funções (cont.)
data Expr = ...
| FunCall Ident [Expr]
�- chamada f(e1, e2, ...)
data FunDef = FunDef Ident Type [(Ident,Type)] Expr
�- t f(x1:t1,..., xn:tn) = expr
data Prog = Prog [FunDef] Stm
�- programa completo: declarações de funções e um comando (�main�)
Exemplo
int incr(int x) { return x+1; }
int main() {
int x, y;
x = 2;
y = incr(x+1);
}
Prog
[FunDef "incr" TyInt [("x",TyInt)] (Add (Var "x") (Num 1))]
[Block [("x", TyInt), ("y",TyInt)]
[ Assign "x" (Num 2)
, Assign "y" (FunCall "incr" [Add (Var "x") (Num 1)])
]
]
Tipos de funções
(t1, t2, . . . , tn) → r
I t1, t2, . . . , tn são os tipos dos argumentos
I r é o tipo do resultado
data Type = ...
| TyFun [Type] Type
Veri�cação de chamadas
Para veri�car uma chamada f (e1, . . . , en)
1. Procurar f no ambiente e obter um tipo (t1, . . . , tn) → r
2. Recursivamente veri�car tipos de e1, . . . , en
3. Se os tipos são iguais a t1, . . . , tn então retornar o tipo do resultado (r); casocontrário erro
Veri�cação de chamadas (cont.)
Uma nova equação na função checkExpr:
checkExpr env (FunCall f args)
= case Map.lookup f env of
Just (TyFun argTypes result) ->
if map (checkExpr env) args == argTypes then result
else error "invalid argument types"
_ -> error "invalid function name"
Veri�cação de de�nições
Para veri�car f (x1 : t1, . . . , xn : tn) : r = e
1. Extendemos o ambiente com {x1 7→ t1, . . . , xn 7→ tn}2. No ambiente extendido: veri�camos o tipo de e
3. Se o tipo resultante for igual a r retornamos um novo ambiente com
{f 7→ (t1, . . . , tn) → r}
Caso contrário: erro.
(Neste caso: o ambiente é simultaneamente herdado e sintetizado.)
Veri�cação de de�nições (cont.)
checkFunDef :: TypeEnv -> FunDef -> TypeEnv
checkFunDef env (FunDef f decls result expr)
= let env' = extendEnv decls env
texpr = checkExpr env' expr
in if texpr == result then
let args = map snd decls
in extendEnv [("f",TyFun args result)] env
else error "wrong result type"
-- função auxiliar (já era usada para blocos)
extendEnv :: TypeEnv -> [Decl] -> TypeEnv
extendEnv env [] = env
extendEnv env ((v,t):rest) = extendEnv (Map.insert v t env) rest
Recursão
I A veri�cação de funções não permite usos recursivos da função, e.g.
int f(int x) = if x > 0 then x*f(x-1) else 1
I A extensão para recursão é simples: basta acresentar uma entrada para f aoambiente usado para tipar o corpo da função2
I Mas só seria útil combinada com a extensão para comandos ou expressões if/else(caso contrário não há forma de terminar a recursão. . . )
2Mais informação em Fundamentos de Linguagens.
Overloading e coerção
Overloading utilizar o mesmo operador para operações em tipos diferentes (e.g. + eminteiros e fracionários)
1 + 2 -- int
1.0 + 2.5 -- float
Coercão conversão implícita ou explícita de tipos
int x = ...;
(float)x + 2.5 -- conversão explícita
x + 2.5 -- conversão implícita
Overloading e coerção (cont.)
I A veri�cação para tipos e operadores pré-de�nidos é simples (e.g. C, Pascal)
I Consideravelmente mais complexa se o programador pode de�nir operadores sobretipos novos (e.g. C++, Haskell,. . . )
Polimor�smo
polimór�co (adjetivo): que se apresenta sob diversas formas.Dicionário Online Priberam
Em programação: a possibilidade de escrever código que opera sobre diferentes tipos.
polimor�smo paramétrico SML/Haskell ou �generics� em Java/C#
reverse :: [t] -> [t] -- em Haskell
void reverse(List<T> list); // em Java
polimor�smo ad-hoc overloading, interfaces, herança. . .
(Tópicos abordados em Fundamentos de Linguagens e Tópicos de Programação Funcional
Avançada.)
Inferência
I Para alguns sistemas é possível inferir os tipos automaticamente em vez de apenasveri�car declarações
I As linguagens funcionais fortemente tipadas usam inferência baseada no algoritmoDamas-Milner (com extensões)
(Tópicos abordados em Fundamentos de Linguagens)