Semântica de Linguagens de Programação

Semântica de Linguagens de Programação

Fabio Mascarenhas

Apresentação

Está é a página da disciplina "Semântica de Linguagens de Programação" (turma 2853 de "Tópicos em Programação"), do professor Fabio Mascarenhas, para o semestre de 2011.2. As aulas da disciplina são às sextas, das 09:00 (pontualmente!) às 12:00, no LAB II do DCC.

Esse curso pretende mostrar os conceitos de linguagens de programação através da implementação de interpretadores para pequenas linguagens que ilustram cada conceito, e como mudanças simples na implementação dos interpretadores têm implicações profundas no comportamento dos programas. Mesmo a semântica de linguagens tão simples como expressões aritméticas tem vários graus de liberdade. Ao final do curso o aluno terá aprendido como várias escolhas do projeto de uma linguagem influenciam o comportamento dos programas nessa linguagem, e terá sido exposto ao mecanismo por trás dos principais paradigmas de linguagens de programação.

Por ser focado em implementação de interpretadores esse curso envolverá bastante programação, e espera-se que o aluno tenha alguma desenvoltura em programação. A linguagem usada em quase todo curso será Racket, um dialeto de Scheme (mais especificamente a IDE DrRacket), mas não é necessário conhecimento prévio nesse linguagem. A escolha de Scheme é pela sua facilidade na escrita de interpretadores, mas os conceitos aprendidos na aula poderão ser aplicados em qualquer linguagem.

Avaliação

Esse é um curso eletivo, então eu assumo que todo aluno matriculado tem interesse na disciplina, e portanto a presença é obrigatória. A maior parte das aulas possui um encadeamento lógico com as aulas antes e depois dela, e faltas são muito prejudiciais ao entendimento do assunto.

Além da presença, também passarei exercícios individuais de uma semana para outra, e esses exercícios podem ser revisados em sala. Eles serão avaliados por esforço e corretude.

Livros

O livro texto da disciplina é o "Programming Languages: Application and Interpretation" (PLAI), de Shriram Krishnamurthi. O autor o disponibilizou online em formato PDF nessa página.

Aulas

12/08/2011

Introdução, Aritmética, with, funções de primeira ordem (PLAI caps. 1 a 4). Veja os slides.

Interpretadores (pequenas variantes em comentários no próprio código):

Como dever de casa, estude a referência rápida de Racket e então adicione ao interpretador F1WAE a expressão if0, com sintaxe {if0 <F1WAE> <F1WAE> <F1WAE>}. Você deve: estender o tipo F1WAE com o novo tipo de nó; estender a função parse para transformar a sintaxe acima em um nó do novo tipo; estender a função subst para efetuar a substituição nas subexpressões dessa expressão; estender interp para interpretar a expressão. Para interpretar if0 o interpretador deve avaliar a primeira subexpressão, e se o resultado dela for 0 avaliar a segunda expressão; se for diferente de 0 ele deve avaliar a terceira expressão.

Casos de teste para if0:

  (test (interp (parse '{if0 0 1 2})
                (list))
        1)
  (test (interp (parse '{if0 1 1 2})
                (list))
        2)
  (test (interp (parse '{if0 {f 5} 1 2})
                (list (fundef 'f 'x (parse '{- x 5}))))
        1)

Prepare o dever de casa como um arquivo f1wae-if0.rkt contendo o interpretador modificado, e traga na próxima aula (19/08/2011). Qualquer dúvida me mande um email, que responderei prontamente!

19/08/2011

Ambientes e F1WAE usando ambientes (PLAI cap. 5), identificadores como posições no ambiente (índices de De Brujin) (PLAI seção 3.5), funções de primeira classe via substituição (PLAI cap. 6 até 6.3). Veja os slides.

Interpretadores:

Como dever de casa, remova a cláusula with do interpretador FWAE, tanto da AST, quanto das funções de substituição/interpretação. O parser ainda deve tratar expressões with gerando uma aplicação de função que tenha o mesmo efeito:

  {with {<name> <exp>} <body>} =>
    {{fun {<name>} <body>} <exp>}

Prepare o dever de casa como um arquivo fae.rkt contendo o interpretador modificado, e traga na próxima aula (26/08/2011).

26/08/2011

Funções de primeira classe via ambientes (PLAI cap. 6 de 6.4 em diante), recursão (capítulos 9 e 10, seção 22.4 do capítulo 22). Veja os slides.

Interpretadores:

02/09/2011

Recursão: definindo fix como uma função de CFAE. Currying como açúcar sintático para funções de múltiplos parâmetros em CFAE. Pares e listas em CFAE e programação funcional usando maps e folds. Introdução a IMP, uma linguagem imperativa simples. Veja os slides.

Interpretadores:

Como dever de casa faça as seguintes tarefas:

09/09/2011

Continuando com IMP, uma linguagem imperativa simples. Interpretador funcional para IMP. Interpretador para IMP usando células (boxes). IMP com escopo local via with: WIMP. Interpretador para WIMP usando células. Interpretador funcional para WIMP: ambientes vs. memória. Locais como valores de primeira classe: PWIMP.

Interpretadores:

16/09/2011

Locais como valores de primeira classe: PWIMP. Aritmética de endereços e arrays. Funções de primeira ordem imperativas: F1PWIMP.

Interpretadores:

23/09/2011

Computações imperativas como valores e composição de computações: unit e bind. Reestruturação de F1PWIMP para usar composição de computações. Uso de um vetor imperativo como memória de F1PWIMP. Açúcar sintático para bind: mdo. Mudando ret para sair da função atual.

Interpretadores:

Como exercício de casa modifique F1PWIMP para poder ter funções com múltiplos parâmetros. Isso compreende quatro partes: modificar os nós app e capp da árvore sintática para operar em listas de argumentos ao invés de só um argumento (tipo PIAE? para list?); modificar o parser de expressões e comandos; modificar o tipo FunDef para ter uma lista de parâmetros; modificar a interpretação das aplicações nos interpretadores para avaliar os argumentos e associar seus valores aos nomes.

A quarta tarefa é a mais difícil, uma dica para ela é definir uma função auxiliar allocn que aloca um espaço contíguo na pilha, retornando o endereço do início do espaço alocado:

(define (allocn n)
  (mdo (free <- (mget 0))
       (mset 0 (+ free n))
       (unit free)))

Uma outra função auxiliar gera um ambiente a partir de uma lista de variáveis e um endereço inicial:

(define (make-env init-env vars base)
  (if (null? vars)
      init-env
      (env-entry (first vars)
                 base
                 (make-env init-env (rest vars) (+ base 1)))))

Também pode-se fazer um foldr para produzir a computação que avalia e empilha os argumentos e depois chama a função:

(foldr (lambda (arg loc/comp)
         (list (- 1 (first loc/comp))
               (mdo (varg <- (interp-exp arg funs env))
                    (mset (first loc/comp) varg)
                    (second loc/comp))))
       (list (+ base (- (length args) 1))  ; endereço do último argumento
             (interp-cmd body (make-env (env-empty) vars base)))
       args)

O segundo elemento do resultado desse fold é a computação que avalia e empilha os argumentos, depois executa a função. Agora é juntar as peças! Vocês terão duas semanas para pensar bem no assunto.

14/10/2011

Funções de primeira classe em uma linguagem imperativa. Interação de closures com a pilha. Unificando as linguagens funcional e imperativa: FIMP. Usando computações com escape não-local para representar return e tratamento de exceções.

Interpretadores:

A aritmética de endereços permitida por deref é poderosa mas bastante perigosa na prática (vide os problemas de segurança da linguagem C). Modifique o interpretador FIMP com exceções para restringir deref para funcionar apenas em valores do tipo endereço e não qualquer número. A primitiva ref também deverá ser modificada para retornar valores desse tipo. Naturalmente vocês precisarão de um novo caso na definição de Value. Teste seu interpretador com exemplos que não usam aritmética de endereços, que devem continuar funcionando, e com exemplos que usam, que devem causar erros.

21/10/2011

Avaliação preguiçosa e sua interação com características imperativas.

Interpretadores:

Adicione funções lazy a FIMP com uma nova forma sintática lfun. Você vai precisar criar um novo valor closl para uma closure de uma função lazy para poder implementar o comportamento correto em app, além do valor expV do interpretador FIMP/L. Reutilize force e delay de FIMP/L. Teste com esse programa que deve mostrar a sequência de Fibonacci:

(eval '{do {set pair {lfun {x y}
                          {fun {z}
                               {if z y x}}}}
         {set zipWith {fun {op l1 l2}
                           {pair {op {l1 0} {l2 0}}
                                 {zipWith op {l1 1} {l2 1}}}}}
         {set fibs {pair 1 
                         {pair 1 
                               {zipWith {fun {x y} {+ x y}}
                                        fibs {fibs 1}}}}}
         {set l fibs}
         {set x 10}
         {while x
                {print {l 0}}
                {set x {- x 1}}
                {set l {l 1}}}} '(pair zipWith fibs l x))

28/10/2011 e 04/11/2011

Orientação a objetos simples: objetos, classes, mensagens. Um interpretador para uma linguagem OO dinâmica simples. Paralelos entre objetos e funções de primeira classe. Contrastando objetos e tipos abstratos de dados.

Interpretadores:

25/11/2011

Noções básicas de verificação de tipos para linguagens funcionais. Julgamentos de tipos e árvores de verificação. Implementando um verificador de tipos para CFAE. Limitações da verificação de tipos: tipando fix e tipos recursivos.

Interpretadores:

A tipagem dos pares de TCFAE não permite expressar listas usando pares pelo mesmo motivo que não podemos expressar coisas {{fun {x} {x x}} {fun {x} {x x}} e a implementação de fix na própria CFAE mostrada aqui. O exercício final é alterar a verificação de tipos dos pares para transformá-los em listas.

Substitua o tipo (pairT t1 t2) que denota um par de um elemento de tipo t1 e um elemento de tipo t2 pelo tipo (listT t) que irá denotar uma lista de elementos de tipo t. A tipagem do termo {pair e1 e2} agora deverá verificar que o tipo de e1 é t e o tipo de e2 é (listT t) para concluir que o tipo de {pair e1 e2} é (listT t). A tipagem de {first e} deverá verificar que e tem o tipo (listT t) para concluir que {first e} tem tipo t, e a tipagem de {rest e} deverá verificar que e tem o tipo (listT t) para concluir que {rest e} tem tipo (listT t). Finalmente, a tipagem de if0 também deverá aceitar uma expressão de tipo (listT t) na condição.

A linguagem precisará de um novo termo {nil t} para indicar uma lista vazia de tipo (listT t). Faça extensões no parser (use a função parse-type no parse de nil), no verificador de tipos e no interpretador para incluir esse termo. Teste seu interpretador com as funções map e flatten definidas no final do arquivo com a implementação de TCFAE.

Contato

Podem entrar em contato pelo meu email que responderei assim que possível.


Last modified: Tue Nov 29 14:23:00 BRST 2011