Você está na página 1de 612

Linguagens de

Programao
Segunda Edio Princpios e Paradigmas

Allen B. Tucker
Robert E. Noonan
Linguagens de
Programao
Princpios e Paradigmas

Allen B. Tucker
Bowdoin College

Robert E. Noonan
College of William and Mary

Traduo
Mario Moro Fecchio
Acauan Fernandes

Reviso Tcnica
Eduardo Marques
Doutor em Engenharia de Sistemas Digitais pela USP
Mestre em Cincia da Computao pela USP
Docente do Instituto de Cincias Matemticas e de Computao ICMC USP

Mrcio Merino Fernandes


PhD em Cincia da Computao pela University Of Edinburgh Esccia
Professor Adjunto da Universidade Federal de So Carlos UFSCAR

Verso impressa
desta obra: 2008

2010

Iniciais_Eletronico iii 6/14/10 10:47:38 AM


Linguagens de programao princpios e paradigmas
Segunda edio
ISBN 978-85-7726-044-7
A reproduo total ou parcial deste volume por quaisquer formas ou meios, sem o consentimento escrito da editora, ilegal e con-
figura apropriao indevida dos direitos intelectuais e patrimoniais dos autores.
2009 McGraw-Hill Interamericana do Brasil Ltda.
Todos os direitos reservados.
Av. Brigadeiro Faria Lima, 201 17. andar
So Paulo SP CEP 05426-100
2009 McGraw-Hill Interamericana Editores, S.A. de C. V.
Todos os direitos reservados.
Prol. Paseo de la Reforma 1015 Torre A
Piso 17, Col. Desarrollo Santa Fe,
Delegacin lvaro Obregn
C.P. 01376, Mxico, D. F.
Traduo da segunda edio em ingls de Programming languages principles and paradigms
2007 by The McGraw-Hill Companies, Inc.
ISBN da obra original: 978-0-07-286609-4
Coordenadora editorial: Guacira Simonelli
Editora de desenvolvimento: Alessandra Borges
Produo editorial: Nilceia Esposito ERJ Composio Editorial
Supervisora de pr-impresso: Natlia Toshiyuki
Preparao de texto: Marta Almeida de S
Diagramao: ERJ Composio Editorial
Design de capa: Rokusek Design
Imagem de capa (USE): Rokusek Design
T89l Tucker, Allen B.
Linguagens de programao [recurso eletrnico] :
princpios e paradigmas /a Allen B. Tucker, Robert E. Noonan ;
traduo: Mario Moro Fecchio ; reviso tcnica: Eduardo
Marques, Mrcio Merino Fernandes. Dados eletrnicos.
Porto Alegre : AMGH, 2010.

Editado tambm como livro impresso em 2008.


ISBN 978-85-63308-56-6

1. Cincia da computao. 2. Linguagem de programao.


I. Noonan, Robert E. II. Ttulo.

CDU 004.43

Catalogao na publicao: Ana Paula M. Magnus CRB-10/Prov-009/10

A McGraw-Hill tem forte compromisso com a qualidade e procura manter laos estreitos com seus leitores.
Nosso principal objetivo oferecer obras de qualidade a preos justos, e um dos caminhos para atingir essa meta
ouvir o que os leitores tm a dizer. Portanto, se voc tem dvidas, crticas ou sugestes, entre em contato
conosco preferencialmente por correio eletrnico (mh_brasil@mcgraw-hill.com) e nos ajude a aprimorar nosso
trabalho. Teremos prazer em conversar com voc. Em Portugal use o endereo servico_clientes@mcgraw-hill.com.

Iniciais_Eletronico iv 6/14/10 10:47:39 AM


Para Maida.
Allen B. Tucker

Para Debbie e Paul.


Robert E. Noonan
Pr e f c i o

O estudo de linguagens de programao evoluiu rapidamente desde que comeamos o desen-


volvimento da primeira edio* deste livro em 1999. Por exemplo, Java se tornou a linguagem
dominante no currculo de cincia da computao, comeando com CS1. A programao gil
emergiu como uma abordagem coerente para o projeto de software e suas preferncias de lin-
guagens so diferentes daquelas da programao tradicional. O uso de mtodos formais em
projeto de software comeou a ser de uso comum e sua importncia agora significativa.
Em resposta a estes e a outros acontecimentos, esta obra espera capturar o interesse
e os novos desafios que acompanham o projeto de linguagens de programao atuais e do
futuro. Por exemplo, esta edio possui uma cobertura maior e mais profunda de todos os
quatro paradigmas de programao e as linguagens que os acompanham.

Cobertura da Linguagem
Paradigma Primeira Edio Esta Edio
Imperativo C
(Captulo 12) Ada
Perl
Orientado a Objetos Java Java
(Captulo 13) Smalltalk
Python
Funcional Scheme Sheme
(Captulo 14) Haskell Haskell
Lgico Prolog Prolog
(Captulo 15)

A segunda maior mudana nesta edio que a discusso de princpios de projeto


de linguagens nos primeiros captulos (2-11) foi bastante ampliada. Acrescentamos novos
exemplos de linguagens atuais (como Python e Perl), usando um estilo informal de apre-
sentao. Alm disso, eliminamos a maior parte da cobertura de linguagens antigas que
no so mais to usadas (como Pascal e Modula).
Os princpios bsicos das linguagens de programao sintaxe, nomes, tipos, se-
mntica e funes so os assuntos dos Captulos 2, 4, 5, 7 e 9, respectivamente. Esses
captulos fornecem um estudo prtico desses princpios usando uma seleo aprofundada
de linguagens e exemplos.
Os leitores que preferirem um tratamento de sintaxe, sistemas de tipo, semntica,
funes e gerenciamento de memria baseados em implementaes encontraro este ma-
terial nos Captulos complementares 3, 6, 8, 10 e 11. Esses captulos podem ser usados
seletivamente para enriquecer os princpios bsicos com os quais eles esto relacionados.

* NE: edio em ingls.

vii
viii Prefcio

Por exemplo, o estudo de sintaxe no Captulo 2 pode ser enriquecido pelo estudo das fases
lxica e sinttica de um compilador no Captulo 3. Enfatizamos que qualquer um destes
captulos complementares pode ser preterido em relao aos demais, especialmente no


incio de um curso sobre linguagens de programao.
Trs captulos complementares incluem sees opcionais com tratamento matem-
tico mais formal. Essas sees esto marcadas com o mesmo smbolo margem como
o deste pargrafo para indicar que so opcionais. O Apndice B fornece uma reviso de
tpicos de matemtica discreta e notaes correspondentes a estas sees para alunos que
precisarem de uma reviso rpida.
Finalmente, os captulos de tpicos especiais (16, 17 e 18) fornecem introdues de-
talhadas ao estudo de manipulao de eventos, concorrncia e corretude de programas.
De modo geral, esta edio contm uma cobertura ampla e profunda dos princpios,
paradigmas e tpicos especiais de linguagens de programao. O livro possui 18 captu-
los. J que cada professor possui diferentes vises sobre o que deve ser enfatizado em um
curso de linguagem de programao, este livro fornece uma diversidade de opes.

N FA S E
Este texto enfatiza um tratamento prtico e completo das questes-chave do projeto
de linguagem de programao. Ele fornece a professores e alunos uma mistura de experi-
ncias baseadas em explicaes e em implementaes. As experincias baseadas em im-
plementaes incluem experimentaes prticas com o projeto e implementaes de um
sub-conjunto modesto de C, chamado Clite, que est integralmente definido no Apndice
A para facilitar a referncia.
Conforme citado anteriormente, esta edio traz um tratamento abrangente dos
principais paradigmas de programao. Acreditamos que, para dominar um paradigma,
os alunos devem us-lo ativamente para resolver um problema de programao. Se, por
exemplo, seus alunos no tiverem experincia com programao funcional, recomenda-
mos que eles aprendam Scheme ou Heskell de forma que consigam completar um projeto
de programao razovel. Parafraseando o comentrio de um revisor:

Para se entender um paradigma voc deve se tornar esse paradigma.

Se, por outro lado, o curso introdutrio de matemtica discreta ou IA j incluir a programao
funcional, possvel pular este captulo e enfatizar outro paradigma ou tpico especial.

O R G AN IZAO DO CU RSO
A Figura 1 mostra como o texto se divide em trs sees principais:

Princpios
Paradigmas
Tpicos especiais

Na primeira seo, os Captulos 2, 4, 5, 7 e 9 cobrem cinco princpios bsicos sin-


taxe, nomes, tipos, semntica e funes. Partes dos captulos restantes (3, 6, 8, 10 e 11)
desta seo podem ser utilizados para abordar o tema de forma mais completa. Por outro
Prefcio ix

Anlise Sistemas Interpretao Interpretao


Sinttica de Tipos Semntica de Funes
3 6 8 10
Captulo

1 2 4 5 7 9 11
Introduo Sintaxe Nomes Tipos Semntica Funes Gerenciamento
de Memria

os Princpios

12 Programao Imperativa

13 Programao Orientada a Objetos


os Paradigmas
14 Programao Funcional

15 Programao Lgica

16 Manipulao de Eventos

os Tpicos Especiais 17 Concorrncia

18 Corretude

| Figura 1 Dependncias do contedo entre os captulos.

lado, cursos avanados ou de ps-graduao podem incluir diversos destes tpicos, em


vez de enfatizar os captulos posteriores.
A segunda seo do texto cobre os quatro principais paradigmas de programao:

Programao imperativa
Programao orientada a objetos
Programao funcional
Programao lgica

Estes captulos so relativamente independentes um do outro e podem ser estudados em


qualquer ordem. A escolha de paradigmas, claro, pode variar de acordo com as prefe-
rncias do professor e o currculo do curso.
O Captulo 12 demonstra as caractersticas-chave da programao imperativa entre
trs diferentes linguagens: C, Ada e Perl. C foi escolhido porque ilustra os problemas
causados por tipificao fraca. Em contraste, Ada fortemente tipada, fornecendo assim
x Prefcio

uma comparao interessante com C. Finalmente, Perl foi escolhida porque ilustra uma
linguagem de scripting tipada dinamicamente. Para professores que inclurem o Captulo
12, recomendamos que pelo menos uma linguagem no familiar aos alunos seja abordada
em profundidade suficiente para se atribuir um bom projeto de programao.
No Captulo 13 as caractersticas de uma linguagem orientada a objetos so explora-
das usando Java, Smalltalk e Python. Smalltalk foi includa por causa da sua simplicida-
de, Java por causa da sua dominncia e Python por causa da sua agilidade em aplicaes
de scripting. O paradigma orientado a objetos continuar a ser importante no futuro.
Os paradigmas de programao funcional e lgico (Captulos 14 e 15) continuam a
ser distintos nas suas aplicaes e respectivos estilos de programao. Nosso tratamento
de programao funcional inclui discusses paralelas de Scheme e Haskell, de modo que
os alunos possam dominar as idias de programao funcional estudando uma das lingua-
gens e omitindo a outra.
A terceira seo do texto cobre trs tpicos especiais:

Manipulao de eventos
Concorrncia
Corretude

Os Captulos 16 e 17, Programao Orientada a Eventos e Programao Concorrente,


representam questes de controle incomuns que podem ser bem tratadas em um estudo
de linguagens de programao. Ambos tm tido cada vez mais visibilidade em aplica-
es de programao, especialmente em computao cientfica e em sistemas embutidos.
Tpicos-chave que os alunos precisam cobrir nestas reas incluem comunicao, deadlo-
cks, passagem de mensagens, no-determinismo, manipulao de eventos, comunicao
interprocessos e uma amostra de muitas e variadas aplicaes (sistemas operacionais,
interaes GUI e sistemas de alarmes de casas) nas quais estes tpicos aparecem com
regularidade.
Finalmente, o Captulo 18 sobre Corretude de Programa discute recentes desenvol-
vimentos no suporte de linguagens de programao a mtodos formais. Por exemplo, os
desenvolvedores em Spark Ada relatam 100 vezes menos erros com taxas de produtividade
3 a 5 vezes mais altas usando uma tcnica chamada corretude por construo (Barnes,
2003). Por acreditarmos que o suporte a abordagens mais formais ao projeto de software
ser cada vez mais importante para as linguagens de programao (assim como a progra-
mao orientada a objetos importante hoje), esperamos que os professores considerem
a incluso deste captulo nos seus cursos de linguagens de programao.
O Captulo 18 comea com uma reviso de semnticas axiomticas (Hoare,
1969) e o seu uso na verificao de programas imperativos. Ele expande esta teoria
cobrindo o conceito de projeto por contrato (Meyer, 1988) em linguagens orienta-
das a objetos e sua aplicao usando Java Modeling Language (JML). Este captulo
Prefcio xi

tambm cobre o conceito de induo estrutural e seu uso para provar a correo de
programas funcionais.
Alguns tpicos dos ltimos captulos nem sempre fazem parte dos cursos que tra-
tam do estudo de paradigmas e linguagens computacionais. Por exemplo, o estudo sobre
programao concorrente, orientada a eventos e corretude de programas podem, cada um,
ser visto separadamente.
Alm disso, boa parte do material dos captulos 3, 6, 8, 10 e 11 pode servir de base
no estudo sobre compiladores.

P R - R EQ U ISIT O S E O U T RAS O RIEN TAES


Os alunos que iniciam esse curso devem ter conhecimento de estruturas de dados.
Sendo que, devem ter se familiarizado com listas encadeadas, pilhas, matrizes flexveis
e tabelas hash.
Alm disso, conhecimento de Java um pr-requisito se no curso forem ministrados
alguns dos seguintes captulos sobre implementaes: 3, 6, 8, 16 e 17. Sem conhecimento
de Java, os alunos devem ter experincia com C, C++ ou C#.
Recomendamos que os alunos tambm tenham acesso a um bom tutorial de Java,
manual de referncia e ambiente de programao. Tais tutoriais e referncias esto dispo-
nveis na Internet (veja, por exemplo, http://java.sun.com/docs/books/tutorial). Algumas
das nossas discusses dependem estritamente de caractersticas que foram introduzidas
em Java 1.4 e 1.5. Essas caractersticas so claramente observadas no texto.
Tambm esperamos que os alunos tenham algum conhecimento matemtico. Embora
este no seja um pr-requisito rgido, essencial para os alunos que queiram se envolver nas
sees orientadas a matemtica em alguns dos captulos (3, 6, 8, 10 e 18). Uma reviso das
idias bsicas de funes, conjuntos, lgica e prova fornecida no Apndice B.
Este texto consistente com as recomendaes do Computing Curricula 2001
(CC2001, 2001). Ele tambm cobre todos os tpicos do curso de Linguagens de Pro-
gramao descrito no Liberal Arts Model Curriculum (Walker e Schneider, 1996) e no
esboo da sua verso mais nova de 2005 (www.lacs.edu).
Quanto ao Computing Curricula 2001, o material neste texto cobre todos os tpi-
cos (PL1 at PL11) da seo de Linguagens de Programao do corpo de conhecimento
bsico. Tambm cobre outros tpicos nesse corpo de conhecimento bsico, como progra-
mao orientada a eventos e concorrente (PF6), gerenciamento de memria (OS5), pro-
gramao lgica e funcional (IS) e engenharia de software (SE). Este texto trata de cada
tpico em maior profundidade do que a sugerida pelo Computing Curricula 2001.

FO NT ES D E LIN GU AGEM E WEBSIT E


Para este texto pode ser usado software com qualquer implementao de Java 1.5
ou superior. Implementamos o software Java para este livro usando o Java 1.5 da Sun. A
seguir uma lista de sites que recomendamos para obter tutorial e outras informaes sobre
as principais linguagens cobertas neste livro.
xii Prefcio

Linguagem Fonte Web*


Ada gnu.org
C, C++ gnu.org
Haskell haskell.org
Java 1.5 java.sun.com
Perl perl.com
Prolog swi-prolog.org
Python python.org
Scheme drscheme.org
Smalltalk squeak.org

S U P LEM EN T O S
Online Learning Center (Centro de aprendizagem on-line)
O Centro de aprendizagem on-line no endereo www.mhhe.com/tucker oferece re-
cursos para o estudante e para o professor para ajudar a desenvolver os conceitos descritos
no livro. Esses materiais esto disponveis em ingls.

Recursos para o Professor


Para o professor, o Online Learning Center, em www.mhhe.com/tucker traz: apre-
sentaes em power point, manual do professor, entre outros. Tudo disponvel em ingls.
Para terem acesso aos recursos on-line, os professores brasileiros precisam obter
uma senha com a McGraw-Hill Interamericana do Brasil. A senha deve ser solicitada por
e-mail: divulgacao_brasil@mcgraw-hill.com. Na Europa, a senha deve ser obtida com a
McGraw-Hill de Portugal: servico_clientes@mcgraw-hill.com.

Recursos para o Estudante


O Online Learning Center, em www.mhhe.com/tucker disponibiliza para o estudante:
Um conjunto de software e suporte para acompanhar este texto com os seguintes
materiais:
Uma implementao Java completa da sintaxe, sistema de tipos e semntica de Clite.

Ag ra d e c ime nto s
Muitas pessoas nos ajudaram no desenvolvimento deste livro. James Lu foi um cola-
borador-chave no incio da conceitualizao da primeira edio. Bill Bynum do College of
William and Mary e Laurie King do College of the Holy Cross contriburam com os Cap-
tulos 4 e 8, respectivamente. David Coppit do William and Mary nos introduziram no uso
de rvores de prova, que aparecem no Captulo 18. Alunos da Bowdoin e William and Mary
contriburam com a primeira edio e em verses iniciais desta edio. De forma especial,
Doug Vail desenvolveu solues para alguns dos problemas mais desafiadores. Wyatt Du-
mas ajudou a reescrever o software para a segunda edio e deu contribuies significativas
para o contedo de dois captulos. Agradecemos a todos os nossos revisores:
Phil Ventura University of Buffalo, SUNY
Aaron Keen California Polytechnic State University, San Luis Obispo
John Donald San Diego State University
Tia Watts Sonoma State University
Ron Olsson University of California, Davis
Thomas D. Rethard University of Texas, Arlington

*NE: Os sites indicados neste livro podero sofrer alteraes ao longo do tempo em razo da natureza dinmica
da Internet.
Prefcio xiii

Alex Thornton University of California, Irvine


Gerald Baumgartner Ohio State University
Ken Slonneger University of Iowa
David R. Falconer California State University, Fullerton
Tae W. Ryu California State University, Fullerton
Qi Cheng University of Oklahoma
Rainey Little Mississippi State University
Jay-Evan J. Tevis Auburn University
John Hannan Pennsylvania State University
Neelam Soundarajan Ohio State University
Robert van Engelen Florida State University
Shannon Tauro University of California, Irvine
Gloria Melara California State University, Northridge
Amer Diwan University of Colorado, Boulder
Susan Gauch University of Kansas
Henri Casanova University of California, San Diego
Cristina V. Lopes University of California, Irvine
Salih Yurttas Texas A&M University
Roman W. Swiniarksi San Diego State University
Amar Raheja California State Polytechnic Universty, Pomona
Franck Xia University of Missouri, Rolla
Rajendra K. Raj Rochester Institute of Technology
Randall D. Beer Case Western Reserve University
Robert M. Cubert University of Florida
Liang Cheng Lehigh University
David Hemmendinger Union College

pela sua leitura cuidadosa e comentrios construtivos por todo o desenvolvimento da


primeira e segunda edies deste texto, que foram aperfeioados pela sua compreenso
coletiva. Os autores oferecem um agradecimento especial a David Hemmendinger da
Union College pela sua cuidadosa edio e extensivas sugestes, a maioria das quais foi
incorporada a esta edio.
Finalmente, gostaramos de agradecer nossos editores, Rebecca Olsen e Alan Apt,
pela sua viso, direo e apoio. Eles orientaram o desenvolvimento da segunda edio
com extraordinria habilidade.

Allen B. Tucker Robert E. Noonan


Bowdoin College College of William and Mary
R e sumo do co n ted o

1 Viso Geral 1

2 Sintaxe 23

3 Anlise Lxica e Sinttica 57

4 Nomes 85

5 Tipos 101

6 Sistemas de Tipos 135

7 Semntica 153

8 Interpretao Semntica 197

9 Funes 225

10 Implementao de Funes 243

11 Gerenciamento de Memria 263

12 Programao Imperativa 277

13 Programao Orientada a Objetos 309

14 Programao Funcional 361

15 Programao Lgica 413

16 Programao Orientada a Eventos 447

17 Programao Concorrente 483

18 Corretude de Programa 519

xv
S um ri o

1 Viso Geral 1 3 Anlise Lxica e Sinttica 57

1.1 Princpios 2 3.1 A Hierarquia de Chomsky 58


1.2 Paradigmas 3 3.2 Anlise Lxica 60
1.3 Tpicos Especiais 5 3.2.1 Expresses Regulares 62
3.2.2 Autmatos de Estados Finitos 63
1.4 Uma Breve Histria 6
3.2.3 Do Projeto ao Cdigo 67
1.5 Sobre o Projeto de Linguagem 11
3.3 Anlise Sinttica 70
1.5.1 Restries de Projeto 11
3.3.1 Definies Preliminares 71
1.5.2 Resultados e Objetivos 14
3.3.2 Anlise Descendente Recursiva 74
1.6 Compiladores e Mquinas Virtuais 18
3.4 Resumo 82
1.7 Resumo 20
Exerccios 82
Exerccios 21

4 Nomes 85
2 Sintaxe 23
4.1 Questes Sintticas 86
2.1 Gramticas 24
4.2 Variveis 88
2.1.1 Gramticas na Forma de
Backus-Naur (BNF) 25 4.3 Escopo 89
2.1.2 Derivaes 26 4.4 Tabela de Smbolos 92
2.1.3 rvores de Anlise 28 4.5 Resolvendo Referncias 93
2.1.4 Associatividade e Precedncia 30
4.6 Escopo Dinmico 94
2.1.5 Gramticas Ambguas 31
4.7 Visibilidade 95
2.2 BNF Estendida 35
4.8 Sobrecarga 96
2.3 A Sintaxe de uma Pequena Linguagem: Clite 37
2.3.1 Sintaxe Lxica 39 4.9 Tempo de Vida 98
2.3.2 Sintaxe Concreta 41 4.10 Resumo 99
2.4 Compiladores e Interpretadores 42 Exerccios 99
2.5 Relacionando Sintaxe e Semntica 48
2.5.1 Sintaxe Abstrata 49 5 Tipos 101
2.5.2 rvores de Sintaxe Abstrata 51
5.1 Erros de Tipos 102
2.5.3 A Sintaxe Abstrata de Clite 51
5.2 Tipagem Esttica e Dinmica 104
2.6 Resumo 54
5.3 Tipos Bsicos 105
Exerccios 55

xvii
xviii Sumrio

5.4 Tipos Compostos 112 7.5.4 A Polmica do GoTo 168


5.4.1 Enumeraes 112 7.6 Semntica de Entrada/Sada 169
5.4.2 Ponteiros 113 7.6.1 Conceitos Bsicos 170
5.4.3 Matrizes e Listas 115 7.6.2 Arquivos de Acesso Seqencial 175
5.4.4 Strings 119 7.6.3 Semntica da Manipulao de
5.4.5 Estruturas 120 Erros de E/S 177
5.4.6 Registros Variantes e Unies 121
7.7 Semntica de Manipulao de Excees 179
5.5 Tipos de Dados Recursivos 123 7.7.1 Estratgias e Questes de Projeto 181
5.6 Funes como Tipos 124 7.7.2 Manipulao de Excees em Ada,
5.7 Equivalncia de Tipos 125 C++ e Java 183
7.7.3 Excees e Asseres 191
5.8 Subtipos 126
7.8 Resumo 194
5.9 Polimorfismo e Genricos 127
Exerccios 194
5.10 Tipos Definidos pelo Programador 132
5.11 Resumo 133
8 Interpretao Semntica 197
Exerccios 133
8.1 Transformaes de Estados e
Funes Parciais 198
6 Sistemas de Tipos 135
8.2 A Semntica de Clite 199
6.1 O Sistema de Tipos de Clite 137 8.2.1 O Significado de um Programa 199
6.2 Converso Implcita de Tipos 144 8.2.2 Semntica de Comandos 201
6.3 Formalizando o Sistema de Tipos de Clite 147 8.2.3 Semntica das Expresses 205
8.2.4 Expresses com Efeitos Colaterais 209
6.4 Resumo 150
8.3 Semntica com Tipagem Dinmica 210
Exerccios 151
8.4 Um Tratamento Formal de Semntica 214
8.4.1 Estados e Transformao de Estados 214
7 Semntica 153 8.4.2 Semntica Denotacional
de um Programa 216
7.1 Motivao 154
8.4.3 Semntica Denotacional dos
7.2 Semntica de Expresses 155 Comandos 217
7.2.1 Notao 155 8.4.4 Semntica Denotacional de
7.2.2 Associatividade e Precedncia 157 Expresses 220
7.2.3 Avaliao de Curto-Circuito 158 8.4.5 Limites de Modelos Semnticos
7.2.4 O Significado de uma Expresso 159 Formais 222
7.3 O Estado do Programa 160 8.5 Resumo 222
7.4 Semntica de Atribuio 162 Exerccios 222
7.4.1 Atribuio Mltipla 162
7.4.2 Comandos de Atribuio versus
Expresses de Atribuio 163 9 Funes 225
7.4.3 Semntica de Cpia versus
9.1 Terminologia Bsica 226
Semntica de Referncia 163
9.2 Chamada e Retorno de Funes 226
7.5 Semntica de Controle de Fluxo 164
7.5.1 Seqncia 164 9.3 Parmetros 227
7.5.2 Condicionais 165 9.4 Mecanismos de Passagem de Parmetros 229
7.5.3 Laos 166 9.4.1 Passagem por Valor 229
9.4.2 Passagem por Referncia 231
Sumrio xix

9.4.3 Passagem por Resultado-valor 12 Programao Imperativa 277


e Resultado 233
9.4.4 Passagem por Nome 234 12.1 O Que Torna uma Linguagem Imperativa? 278
9.4.5 Passagem de Parmetros em Ada 235 12.2 Abstrao Procedural 280
9.5 Registros de Ativao 236 12.3 Expresses e Atribuio 281
9.6 Funes Recursivas 237 12.4 Suporte de Biblioteca para Estruturas de Dados 283
9.7 Pilha de Tempo de Execuo 238 12.5 Programao Imperativa e C 284
9.8 Resumo 240 12.5.1 Caractersticas Gerais 285
Exerccios 241 12.5.2 Exemplo: Grep 286
12.5.3 Exemplo: Mdia 288
12.5.4 Exemplo: Diferenciao Simblica 289
10 Implementao de Funes 243
12.6 Programao Imperativa e Ada 290
10.1 Declarao e Chamada de Funes em Clite 244 12.6.1 Caractersticas Gerais 293
10.1.1 Sintaxe Concreta 244 12.6.2 Exemplo: Mdia 295
10.1.2 Sintaxe Abstrata 246 12.6.3 Exemplo: Multiplicao de Matrizes 296
10.2 Completando o Sistema de Tipos de Clite 247 12.7 Programao Imperativa e Perl 296
12.7.1 Caractersticas Gerais 299
10.3 Semntica de Chamada e Retorno
12.7.2 Exemplo: Grep 300
de Funes 249
12.7.3 Exemplo: Enviando Notas 303
10.3.1 Funes no-void 250
10.3.2 Efeitos Colaterais Revisitados 251 12.8 Resumo 307
10.4 Tratamento Formal de Tipos e Semnticas 252 Exerccios 307
10.4.1 Mapas de Tipos para Clite 252
10.4.2 Formalizando as Regras de Tipo 13 Programao Orientada
de Clite 254
10.4.3 Formalizando a Semntica de Clite 255 a Objetos 309

10.5 Resumo 260 13.1 Preldio: Tipos de Dados Abstratos 310


Exerccios 260 13.2 O Modelo Objeto 315
13.2.1 Classes 315
13.2.2 Visibilidade e Ocultamento
11 Gerenciamento de Memria 263
de Informao 318
11.1 A Memria Heap 264 13.2.3 Herana 319
11.2 Implementao de Matrizes Dinmicas 266 13.2.4 Herana Mltipla 321
11.2.1 Problemas de Gerenciamento de 13.2.5 Polimorfismo 323
Heap: Lixo 267 13.2.6 Modelos 325
13.2.7 Classes Abstratas 326
11.3 Coleta de Lixo 268
13.2.8 Interfaces 327
11.3.1 Contagem de Referncia 269
13.2.9 Tabela de Mtodo Virtual 329
11.3.2 Marcar-Varrer 271
13.2.10 Identificao em Tempo de Execuo 330
11.3.3 Coleta de Cpias 273
13.2.11 Reflexo 331
11.3.4 Comparao das Estratgias 274
13.3 Smalltalk 332
11.4 Resumo 275
13.3.1 Caractersticas Gerais 333
Exerccios 276 13.3.2 Exemplo: Polinmios 336
13.3.3 Exemplo: Nmeros Complexos 338
13.3.4 Exemplo: Conta Bancria 340
xx Sumrio

13.4 Java 340 15.2 Programao Lgica em Prolog 417


13.4.1 Exemplo: Diferenciao Simblica 341 15.2.1 Elementos de um Programa Prolog 417
13.4.2 Exemplo: Backtracking 343 15.2.2 Aspectos Prticos de Prolog 425
13.5 Python 350 15.3 Exemplos Prolog 430
13.5.1 Caractersticas Gerais 351 15.3.1 Diferenciao Simblica 430
13.5.2 Exemplo: Polinmios 352 15.3.2 Resolvendo Palavras Cruzadas 431
13.5.3 Exemplo: Fraes 354 15.3.3 Processamento de Linguagem
13.6 Resumo 356 Natural 433
15.3.4 Semntica de Clite 436
Exerccios 357 15.3.5 O Problema das Oito Rainhas 440
15.4 Resumo 443
14 Programao Funcional 361 Exerccios 443

14.1 Funes e o Clculo Lambda 362


16 Programao Orientada a
14.2 Scheme 366
14.2.1 Expresses 367 Eventos 447
14.2.2 Avaliao de Expresses 368
16.1 Controle Acionado por Eventos 448
14.2.3 Listas 368
16.1.1 ModeloVisualizaoControle 449
14.2.4 Valores Elementares 371
16.1.2 Eventos em Java 450
14.2.5 Fluxo de Controle 372
16.1.3 Aplicaes GUI Java 453
14.2.6 Definindo Funes 372
16.2 Manipulao de Eventos 454
14.2.7 Expresses Let 375
16.2.1 Cliques do Mouse 454
14.2.8 Exemplo: Semnticas de Clite 378
16.2.2 Movimento do Mouse 456
14.2.9 Exemplo: Diferenciao Simblica 382
16.2.3 Botes 456
14.2.10 Exemplo: O Problema das
16.2.4 Rtulos, reas de Texto e Campos
Oito Rainhas 384
de Texto 458
14.3 Haskell 388 16.2.5 Caixas de Seleo 459
14.3.1 Introduo 389
16.3 Trs Exemplos 461
14.3.2 Expresses 390
16.3.1 Uma Interface GUI Simples 461
14.3.3 Listas e Extenses de Listas 391
16.3.2 Criando um Applet Java 467
14.3.4 Tipos e Valores Elementares 394
16.3.3 Jogos Interativos Acionados
14.3.5 Fluxo de Controle 395 por Eventos 468
14.3.6 Definindo Funes 395
16.4 Outros Aplicativos Acionados por Eventos 476
14.3.7 Tuplas 399
16.4.1 Caixa Eletrnico de Banco 476
14.3.8 Exemplo: Semnticas de Clite 400
16.4.2 Sistema de Segurana Domstica 478
14.3.9 Exemplo: Diferenciao Simblica 404
14.3.10 Exemplo: O Programa das 16.5 Resumo 479
Oito Rainhas 405 Exerccios 479
14.4 Resumo 408
Exerccios 408
17 Programao Concorrente 483

17.1 Conceitos de Concorrncia 484


15 Programao Lgica 413 17.1.1 Histria e Definies 485
17.1.2 Controle de Thread e Comunicao 486
15.1 Lgica e Clusulas de Horn 414 17.1.3 Corridas e Deadlocks 487
15.1.1 Resoluo e Unificao 416 17.2 Estratgias de Sincronizao 490
17.2.1 Semforos 490
17.2.2 Monitores 491
Sumrio xxi

17.3 Sincronizao em Java 494 18.4 Corretude de Programas Funcionais 548


17.3.1 Threads em Java 494 18.4.1 Recurso e Induo 549
17.3.2 Exemplos 496 18.4.2 Exemplos de Induo Estrutural 550
17.4 Comunicao Interprocessos 506 18.5 Resumo 553
17.4.1 Endereos IP, Portas e Sockets 507 Exerccios 553
17.4.2 Um Exemplo de ClienteServidor 507
17.5 Concorrncia em Outras Linguagens 513
A Definio de Clite 557
17.6 Resumo 515
A.1 Sintaxe Lxica e Concreta de Clite 558
Exerccios 516
A.2 Sintaxe Abstrata de Clite 559
A.3 Sistema de Tipos de Clite 559
18 Corretude de Programa 519 A.4 Semnticas de Clite 561
18.1 Semnticas Axiomticas 521 A.5 Acrescentando Funes Clite 563
18.1.1 Conceitos Fundamentais 521 A.5.1 Sintaxe Lxica e Concreta 563
18.1.2 A Regra de Atribuio 525 A.5.2 Sintaxe Abstrata 564
18.1.3 Regras de Conseqncia 525 A.5.3 Sistema de Tipos 564
18.1.4 Corretude da Funo Max 526 A.5.4 Semntica 565
18.1.5 Corretude de Programas
com Laos 527 B Reviso Matemtica Discreta 567
18.1.6 Perspectivas em Mtodos Formais 530
B.1 Sries e Relaes 567
18.2 Ferramentas de Mtodos Formais: JML 532
18.2.1 Manipulao de Exceo JML 538 B.2 Diagramas 571
B.3 Lgica 572
18.3 Corretude de Programas Orientados a Objeto 539
18.3.1 Projeto por Contrato 540 B.4 Regras de Inferncia e Prova Direta 576
18.3.2 A Invariante de Classe 541 B.5 Prova por Induo 577
18.3.3 Exemplo: Corretude de uma Glossrio 579
Aplicao Stack (Pilha) 542 Bibliografia 587
18.3.4 Observaes Finais 548
ndice Remissivo 591
S o br e o s Pr incip a is Au to res

Allen B. Tucker Professor Pesquisador de Cincias Naturais da Fundao Anne T.


and Robert M. Bass no Departamento de Cincias da Computao no Bowdoin College.
Graduou-se (Bachelor of Arts) em matemtica na Wesleyan University e MS e PhD em
cincia da computao na Northwestern University.
O Professor Tucker tem trabalhos publicados nas reas de linguagens de programao,
projeto de software, processamento de linguagem natural e curriculum design. Atua como
Fulbright Lecturer na Ternopil Academy of National Economy na Ucrnia, como Erskine
Lecturer visitante na University of Canterbury na Nova Zelndia, e Esigelec Lecturer visi-
tante na Frana. Ele associado ACM (Association of Computing Machinery).

Robert E. Noonan Professor de Cincia da Computao no College of William and


Mary, onde lecionou por 30 anos. Possui o grau A.B. (Artium Baccalaureatus) em ma-
temtica no Providence College e graus MS e PhD em cincia da computao na Purdue
University.
Tem publicaes nas reas de linguagens de programao, criao de compiladores
e engenharia de software. membro da ACM (Association of Computing Machinery),
SIGPLAN (Special Interest Group of ACM), SIGCSE (Special Interest Group on Com-
puter Science Education), e do Liberal Arts Computer Science (LACS) Consortium.

xxiii
Viso Geral 1
Uma boa linguagem de programao um universo conceitual
para pensar em programao.
Alan Perlis

VISO GERAL DO CAPTULO

1.1 PRINCPIOS 2

1.2 PARADIGMAS 3

1.3 TPICOS ESPECIAIS 5

1.4 UMA BREVE HISTRIA 6

1.5 SOBRE O PROJETO DE LINGUAGEM 11

1.6 COMPILADORES E
MQUINAS VIRTUAIS 18

1.7 RESUMO 20

EXERCCIOS 21

Da mesma forma que as nossas linguagens naturais, as linguagens de programao


facilitam a expresso e a comunicao de idias entre pessoas. Entretanto, linguagens de
programao diferem das linguagens naturais de duas maneiras importantes. Em primei-
ro lugar, linguagens de programao tambm permitem a comunicao de idias entre
pessoas e computadores. Em segundo lugar, as linguagens de programao possuem um
domnio de expresso mais reduzido do que o das linguagens naturais. Isso quer dizer que
elas facilitam apenas a comunicao de idias computacionais. Assim, uma linguagem de

1
2 Captulo 1 Viso Geral

programao deve satisfazer requisitos diferentes daqueles de uma linguagem natural. Este
texto explora esses requisitos e as alternativas de projeto de linguagens que eles evocam.
Neste estudo, identificamos as muitas semelhanas entre linguagens de programa-
o e linguagens naturais. Tambm examinamos as diferenas fundamentais que so
impostas pela configurao computacional na qual um programa deve funcionar. Exa-
minamos as caractersticas das linguagens de programao tanto abstratamente quanto
ativamente. Isso significa que combinamos um tratamento conceitualmente rico de pro-
jeto de linguagens de programao com um estudo prtico em laboratrio sobre como
esses conceitos afetam projetistas de linguagens e programadores em uma ampla faixa
de domnios de aplicaes.
Este estudo importante porque os alunos de cincia da computao de hoje se-
ro os projetistas e usurios das linguagens de programao de amanh. Para se tornar
um projetista e usurio de linguagens bem informado voc precisar compreender as
linguagens de forma ampla suas caractersticas, seus pontos fortes e fracos em uma
ampla gama de estilos de programao e suas aplicaes. Conhecer uma linguagem e
o domnio de sua aplicao no fornece tal amplitude de conhecimento. Este livro lhe
ajudar a obter tal amplitude.

1 .1 P R I NCP IO S
Projetistas de linguagens possuem um vocabulrio bsico sobre a estrutura, o
significado e as preocupaes pragmticas dessas ferramentas que os auxiliam a en-
tender como as linguagens funcionam. Esse vocabulrio se divide em trs categorias
principais que chamamos de princpios de projeto de linguagens:
Sintaxe
Nomes e tipos
Semntica
Muitos dos conceitos dessas categorias so emprestados da lingstica e da ma-
temtica, como veremos a seguir. Juntas, essas categorias fornecem um foco organi-
zacional para os Captulos bsicos 2, 4, 5, 7 e 9, respectivamente. Mais profundidade
de estudo em cada categoria apresentada nos captulos associados (3, 6, 8, 10 e 11),
conforme explicado a seguir.

Sintaxe A sintaxe de uma linguagem descreve o que constitui um programa estru-


turalmente correto. Ela responde a muitas questes. Qual a gramtica para se escrever
programas na linguagem? Qual o conjunto bsico de palavras e smbolos que os progra-
madores usam para escrever programas estruturalmente corretos?
Veremos que a maioria da estrutura sinttica de linguagens modernas de progra-
mao definida com o uso de um formalismo lingstico denominado gramtica livre
de contexto. Outros elementos de sintaxe esto fora do domnio de gramticas livres de
contexto e so definidos por outros meios. Um tratamento cuidadoso da sintaxe das lin-
guagens de programao aparece no Captulo 2.
Um estudo de sintaxe de linguagens traz muitas questes. Como um compilador
analisa a sintaxe de um programa? Como os erros de sintaxe so detectados? Como uma
gramtica livre de contexto facilita o desenvolvimento de um analisador sinttico? Essas
questes mais profundas sobre sintaxe so abordadas no Captulo 3.

Nomes e Tipos O vocabulrio de uma linguagem de programao inclui um con-


junto de regras cuidadosamente projetado para nomear entidades variveis, funes,
classes, parmetros e assim por diante. Nomes de entidades tambm tm outras proprie-
dades durante a vida de um programa, como seu escopo, visibilidade e ligao. O estudo
de nomes em linguagens de programao e seu impacto sobre a sintaxe e a semntica de
um programa o assunto do Captulo 4.
1.2 Paradigmas 3

Os tipos de uma linguagem denotam os tipos de valores que os programas podem


manipular: tipos simples, tipos estruturados e tipos mais complexos. Entre os tipos sim-
ples esto valores inteiros, nmeros decimais, caracteres e boleanos. Tipos estruturados
incluem strings de caracteres, listas, rvores e tabelas hash. Tipos mais complexos in-
cluem funes e classes. Os tipos so discutidos em maior extenso no Captulo 5.
Um sistema de tipos permite ao programador entender e implementar apropriadamente
operaes sobre valores de diversos tipos. Um sistema de tipos especificado cuidadosamente
permite ao compilador executar uma rigorosa verificao de tipos em um programa antes
da sua execuo, evitando assim erros em tempo de execuo que podem ocorrer devido
a operandos de tipos inapropriados. A especificao completa e a implementao de um
sistema de tipos o foco de um estudo mais aprofundado no Captulo 6.

Semntica O significado de um programa definido pela sua semntica, ou seja,


quando um programa executado, o efeito de cada comando sobre os valores das variveis
no programa dado pela semntica da linguagem. Assim, quando escrevemos um pro-
grama, devemos entender idias bsicas como o efeito exato que uma atribuio possui
sobre as variveis do programa. Se tivermos um modelo semntico que seja independente
de alguma plataforma especfica, podemos aplic-lo a uma diversidade de mquinas nas
quais essa linguagem possa ser implementada. Estudamos semntica no Captulo 7.
A implementao de semntica em tempo de execuo tambm interessante
em um estudo mais profundo de semntica. Como um interpretador funciona e qual a
conexo entre ele e a especificao da semntica de uma linguagem? Essas questes
mais profundas so estudadas no Captulo 8.
As funes representam o elemento chave da abstrao procedural em qualquer
linguagem. Uma compreenso da semntica da definio e chamada de funes
fundamental em qualquer estudo de linguagens de programao. A implementao
de funes tambm requer uma compreenso dos elementos estticos e dinmicos de
memria, incluindo a pilha de tempo de execuo. A pilha tambm nos auxilia a
entender outras idias como o escopo de um nome e o tempo de vida de um objeto.
Esses tpicos so tratados no Captulo 9.
A implementao da pilha de chamadas e retornos de funes um tpico central
que merece estudo mais aprofundado. Alm disso, estratgias para o gerenciamento de
outra rea de memria chamada de heap so importantes para a compreenso de objetos
dinmicos como matrizes. Tcnicas de gerenciamento de heap, chamadas coleta de lixo,
esto muito relacionadas implementao destes objetos dinmicos. A pilha e o heap so
estudados em detalhes nos Captulos 10 e 11, respectivamente.

1 .2 PA R AD IG M AS
De modo geral, pensamos em um paradigma como um padro de pensamento
que guia um conjunto de atividades relacionadas. Um paradigma de programao
um padro de resoluo de problemas que se relaciona a um determinado gnero de
programas e linguagens. Quatro paradigmas de programao distintos e fundamentais
evoluram nas ltimas trs dcadas:
Programao imperativa
Programao orientada a objeto
Programao funcional
Programao lgica
Algumas linguagens de programao so intencionalmente projetadas para supor-
tar mais de um paradigma. Por exemplo, C++ uma linguagem imperativa e orientada
4 Captulo 1 Viso Geral

Controle Aritmtica/lgica

Entrada Sada

Programa

Variveis

Memria

| Figura 1.1 O modelo computacional de von Neumann-Eckert

a objeto, enquanto a linguagem experimental Leda (Budd, 1995) projetada para


suportar os paradigmas de programao imperativa, orientada a objeto, funcional e
lgica. Essas linguagens so reminiscncias de trabalhos anteriores (notadamente PL/
I, Algol 68 e Ada) para projetar uma nica linguagem que fosse mais de propsito
geral do que outras linguagens de programao do seu tempo. Com exceo de C++,
esses esforos falharam ao tentar atrair interesse sustentado.

Programao Imperativa A programao imperativa o paradigma mais an-


tigo, j que est fundamentado no modelo computacional clssico de von Neumann-
Eckert (veja a Figura 1.1). Nesse modelo, tanto o programa quanto as suas variveis so
armazenados juntos, e o programa contm uma srie de comandos para executar clculos,
atribuir valores a variveis, obter entradas, produzir sadas ou redirecionar o controle para
outro ponto nessa srie de comandos.
A abstrao procedural um componente para a programao imperativa assim como as
atribuies, os laos, as seqncias, os comandos condicionais e a manipulao de exce-
es. As linguagens de programao imperativa predominantes incluem Cobol, Fortran,
C, Ada e Perl. O paradigma de programao imperativa o assunto do Captulo 12.

Programao Orientada a Objeto A programao orientada a objeto (POO)


fornece um modelo no qual um programa uma coleo de objetos que interagem entre
si, passando mensagens que transformam seu estado. Neste sentido, a passagem de men-
sagens permite que objetos de dados se tornem ativos em vez de passivos. Essa carac-
terstica ajuda a distinguir melhor a programao OO da imperativa. A classificao de
objetos, herana e a passagem de mensagens so componentes fundamentais da progra-
mao OO. Linguagens orientadas a objetos importantes so Smalltalk, C++, Java e C#.
A programao OO ser estudada no Captulo 13.

Programao Funcional A programao funcional modela um problema com-


putacional como uma coleo de funes matemticas, cada uma com um espao de
entrada (domnio) e resultado (faixa). Isso separa a programao funcional das lingua-
gens que possuem o comando de atribuio. Por exemplo, o comando de atribuio no

x = x + 1

faz sentido na programao funcional ou na matemtica.


1.3 Tpicos Especiais 5

As funes interagem e combinam entre si usando composio funcional, condi-


es e recurso. Importantes linguagens de programao funcional so Lisp, Scheme,
Haskell e ML. A programao funcional discutida e ilustrada no Captulo 14.

Programao Lgica A programao lgica (declarativa) permite a um programa


modelar um problema declarando qual resultado o programa deve obter, em vez de como
ele deve ser obtido. s vezes, essas linguagens so chamadas de baseadas em regras, j
que as declaraes do programa se parecem mais com um conjunto de regras ou restries
sobre o problema, em vez de uma seqncia de comandos a serem executados.
Interpretar as declaraes de um programa lgico cria um conjunto de todas as
solues possveis para o problema que ele especifica. A programao lgica tambm
fornece um veculo natural para se expressar o no-determinismo, o que apropriado
para problemas cujas especificaes sejam incompletas. A principal linguagem de
programao lgica Prolog, e o paradigma de programao lgica descrito no
Captulo 15.

1 .3 T P ICO S ESP ECIAIS


Alm desses quatro paradigmas, diversos tpicos essenciais para o projeto de lin-
guagem de programao merecem abordagem extensiva neste livro-texto. Esses tpicos
tendem a ser universais, na medida em que aparecem em dois ou mais dos paradigmas
anteriores, em vez de em apenas um. Cada um deles brevemente introduzido agora:
Manipulao de eventos
Concorrncia
Correo

Manipulao de Eventos A manipulao de eventos ocorre com programas que


respondem a eventos gerados em uma ordem imprevisvel. De certa forma, um programa
orientado a evento apenas um programa cujo comportamento inteiramente determi-
nado por questes de manipulao de eventos. A manipulao de eventos est freqente-
mente acoplada ao paradigma orientado a objeto (por exemplo, Java applets), embora ela
ocorra dentro dos paradigmas imperativos tambm (por exemplo, Tcl/Tk). Os eventos se
originam de aes dos usurios na tela (cliques de mouse ou pressionamentos de teclas,
por exemplo) ou ento de outras fontes (como leituras de sensores em um rob). Lingua-
gens importantes que suportam a manipulao de eventos incluem Visual Basic, Java e
Tcl/Tk. Esse tpico apresentado no Captulo 16.

Concorrncia A programao concorrente pode ocorrer dentro do paradigma impe-


rativo, orientado a objeto, funcional ou lgico. A concorrncia ocorre quando o programa
possui uma coleo de elementos assncronos que podem compartilhar informaes ou
sincronizar-se entre si em intervalos de tempo. A concorrncia tambm ocorre dentro
de um processo individual como a execuo paralela de diferentes iteraes de um lao.
Linguagens de programao concorrente incluem SR (Andrews e Olsson, 1993), Linda
(Carriero e Gelenter, 1989) e High Performance Fortran (Adams e outros, 1997). A pro-
gramao concorrente ser estudada no Captulo 17.

Correo A correo de programa um assunto que, at recentemente, tem atrado


interesse apenas acadmico. Todavia, linguagens mais novas e recursos de linguagens
esto evoluindo para suportar o projeto de programas demonstravelmente corretos em
uma diversidade de domnios de aplicaes. Um programa correto se satisfaz sua espe-
cificao formal para todas as suas entradas possveis. A prova de correo um assunto
6 Captulo 1 Viso Geral

complexo, mas ferramentas de linguagem para tratamento formal da correo por pro-
gramadores esto agora se tornando disponveis. O sistema Spark/Ada (Barnes, 2003) e
a Java Modeling Language (Leavens et al., 1998) fornecem bons exemplos. Introduzimos
o tpico de correo de programas no Captulo 18.

1 .4 U MA BR EV E H IST RIA
As primeiras linguagens de programao foram as linguagens de mquina e as
linguagens assembly dos primeiros computadores, comeando na dcada de 1940.
Centenas de linguagens de programao e dialetos foram desenvolvidos desde ento.
A maioria teve tempo de vida e utilidade limitados, enquanto algumas tiveram amplo
sucesso em um ou mais domnios de aplicaes. Muitas desempenharam um papel
importante na influncia sobre o projeto de futuras linguagens.
Um resumo do desenvolvimento histrico de diversas linguagens de progra-
mao influentes aparece na Figura 1.2. Embora certamente no seja completa, a
Figura 1.2 identifica alguns dos eventos e das tendncias mais influentes. Cada seta
na Figura 1.2 indica uma significativa influncia do projeto de uma linguagem mais
antiga para uma sucessora.
A dcada de 1950 marcou o incio da era das linguagens de ordem mais alta
(abreviadas em ingls como HOLs). Uma HOL se distingue de uma linguagem de
mquina ou assembly porque seu estilo de programao independente de alguma
arquitetura de mquina especfica. As primeiras linguagens de ordem mais alta foram
Fortran, Cobol, Algol e Lisp. Fortran e Cobol sobreviveram e se desenvolveram bas-
tante desde o seu surgimento no final da dcada de 1950. Essas linguagens tiveram
muitos seguidores e possuem uma grande quantidade de cdigo legado no qual os pro-
gramadores de hoje realizam manuteno. Por outro lado, Lisp teve substancialmente
seu uso diminudo e Algol desapareceu completamente.
Contudo, os projetos inovadores dessas primeiras linguagens tiveram grande in-
fluncia sobre suas sucessoras. Por exemplo, a demonstrao de Fortran de que nota-
o algbrica poderia ser traduzida em cdigo eficiente agora admitida como padro,
assim como a introduo da estrutura de registros em Cobol, o projeto de Pascal para a
compilao em um passo e a demonstrao de Algol de que uma gramtica lingstica
poderia definir formalmente sua sintaxe.
Talvez a maior motivao para o desenvolvimento de linguagens de programao
nas ltimas dcadas tenha sido a rpida evoluo da demanda de poder computacional
e as novas aplicaes por parte de uma grande e diversa comunidade de usurios. As
seguintes comunidades de usurios podem reivindicar uma participao importante no
cenrio das linguagens de programao:
Inteligncia artificial
Educao
Cincia e engenharia
Sistemas de informao
Sistemas e redes
World Wide Web
Os domnios dos problemas computacionais dessas comunidades so todos dife-
rentes, assim como as principais linguagens de programao que se desenvolveram em
torno delas. A seguir, esboamos os principais objetivos computacionais e os projetos de
linguagem que servem a cada uma dessas comunidades.

Inteligncia Artificial A comunidade de programao de inteligncia artifi-


cial est ativa desde o incio da dcada de 1960. Esta comunidade se preocupa com o
1.4 Uma Breve Histria 7

1955 60 65 70 75 80 85 90 95 2000 05

Fortran Fortran 66 Fortran 77 Fortran 90 Fortran 97 (HPF) Fortran 04

Basic Visual Basic VB NET


Cobol Cobol 68 Cobol 74 Cobol 85 Cobol 02

PL/I
Modula 2 Modula 3
Algol 60 Algol 68
Pascal Ada 83 Ada 95 Spark/Ada

IAL Jovial Concurrent


Pascal Eiffel
JML
Simula Smalltalk
Java Java 5

BCPL C C++ C++ 03 C#


Tcl/Tk
Python Python 2.4
awk
Perl PHP
APL APL2
CLOS
Lisp Common Lisp
Scheme Haskell Haskell 98
Miranda
Iswim ML SML OCAML

Prolog CLP Prolog


Standard
SEQUEL SQL 92 SQL 99
indica
influncia de projeto

| Figura 1.2 Um Resumo da Histria das Linguagens de Programao

desenvolvimento de programas que modelam o comportamento da inteligncia hu-


mana, a deduo lgica e a cognio. A manipulao de smbolos, as expresses fun-
cionais e o projeto de sistemas de prova lgica tm sido os objetivos centrais desse
trabalho em andamento.
Os paradigmas da programao funcional e da programao lgica se desenvol-
veram bastante por intermdio dos esforos dos programadores de inteligncia artificial.
8 Captulo 1 Viso Geral

Linguagens de programao funcional proeminentes nos ltimos anos incluem Lisp,


Scheme, ML e Haskell. As linguagens de programao lgica proeminentes incluem
Prolog e CLP.
A primeira linguagem de IA, Lisp (um acrnimo de List Processor), foi projetada
por John McCarthy em 1960. A Figura 1.2 sugere que Lisp foi dominante nos primeiros
anos e tornou-se menos dominante nos anos mais recentes. Entretanto, as caractersticas
bsicas de Lisp motivaram o desenvolvimento de linguagens mais recentes como Sche-
me, ML e Haskell. A forte relao entre Lisp e lambda calculus (um formalismo para
modelar a natureza de funes matemticas) fornece uma base matemtica slida
para a posterior evoluo desses sucessores. O lambda calculus e seu relacionamento
com linguagens funcionais so mais explicados no Captulo 14.
Na rea de programao lgica, apenas uma linguagem, a Prolog, tem sido o ator
principal, e a Prolog tem tido pouca influncia sobre o projeto de linguagens em outras
reas de aplicao.

Educao Nas dcadas de 1960 e 1970, diversas linguagens importantes foram pro-
jetadas com o objetivo principal de ensinar programao aos alunos. Por exemplo, Basic
foi projetada na dcada de 1960 por John Kemeny para facilitar o aprendizado de pro-
gramao por meio de compartilhamento de tempo, uma arquitetura na qual um nico
computador conectado diretamente a diversos terminais de uma s vez. Cada usurio de
terminal compartilha tempo no computador recebendo regularmente uma pequena fatia
de tempo de poder computacional. Basic teve grande popularidade no decorrer dos anos,
especialmente como uma linguagem de ensino em escolas de segundo grau e em progra-
mas de cincia de nvel superior.
A linguagem Pascal, uma derivativa de Algol, foi projetada na dcada de 1970 com
o propsito de ensinar programao. Pascal serviu durante muitos anos como a principal
linguagem de ensino no currculo de cincia da computao em nvel superior.
Durante a ltima dcada, essas linguagens tm sido substitudas em programas
educacionais por linguagens de apelo comercial como C, C++ e Java. Essa mudan-
a tem benefcios e desvantagens. Por um lado, aprender uma linguagem de apelo
comercial fornece aos graduados uma ferramenta de programao que eles podem
usar imediatamente quando se iniciarem na profisso de computao. Por outro lado,
tal linguagem inerentemente mais complexa e menos prtica de aprender como pri-
meira linguagem em um curso de graduao.
O recente surgimento de Python pode fornecer um meio pelo qual cursos introdu-
trios de cincia da computao podem retornar simplicidade e se concentrar nova-
mente no ensino dos primeiros princpios. Por exemplo, Python possui uma sintaxe e
uma semntica mais transparentes, o que a torna mais acessvel de ser bem entendida
por um novato do que qualquer uma das suas alternativas com apelo comercial. Alm
disso, cursos introdutrios que utilizam Python parecem inserir uma variedade mais
rica de tpicos de cincia da computao do que cursos usando C, C++ ou Java.

Cincia e Engenharia A comunidade de programao cientfica e de engenharia


desempenhou um papel importante no incio da histria da computao e continua a de-
sempenhar um papel importante atualmente. Os primeiros programas foram escritos na
dcada de 1940 para prever as trajetrias balsticas durante a Segunda Guerra Mundial,
usando a bem conhecida frmula fsica que caracteriza corpos em movimento. Esses pro-
gramas foram escritos primeiro em linguagem de mquina e assembly, por matemticos
especialmente treinados.
Uma fora motriz importante por trs de aplicaes cientficas e de engenharia por
toda a histria a necessidade de se obter o maior poder de processamento possvel. O
poder de processamento dos supercomputadores de hoje medido em teraflops (trilhes
1.4 Uma Breve Histria 9

de operaes de ponto flutuante por segundo), e o lder atual trabalha a uma velocidade de
280 teraflops executando um teste de desempenho-padro chamado LINPAK (veja mais
informaes em www.top500.org). Muitas das aplicaes cientficas e de engenharia de
hoje so modelos de sistemas naturais complexos em reas como a bioinformtica e as
cincias da terra e atmosfricas.
A primeira linguagem de programao cientfica, Fortran I, foi projetada por
John Backus na IBM em 1954 (Backus et al., 1954). O acrnimo Fortran uma
abreviao de Formula Translator (Tradutor de Frmulas). Fortran provavelmen-
te a linguagem de programao cientfica mais usada atualmente.
Verses iniciais de Fortran tiveram muitos problemas, todavia. O mais difcil era
a questo da consistncia o mesmo programa em Fortran era executado de forma
diferente em mquinas diferentes ou ento nem era executado. Esses problemas gera-
ram diversos esforos novos. Um destes produziu a linguagem Algol, abreviao de
Algorithmic Language (Linguagem Algortmica), que foi projetada por um comit
internacional em 1959. O principal objetivo do projeto de Algol era fornecer uma
linguagem mais bem definida que Fortran tanto para a computao quanto para a
apresentao de algoritmos cientficos e matemticos.
Algol foi chamada originalmente de International Algebraic Language (IAL
Linguagem Algbrica Internacional). A linguagem Jovial foi projetada por Jules
Schwartz na dcada de 1960 para refinar e aumentar os recursos de IAL. Esse acr-
nimo significa Jules Own Version of the International Algebraic Language (Verso
Prpria de Jules da Linguagem Algbrica Internacional). Jovial foi amplamente usada
em aplicaes do Departamento de Defesa dos Estados Unidos.
Outra linguagem interessante denominada APL (abreviao de A Programming
Language Uma Linguagem de Programao) (Iverson, 1962) foi projetada por Kenneth
Iverson na dcada de 1960 para facilitar a programao rpida de lgebra de matrizes e
outros clculos matemticos. APL tinha um conjunto de caracteres estendidos que in-
clua operadores de matrizes de smbolo nico que poderiam substituir o tdio da escrita
de laos for na maioria dos casos. A proliferao de tais smbolos especiais requereu o
projeto de um teclado especializado para facilitar a digitao de programas em APL. Tais
programas ficaram conhecidos pela sua brevidade; o clculo de uma matriz que requeria
um lao for explcito em uma linguagem convencional s precisava de um nico smbolo
em APL. A brevidade da APL tambm foi sua maldio aos olhos de muitas pessoas, ou
seja, a maioria dos programas em APL era to sucinta que desafiava a compreenso de to-
dos que no fossem tcnicos capacitados. O custo do suporte a um conjunto de caracteres
especializado e um teclado estilizado tambm contribuiu para o fim da APL.
At hoje, a computao cientfica permanece como uma atividade central na histria
da programao e das linguagens de programao. O domnio de seus problemas se refe-
re principalmente execuo de clculos complexos de forma muito rpida e precisa. Os
clculos so definidos por modelos matemticos que representam fenmenos cientficos.
Eles so implementados principalmente por meio do uso do paradigma de programao
imperativa. Linguagens modernas de programao que so amplamente usadas na rea da
programao cientfica incluem Fortran 90 (Chamberland, 1995), C (Kernighan e Ritchie,
1988), C++ (Stroustrup, 1997) e High Performance Fortran (Adams e outros, 1997).
Quanto mais complexo o fenmeno cientfico se torna, maior a necessidade de
linguagens de programao e computadores altamente sofisticados e paralelos. Assim, a
programao concorrente fortemente motivada pelas necessidades de tais aplicaes
cientficas como a modelagem de sistemas climticos e de fluxo do oceano. Algumas
linguagens, como High Performance Fortran, suportam programao concorrente por
meio da incluso de recursos a uma linguagem bsica amplamente usada (Fortran).
Outras, como SR e Occam, so projetadas especificamente para suportar programao
concorrente. Linguagens de propsito geral, como Java, suportam concorrncia como
um dos seus muitos objetivos de projeto.
10 Captulo 1 Viso Geral

Sistemas de Informao Programas projetados para instituies gerenciarem


seus sistemas de informao provavelmente so os mais prolficos do mundo. As corpo-
raes perceberam, na dcada de 1950, que o uso de computadores poderia reduzir bas-
tante o tdio da manuteno de registros e melhorar a preciso e a confiabilidade do que
eles poderiam realizar. Os sistemas de informao encontrados em corporaes incluem
o sistema de folha de pagamento, o sistema de contabilidade, os sistemas de marketing e
as vendas on-line, os sistemas de estoque e produo, e assim por diante. Tais sistemas se
caracterizam pela necessidade de processar grandes quantidades de dados (muitas vezes
organizados nos chamados bancos de dados), mas requerem transformaes relativamen-
te simples sobre os dados que esto sendo processados.
Tradicionalmente, os sistemas de informao tm sido desenvolvidos em lingua-
gens de programao como Cobol e SQL. Cobol foi projetada primeiramente no final
da dcada de 1950 por um grupo de representantes da indstria que queriam desenvol-
ver uma linguagem que fosse portvel em uma diversidade de diferentes arquiteturas
de mquina. Cobol significa COmmon Business Oriented Language (Linguagem Co-
mum Orientada aos Negcios), usa a lngua inglesa como base da sua sintaxe e suporta
um estilo de programao imperativo.
Programas em Cobol so construdos a partir de clusulas, sentenas e pargrafos e,
geralmente, tendem a ser mais prolixos que programas similares em outras linguagens. O
objetivo era definir uma linguagem que fosse fcil para os programadores assimilarem. Se
esse objetivo foi ou no atingido algo que ainda est aberto a discusso. Apesar disso,
Cobol se tornou rapidamente, e ainda continua sendo, a linguagem de programao mais
amplamente usada para aplicaes de sistemas de informao.
Em contraste, SQL (Pratt, 1990) emergiu na dcada de 1980 como uma ferra-
menta de programao declarativa para especificao de bancos de dados, gerao de
relatrios e recuperao de informaes. SQL significa Structured Query Language
(Linguagem de Consulta Estruturada) e a linguagem predominante usada para a es-
pecificao e a recuperao de informaes de bancos de dados relacionais. O modelo
relacional de bancos de dados amplamente usado, em parte por causa do seu forte
fundamento matemtico em lgebra relacional.
Mais recentemente, os negcios desenvolveram ampla gama de aplicaes de co-
mrcio eletrnico. Estas aplicaes muitas vezes usam um modelo cliente-servidor de
projeto de programa, que interage com os usurios em locais remotos e fornece acesso
simultneo a um banco de dados compartilhado. Um bom exemplo desse modelo um
sistema de encomenda de livros on-line, no qual o banco de dados reflete o estoque de
livros da empresa e a interao auxilia o usurio na pesquisa pelo banco de dados, na
seleo dos livros e no processo de encomenda. A programao orientada a evento
essencial nessas aplicaes, e os programadores combinam linguagens como Java, Perl,
Python e SQL para implement-las.

Sistemas e Redes Programadores de sistemas projetam e realizam a manuteno do


software bsico que executa os sistemas componentes do sistema operacional, software
de rede, compiladores e depuradores de linguagens de programao, mquinas virtuais
e interpretadores, sistemas embarcados e de tempo real (em telefones celulares, ATMs,
aeronaves etc.). Esses tipos de software esto intimamente relacionados com as arquitetu-
ras de mquinas especficas, como a Intel/AMD x86 e a Apple/Motorola/IBM PowerPC.
A maioria desses programas escrita em C, que permite que os programadores che-
guem a um nvel muito prximo do nvel de linguagem de mquina. A programao de
sistemas geralmente feita com o uso do paradigma de projeto imperativo. Todavia, os
programadores de sistemas tambm devem lidar com a programao concorrente e orien-
tada a eventos, alm de ter uma preocupao especial com a correo do programa.
1.5 Sobre o Projeto de Linguagem 11

Assim, o exemplo primrio de uma linguagem de programao de sistemas C,


projetada no incio da dcada de 1970, em parte para suportar a codificao do sistema
operacional Unix. De fato, cerca de 95% do cdigo do sistema Unix escrito em C.
C++ foi projetado por Bjarne Stroustrup na dcada de 1980 como uma extenso de C
para fornecer novos recursos que suportariam a programao orientada a objeto.
O nome da linguagem de programao Ada vem de Ada Lovelace, que, acredita-se,
pode ter sido a primeira pessoa a programar computadores. No incio da dcada de 1800,
ela trabalhava com o inventor do computador Charles Babbage. O desenvolvimento da
Ada foi financiado pelo Departamento de Defesa dos Estados Unidos, cuja idia origi-
nal era ter uma nica linguagem que suportasse todas as aplicaes desse departamento,
especialmente aplicaes de controle e sistemas embarcados. Embora Ada nunca tenha
atingido esse objetivo especfico, seu projeto tem alguns recursos notveis. Hoje, Ada
oferece uma base robusta sobre a qual o compilador Spark disponibiliza ferramentas de
suporte correo de programas.
Linguagens de script so largamente usadas atualmente para uma diversidade de
tarefas de sistema. Por exemplo, um programa awk pode ser projetado rapidamente
para verificar a consistncia em um arquivo de senhas de uma mquina Unix. Algumas
das principais linguagens de script so awk (Kernighan e Pike, 1984), Perl (Wall et al.,
1996b), Tcl/Tk (Ousterhout, 1994) e Python (Lutz, 2001). Tratamos das linguagens de
script no Captulo 12, no qual a programao em Perl explorada em algum detalhe.

World Wide Web A rea mais dinmica para novas aplicaes de programao
a Internet, que o veculo que permite o comrcio eletrnico e uma ampla gama de apli-
caes acadmicas, governamentais e industriais. A noo de computao centrada na
Web e, conseqentemente, de programao centrada na Web motivada por um modelo
interativo no qual um programa permanece ativo continuamente esperando pelo prximo
evento, respondendo a este evento e retornando a seu estado continuamente ativo.
Linguagens de programao que suportam a computao centrada na Web usam
a programao orientada a evento, que promove a interao sistema-usurio. A
computao centrada na Web tambm usa o paradigma orientado a objeto, j que
diversas entidades que aparecem na tela do usurio so modeladas de forma mais
natural como objetos que enviam e recebem mensagens. As linguagens de programa-
o que suportam computao centrada na Web incluem Perl, PHP (Hughes, 2001),
Visual Basic, Java e Python.

1 .5 S OBR E O P RO JET O DE LIN GU AGEM


O projeto de linguagem de programao um desafio enorme. Os projetistas de
linguagem so as pessoas que criam um meio de linguagem que permite aos progra-
madores resolver problemas complexos. Para atingirem seu objetivo os projetistas
devem trabalhar dentro de diversas restries prticas e adotar objetivos especficos
que se combinam para fornecer um foco a esse desafio. Esta seo d uma viso geral
desses objetivos e dessas restries de projeto.

1.5.1 Restries de Projeto


Os seguintes elementos de configuraes computacionais impem importantes
restries para os projetistas de linguagem:
Arquitetura
Configurao tcnica
Padres
Sistemas legados
12 Captulo 1 Viso Geral

Arquitetura Linguagens de programao so projetadas para computadores. Este


fato tanto uma bno quanto uma maldio para os projetistas de linguagem. uma
bno porque uma linguagem bem projetada e implementada pode melhorar muito a
utilidade do computador em um domnio de aplicao. uma maldio porque a maio-
ria dos projetos de computadores nas ltimas dcadas foram limitados pelas idias de
arquitetura do modelo clssico de von Neumann-Eckert, discutido anteriormente. Muitas
linguagens, como Fortran, Cobol e C, esto de acordo com essa arquitetura, enquanto
outras, como Lisp, no esto.
Por alguns anos se tornou interessante considerar a idia de arquitetura computa-
cional como derivada do projeto de linguagem, em vez de como uma precursora. Na
dcada de 1960, Burroughs projetou a B5500, que tinha uma arquitetura de pilha espe-
cialmente apropriada para executar programas em Algol. Outro esforo produziu o tipo
de mquinas Lisp, que emergiram no incio da dcada de 1980. Essas mquinas eram
configuradas de modo que os programas em Lisp fossem executados eficientemente
nelas e tiveram certo grau de sucesso durante alguns anos. Entretanto, as arquiteturas de
mquina Lisp foram ofuscadas no final da dcada de 1980 pelo advento das arquiteturas
RISC (Reduced Instruction Set Computer Computadores com Conjunto Reduzido de
Instrues), nas quais programas em Lisp podiam ser implementados eficientemente.
Assim, quando consideramos as virtudes de diversas opes de projeto de lin-
guagens, sempre ficamos restritos pela necessidade de implementar a linguagem efi-
cientemente e de forma eficaz dentro das restries impostas pelas variaes atuais do
modelo clssico de von Neumann. A noo de que um bom projeto de linguagem pode
levar a uma arquitetura computacional radicalmente nova e comercialmente vivel
possivelmente no provvel.

Configurao Tcnica As linguagens so restritas no apenas pelos limites das arqui-


teturas de computadores, mas tambm devem satisfazer a outras restries impostas pelas
configuraes tcnicas nas quais elas so usadas: a rea de aplicao, o sistema operacional,
o IDE (Integrated Development Environment Ambiente de Desenvolvimento Integrado), a
rede e as outras preferncias de uma determinada comunidade de programao. Por exem-
plo, Fortran utilizado em determinadas plataformas por diferentes compiladores para se
adaptar s necessidades de programadores cientficos. Estes programadores trabalham em
diversas profisses que usam seus estilos prprios de projeto de software, ferramentas e
(acima de tudo) suas prprias linguagens naturais para comunicar-se entre eles. Essa viso
do cenrio complexo para o projeto de linguagens resumida na Figura 1.3.
Algumas linguagens so intencionalmente mais de uso geral no seu projeto, tendo
como objetivo ser teis aos interesses de uma ampla gama de aplicaes. Por exemplo,
Ada (The Department of Defense, 1983) foi projetada para ser til para todas as aplicaes
suportadas pelo Departamento de Defesa dos Estados Unidos, enquanto Cobol (Brown,
1977) foi projetada para suportar todas as aplicaes orientadas a negcio. Embora Cobol
tenha sido moderadamente bem-sucedida no seu objetivo, Ada o foi muito menos.
Outras linguagens so projetadas para serem de propsito especfico na sua na-
tureza. Por exemplo, Prolog (Clocksin e Mellish, 1997) foi projetada para servir aos
interesses restritos do processamento de linguagem natural, s provas de teoremas e
aos sistemas especializados. C foi projetada principalmente para suportar os interesses
da programao de sistemas, embora tenha, desde ento, sido adotada por uma faixa mais
ampla de aplicaes. Spark/Ada e JML foram projetadas, respectivamente, para supor-
tar a prova formal de correo de programas em Ada e Java.

Padres Quando uma linguagem de programao tem um uso suficientemente am-


plo entre programadores, geralmente se inicia um processo de padronizao, ou seja,
feito um esforo para definir um padro independente da mquina da linguagem qual
1.5 Sobre o Projeto de Linguagem 13

| Figura 1.3 Nveis de Abstrao


na Computao
Linguagem natural

rea de aplicao

Linguagem de programao

Compilador/interpretador

Interface de usurio (IDE)

Sistema operacional

Linguagem de mquina

todos os seus programadores devem aderir. A padronizao da linguagem geralmente a


estabiliza em diferentes plataformas e grupos de programao, viabilizando a portabili-
dade dos programas.
As duas maiores organizaes que supervisionam e mantm padres de lingua-
gens de programao so a American National Standards Institute (ANSI) e a Inter-
national Standards Organization (ISO). Diversas linguagens foram padronizadas com
o passar dos anos desde que o processo de padronizao de linguagens comeou.
Algumas delas, com suas datas de padronizao, so:

ANSI/ISO Cobol (2002)


ISO Fortran (2004)
ISO Haskell (1998)
ISO Prolog (2000)
ANSI/ISO C (1999)
ANSI/ISO C++ (2003)
ANSI/ISO Ada (2005)
ANSI Smalltalk (2002)
ISO Pascal (1990)

O processo de padronizao de linguagens complexo e lento, com muito tempo de


envolvimento da comunidade e geralmente tem uma definio volumosa da lingua-
gem-padro como resultado.
A padronizao de linguagens de programao acompanhada da padronizao
de conjuntos de caracteres (por exemplo, os conjuntos ASCII e UNICODE) e biblio-
tecas (por exemplo, a C++ Standard Template Library) que suportam diretamente as
atividades de programao.
O valor da padronizao para a comunidade que os projetistas de software e hardware
participam do processo e se comprometem com suas implementaes de compiladores e in-
14 Captulo 1 Viso Geral

terpretadores para que estejam em conformidade com o padro. Tal conformidade essen-
cial na manuteno da portabilidade em diferentes plataformas de hardware e compiladores.
Alguns tm argumentado que a padronizao de linguagens uma influncia negativa
porque inibe inovaes no projeto de linguagens, ou seja, verses-padro das linguagens
tendem a durar muito tempo, perpetuando, dessa forma, a vida de caractersticas pobres
junto aos seus recursos mais valiosos. Entretanto, os padres ISO e ANSI so revisados a
cada cinco anos, o que fornece certa proteo contra a obsolescncia prolongada.
Mais informaes sobre padres especficos de uma linguagem e o prprio proces-
so de padronizao podem ser encontradas em www.ansi.org e www.iso.org.

Sistemas Legados bem sabido que a maior parte do tempo de um programador


gasto na manuteno de sistemas legados. Tais sistemas so aqueles artefatos de software
que foram projetados e implementados pela equipe anterior de programao, mas que so
mantidos e atualizados pela equipe atual. A maior parte de cdigo em sistemas legados
est provavelmente escrita em Cobol, a linguagem de programao predominante para
sistemas de informao nas ltimas quatro dcadas.
Para suportar a manuteno de cdigo legado, verses atualizadas e melhoradas
de linguagens antigas devem ser compatveis para trs com seus predecessores, ou
seja, os programas antigos devem continuar a ser compilados e executados quando
novos compiladores forem desenvolvidos para a verso atualizada. Assim, todos os
recursos semnticos e sintticos, mesmo os que forem menos desejveis a partir do
ponto de vista esttico, no podem ser retirados sem prejudicar a integridade do c-
digo legado.
Por esse motivo, linguagens de programao mais antigas se tornaram sobre-
carregadas com recursos quando novas verses surgiram; as linguagens raramente se
tornam mais compactas enquanto se desenvolvem. Isso particularmente verdadeiro
para Fortran, Cobol e C++, que foi projetada como uma verdadeira extenso de C para
manter compatibilidade para trs com cdigo legado.
O projeto de Java embora muitos dos seus recursos lembrem C++ , afastou-se
dessa tradio. Como tema central, os projetistas de Java queriam liberar sua nova
linguagem de ter que suportar os recursos menos desejveis de C++, de forma que eles
simplesmente os cortaram. O resultado foi uma linguagem mais compacta, pelo menos
temporariamente, j que verses recentes de Java adicionaram muitos recursos novos
sem retirar um conjunto comparativamente grande de recursos obsoletos. Talvez seja
inevitvel que quando qualquer linguagem amadurece se torne naturalmente mais carre-
gada de recursos para atender s crescentes demandas do domnio das aplicaes.

1.5.2 Resultados e Objetivos


luz desses requisitos, somos levados a fazer duas perguntas importantes:

1 Como uma linguagem de programao surge e se torna bem-sucedida?


2 Quais so as caractersticas-chave que tornam uma linguagem de programao
ideal?

Examinando rapidamente o passado, primeiro observamos que algumas lingua-


gens de programao bem-sucedidas foram projetadas por indivduos, outras, por comits
de toda uma indstria, e outras ainda foram o produto de um forte apoio por parte dos
seus desenvolvedores comerciais. Por exemplo, Lisp e C++ foram projetadas principal-
mente por indivduos (John McCarthy e Bjarne Stroustrup, respectivamente), enquanto
1.5 Sobre o Projeto de Linguagem 15

Algol, Cobol e Ada foram projetadas por comits1. PL/I, Java e C# so produtos dos
seus desenvolvedores comerciais corporativos (IBM, Sun e Microsoft, respectivamen-
te). Assim, no est claro se o processo de projeto individual, comit ou alavancagem
corporativa possui uma influncia total sobre o sucesso de um projeto de linguagem.
J que este estudo tem como objetivo preparar os leitores para avaliar e comparar
linguagens de programao de modo geral, importante termos um pequeno conjunto
de caractersticas-chave por meio das quais voc possa fazer isso. Ns as chamaremos de
objetivos de projeto, j que tm servido como medidas eficazes de projetos bem-su-
cedidos de linguagens com o passar dos anos:
Simplicidade e legibilidade
Clareza nas ligaes
Confiabilidade
Suporte
Abstrao
Ortogonalidade
Implementao eficiente

Simplicidade e Legibilidade Os programas devem ser fceis de escrever. Tam-


bm devem ser compreensveis e fceis de ler pelo programador mediano. Uma lingua-
gem de programao ideal deve, ento, suportar a escrita e a leitura. Alm disso, deve ser
fcil de aprender e ensinar.
Algumas linguagens, como Basic, Algol e Pascal, foram intencionalmente pro-
jetadas para facilitar a clareza de expresso. Basic, por exemplo, tinha um conjunto
de instrues muito pequeno. Algol 60 tinha uma linguagem de publicao que for-
necia um formato-padro para a composio de programas que apareciam em artigos
publicados. Pascal foi projetada explicitamente como uma linguagem para ensino,
com recursos que facilitavam o uso de princpios da programao estruturada.
Outras linguagens foram projetadas para minimizar o nmero total de teclas
pressionadas que era necessrio para expressar um algoritmo ou a quantidade de ar-
mazenamento que o compilador requereria. Com certeza, os projetistas de APL e C
valorizaram essas economias.

Clareza nas Ligaes Um elemento de linguagem est conectado a uma proprie-


dade quando essa propriedade definida para ele. Uma boa linguagem deve ser muito
clara sobre quando ocorre a ligao principal para cada elemento com sua propriedade.
Aqui esto os principais tempos de ligao.
Tempo de definio da linguagem: Quando a linguagem definida, os tipos bsicos
de dados so ligados a rtulos especiais denominados palavras reservadas, que os
representam. Por exemplo, nmeros inteiros (integers) so ligados ao identificador
int, e nmeros reais (fracionrios) so ligados a float na linguagem C.
Tempo de implementao da linguagem: Quando o compilador ou interpretador
da linguagem escrito, os valores so ligados a representaes de mquina. Por
exemplo, o tamanho de um valor int em C determinado no momento da imple-
mentao da linguagem.

1. No caso de Ada, o processo de projeto tambm teve um elemento de competio diversos projetos
competindo foram avaliados, e Ada surgiu como a linguagem mais propcia para satisfazer s necessidades
do Departamento de Defesa dos Estados Unidos da Amrica.
16 Captulo 1 Viso Geral

Tempo de escrita do programa: Quando os programas so escritos em algumas lin-


guagens, os nomes de variveis so ligados a tipos, os quais permanecem ligados
com esses nomes por toda a execuo do programa. Por exemplo, uma varivel
pode ser ligada ao seu tipo quando ela declarada, como na declarao

int x;

que liga a varivel x ao tipo int.


Tempo de compilao: Quando os programas so compilados, os comandos e as
expresses desses programas so ligados a seqncias de instrues em linguagem
de mquina equivalente.
Tempo de carga do programa: Quando o cdigo de mquina carregado, as variveis
estticas so atribudas a endereos fixos de memria, a pilha de tempo de execuo
alocada a um bloco de memria, assim como o prprio cdigo de mquina.
Tempo de execuo do programa: Quando os programas esto sendo executados,
as variveis so ligadas a valores, como na execuo da atribuio x = 3.
s vezes, um elemento pode ser ligado a uma propriedade em qualquer uma dentre
diversas alternativas de tempos nessa srie contnua. Por exemplo, a ligao de um valor
com uma constante pode ser feita em tempo de compilao/carga de um programa ou no
incio da execuo. Quando tais escolhas so possveis, a noo de ligao precoce sig-
nifica simplesmente que um elemento ligado a uma propriedade o mais rpido possvel
(em vez de mais tarde) nessa srie de tempo. A ligao tardia significa um atraso at a
ltima oportunidade possvel.
Como veremos no Captulo 4, a ligao precoce leva a uma melhor deteco de
erros e geralmente menos custosa. Todavia, ligaes tardias levam a uma maior
flexibilidade de programao (conforme ilustrado no Captulo 14). De modo geral,
um projeto de linguagem deve considerar todas essas alternativas, e decises sobre
ligaes so tomadas, em ltima instncia, pelo projetista da linguagem.

Confiabilidade O programa se comporta da mesma forma toda vez que ele exe-
cutado com os mesmos dados de entrada? Ele se comporta da mesma forma quando
executado em diferentes plataformas? O seu comportamento pode ser especificado inde-
pendentemente de modo a permitir a sua verificao formal (ou informal)?
Particularmente pertinente a essas questes a necessidade de se projetar meca-
nismos apropriados de manipulao de excees na linguagem. Alm disso, lingua-
gens que restrinjam o uso de aliases e vazamento de memria, que suportem tipagem
forte, tenham sintaxe e semntica bem definidas e que suportem a verificao e vali-
dao de programas tm uma vantagem nessa categoria.

Suporte Uma boa linguagem de programao deve ser facilmente acessvel por algum
que queira aprend-la e instal-la no seu prprio computador. De forma ideal, seus compi-
ladores devem ser de domnio pblico, em vez de serem propriedade de uma corporao e
caros para se obter. A linguagem deve ser implementada em mltiplas plataformas. Cursos,
livros-texto, tutoriais e um grande nmero de pessoas familiarizadas com a linguagem so
vantagens que ajudam a preservar e estender a vitalidade de uma linguagem.
Questes relacionadas a custo podem ser de maior preocupao para alunos e
programadores individuais, em vez de para funcionrios governamentais e corporati-
vos, cujos custos de software so geralmente cobertos por suas empresas. A histria
das linguagens de programao tem apresentado sucesso em ambos os lados. Por
exemplo, C, C++ e Java so linguagens no proprietrias, disponveis no domnio
pblico para uma ampla variedade de plataformas. Por outro lado, C# e Eiffel so
1.5 Sobre o Projeto de Linguagem 17

linguagens suportadas por empresas cujo uso restringido pelo seu custo e as plata-
formas/IDEs nas quais elas so implementadas.

Abstrao A abstrao um aspecto fundamental do processo de projeto de pro-


gramas. Os programadores gastam muito tempo construindo abstraes, tanto de dados
quanto procedurais, para explorar a reutilizao de cdigo e evitar reinvent-lo. Uma boa
linguagem de programao suporta to bem a abstrao de dados e procedural que a
ferramenta de projeto preferida na maioria das aplicaes.
Bibliotecas que acompanham linguagens modernas de programao comprovam
a experincia acumulada de programadores na construo de abstraes. Por exemplo,
as bibliotecas de classes Java contm implementaes de estruturas de dados bsicos
(como vetores e pilhas) que, em linguagens anteriores, tinham de ser projetadas expli-
citamente pelos prprios programadores. Com qual freqncia ns reinventamos um al-
goritmo de ordenao ou uma estrutura de dados de lista encadeada que provavelmente
j tenha sido implementado milhares de vezes antes?

Ortogonalidade Uma linguagem dita ortogonal se os seus comandos e recursos


so construdos sobre um conjunto pequeno e mutuamente independente de operaes
primitivas. Quanto mais ortogonal uma linguagem, menos regras excepcionais so neces-
srias para escrever programas corretos. Assim, programas em uma linguagem ortogonal
muitas vezes tendem a ser mais simples e mais claros do que aqueles em uma linguagem
no-ortogonal.
Como um exemplo de ortogonalidade, considere a passagem de argumentos em
uma chamada de funo. Uma linguagem integralmente ortogonal permite que qualquer
tipo de objeto, incluindo uma definio de funo, seja passado como argumento. Vere-
mos exemplos disso na nossa discusso sobre programao funcional no Captulo 14.
Outras linguagens restringem os tipos de objetos que podem ser passados em
uma chamada. Por exemplo, a maioria das linguagens imperativas no permite que
definies de funes sejam passadas como argumentos e, portanto, no so ortogo-
nais em relao a isso.
A ortogonalidade tende a se correlacionar com a simplicidade conceitual, j que
o programador no precisa manter muitas regras excepcionais na sua cabea. Alan
Perlis coloca isso da seguinte maneira:

melhor fazer cem funes operarem em uma estrutura de dados do que dez
funes em dez estruturas de dados.

Por outro lado, a no-ortogonalidade, muitas vezes est relacionada eficincia


porque suas regras excepcionais eliminam as opes de programao que consumi-
riam tempo ou espao.

Implementao Eficiente Os recursos e as construes de uma linguagem devem


permitir uma implementao prtica e eficiente em plataformas contemporneas.
Como um contra-exemplo, Algol 68 era um projeto de linguagem elegante, mas
suas especificaes eram to complexas que era (quase) impossvel implement-las
efetivamente. Verses iniciais de Ada foram criticadas pelas suas caractersticas de
tempo de execuo ineficientes, j que Ada foi projetada, em parte, para suportar
programas que seriam executados em tempo real. Programas embarcados em siste-
mas como aeronaves tinham de responder imediatamente a uma mudana brusca nos
valores de entrada, como vento e velocidade. Programas em Ada ficavam na interface
entre os sensores que forneciam as leituras e os mecanismos que deveriam responder
a elas. Implementaes iniciais de Ada ficavam longe desses ambiciosos objetivos de
desempenho. Os crticos mais severos do desempenho de Ada ficaram conhecidos por
dizer Bom, existe tempo real e tempo de Ada!
18 Captulo 1 Viso Geral

Implementaes iniciais de Java receberam crticas por esse mesmo motivo, em-
bora refinamentos recentes no sistema de compilao Java da Sun tenham melhorado
seu desempenho em tempo de execuo.

1 .6 COM PILAD O RES E M Q U IN AS V IRT U AIS


Qualquer implementao de uma linguagem de programao requer que os pro-
gramas nessa linguagem sejam analisados e ento traduzidos em um formato que
possa ser:
1 Executado por um computador (por exemplo, uma mquina real) ou
2 Executado por um interpretador (por exemplo, um software que simule uma
mquina virtual e seja executado em uma mquina real).
A traduo do primeiro tipo , muitas vezes, chamada de compilao, enquanto
a traduo do segundo tipo chamada interpretao.

Compiladores O processo de compilao traduz um programa-fonte para a lingua-


gem de um computador. Depois, o cdigo de mquina resultante pode ser executado
nesse computador. Esse processo mostrado na Figura 1.4. Por exemplo, Fortran, Cobol,
C e C++ so linguagens compiladas tpicas.
Os cinco passos do processo de compilao so anlise lxica, anlise sinttica, verifi-
cao de tipos, otimizao de cdigo e gerao de cdigo. Os trs primeiros passos se rela-
cionam a encontrar e relatar erros para o programador. Os dois ltimos passos se relacionam
gerao de cdigo de mquina eficiente para ser executado no computador-alvo.
O cdigo de mquina de um programa compilado se combina com sua entrada
para ser executado em um passo separado que segue a compilao. Erros em tempo
de execuo geralmente so rastreveis no programa-fonte por meio do uso de um
depurador.

Programa-
fonte

Analisador Analisador Verificador Otimizador Gerador


lxico sinttico de tipos de cdigo de cdigo

Cdigo
de mquina

Entrada Computador

Sada

| Figura 1.4 O Processo de Compilao e Execuo


1.6 Compiladores e Mquinas Virtuais 19

Programa-
fonte
Sintaxe
abstrata
Analisador Analisador Verificador
Interpretador
lxico sinttico de tipos

Entrada
Computador

Sada

| Figura 1.5 Mquinas Virtuais e Interpretadores

Teremos algumas oportunidades de explorar os trs primeiros passos da compi-


lao nos Captulos 3 e 6. Entretanto, os assuntos de gerao e otimizao de cdigo
so normalmente estudados em um curso de compilador, e no sero abordados
neste texto.

Mquinas Virtuais e Interpretadores Outras linguagens so implementadas


por meio do uso de um processo interpretativo, conforme mostrado na Figura 1.5. Aqui,
o programa fonte traduzido para uma forma abstrata intermediria, que ento executa-
da de modo interpretado. Lisp e Prolog, por exemplo, so, muitas vezes, implementadas
usando interpretadores (embora compiladores para essas linguagens tambm existam).
Como a Figura 1.5 sugere, os primeiros trs passos de um compilador tambm
acontecem em um interpretador. Entretanto, a representao abstrata do programa que
sai desses trs passos se torna o objeto de execuo de um interpretador. O prprio
interpretador um programa que executa os passos do programa abstrato enquanto
executado em uma mquina real. O interpretador geralmente escrito em uma lingua-
gem distinta da que est sendo interpretada.
s vezes, uma linguagem projetada de modo que o compilador seja escrito uma
s vez, direcionando o cdigo para uma mquina virtual abstrata, e ento essa mquina
virtual implementada por um interpretador em cada uma das diferentes mquinas reais.
Este o caso de Java, cuja mquina abstrata foi chamada de Java Virtual Machine
(JVM) (Lindholm e Yellin, 1997). Ao fazer essa escolha, os projetistas da linguagem
Java desistiram um pouco da eficincia (j que cdigo interpretado geralmente requer
mais recursos do que cdigo de mquina) em favor de flexibilidade e portabilidade, ou
seja, qualquer mudana na especificao da linguagem Java pode ser implementada
pela alterao de um nico compilador, em vez de uma famlia de compiladores.
Uma vantagem importante da compilao sobre a interpretao que o desempe-
nho em tempo de execuo de um programa compilador geralmente mais eficiente (em
tempo e espao) do que seu desempenho interpretado. Por outro lado, a qualidade da
interao com o sistema que o programador desfruta pode ser melhor com um interpre-
tador do que com um compilador. J que o desenvolvimento de programas interativo,
segmentos individuais de programas (funes) podem ser testados na medida em que so
20 Captulo 1 Viso Geral

projetados, em vez de esperar at que o programa inteiro no qual eles devem ser inse-
ridos seja completado2.
Verses posteriores de Java obtiveram de volta um pouco da eficincia perdida
pela insero de um compilador just-in-time (JIT) da JVM. Esse recurso permite que
o byte code da JVM seja traduzido durante a execuo em cdigo nativo da mquina
hospedeira antes que seja executado.
O conceito de mquina virtual valioso por outros motivos que podem compen-
sar sua inerente perda de eficincia. Por exemplo, oportuno implementar uma lin-
guagem e seu interpretador usando uma mquina virtual existente, visando ao projeto
ou experimentao com a prpria linguagem. De forma semelhante, til estudar
fundamentos e paradigmas de projeto de linguagens usando um interpretador dispon-
vel que facilite a experimentao e a avaliao de programas nessa linguagem.
O conceito de mquina virtual possui um valor prtico imediato neste estudo,
ou seja, voc ter acesso a um interpretador para um pequeno subconjunto C deno-
minado Clite. O uso do Clite facilita muito o nosso trabalho porque elimina detalhes
especficos de mquina que muitas vezes podem esconder os princpios e as idias que
seriam ensinadas. Por exemplo, no estudo da sintaxe da linguagem, voc pode exerci-
tar o interpretador Clite para explorar a estrutura sinttica de diferentes elementos da
linguagem como expresses aritmticas e laos.

1 . 7 RE S UM O
O estudo de linguagens de programao inclui princpios, paradigmas e tpicos
especiais. Os princpios so estudados tanto conceitualmente quanto na prtica, por
intermdio do interpretador Clite.
O domnio de um ou mais novos paradigmas programao imperativa, orientada
a objeto, funcional ou lgica tambm importante para este estudo. Essa atividade nos
ajuda a apreciar uma faixa mais ampla de aplicaes computacionais e descobrir aborda-
gens para a resoluo de problemas com os quais ainda no estamos familiarizados.
A investigao de um ou mais tpicos especiais manipulao de eventos, con-
corrncia ou correo nos permite examinar com cuidado trs caractersticas es-
pecficas de projeto de linguagem e os desafios de programao que envolvem sua
utilizao efetiva.
No total, esperamos que este estudo ajude a ampliar sua viso e suas habilidades
tcnicas no vasto cenrio das linguagens de programao. Em especial, voc deve es-
perar obter:
A apreciao do uso de ferramentas prticas para examinar os princpios de projeto de
linguagem.
A apreciao do valor de diferentes paradigmas de programao que sejam especial-
mente poderosos em domnios especficos de aplicaes.
Experincias em laboratrio com novas linguagens e ferramentas de projeto, tanto para
testar os princpios quanto para dominar novas tcnicas de resoluo de problemas.
Para apoiar este estudo voc pode ocasionalmente visitar o site do livro, que traz
tambm outros materiais de apoio pedaggico e de aprendizado.

2. Entretanto, o desenvolvimento de depuradores e IDEs modernos para linguagens compiladas tem neutrali-
zado substancialmente essa vantagem nos ltimos anos.
1.7 Resumo 21

E XE RC CI O S
1.1 Uma pesquisa on-line sobre linguagens de programao produzir links para fontes de informa-
o confiveis de todas as linguagens de programao importantes do passado e do presente. Para
cada uma das seguintes linguagens, use a Internet para aprender algo sobre ela. Escreva, com
suas prprias palavras, um breve resumo (um pargrafo) das suas caractersticas diferenciais,
assim como sua relao histrica com outras linguagens que a precederam ou seguiram.
(a) Eiffel
(b) Perl
(c) Python

1.2 D um exemplo de um comando em C, C++ ou Java, que seja especialmente ilegvel. Reescreva
esse comando em um estilo mais legvel. Por exemplo, voc j viu a expresso A[i++] em um
programa C/C++?

1.3 Cdigo ilegvel no algo exclusivo de C, C++ e Java. Considere as seguintes opinies forte-
mente defendidas sobre as fraquezas de determinadas linguagens nas ltimas quatro dcadas
ou mais:
praticamente impossvel ensinar boa programao a alunos que tiveram uma exposio
anterior a BASIC; como programadores em potencial, eles ficam mentalmente mutilados
alm da esperana de regenerao. E. Dijkstra
O uso de COBOL enfraquece a mente; seu ensinamento, portanto, deve ser considerado
como ofensa criminal. E. Dijkstra
APL um equvoco levado perfeio. a linguagem do futuro para as tcnicas de progra-
mao do passado: ela cria uma nova gerao de programadores preguiosos. E. Dijkstra
No existe agora, e nem existir, uma linguagem de programao na qual seja difcil
escrever programas ruins. L. Flon
(a) Dijkstra parece no ter muita considerao com Basic, Cobol ou APL. Entretanto, ele teve
muita considerao com Algol e suas sucessoras. Faa uma leitura eficaz na Internet para de-
terminar quais as caractersticas gerais que Algol possua que a tornava superior a linguagens
como Basic, Cobol e APL.
(b) O que Flon quer dizer com essa ltima declarao? As linguagens de programao so ineren-
temente imperfeitas? Ou ele est sugerindo que os programadores so inerentemente ineptos?
Ou existe uma interpretao intermediria a essas duas? Explique.

1.4 D uma caracterstica de C, C++ ou Java que ilustre a ortogonalidade. D uma caracterstica
diferente da discutida no texto que ilustre a no-ortogonalidade.

1.5 Duas implementaes diferentes de uma linguagem so incompatveis se houver programas que
sejam executados de forma diferente (do resultados diferentes) sob uma implementao em
relao outra. Aps ler em um site e em outras fontes sobre verses anteriores de Fortran, voc
pode determinar se ela tinha verses incompatveis? De que forma especfica (tipo de comando,
tipo de dado etc.) esta incompatibilidade aparecia? O que pode ser feito para eliminar incompa-
tibilidades entre duas implementaes diferentes de uma linguagem?

1.6 O trabalho de padronizao para a linguagem C comeou em 1982 com um grupo de trabalho
ANSI, e o primeiro padro C foi concludo em 1989. Este trabalho foi posteriormente aceito
como um padro ISO em 1990 e continua sendo at hoje. Leia o suficiente na Internet para deter-
minar quais alteraes significativas foram feitas no padro C desde 1990.
22 Captulo 1 Viso Geral

1.7 Encontre o padro C++ na Internet. O que significa no conformidade quando o padro discute
uma caracterstica da linguagem suportada por um determinado compilador? Para o compilador
C++ no seu computador, h caractersticas que no possuem conformidade?

1.8 Aps aprender o suficiente no site de Java (java.sun.com) e em outras fontes, o que voc pode dizer
sobre a situao do trabalho de padronizao de Java pela ANSI e pela ISO neste momento?

1.9 Encontre a verso Python 2.4 na Internet. Quais so as caractersticas novas que essa verso
adiciona a Python 2.3? Quais caractersticas antigas da Python so eliminadas pela verso mais
nova, se houver alguma?

1.10 Compare duas linguagens que voc conhea usando os objetivos do projeto de linguagem
destacados na Seo 1.5.2. Para cada objetivo, determine qual das duas linguagens satisfaz
melhor esse objetivo e justifique sua concluso com um exemplo. Ou seja, na comparao de
C e Java voc poderia concluir que C possui implementao mais eficiente porque ele compila
em cdigo nativo em vez de em um interpretador.
Sintaxe 2
... uma linguagem que simples para ser analisada pelo compilador tambm
simples para ser analisada pelo programador humano.
Niklaus Wirth (1974)

VISO GERAL DO CAPTULO

2.1 GRAMTICAS 24
2.2 BNF ESTENDIDA 35
2.3 A SINTAXE DE UMA PEQUENA LINGUAGEM: CLITE 37
2.4 COMPILADORES E INTERPRETADORES 42
2.5 RELACIONANDO SINTAXE E SEMNTICA 48
2.6 RESUMO 54
EXERCCIOS 55

Quando pensamos em um programa, distinguimos sua forma (como ele escrito)


do seu significado (o que acontece quando o executamos). De modo geral, a forma de
um programa a sua sintaxe, enquanto o seu significado denominado semntica. Os
compiladores detectam erros de sintaxe nos nossos programas, como erros de tipo e de

23
24 Captulo 2 Sintaxe

declarao, enquanto os erros em tempo de execuo, como uma diviso por zero, so de
natureza semntica. Podemos definir sintaxe mais precisamente da seguinte maneira:
Definio: A sintaxe de uma linguagem de programao uma descrio precisa
de todos os seus programas gramaticalmente corretos.
A sintaxe pode ser descrita por um conjunto de regras, da mesma forma que uma
linguagem natural. Por exemplo, a regra de que uma sentena deve ser finalizada com
um terminador apropriado (um ponto final, um ponto de interrogao ou de exclamao)
parte da sintaxe da lngua portuguesa. Para uma linguagem de programao, uma des-
crio clara e concisa da sua sintaxe especialmente importante; sem tal especificao,
escritores de compiladores e programadores no trabalhariam bem.
Mtodos formais para definir a sintaxe tm sido usados desde o surgimento de Algol
no incio da dcada de 1960 [Naur (ed.), 1963] e tm sido usados para definir a sintaxe da
maioria das linguagens desde ento. Neste captulo, exploramos o uso tanto de mtodos
formais quanto de informais para definir a sintaxe de uma linguagem de programao nos
nveis lxico, concreto e de sintaxe abstrata.
Definio: A sintaxe lxica de uma linguagem define as regras para os smbolos b-
sicos, incluindo identificadores, literais (por exemplo, inteiros e pontos flutuantes),
operadores e pontuao.
Definio: A sintaxe concreta de uma linguagem se refere representao real
desses programas usando smbolos lxicos como seu alfabeto.
Definio: A sintaxe abstrata considera apenas as informaes essenciais dos
programas, sem se preocupar com idiossincrasias sintticas como pontuao ou
parnteses.
A sintaxe abstrata uma ferramenta muito til para relacionar as expresses sintti-
cas de uma linguagem com a sua semntica, como veremos em captulos posteriores.
Para auxiliar a ilustrar nosso estudo, introduzimos a minilinguagem imperativa sim-
ples Clite, que ser uma ferramenta til para examinarmos as propriedades sintticas e
semnticas de linguagens de programao. Clite possui as seguintes caractersticas:
um subconjunto de C/C++, de modo que os programas escritos em Clite sero
compilados e executados com o uso de um compilador padro C/C++, s precisan-
do de alguns #include padro.
Diferentemente de C e C++, Clite possui uma especificao sinttica concisa, que
facilita a discusso de conceitos fundamentais de sintaxe.
A simplicidade de Clite facilita a implementao de um interpretador para ela,
fornecendo um suplemento prtico para a aprendizagem.

2 .1 G R A MT ICAS
A sintaxe de uma linguagem pode ser amplamente especificada por meio de um for-
malismo denominado gramtica. Uma gramtica escrita em uma linguagem de descri-
o de linguagens, ou metalinguagem, e seu propsito definir todas as strings (cadeias
de caracteres) permitidas que puderem formar um programa sintaticamente correto. Gra-
mtica uma metalinguagem fundamentada em uma teoria formal desenvolvida pelo lin-
gista Noam Chomsky (1957), que definiu quatro nveis de gramtica, conhecidos como
regular, livre de contexto, sensvel ao contexto e irrestrita. A hierarquia de Chomsky
apresentada com mais detalhes na Seo 3.1.
2.1 Gramticas 25

Definio: Uma gramtica livre de contexto possui um conjunto de produes P,


um conjunto de smbolos terminais T e um conjunto de smbolos no-terminais N,
um dos quais, S, distinguido como o smbolo inicial.
Definio: Uma produo gramatical possui a forma A , em que A um sm-
bolo no-terminal e uma string de smbolos no-terminais e terminais.
Uma forma de gramtica livre de contexto, denominada Forma de Backus-Naur
(abreviada como BNF, de Backus-Naur Form) tem sido amplamente usada para definir a
sintaxe das linguagens de programao.

2.1.1 Gramticas na Forma de Backus-Naur (BNF)


Em 1960, a BNF foi adaptada da teoria de Chomsky por John Backus e Peter Naur
para expressar uma definio sinttica formal para a linguagem de programao Algol
[Naur (ed.), 1963]. Da mesma forma que em muitos textos, usamos o termo gramtica
BNF como sinnimo de gramtica livre de contexto. Veja na Observao (pg. 26) uma
discusso mais completa sobre as diferenas entre as duas.
Uma produo uma regra para reescrita que pode ser aplicada a uma string de sm-
bolos denominada forma sentencial. Uma produo interpretada da seguinte maneira:
o smbolo A , muitas vezes, chamado de lado esquerdo, enquanto a string chamada
de lado direito da produo. Nas gramticas BNF, os conjuntos de smbolos terminais e
no-terminais so disjuntos.
Quando uma gramtica BNF usada para definir a sintaxe de uma linguagem de pro-
gramao, os no-terminais N identificam as categorias gramaticais da linguagem como
Identificador, Inteiro, Expresso, Comando e Programa. O smbolo inicial S identifica a
principal categoria gramatical sendo definida pela gramtica (normalmente Programa) e
geralmente definido na primeira produo. Os smbolos terminais T formam o alfabeto
bsico a partir do qual os programas so construdos.
Para ilustrar essas idias, aqui est um par de produo que define a sintaxe da cate-
goria gramatical dgitoBinrio:

dgitoBinrio 0
dgitoBinrio 1

Este par define um dgitoBinrio como 0 ou 1, mas nada mais alm disso. Os smbo-
los no-terminais so todos os smbolos que aparecem no lado esquerdo de, pelo menos,
uma produo. Para a gramtica anterior, dgitoBinrio o nico no-terminal. Os smbo-
los terminais so todos os outros smbolos que aparecem nas produes; para a gramtica
anterior, 0 e 1 so os smbolos terminais.
Quando em uma srie de produes todas tiverem o mesmo smbolo no-terminal
nos seus lados esquerdos, elas podem ser combinadas em uma nica produo. Por exem-
plo, as duas produes anteriores podem ser abreviadas como:

dgitoBinrio 0 | 1

Neste caso, as alternativas so separadas por uma barra vertical ( | ), que significa
literalmente ou, de forma que a interpretao permanece a mesma que a do par de
produes original. Neste exemplo, tanto a seta para a direita quanto a barra vertical so
metassmbolos, que so smbolos que fazem parte da metalinguagem e no fazem parte
da linguagem que est sendo definida.
26 Captulo 2 Sintaxe

Observao
Forma de Backus-Naur
A BNF comeou como uma metalinguagem especfica usada para definir a sintaxe da
Algol 60. Os no-terminais consistiam de nomes que eram escritos entre sinais ( e ),
que tambm apareciam no lado esquerdo de pelo menos uma produo. Todos os outros
smbolos menos os metassmbolos :: e | eram interpretados como smbolos terminais.
Assim, a gramtica para um Inteiro apareceria na BNF como:

<inteiro> :: = <dgito> | <inteiro> | <dgito>


<dgito> :: = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Mais tarde, um padro ISO (ISO 14977 [1996]) foi desenvolvido para gramticas BNF,
mas tem sido amplamente ignorado.
A notao BNF original tentou incorporar as limitaes do conjunto de caracteres
dos mecanismos de entrada da poca. Como a tecnologia dos computadores possibilitou o
uso dos dispositivos-padro de entrada para criar notaes mais expressivas, as notaes
mais limitadas foram abandonadas. Vemos um exemplo disso neste texto, que contm
smbolos matemticos cuja digitao pelos autores seria inimaginvel h 40 anos.
Em homenagem ao trabalho pioneiro de Backus e Naur no desenvolvimento da BNF
e no seu uso para definir a sintaxe de Algol, usamos o termo gramtica BNF como sin-
nimo do termo gramtica livre de contexto usado por Chomsky.

O lado direito de uma produo pode ser qualquer seqncia de smbolos terminais
e no-terminais, permitindo que uma variedade de construes interessantes sejam defi-
nidas concisamente. Considere a seguinte gramtica BNF Ginteiro, que define a categoria
gramatical Inteiro como uma seqncia de Dgitos decimais.

Inteiro Dgito | Inteiro Dgito


Dgito 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Aqui, a segunda produo define os dgitos decimais habituais. A primeira produo


permite que um Inteiro seja um Dgito apenas ou um Inteiro seguido por um Dgito. A
segunda alternativa ilustra o uso de recurso para definir uma seqncia de dgitos arbi-
trariamente longa. Esta produo define assim um Inteiro como uma seqncia de um ou
mais dgitos.

2.1.2 Derivaes
Para determinar se uma determinada string de smbolos pertence a uma categoria
gramatical, as regras de produo para essa categoria podem ser usadas para derivar a
string. Por exemplo, suponha que queiramos determinar se 352 um Inteiro. Para fazer
isso, podemos desenvolver uma derivao para essa string usando as regras de produo
da gramtica.

1 Primeiro, escreva o smbolo inicial Inteiro.


2 Substitua o Inteiro pela string Inteiro Dgito, o que permitido pela segunda alter-
nativa no lado direito da primeira regra de produo.
3 Substitua Inteiro Dgito por Inteiro nesta string, usando novamente a mesma regra,
produzindo a string Inteiro Dgito Dgito.
2.1 Gramticas 27

4 Substitua Dgito por Inteiro nessa string, desta vez usando a primeira alternativa da
primeira regra de produo, obtendo Dgito Dgito Dgito.
5 Substitua 3 como um tipo especfico de Dgito usando a segunda regra de produ-
o, obtendo a string 3 Dgito Dgito.
6 Substitua 5 pelo prximo Dgito nessa string, obtendo a string 3 5 Dgito.
7 Finalmente, substitua 2 por Dgito nessa string, derivando a string 352.

Tecnicamente, uma derivao uma seqncia de strings separadas pelo smbolo no


qual em cada etapa um no-terminal substitudo pelo lado direito de uma de suas pro-
dues. A primeira string da srie a categoria gramatical desejada e a ltima a string
a ser derivada. A seqncia anterior de passos , portanto, apropriadamente escrita da
seguinte maneira:

Inteiro Inteiro Dgito


Inteiro Dgito Dgito
Dgito Dgito Dgito
3 Dgito Dgito
35 Dgito
352

Aqui, cada instncia de denota a aplicao de uma nica regra de produo para
transformar uma string um passo mais prximo da string a ser derivada. Cada string em
tal derivao chamada de forma sentencial, a qual pode conter tanto smbolos terminais
quanto no-terminais. Por exemplo, a forma sentencial 3 Dgito Dgito ocorre no quarto
passo dessa derivao.
Assim, acabamos de derivar a string 352 como uma instncia da categoria gramati-
cal Inteiro de acordo com a gramtica Ginteiro. Nossa derivao uma prova por construo
de que

Inteiro * 352

Isso significa que a string 352 pode ser derivada da categoria gramatical Inteiro em
zero ou mais passos. O smbolo * uma instncia da notao de estrela de Kleene, na
qual a estrela * usada para denotar zero ou mais ocorrncias do smbolo sua esquerda,
neste caso, o smbolo de derivao ().
Agora podemos definir precisamente o significado do termo linguagem a partir de
um ponto de vista puramente sinttico:
Definio: A linguagem L definida por uma gramtica BNF G o conjunto de
todas as strings terminais que podem ser derivadas do smbolo inicial.
Por exemplo, a linguagem definida pela gramtica Ginteiro o conjunto de todas
as strings que so seqncias finitas de dgitos decimais, formando assim a catego-
ria sinttica Inteiro. A string 352 um membro especfico dessa linguagem, j que
podemos deriv-la usando as produes de Ginteiro. A derivao dada anteriormente
chamada de derivao mais esquerda porque, a cada etapa, substitumos o no-
terminal mais esquerda na forma seqencial por uma de suas alternativas. Muitas
outras seqncias de derivao so possveis. Por exemplo, poderamos substituir
28 Captulo 2 Sintaxe

o no-terminal mais direita em cada passo, em cujo caso teramos uma derivao
mais direita, ou seja:

Inteiro Inteiro Dgito


Inteiro 2
Inteiro Dgito 2
Inteiro 52
Dgito 52
352

2.1.3 rvores de Anlise


Outra forma de demonstrar que uma determinada string faz parte da linguagem defi-
nida por uma gramtica BNF descrever a derivao em uma forma grfica. Esta forma
chamada de rvore de anlise, na qual cada passo da derivao corresponde a uma nova
subrvore. Por exemplo, o passo da derivao:

Inteiro Inteiro Dgito

pode ser escrito como a subrvore:

Inteiro

Inteiro Dgito

A raiz de cada subrvore em uma rvore de anlise o modo correspondente ao


no-terminal para o qual a substituio est sendo feita em uma derivao, e os filhos
imediatos da esquerda para a direita correspondem ao lado direito da produo usada
nessa substituio.
Uma rvore de anlise completa para a derivao da string 352 mostrada na Figura
2.1. Observe que, uma vez que a rvore de anlise desenhada, a ordem dos passos da
derivao perdida; tanto as derivaes mais esquerda quanto as mais direita resultam
na mesma rvore.

| Figura 2.1 rvore de Anlise para 352 Inteiro


| como um Inteiro

Inteiro Dgito

Inteiro Dgito 2

Dgito 5

3
2.1 Gramticas 29

Expresso

Expresso + Termo

Expresso Termo 3

Termo 4

| Figura 2.2 Anlise da String 5 4 3

A anlise da rvore possui diversas outras caractersticas notveis. Primeiro, o n


da raiz da rvore de anlise sempre contm o smbolo inicial da gramtica (Inteiro, neste
exemplo). Em segundo lugar, cada n interno contm um no-terminal (neste caso, In-
teiro ou Dgito). Na verdade, sempre h o mesmo nmero de ns internos na rvore de
anlise que so passos () na derivao (6, neste exemplo). Em terceiro lugar, cada n
interno tem como seus descendentes diretos os elementos que aparecem no lado direito
de uma regra gramatical, na ordem da esquerda para a direita. Finalmente, as folhas da r-
vore de anlise so sempre smbolos terminais da gramtica. Ler os ns folha da esquerda
para a direita reconstri a string que for analisada (352, neste exemplo).
Resumindo, uma derivao uma representao linear simples de uma rvore de
anlise, e muitas vezes mais til quando a string derivada no possui uma estrutura
gramatical de interesse. Uma rvore de anlise preferida quando a estrutura gramatical
mais complexa, como veremos em diversos exemplos a seguir.
Considere a gramtica G0, que define a linguagem de expresses aritmticas tendo
os operadores e e os operandos inteiros de um dgito.1

Expresso Expresso Termo | Expresso Termo | Termo


Termo 0 | ...| 9 | (Expresso)

Uma rvore de anlise para a string 5 4 3 para a gramtica G0 demonstrada na


Figura 2.2.
A estrutura dessa rvore de anlise um pouco mais interessante do que para a gra-
mtica Ginteiro. Uma interpretao da esquerda para a direita das folhas dessa rvore su-
gere que a expresso avaliada como se tivesse sido escrita (5 4) 3. Esta interpre-
tao produz um resultado numrico diferente do que se calcularmos 5 ( 4 3). Isso
reflete o fato de que o operando da esquerda do operador uma Expresso da qual

1. Nas gramticas para uso humano, seqncias longas de alternativas so, muitas vezes, encurtadas. Assim,
0 | ... | 9 significa 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 na segunda regra de G0.
30 Captulo 2 Sintaxe

subseqentemente deriva a string 5 4. De forma semelhante, a string 3, que derivada


do Termo direita do operador , seu operando direito.

2.1.4 Associatividade e Precedncia


O exemplo da Seo 2.1.3 mostra que a estrutura de uma rvore de anlise pode ser
usada para interpretar o significado de uma string derivada. Aqui exploramos como a
estrutura de uma gramtica pode ser usada para definir a associatividade e a precedncia
de operadores em expresses.
Definio: Um operador possui maior precedncia do que outro se o primeiro
precisar ser analisado antes em todas as expresses sem parnteses que envolve-
rem apenas os dois operadores.
Na matemtica comum, nas expresses 5 4 3 e 5 4 3, a multiplicao
sempre avaliada antes da adio e, assim, possui maior precedncia. Assim, essas expres-
ses so equivalentes a 5 (4 3) e (5 4) 3, respectivamente.
Definio: Associatividade especifica se os operadores de precedncia igual de-
vem ser executados da esquerda para a direita ou da direita para a esquerda.
Na matemtica comum, o operador binrio menos possui associatividade esquer-
da, de modo que a expresso 5 4 3 equivalente a (5 4) 3.
Considere novamente a rvore de anlise para a string 5 4 3 apresentada na
Figura 2.2. Em um sentido muito real, a ocorrncia dos operadores especficos e
irrelevante na determinao da estrutura da rvore de anlise. Para a string 5 4 3
obtemos a mesma estrutura de rvore, exceto os operadores e que esto alternados. De
fato, a mesma estrutura de rvore ocorre para as strings 5 4 3 e 5 4 3. A partir
dessa observao, podemos concluir que os operadores e possuem a mesma prece-
dncia nessa gramtica. Isso acontece porque cada um dos operadores e ocorre como
uma alternativa na definio da Expresso.
Alm disso, o uso de recurso esquerda nas regras de produo para a Expresso na
gramtica G0 torna esses operadores associativos esquerda. Se a recurso direita tives-
se sido usada na gramtica, os operadores teriam sido de associao direita.
Com esta compreenso, podemos definir uma gramtica G1 um pouco mais interessante
para os operadores binrios , , *, /, % (resto) e ** (exponenciao)2. Em uso matemtico
normal, os primeiros quatro operadores so associativos pela esquerda, enquanto o opera-
4
dor de exponenciao associativo pela direita (ou seja, 23 significa 281 em vez de 84).

Expresso Expresso Termo | Expresso Termo | Termo


Termo Termo * Fator | Termo / Fator |
Termo % Fator | Fator
Fator Primrio ** Fator | Primrio
Primrio 0 | ... | 9 | (Expresso)

A precedncia determinada pelo comprimento da derivao mais curta a partir do


smbolo inicial at o operador, enquanto a associatividade esquerda ou direita deter-
minada pelo uso de recurso esquerda ou direita, respectivamente. Essas propriedades
so resumidas para a gramtica G1 na Tabela 2.1.

2. Linguagens do estilo de C, incluindo C++ e Java, no tm um operador de exponenciao. Embora o sinal


de intercalao fosse uma escolha natural, j est usado para o ou exclusivo de bit nessas linguagens. Assim,
adotamos o operador de exponenciao de Fortran (**).
2.1 Gramticas 31

| Tabela 2.1 Precedncia Associatividade Operadores

| Associatividade e 3 Direita **
| Precedncia 2 Esquerda * / %
| para a Gramtica G1 1 Esquerda +

Considere a rvore de anlise da Figura 2.3 para 4 ** 2 ** 3 5 * 6 7 que usa a


gramtica G1. Esta rvore de anlise interpreta a expresso como se tivesse sido escrita
((4 ** (2 ** 3)) (5 * 6)) 7.
A rvore de anlise demonstra claramente que a exponenciao associa da direita para a
esquerda. Embora no mostrado neste exemplo, a gramtica G1 permite que os operado-
res de multiplicao e diviso se associem da esquerda para a direita. Esta rvore tambm
mostra a precedncia de operadores da exponenciao versus adio e da adio versus
multiplicao. Para a gramtica G1, os leitores devem se convencer que esta a nica
rvore de anlise que pode ser derivada da string 4 ** 2 ** 3 5 * 6 7. Isso pode ser
provado experimentando-se diferentes estratgias e mostrando que nenhuma outra rvore
com a raiz Expresso e folhas 4 ** 2 ** 3 5 * 6 7 pode ser derivada usando G1.
Entender a relao entre a estrutura da gramtica e a associatividade e a precedncia
de seus operadores importante para projetistas de linguagem. Nem todas as linguagens
seguem os mesmos princpios quanto a isso. Por exemplo, todos os operadores aritm-
ticos de Smalltalk [Goldberg e Robson, 1989] associam estritamente da esquerda para a
direita, sem qualquer precedncia, enquanto os operadores APL [Iverson, 1962] associam
da direita para a esquerda, sem qualquer precedncia.

2.1.5 Gramticas Ambguas


Ao projetar uma gramtica, importante evitar fazer especificaes ambguas, ou
seja, que possam ser interpretadas de duas ou mais formas diferentes.
Definio: Uma gramtica ambgua se sua linguagem contiver pelo menos uma
string com duas ou mais rvores de anlise.
Normalmente, prefervel uma gramtica no ambgua porque s deve haver uma
interpretao vlida para cada string em cada linguagem. Entretanto, a ambigidade pode
ser tolervel em alguns casos, especialmente quando sua introduo reduz significativa-
mente o nmero de regras da gramtica, ou seja, quando h um balanceamento entre o
tamanho da gramtica e as informaes que ela est tentando expressar.
Analise a gramtica G2, que uma verso ambgua de G1:

Expresso Expresso Operador Expresso | (Expresso) | Inteiro


Operador | | * | / | % | **

A ambigidade dessa gramtica pode ser eliminada usando-se a Tabela 2.1.


Observe que tal gramtica se desenvolve apenas em uma nova alternativa quando cada
novo operador adicionado. Para linguagens como C, C++ e Java, o nmero de nveis de
precedncia e operadores torna suas gramticas bastante grandes. Uma alternativa inser-
o de precedncia na gramtica usar uma gramtica ambgua e ento especificar a prece-
dncia e a associatividade separadamente, por exemplo em uma tabela (veja a Tabela 2.1).
32 Captulo 2 Sintaxe

Expresso

Expresso + Termo

Expresso + Termo Fator

Termo Termo * Fator Primrio

Fator Fator 6 7

Primrio ** Fator 5

4 Primrio ** Fator

2 Primrio

| Figura 2.3 Anlise de 4 ** 2 ** 3 5 * 6 7 para a Gramtica G1

O nmero de regras da gramtica ligeiramente maior para linguagens como C++ por-
que elas contm operadores de prefixos e sufixos unrios e diversos tipos diferentes de
operandos. Entretanto, uma gramtica completamente no ambgua para expresses C++
teria um no-terminal distinto para cada nvel de precedncia, aumentando assim dema-
siadamente seu tamanho e sua complexidade.
Para mostrar que G2 por si s ambgua, s precisamos encontrar alguma string que
tenha duas rvores de anlise distintas. Observando que a linguagem da gramtica G0
um subconjunto da linguagem de G2, usamos o exemplo 5 4 3 na Figura 2.4 para
mostrar duas rvores de anlise distintas. Usando a interpretao natural de cada uma, a
rvore da esquerda avaliada como 4, enquanto a rvore da direita avaliada como 2.
2.1 Gramticas 33

Expresso Expresso

Expresso Operador Expresso Expresso Operador Expresso

Expresso Operador Expresso + 3 Expresso Operador Expresso

5 4 + 3

(a) (b)

| Figura 2.4 Anlise Ambgua de 5 4 3

Outro exemplo de ambigidade sinttica conhecido como o problema do seno


pendente. Analise o seguinte segmento de gramtica Gse:

ComandoSe se (Expresso) Comando |


se (Expresso) Comando seno Comando
Comando Atribuio | ComandoSe | Bloco
Bloco { Comandos }
Comandos Comandos Comando | Comando

No difcil ver que essas regras especficas produzem o seno pendente, j que elas
permitem duas ligaes diferentes de uma clusula seno a um se aninhado. Analise o
seguinte exemplo:

se (x<0)
se (y<0) y = y 1;
seno y = 0;

A gramtica Gse permite que a clusula seno seja ligada a qualquer um dos comandos
se. Estas rvores de anlise alternativas esto esboadas na Figura 2.5, na qual a linha
quebrada representa uma rvore omitida.
A questo aqui decidir a qual se o seno deve ser ligado. Se a anlise ligar a clusula
seno ao segundo comando se, y se tornar 0 sempre que x<0 e y<=0. Todavia, se a anlise
atribuir a clusula seno ao primeiro comando se, y se tornar 0 sempre que x>=0.
Algumas especificaes de linguagem, como C e C++ (Stroustrup, 1997), resol-
vem esta ambigidade dando uma descrio informal em lngua inglesa sobre como a
ligao deve ser feita. Por exemplo, tal descrio declararia que cada clusula seno
associada ao comando se precedente textualmente mais prximo. Se uma ligao di-
ferente for desejada, o programador deve inserir chaves { }. Por exemplo, para forar
34 Captulo 2 Sintaxe

ComandoSe ComandoSe

if ( Expresso ) Comando if ( Expresso ) Comando else Comando

x<0 ComandoSe x<0 ComandoSe y = 0;

if ( Expresso ) Comando else Comando if ( Expresso ) Comando

y<0 y = y1; y = 0; y<0 y = y1;

| Figura 2.5 A Ambigidade do Seno Pendente

a ligao da clusula seno com o primeiro se (mais externo) no exemplo anterior, o


programador inseriria chaves da seguinte forma:

se (x<0)
{ se (y<0) y = y 1 ; }
seno y = 0;

Agora a segunda linha identifica um Bloco, que especificado na gramtica Gse


como uma lista de um ou mais comandos dentro de chaves. claro que um ComandoSe
um comando perfeitamente bom para colocar entre chaves.3
Java resolve o problema do seno pendente expandindo a gramtica BNF para um
ComandoSe de uma forma interessante. A especificao Java (Gosling et al., 1996) separa
a definio em duas categorias sintticas diferentes, ComandoSeEnto e ComandoSeEn-
toSeno, cada uma das quais uma subcategoria da categoria geral Comando:

ComandoSeEnto se (Expresso) Comando


ComandoSeEntoSeno se (Expresso) ComandoSeNoCurto
seno Comando

Observe que a segunda destas regras requer um ComandoSeNoCurto antes da sua parte
seno. Um ComandoSeNoCurto inclui todas as declaraes que no sejam Declara-
esSeEnto, mas pode incluir outras DeclaraesSeEntoSeno. Essa condio elimina
um ComandoSeEnto como a primeira alternativa antes de uma clusula seno, elimi-
nando assim qualquer ambigidade sobre como associar a clusula seno quando ela
ocorrer em um programa. Se houver outro comando se aninhado neste ComandoSeEn-
toSeno, ele deve ter uma clusula seno explcita4.

3. Este projeto de sintaxe da declarao se teve incio com Algol-60 [Naur (ed.), 1963], que usava as palavras
reservadas begin para a chave esquerda e end para a direita. Pascal tambm segue essa conveno. Linguagens
de estilo C, comeando com C, usam a chave esquerda para begin e a chave direita para end.
4. Esta estratgia pode ser consideravelmente mais complicada do que o resumo acima sugeriria. Por exemplo,
cada comando de lao derivvel de ComandoSeNoCurto deve ter um no-terminal especial que restrinja um
corpo de lao para um ComandoSeNoCurto.
2.2 BNF estendida 35

Uma terceira abordagem ao problema do seno pendente se originou com Algol-68


[van Wijngaarden (ed.) et al., 1969], na qual construes naturalmente aninhadas como
comandos se, laos e funes/procedimentos terminam cada uma com uma palavra-
chave nica. A conveno em Algol-68 era que a palavra-chave que servisse como tr-
mino fosse a palavra-chave da abertura escrita ao contrrio. Assim, o problema do seno
pendente foi resolvido porque cada comando se, com ou sem uma clusula seno, tinha
que ser terminado com a palavra-chave se. Por exemplo, a ambigidade mostrada na
Figura 2.5 seria resolvida pelo programador Algol-68 de uma das duas formas a seguir,
respectivamente:

se (x<0)
se (y<0)
y := y-1;
seno y := 0;
es
es
se (x<0)
se (y<0)
y := y-1;
es
seno y := 0;
es

Algumas linguagens recentes adotaram alguma forma dessa conveno, incluindo shell
Bourne [Kernighan e Pike, 1984], Modula [Wirth, 1982], SR [Andrews e Olsson, 1993]
e Ada [Cohen, 1996].

2 .2 B NF ES T EN DIDA
Desde Algol 60, alguma forma de BNF tem feito o trabalho pesado de descrever a
sintaxe de linguagens de programao. Diversas variaes menores foram introduzidas,
mas no afetaram o poder expressivo bsico da BNF. Essas variaes foram introduzidas
principalmente para melhorar a clareza e a conciso das descries de sintaxe.
A BNF Estendida (abreviada EBNF) simplifica a escrita de uma gramtica introdu-
zindo metassmbolos para iterao, opo e escolha. Considere a gramtica BNF G0 da
Seo 2.1.3, por exemplo:

Expresso Expresso Termo | Expresso Termo | Termo


Termo 0 | ... | 9| (Expresso)

A primeira dessas regras define uma Expresso como uma srie (ou lista) de um ou mais
Termos separados por sinais ou . A EBNF permite a eliminao de recurso esquer-
da nesta regra da seguinte maneira:

Expresso Termo { ( | ) Termo }

Nessa regra tanto as chaves quanto os parnteses so metassmbolos. As chaves { }


denotam zero ou mais ocorrncias dos smbolos que esto dentro delas. Os parnteses
( ) envolvem uma srie de alternativas das quais uma deve ser escolhida. Colchetes [ ],
36 Captulo 2 Sintaxe

quando usados como metassmbolos EBNF, especificam uma seqncia opcional de sm-
bolos (colchetes no so usados neste exemplo).
Uma complicao adicional com EBNF a distino do uso de chaves, parnteses
e colchetes como metassmbolos do seu uso como smbolos terminais comuns. Wirth
(1977) props envolver todos os smbolos terminais entre aspas ( ). Uma conveno
mais moderna que todos os smbolos terminais sejam escritos com uma fonte especial.
Aqui, convencionamos usar uma fonte de largura fixa (por exemplo, os smbolos (, ),
e na gramtica anterior) e os metassmbolos (outros que no a seta para a direita),
em negrito.
Um exemplo do uso de colchetes como metassmbolo ocorre na verso EBNF da
gramtica Gse da Seo 2.1.5:

ComandoSe se ( Expresso ) Comando [ seno Comando ]

Esta regra deve ser lida como se um ComandoSe fosse definido para ser um se terminal,
seguido por uma Expresso entre parnteses, seguida por um Comando, seguido por uma
parte seno opcional entre parnteses consistindo de um seno terminal seguido por um
Comando. Como sua contraparte na Seo 2.1.5, esta gramtica ambgua.
Preferimos a notao EBNF, que muitas vezes chamada de EBNF estilo-Wirth,
para descrever e ilustrar sintaxe de linguagem no restante deste livro. Essa preferncia
guiada pelo fato de que as definies de sintaxe de linguagem em EBNF tendem a ser
ligeiramente mais claras e mais breves do que as definies BNF. Em especial, a EBNF
elimina o uso desnecessrio de definies recursivas para descrever listas, que so funda-
mentais na sintaxe de linguagens de programao. Existem listas de declaraes, listas de
comandos, listas de parmetros, e assim por diante.
Uma variante da EBNF, que chamamos de EBNF estilo C, usada por Kernighan e
Ritchie (1988) na definio de ANSI C, por Stroustrup (1977) na definio de C++ e por
Gosling et al. na definio de Java. Uma descrio desta notao fornecida por Strous-
trup (1991, p. 478):

Na sintaxe usada neste manual, as categorias sintticas so indicadas pelo tipo itlico
e palavras literais e caracteres em tipo de largura constante. As alternativas so
listadas em linhas separadas, exceto em alguns casos em que um conjunto grande
de alternativas apresentado em uma linha, marcado pela expresso um de. Um
no-terminal ou terminal opcional indicado por opc...

O subscrito opc corresponde aos nossos metacolchetes, porm sem a capacidade


de agrupar uma seqncia opcional de diversos smbolos terminais e no-terminais. As-
sim, a gramtica Gse seria escrita na notao que se segue:

comandose :
se (expresso) comando partesenoopc
parteseno :
seno comando

Outra variao da EBNF o diagrama de sintaxe, que foi popularizado pelo seu uso na
definio sinttica da linguagem Pascal (Jensen e Wirth, 1975). Diagramas de sintaxe podem
auxiliar a esclarecer a sintaxe de diversas construes de linguagens quando estas so ensi-
nadas a programadores iniciantes. Eles foram amplamente usados em livros-textos de Pascal
2.3 A sintaxe de uma pequena linguagem: Clite 37

Expresso
Termo

| Figura 2.6 Diagrama de Sintaxe para Expresses com Adio e Subtrao

escritos para cursos introdutrios de programao; veja, por exemplo, (Cooper e Clancy,
1985). A Figura 2.6 mostra um exemplo de diagrama de sintaxe para a idia de Expresso
com qualquer nmero de operadores adicionais.
O significado desse diagrama de sintaxe auto-explicativo de acordo com seu for-
mato. Lemos o diagrama da esquerda para a direita, e todos os caminhos que levam para
a seta mais direita definem Expresses legais, ou seja, uma Expresso consiste de um
Termo seguido por uma seqncia de zero ou mais ocorrncias de junto a um Termo.
Em outras palavras, uma Expresso uma seqncia ou uma lista de um ou mais termos
separados por smbolos .
Nem a EBNF nem o diagrama de sintaxe so mais poderosos do que a BNF para
descrever formalmente uma sintaxe de linguagem. Podemos demonstrar isso facilmente
para a EBNF estilo Wirth da seguinte forma. Suponha que A seja um no-terminal e x,
y, z sejam seqncias arbitrrias de terminais e no-terminais. Qualquer regra EBNF que
tenha metachaves:

A x{y}z

pode ser reescrita de forma equivalente sem metachaves da seguinte forma:

A x A' z
A' | y A'

em que A' um novo no-terminal nico. Observe que a primeira alternativa na regra
para A' uma string vazia , o que significa que A' pode ser substitudo por nada. As
estratgias para a eliminao de metachaves (opo) e metaparnteses (grupo alternativo)
das regras EBNF so semelhantes e deixadas como exerccio.
Assim, a alternativa EBNF , muitas vezes, usada na descrio da sintaxe de uma
linguagem porque produz regras mais simples do que a BNF. Demonstramos isso na
Seo 2.3 descrevendo a sintaxe de uma linguagem simples que um subconjunto de
C/C++.

2.3 A SINTAXE DE UMA PEQUENA LINGUAGEM: CLITE


As idias discutidas neste captulo fornecem uma base para a descrio da sintaxe de
uma linguagem de programao completa. Entretanto, para evitar o excesso de detalhes, deci-
dimos definir um subconjunto de C, uma linguagem que chamamos de Clite. Esta linguagem
deve ser familiar a qualquer pessoa que tenha escrito um programa em C, C++ ou Java.
Uma gramtica completa para Clite, denominada GClite, mostrada na Figura 2.7.
Essa gramtica muito menor do que as de diversas linguagens reais, conforme mostrado
na Tabela 2.2.
Nesta discusso, no imitaremos o estilo comumente usado para definir uma lingua-
gem (por exemplo, Jensen e Wirth, 1975), em que cada construo importante introduzida
primeiramente apresentando-se sua sintaxe, depois sua semntica de tempo de compila-
o e, finalmente, sua semntica de tempo de execuo.
38 Captulo 2 Sintaxe

Programa int main ( ) { Declaraes Comandos}


Declaraes { Declarao }
Declarao Tipo Identificador [ [ Inteiro ] ] { , Identificador [ [ Inteiro ] ] } ;
Tipo int | bool | float | char
Comandos { Comando }
Comando ; | Bloco | Atribuio | ComandoSe | ComandoEnquanto
Bloco {Comandos}
Atribuio Identificador [ [ Expresso ] ] = Expresso ;
ComandoSe se ( Expresso ) Comando [ seno Comando]
ComandoEnquanto enquanto ( Expresso ) Comando

Expresso Conjuno { || Conjuno }


Conjuno Igualdade { && Igualdade }
Igualdade Relao [ OpIgual Relao ]
OpIgual == | !=
Relao Adio [ OpRel Adio ]
OpRel < | <= | > | >=
Adio Termo { OpAdio Termo }
OpAdio |
Termo Fator { OpMult Fator }
OpMult * | / | %
Fator [ OpUn ] Primrio
OpUn | !
Primrio Identificador [ [ Expresso ] ] | Literal | ( Expresso )
| Tipo ( Expresso )

Identificador Letra { Letra | Dgito }


Letra a | b | ... | z | A | B | ... | Z
Dgito 0 | 1 | ... | 9
Literal Inteiro | Boleano | Ponto Flutuante | Caractere
Inteiro Dgito { Dgito }
Boleano verdadeiro | falso
Ponto Flutuante Inteiro . Inteiro
Caractere ' CaractereASCII '

| Figura 2.7 A sintaxe de Clite


2.3 A sintaxe de uma pequena linguagem: Clite 39

| Tabela 2.2 Tamanhos de Gramticas de Diversas Linguagens

Pginas
Linguagem Referncia
Aproximadas

Pascal 5 (Jensen e Wirth, 1975)


C 6 (Kernighan e Ritchie, 1988)
C++ 22 (Stroustrup, 1997)
Java 14 (Gosling et al., 1996)

Em vez disso, nosso propsito aqui examinar em maior profundidade a sintaxe, dei-
xando a discusso a respeito da semntica e outras questes de projeto para captulos
posteriores.
Embora a gramtica GClite realize um bom trabalho na apresentao da sintaxe de diver-
sas construes, um bom nmero de questes sintticas relacionadas no so abordadas:

1 Comentrios.
2 O papel do espao em branco.
3 A distino entre smbolos como o dos dois smbolos e .
4 A distino entre os identificadores e as palavras-chave como se.

O restante desta seo abordar estas e outras questes sintticas.


A gramtica GClite possui dois nveis: o lxico e o sinttico. Como veremos na Seo
2.4, estes nveis correspondem a partes distintas de um compilador. Todas as questes
listadas anteriormente so lxicas.

2.3.1 Sintaxe Lxica


As ltimas oito regras de gramtica da Figura 2.7, comeando com a definio de um
Identificador, abordam questes lxicas. Elas definem a sintaxe de um Identificador e di-
versas formas de Literal. Alm disso, todas as palavras-chave e outros smbolos terminais
nas primeiras 23 regras de produo de GClite tambm fazem parte do nvel lxico.
O alfabeto de entrada da sintaxe lxica o conjunto de caracteres ASCII que podem
ser impressos, o que exclui o caractere NUL (cdigo ASCII 0), os caracteres de controle
(cdigos ASCII 1-26), excetuando o caractere de tabulao (9), DEL (127) e todos os
caracteres cujos cdigos ultrapassarem 127. O conjunto de caracteres ASCII descrito
em detalhes na Seo 5.3. As strings terminais que podem ser derivadas na sintaxe lxica
so chamadas de tokens e classificadas nos seguintes grupos:
Identificadores
Literais, incluindo Inteiros, true e false, Pontos Flutuantes e Caracteres
Palavras-chave: bool char else false float if int main true while
Operadores: = || && == != < <= > >= * / % ! [ ]
Pontuao: ; , { } ( )
Normalmente, os tokens no podem conter espaos em branco. A definio de
espao em branco comumente inclui os caracteres de espao e tabulao, o carac-
40 Captulo 2 Sintaxe

tere ou os caracteres delimitando o final de linha e os comentrios. Por exemplo,


quando digitado sem um espao em branco, tratado como um token, mas o
texto constitui dois tokens. De forma semelhante, analise o seguinte fragmento
de cdigo de programa em Pascal:

while a < b do

Aqui, o espao em branco necessrio entre a palavra-chave while e o identificador a e


tambm entre b e do. O espao em branco no necessrio para separar a de < ou < de b.
Os tokens so analisados pelos compiladores com o uso de um algoritmo vido que
procura pela maior seqncia de caracteres que constituam um token vlido independente
do seu contexto. No exemplo anterior, sem o espao em branco aps o while, os carac-
teres whilea formariam um token vlido do tipo Identificador, embora tal identificador
possa no ter sido declarado e whilea < no constitua um prefixo de comando vlido. De
forma oposta, a seqncia de caracteres a< no forma um token vlido. Assim, espaos
em branco entre estes dois caracteres so desnecessrios.
Observe que a gramtica GClite no define a sintaxe de um comentrio, deixando-o
para ser definido fora da gramtica, como no caso das definies de Pascal (Jensen e
Wirth, 1975) e C (Kernighan e Ritchie, 1988). Clite usa a conveno de comentrios //
de C++.
Um Identificador constitudo de letras (AZ, az) e dgitos (09), e comea com
uma letra. Assim, por essa definio, a string se tanto um Identificador quanto uma
palavra-chave (veja a definio de ComandoSe). Como, ento, os identificadores so
distinguidos das palavras-chave? Na maioria das linguagens, todas as palavras-chave so
as assim chamadas palavras reservadas, no sentido de que nenhum identificador pode
ser escrito da mesma forma que uma palavra-chave. Clite segue esta conveno e suas
palavras reservadas so:
bool else float int true
char false if main while

Observe que main no reservada em C ou C++. Em vez disso, um identificador


especial que nomeia a funo na qual a execuo do programa comea.
A gramtica de Clite na Figura 2.7 define quatro tipos de literais. Literais Inteiro so
convencionais, consistindo de uma seqncia de um ou mais dgitos decimais. Algumas
linguagens permitem que literais inteiros sejam expressos em outras bases alm de 10,
usando uma diversidade de convenes.
Um literal de ponto flutuante possui uma seqncia de dgitos com um ponto de-
cimal interno. Pelo menos um dgito requerido antes e depois do ponto decimal. Esta
conveno foi adotada pelos autores h muitos anos, ao aprenderem que o nmero 5. era
permitido em Fortran, mas o nmero .5 no era. A conveno oposta era verdadeira em
Algol. Todavia, os nmeros 5.0 e 0.5 so permitidos em ambas as linguagens. A maioria
das linguagens fornece nmeros de ponto flutuante, tanto em notao cientfica quanto
com preciso simples ou dupla.
A gramtica Clite tambm no define a faixa de valores vlidos para nmeros intei-
ros ou de ponto flutuante e no diz como eles so armazenados. Estas so questes de tipo
e semntica, que discutimos com mais detalhes nos Captulos 58.
Os literais Boleanos de Clite so as palavras reservadas true e false. Literais do
tipo Caractere so compostos de um nico caractere ASCII imprimvel (veja a Seo 5.1)
entre aspas simples ('), como 'a' e 'b'.
2.3 A sintaxe de uma pequena linguagem: Clite 41

2.3.2 Sintaxe Concreta


A estrutura sinttica de um programa completo uma rvore derivada da seqncia
de tokens do programa e da sua gramtica. A sintaxe concreta de um programa a rvore
que representa uma anlise da sua seqncia de tokens, comeando com o smbolo inicial
da sua gramtica. Por exemplo, a raiz de qualquer rvore de anlise de um programa em
Clite a categoria sinttica Programa, tendo como descendentes diretos uma srie de
tokens incluindo main, uma subrvore de Declaraes e uma subrvore de Comandos.
As linguagens de estilo C usam ponto-e-vrgula ; como terminador de declaraes
e comandos, em vez de um separador de comandos como em Algol e Pascal. Apenas o
comando Bloco no termina com um ponto-e-vrgula, mas, sim, com uma chave.
Uma Declarao em Clite consiste de um tipo seguido de uma lista de identificado-
res separados por vrgulas. Os tipos internos so: int, float, bool e char. Observe que
este estilo de declarao o oposto da conveno em Pascal, Ada e UML, nos quais a lista
de identificadores precede o tipo. Aqui esto dois exemplos:

int i, j;
float x;

Comandos em Clite incluem Atribuio usando como operador de atribuio,


Bloco (uma srie de comandos entre chaves { }), DeclaraoSe e DeclaraoEnquanto.
Observe que a regra para DeclaraoSe torna a gramtica ambgua, sofrendo do proble-
ma do seno pendente. Isto resolvido da forma habitual (Stroustrup, 1991):

A ambigidade do seno resolvida conectando-se um seno ao ltimo se sem o


seno correspondente que for encontrado.

Essa a nica ambigidade na gramtica de Clite da Figura 2.7.


A sintaxe concreta de Clite permite expresses sobre diversos operadores. O uso de
metachaves { } em diversos nveis de precedncia, como na regra da Adio, feito para
inferir associatividade esquerda. Observe que o uso de metachaves (inferindo opcional)
usado para definir tanto Igualdade quanto Relao, tornando os operadores OpIgual e
OpRel no-associativos. Metaparnteses no so usados na gramtica Clite da Figura 2.7.
Observe que a seo intermediria da Figura 2.7 dedicada definio no-ambgua
de sete nveis de precedncia, seus operadores associados e a associatividade. Para C++
completo, quatro pginas de Stroustrup, 1977, so dedicadas a definir a sintaxe concreta
de expresses. Isso resulta do grande nmero de operadores e dos nveis de precedncia.
Em contraste, a definio de C (Kernighan e Ritchie, 1988) usa uma gramtica de ex-
presso ambgua, deixando a precedncia e a associatividade de operadores para serem
especificadas fora da gramtica.
Mesmo quando a subgramtica da expresso no ambgua, a compreenso me-
lhorada especificando-se a precedncia e a associatividade de operadores de expresso
em uma tabela, que para Clite dada na Tabela 2.3, da maior para a menor precedncia.
Essa especificao difere de C/C++ de duas formas importantes. Primeiro, Clite pos-
sui muito menos operadores e nveis de precedncia resultantes. Para ver uma comparao
de C, C++ e Java, observe a Tabela 2.4. Na tabela, os nveis de precedncia so mostrados
do mais alto para o mais baixo, separados do nvel seguinte por uma linha horizontal.
42 Captulo 2 Sintaxe

| Tabela 2.3 Operadores Associatividade

| Precedncia de Operadores Unrio ! nenhuma


| e Associatividade em Clite */% esquerda
esquerda
< <= > >= nenhuma
== != nenhuma
&& esquerda
|| esquerda

Em segundo lugar, nem os operadores de igualdade nem os relacionais so associa-


tivos, o que uma idia emprestada de Ada (The Department of Defense, 1983). Em
C/C++ voc pode escrever expresses como:

se (a < x < b)

que presumivelmente est tentando testar se x possui um valor estritamente entre a e b.


Entretanto, enquanto b > 1, a expresso ser avaliada como true. Isso resulta do fato de
que a < x avaliado como 0 ou 1. Assim, tornar esses operadores associativos uma
deciso de projeto questionvel.
Nada na gramtica de Clite especifica os tipos de operandos requeridos por cada
operador ou o tipo do resultado retornado. Estas so questes de tipo e semntica no
abordadas pela sintaxe concreta e so discutidas mais integralmente nos Captulos 58.

2 .4 CO MP ILAD O RES E IN T ERP RETADO RES


A estrutura lgica de um compilador mostrada na Figura 2.8. Cada caixa na figura
representa uma fase, que uma operao logicamente coesa que transforma o programa-
fonte de uma representao para outra, conforme explicado a seguir. Em contraste est
a noo de um passo, que uma leitura completa da representao corrente do progra-
ma-fonte. Muitos compiladores de um passo foram escritos; em contraste, muitos com-
piladores PL/I usavam mltiplos passos na fase semntica. Compiladores de otimizao
geralmente so de mltiplos passos.
Um programa-fonte comea como um fluxo de caracteres. A funo do analisador
lxico (ou lexer) varrer o programa e transform-lo em um fluxo de tokens. Como parte
dessa transformao, todos os espaos em branco, incluindo comentrios, so descar-
tados. De forma semelhante, todas as seqncias que no formarem tokens vlidos so
descartadas, aps ser gerada uma mensagem de erro.
J que as fases de anlise lxica e sinttica so baseadas em uma gramtica, os
motivos para se ter uma fase de anlise lxica separada precisam ser explicados. Histori-
camente, estes motivos so:

1 O projeto do analisador lxico fundamentado em um modelo de mquina mais


rpido e muito mais simples do que o modelo livre de contexto usado para anlises
sintticas.
2 Para um compilador que no seja de otimizao, aproximadamente 75% do tem-
po total de compilao, muitas vezes, consumido na anlise lxica. Qualquer
melhoria que faa com que esta fase seja executada mais rapidamente possui um
impacto considervel sobre o tempo total de compilao.
2.4 Compiladores e interpretadores 43

| Tabela 2.4. Precedncia e Operadores em C, C++ e Java

Operao Operadores
(precedncia descendente) Associatividade C C++ Java
Resoluo de escopo esquerda ::

Seleo de membro esquerda . . .


Seleo de membro ponteiro esquerda -> ->
Subscrito esquerda [] [] []
Chamada de funo esquerda () () ()
Construo de valor esquerda () () ()
Ps-incremento esquerda ++ ++ ++
Ps-decremento esquerda -- -- --

Tamanho do objeto direita sizeof sizeof


Tamanho do tipo direita sizeof sizeof
Pr-incremento direita ++ ++ ++
Pr-decremento direita -- -- --
Complemento direita ~ ~ ~
Menos unrio direita - - -
Mais unrio direita + + +
Endereo de direita & &
Desreferncia direita * *
Aloca direita new new
Desaloca direita delete
Desaloca matriz direita delete[]
Converso direita () () ()

Seleo de membro esquerda .* .*


Seleo de membro ponteiro esquerda ->* ->*

Multiplica esquerda * * *
Divide esquerda / / /
Mdulo esquerda % % %

Adio esquerda + + +
Subtrao esquerda - - -

Shift para esquerda esquerda << << <<


Shift para direita esquerda >> >> >>
Menor que esquerda < < <
Menor ou igual a esquerda <= <= <=
Maior que esquerda > > >
Maior ou igual a esquerda >= >= >=
44 Captulo 2 Sintaxe

| Tabela 2.4. Precedncia e Operadores em C, C++ e Java

Operao Operadores
(precedncia descendente) Associatividade C C++ Java
Igual esquerda == == ==
No igual esquerda != != !=

E de bit esquerda & & &

OU exclusivo de bit esquerda ^ ^ ^

OU inclusivo de bit esquerda | | |

E lgico esquerda && && &&

OU inclusivo lgico esquerda || || ||

Atribuio condicional esquerda ? : ? : ? :

Atribuio direita = = =
Atribuio com multiplicao direita *= *= *=
Atribuio com diviso direita /= /= /=
Atribuio com mdulo direita %= %= %=
Atribuio com adio direita += += +=
Atribuio com subtrao direita -= -= -=
Atribuio com shift para esquerda direita <<= <<= <<=
Atribuio com shift para direita direita >>= >>= >>=
Atribuio com E direita &= &= &=
Atribuio com OU inclusivo direita |= |= |=
Atribuio com OU exclusivo direita ^= ^= ^=

Gera exceo esquerda throw throw

Seqenciamento de vrgulas esquerda , , ,

3 Antes da criao de ASCII, cada computador tinha seus prprios caracteres idios-
sincrticos. Esconder esse conjunto de caracteres no analisador lxico tornou mais
fcil portar um compilador para uma nova mquina. Mesmo hoje ainda existem
vestgios desse problema, com os conjuntos de caracteres ASCII, Unicode e EBCDIC
sendo usados.
4 A conveno de fim de linha tambm varia de um sistema operacional para outro.
Por exemplo, o Apple Macintosh (antes do Mac OS/X) usa o retorno de carro (AS-
CII 13) como um caractere de fim de linha, Linux/Unix usa um caractere de nova
(ou alimentao de) linha (ASCII 10), e o Microsoft Windows usa uma combina-
o retorno de carro/nova linha. Para muitas linguagens modernas, mas no todas,
este problema manipulado pela biblioteca de suporte de tempo de execuo da
linguagem. Antigamente isso no ocorria, de modo que essa dependncia do siste-
ma operacional foi escondida dentro do analisador lxico.

A segunda fase de um compilador o analisador, ou fase da anlise sinttica. O


analisador sinttico l um fluxo de tokens e constri uma rvore de anlise, de acordo com
2.4 Compiladores e interpretadores 45

| Figura 2.8 Principais Etapas no Programa-fonte


| Processo de Compilao

Analisador
lxico

Tokens

Analisador
sinttico

Sintaxe abstrata

Analisador
semntico

Cdigo intermedirio (CI)

Otimizador
de cdigo

Cdigo intermedirio (CI)

Gerador de
cdigo

Cdigo de mquina

as regras da gramtica. Um analisador completo desenvolvido para Clite na Seo 3.3.


A sada de um analisador pode ser uma rvore de anlise, conforme mostrado na Figura
2.9. De forma alternativa, a sada pode ser uma rvore de sintaxe abstrata, cujas vantagens
sero discutidas mais integralmente na Seo 2.5.
46 Captulo 2 Sintaxe

Atribuio

Identificador = Expresso ;

Conjuno
z

Igualdade

Relao

Adio

Termo

Termo OpAdio Termo

Fator + Fator OpMult Fator

Primrio Primrio Primrio


*

Identificador Literal Identificador

x 2 y

| Figura 2.9 rvore de Anlise para z = x + 2 * y;


2.4 Compiladores e interpretadores 47

Como exemplo de anlise, considere a anlise da entrada z = x + 2 * y; , com o


objetivo de obter uma rvore de anlise para Atribuio, usando a gramtica GClite. A regra
da gramtica Atribuio requer um Identificador seguido de um , uma Expresso, e ;.
Para que essa string seja analisada com sucesso, z deve ser uma instncia da categoria
gramatical Identificador e x + 2 * y deve ser uma instncia da categoria Expresso. As-
sim, a subrvore com raiz em Expresso na Figura 2.9 deve ser desenvolvida como uma
tarefa separada, usando diferentes regras de gramtica, ou seja, regras adicionais para
Termo, Fator e Primrio entram em ao quando estas subrvores so desenvolvidas.
A terceira fase do compilador a fase da anlise semntica ou verificador de tipo.
Esta fase responsvel por garantir que as regras de semntica de tempo de compilao
da linguagem sejam impostas com as seguintes condies:
Que todos os identificadores referenciados no programa sejam declarados.
Que os operandos para cada operador possuam um tipo apropriado.
Que as operaes de converses envolvidas, por exemplo, inteiro para float, sejam
inseridas onde for necessrio.
Se alguma regra de semntica de tempo de compilao for violada, uma mensagem
de erro apropriada ser gerada. A sada dessa fase uma rvore de cdigo intermedirio
(CI), que basicamente uma rvore de sintaxe abstrata transformada em um nvel mais
baixo de abstrao. Por exemplo, em Clite, o token usado para indicar a adio de
inteiros e pontos flutuantes. Na rvore CI poderia haver operadores separados para cada
tipo de adio. As principais questes de verificao de tipos, assim como o verificador
de tipos de Clite, so discutidas com mais detalhes em um captulo posterior.
O propsito da fase de otimizao de cdigo de um compilador melhorar o cdi-
go intermedirio baseado na arquitetura da mquina-alvo. Melhorias comuns incluem
encontrar expresses constantes e avali-las em tempo de compilao, reordenar cdigo
para melhorar o desempenho da cache, encontrar subexpresses comuns e substitu-las
por uma referncia a um temporrio, e assim por diante. Um bom otimizador de cdigo
pode aumentar bastante a velocidade de execuo de um programa.
A fase final do compilador a fase de gerao de cdigo, na qual elaborado o c-
digo especfico para a mquina-alvo. Esta fase responsvel por decidir quais instrues
de mquina usar, como alocar registradores e outros detalhes dependentes da mquina.
Em contraste com um compilador, um interpretador uma forma de tradutor no
qual as duas ltimas fases do compilador so substitudas por um programa que executa
diretamente (ou interpreta) o cdigo intermedirio. H dois tipos gerais de interpre-
tadores: puros e mistos. Em um interpretador puro, cada declarao colocada em
tokens, analisada, verificada semanticamente e interpretada cada vez que executada.
A maioria dos tradutores Basic e virtualmente todos os interpretadores de comandos
de shell, como por exemplo o shell Bourne, so interpretadores puros. Em contraste, a
maioria das linguagens de scripting, como por exemplo Perl, traduzida por intermdio
dos interpretadores mistos, que, a cada execuo, primeiro traduzem todo o script (ou
programa) em cdigo intermedirio e depois, repetidamente, interpretam esse cdigo
intermedirio sem mais tradues.
O JDK Java da Sun um exemplo incomum de um sistema interpretador misto no
qual o compilador Java javac compila cdigo-fonte em Java para uma forma externa
de cdigo intermedirio, conhecido como um arquivo de byte code, que interpretado
com o uso de uma mquina virtual chamada de Java Virtual Machine (JVM). O in-
terpretador de byte code Java denominado java e simula o comportamento da JVM.
Complicando mais a situao, nos ltimos anos a Sun introduziu um gerador de cdigo
48 Captulo 2 Sintaxe

on the fly para o interpretador que traduz partes freqentemente executadas do progra-
ma em cdigo de mquina nativo.
Captulos posteriores usam Clite como base para discutir sistemas de tipo e semn-
tica assim como para representar expresses em um exemplo recorrente sobre diferencia-
o simblica. Este captulo continua a discusso de sintaxe examinando a transio de
sintaxe para semntica.

2 .5 R EL ACIO N AN DO SIN TAXE E SEM N T ICA


At agora, exploramos ferramentas para definir os nveis sinttico e semntico de
uma linguagem incluindo uma viso geral das fases de anlise lxica e sinttica de um
compilador. Pareceria que a sada do analisador (fase de anlise sinttica) deveria ser uma
rvore de anlise. Nesta seo, analisamos a idia de uma alternativa arvore de anlise
para representar a estrutura e o contedo de um programa.
A necessidade de tal alternativa mltipla. Examine novamente a rvore de anlise
dada na Figura 2.9. J que muitos dos no-terminais nessa rvore no expressam informa-
es, seria til elimin-los. Suponha que executemos as seguintes transformaes nessa
rvore de anlise:

1 Descarte todos os smbolos terminais separadores (ou pontuao), como ponto e


vrgula.
2 Descarte todos os no-terminais que sejam razes triviais, ou seja, os que tenham
apenas uma nica subrvore. Um exemplo o smbolo Termo que o n interior
mais esquerda derivvel de Expresso.
3 Finalmente, substitua os no-terminais remanescentes por operadores que sejam
uma folha de uma de suas subrvores imediatas. Um exemplo substituir o sm-
bolo Atribuio pelo operador .

O que resulta uma rvore como a mostrada na Figura 2.10, chamada de rvore de
sintaxe abstrata. til comparar esta rvore com a rvore de anlise original da Figura
2.9. A rvore de sintaxe abstrata contm menos ns intermedirios e subrvores, embora
tenha a mesma estrutura da expresso original. Assim, esta rvore contm todas as in-
formaes importantes da rvore de anlise original, mas muito menor. Esses ganhos
seriam muito maiores durante a anlise de um programa completo que usasse a gramtica
inteira para Clite.
Um segundo argumento para usar uma sintaxe abstrata que ela pode revelar os
elementos sintticos essenciais em um programa sem especificar em excesso os detalhes
de como eles so escritos pelo programador. Considere, por exemplo, os seguintes co-
mandos de lao Pascal e C/C++:
Pascal C/C++
while i<n do begin while (i<n) {
i := i + 1 i = i + 1;
end }

Estes dois laos obviamente obtm o mesmo resultado; suas diferenas sintticas
no so essenciais para o processo fundamental do lao que elas representam.
Ao pensar abstratamente sobre um lao, descobrimos que os nicos elementos essen-
ciais so uma expresso de teste (i < n neste caso) para continuar o lao e um comando
2.5 Relacionando sintaxe e semntica 49

Identificador +

z Identificador *

x
Literal Identificador

2 y

| Figura 2.10 rvore de Sintaxe Abstrata para z = x + 2 * y;

que seja o corpo do lao a ser repetido (incrementando a varivel i, neste caso). Todos os
outros elementos que aparecem nesses dois comandos de lao constituem perfumaria
sinttica no essencial.

2.5.1 Sintaxe Abstrata


Assim, valioso definir a estrutura de uma linguagem de programao em um nvel mais
funcional do que aquele que oferecido pela sua sintaxe EBNF. Sintaxe abstrata uma
notao que permite a um analisador retirar a perfumaria sinttica e gerar uma rvore que
contenha apenas os elementos essenciais da computao.
A sintaxe abstrata de uma linguagem de programao pode ser definida, por meio de
um conjunto de regras, da seguinte forma:

Lhs Rhs

em que Lhs o nome de uma classe de sintaxe abstrata e Rhs define a classe como:

1 Uma lista de uma ou mais alternativas. Por exemplo, na sintaxe concreta da gram-
tica Clite da Figura 2.7, h apenas quatro tipos de Expresso: identificadores, literais
ou valores, aquelas com um operador unrio e aquelas com um operador binrio.
2 Uma lista de componentes essenciais que definem um membro dessa classe, se-
parados por ponto-e-vrgula (;). Cada componente desses possui a forma de uma
declarao comum, identificando uma classe de sintaxe abstrata e uma lista de um
ou mais nomes de campos, separados por vrgulas (,).
50 Captulo 2 Sintaxe

Atribuio = Varivel target; Expresso source


Expresso = Varivel | Valor | Binria | Unria
Binria = Operador op; Expresso term1, term2
Unria = Operador op; Expresso term
Varivel = String id
Valor = Inteiro value
Operador = + | - | * | / | !

| Figura 2.11 Sintaxe Abstrata Parcial para a Gramtica da Figura 2.7

Uma classe de sintaxe abstrata, em ambas as alternativas, pode corresponder a um dom-


nio matemtico bem conhecido como Inteiro, String ou Boleano. O propsito dos nomes
dos campos permitir que o processamento semntico referencie componentes abstratos
por nome.
Considere, por exemplo, as regras de sintaxe abstrata para Atribuio e Expresso
que aparecem na Figura 2.7; uma sintaxe abstrata para esta gramtica demonstrada na
Figura 2.11. A regra para Expresso uma instncia do primeiro tipo de regra de sintaxe
abstrata, enquanto Atribuio uma instncia do segundo tipo de regra. A definio de
uma expresso Binria indica a potencial utilidade de se ter nomes de campos, j que con-
tm duas Expresses. No surpreendente que, j que a gramtica concreta recursiva,
tambm o a definio de sintaxe abstrata.
Por exemplo, analise a sintaxe abstrata de uma expresso Binria. A Figura 2.11
define a classe de sintaxe abstrata Expresso como tendo trs componentes: op, que um
membro da classe de sintaxe abstrata Operador; e term1 e term2, que so membros da
classe de sintaxe abstrata Expresso.
Um subproduto imediato do uso de sintaxe abstrata que ela fornece uma base para
definir a estrutura abstrata de uma linguagem como um conjunto de classes Java. A se-
guir, por exemplo, est uma definio de prottipo de classe Java para a sintaxe abstrata
de Expresso:

class Binria extends Expresso {


Operador op;
Expresso term1, term2;
}

Embora esta definio de prottipo ignore muitas questes de implementao (visibili-


dade, construtores, mtodos de acesso e assim por diante), ela contm todas as variveis
de instncia que caracterizam a classe Binria, ou seja, uma Binria um tipo de Ex-
presso e possui um op, que um Operador, e o par term1 e term2, que so membros
da classe Expresso5.
Na Figura 2.11, a classe de sintaxe abstrata Expresso representa um grupo que
consiste de mltiplas classes de sintaxe abstrata, incluindo variveis, valores inteiros (li-
terais) e expresses que envolvem operadores binrios e unrios e o nmero apropriado
de operandos.

5. A noo de hierarquia de classe que foi construda em linguagens orientadas a objeto como Java torna direta
a implementao dessa especificao em particular. Outra caracterstica de objetos Java que surge com esta
abordagem de sintaxe abstrata a capacidade de consultar um objeto para determinar sua classe. Exploraremos
esses recursos de Java quando discutirmos os sistemas de tipo e semntica de tempo de execuo de linguagens
em captulos posteriores.
2.5 Relacionando sintaxe e semntica 51

Em termos de implementao de Java, grupos como Expresso seriam implementados


como uma classe abstrata, j que nenhum objeto poderia ser criado como uma nova Ex-
presso().

abstract class Expresso { }

Esperamos que agora j tenhamos justificado a separao da sintaxe abstrata da con-


creta. Embora as duas sejam, de certa forma, redundantes, de outra, elas no o so, ou
seja, a sintaxe concreta informa ao programador concretamente o que deve escrever para
ter um programa vlido na linguagem X. Entretanto, a sintaxe abstrata permite que pro-
gramas vlidos na linguagem X e na Y compartilhem representaes abstratas comuns.
De forma ideal, um interpretador ou sistema em tempo de execuo se preocupa menos
sobre como algumas idias, como laos, so expressas concretamente, e mais sobre o que
determinados requisitos computacionais devem ser comunicados da expresso para o
ambiente em tempo de execuo para o programa.
Este o papel da sintaxe abstrata: fornecer uma ligao entre a sintaxe e a semntica,
entre forma e funo. Exploramos o uso dessa ligao em captulos posteriores.

2.5.2 rvores de Sintaxe Abstrata


Analise as regras na Figura 2.11 que definem a classe sinttica abstrata Expresso
e suas subclasses Binria e Unria. Estas regras mencionam os principais elementos de
uma Expresso sem considerar como ela formada concretamente pelo analisador, ou
seja, a sintaxe concreta fornece a definio da forma de uma expresso, enquanto a sinta-
xe abstrata fornece a definio dos seus elementos essenciais.
Uma rvore de sintaxe abstrata fornece um meio conciso para descrever os elemen-
tos individuais de uma Expresso, eliminando as subrvores e os ns intermedirios que
no contiverem informaes essenciais. Para construir uma rvore de sintaxe abstrata,
cada categoria sinttica abstrata como Binria possui um n associado com tantos
campos quanto houver elementos distintos no lado direito da sua regra. Por exemplo, o
n associado Binria possui trs campos e se parece com a Figura 2.12.
Em contraste, a categoria de sintaxe abstrata Expresso no possui campos de infor-
mao; ela serve apenas como um mecanismo de agrupamento. Assim, uma Expresso
no precisa ser implementada como um n; como j foi observado, uma implementao
em Java normalmente implementaria uma Expresso como uma classe abstrata. Como
exemplo, a Figura 2.13 mostra uma implementao em Java da rvore de sintaxe abstrata
para a declarao de atribuio z = x + 2 * y;.

2.5.3 A Sintaxe Abstrata de Clite


A sintaxe abstrata, como discutido na Seo 2.5, forma uma base para conectar as
fases de anlise sinttica e semntica de um compilador. Conforme vimos, ela possibilita
uma rvore de anlise consideravelmente mais compacta do que a sintaxe concreta, mas,
ainda assim, expressa todas as informaes requeridas para o processamento semntico,
ou seja, a sintaxe abstrata adequada para determinar o significado de um programa.
Nos prximos captulos, Clite ser usado como um veculo para discutir muitos as-
pectos tcnicos de linguagens de programao orientadas a objeto e imperativas modernas.
Por ser um subconjunto de C/C++, Clite ser familiar para os leitores que tiverem escrito

| Figura 2.12 A Estrutura de um N Binrio op term1 term2


52 Captulo 2 Sintaxe

Atribuio

Varivel Binrio

Binrio
Operador Varivel

+ x

Operador Valor Varivel

* 2 y

| Figura 2.13 rvore de Sintaxe Abstrata para a Atribuio z = x + 2 * y;

alguns programas em C, C++, Ada ou Java. Em captulos posteriores, a sintaxe abstrata de


Clite servir como um ponto de partida para a discusso de caractersticas de sistemas
de tipo, semntica de tempo de execuo e gerenciamento de memria. Esta seo intro-
duz a sintaxe abstrata de Clite.
A sintaxe abstrata de Clite estende as idias bsicas apresentadas na seo anterior.
Um resumo informal das categorias e de seus significados apresentado a seguir:

Programa: tendo Declaraes e um Composto.


Declaraes: uma seqncia de Declaraes individuais.
Declarao: uma varivel individual, seu tipo e tamanho (se for uma matriz).
Tipo: um membro do conjunto {int, bool, float, char}.
Comando: tendo subclasses Composto, Pular, Atribuio, Condicional e Lao.
Composto: uma seqncia de Comandos individuais que so executados na ordem
em que aparecem. Cada um aparece no mesmo nvel em uma rvore de sintaxe
abstrata.
Pular: o comando vazio.
Atribuio: um comando que atribui o valor de uma Expresso a uma varivel.
Condicional: tendo uma Expresso (o teste) e dois Comandos (thenbranch e else-
branch), um dos quais executado e o outro, pulado. No caso de um comando
se-ento, a ramificao elsebranch instanciada como um comando Pular.
2.5 Relacionando sintaxe e semntica 53

Programa = Declaraes decpart; Comandos body ;


Declaraes = Declarao*
Declarao = DecVarivel | DecMatriz
DecVarivel = Varivel v ; Tipo t
DecMatriz = Varivel v ; Tipo t ; Inteiro size
Tipo = int | bool | float | char
Comandos = Comando*
Comando = Pular | Bloco | Atribuio | Condicional | Lao
Pular =
Bloco = Comandos
Condicional = Expresso test ; Comando thenbranch, elsebranch
Lao = Expresso test ; Comando body
Atribuio = RefVarivel target ; Expresso source
Expresso = RefVarivel | Valor | Binria | Unria
RefVarivel = Varivel | RefMatriz
Binria = Operador op ; Expresso term1, term2
Unria = OpUnrio op ; Expresso term
Operador = OpBoleano | OperadorRel | OperadorAritm
OpBoleano = && | ||
OpRelacional = = | != | < | <= | > | >=
OpAritmtico = + | | * | /
OpUnrio = ! |
Varivel = String id
RefMatriz = String id ; Expresso index
Valor = ValorInt | ValorBool | ValorFloat | ValorChar
ValorInt = Integer intValue
ValorFloat = Float floatValue
ValorBool = Bolean boolValue
ValorChar = Character charValue
| Figura 2.14 Sintaxe Abstrata de Clite

Lao: tendo uma Expresso (o teste) e um Comando (o corpo do lao) que repetido
enquanto o teste continuar sendo verdadeiro.
Expresso: tendo subclasses Varivel, Valor, Binria e Unria.
Binria: tendo um Operador e duas Expresses.
Unrio: tendo um Operador e uma nica Expresso.
54 Captulo 2 Sintaxe

Operador: &&, ||, !, , , , !, , , , , * e /, representando:


Os operadores boleanos: e, ou, no;
Os operadores relacionais: menor, menor ou igual, igual, no igual, maior,
maior ou igual; e
Os operadores aritmticos: mais, menos, vezes, dividir.
Valor: tendo subclasses ValorInt, ValorFloat, ValorBool e ValorChar.
ValorInt: tendo um valor inteiro.
ValorFloat: tendo um valor em ponto flutuante, representando uma aproximao de
computador para um valor no-inteiro.
ValorBool: tendo um valor boleano, ou seja, verdadeiro ou falso.
ValorChar: tendo um nico valor do tipo caractere.
Uma descrio formal da sintaxe abstrata de Clite apresentada na Figura 2.14. Esta
descrio fornece uma base adequada, a partir da qual uma rvore de sintaxe abstrata para
um programa completo pode ser construda.
Um analisador completo para Clite, capaz de produzir rvores de sintaxe abstrata
para programas Clite (como o fragmento mostrado na Figura 2.13), est disponvel
no site do livro. Esta uma ferramenta til para leitores que queiram trabalhar com
os aspectos prticos de sintaxe que aparecem nos muitos exerccios no final dos pr-
ximos captulos.

2 . 6 R ES UM O
Este captulo introduziu a idia bsica de sintaxe e sua importncia para o projeto de lin-
guagens atuais de programao. A sintaxe se refere estrutura do texto de um programa,
enquanto a semntica se refere ao seu significado ou comportamento em tempo de execu-
o. Os programadores se beneficiam de ter uma linguagem cuja sintaxe seja facilmente
aprendida e claramente compreendida.
A ambigidade na descrio sinttica ocorre quando um fragmento de programa
possui duas ou mais interpretaes diferentes. A ambigidade sinttica geralmente pode
ser evitada, especialmente quando uma metalinguagem formal como a EBNF usada.
Muitas caractersticas de linguagens, como a associatividade e a precedncia de opera-
dores, podem ser claras e concisamente definidas por tal descrio sinttica.
A sintaxe de uma linguagem descrita convenientemente em dois nveis o nvel
lxico e o concreto. A sintaxe lxica ajuda a definir diversas classes fundamentais de
tokens de uma linguagem, como identificadores e constantes.
Um compilador possui diversas fases, sendo que as duas primeiras so as anlises
lxica e sinttica. As fases restantes de um compilador anlise semntica, otimizao
de cdigo e gerao de cdigo dependem do trmino bem-sucedido das duas primeiras.
A noo de sintaxe abstrata fornece um meio pelo qual os elementos essenciais de um
programa podem ser passados para essas fases posteriores para uma anlise eficiente.
Uma pequena minilinguagem denominada Clite introduzida neste captulo para
auxiliar a elucidar diversas questes de sintaxe. Retornaremos a esta linguagem em cap-
tulos posteriores, quando explorarmos questes de projeto de linguagens alm da sintaxe
tipos, nomes, funes e semntica.
2.6 Resumo 55

E XE RC CI O S
2.1 Usando a gramtica de Ginteiro, desenvolva uma derivao mais esquerda para o inteiro 4520.
Quantos passos so necessrios para essa derivao? De modo geral, quantos passos so requeridos
para derivar um inteiro com um nmero arbitrrio, digamos d, de Dgitos?

2.2 Usando a gramtica Ginteiro, desenvolva uma derivao mais direita para o inteiro 4520.

2.3 Desenvolva uma derivao mais esquerda para o valor identificador a2i, usando a sintaxe BNF
dada na Figura 2.7.

2.4 Desenvolva uma derivao mais direita para o valor identificador a2i, usando a sintaxe BNF dada
na Figura 2.7.

2.5 Usando a gramtica da Figura 2.7, desenhe uma rvore de anlise para cada um dos seguintes:
(a) x = x + a 1;
(b) a = b * c / d;
(c) i = i + j * k 3;

2.6 Usando a seguinte gramtica:

Expresso Expresso Termo | Expresso * Termo | Termo


Termo 0 | ... | 9 | (Expresso)

desenhe uma rvore de anlise para cada um dos seguintes:


(a) 5 + 4 * 3
(b) 5 * 4 + 3

2.7 Usando a seguinte gramtica:

Expresso Termo Expresso | Termo * Expresso | Termo


Termo 0 | ... | 9 | (Expresso)

desenhe uma rvore de anlise de cada um dos seguintes:


(a) 5 + 4 * 3
(b) 5 * 4 + 3

2.8 Usando a seguinte gramtica:

Expresso Expresso Termo | Termo


Termo Termo Fator | Fator
Fator 0 | ... | 9 | (Expresso)

desenhe uma rvore de anlise para cada um dos seguintes:


(a) 5 + 4 * 3
(b) 5 * 4 + 3

2.9 Usando a seguinte gramtica:

Expresso Expresso Expresso | Expresso * Expresso |


0 | ... | 9 | (Expresso)

desenhe uma rvore de anlise para cada um dos seguintes:


(a) 5 + 4 * 3
(b) 5 * 4 + 3
56 Captulo 2 Sintaxe

2.10 Usando a seguinte gramtica:

Expresso Expresso Expresso | * Expresso Expresso |


0 | ... | 9
desenhe uma rvore de anlise para cada um dos seguintes:
(a) + 5 * 4 3
(b) + * 5 4 3

2.11 Argumente de forma convincente que a rvore de anlise apresentada na Figura 2.2 para a expres-
so 5 4 3 a nica rvore de anlise possvel. Dica: enumere as outras possibilidades e mostre
que elas no funcionam.

2.12 Mostre como a subgramtica Java para ComandoSeEnto elimina qualquer ambigidade no co-
mando se, ou seja, esboce a rvore de anlise usando as regras BNF Java e ento argumente que
nenhuma outra rvore de anlise pode ser encontrada usando estas regras.

2.13 Analise as regras de gramtica para ComandoSe e Comando dadas na Seo 2.1.5. Mostre
como elas podem ser alteradas para eliminar sua ambigidade ilustrada no texto, ou seja, mos-
tre como a gramtica pode ser alterada de modo que o programador possa distinguir explicita-
mente as duas associaes possveis do seno pendente, conforme mostrado a seguir:

se (x<0) se (x==0) y = y 1; seno y = 0; es


se (x<0) se (x==0) y = y 1; es seno y = 0; es

Aqui, a nova palavra-chave es usada para terminar uma declarao que comea com a palavra-
chave se. (Isso funciona como um colchete direito, onde se o esquerdo).

2.14 D uma gramtica e um exemplo de comando se para cada uma das seguintes linguagens: (a) Perl,
(b)Python, (c) Ada.

2.15 D regras de traduo para alternativas EBNF envolvendo metacolchetes e metaparnteses para
BNF padro.

2.16 Reescreva a gramtica G2 como um conjunto de diagramas de sintaxe.

2.17 Escolha uma das seguintes linguagens: Perl, Python, Ada, C, C++, Java ou outra linguagem aprova-
da pelo professor. Consulte uma fonte autorizada e escreva um relatrio que resuma a definio da
linguagem quanto ao tamanho e ao tipo da sua gramtica, a apario de ambigidade na gramtica,
seu nmero de palavras reservadas, a precedncia e a associatividade de operadores e assim por
diante. Cite suas fontes.

2.18 Pesquise o nmero de palavras reservadas em cada uma das seguintes linguagens: (a) Perl, (b) Python,
(c) Ada, (d) C, (e) C++, (f) Java.

2.19 D um conjunto de regras de gramtica que defina a sintaxe de uma declarao de varivel em Perl.
Exemplifique.

2.20 Desenhe uma rvore de sintaxe abstrata para cada uma das rvores de anlise que voc definiu para
o Exerccio 2.5, usando a sintaxe abstrata para Atribuio apresentada na Figura 2.11.
Anlise Lxica
e Sinttica 3
Perfumaria sinttica causa cncer no ponto-e-vrgula.
Alan Perlis

VISO GERAL DO CAPTULO

3.1 A HIERARQUIA DE CHOMSKY 58


3.2 ANLISE LXICA 60
3.3 ANLISE SINTTICA 70
3.4 RESUMO 82
EXERCCIOS 82

O propsito deste captulo fornecer uma viso geral da implementao de um anali-


sador lxico (lexer) e um analisador (sinttico). Devido ao fato de o analisador lxico estar
fundamentado em um modelo mais simples do que uma gramtica livre de contexto, o
captulo comea com um breve exame na hierarquia de gramticas de Chomsky.
Na Seo 3.2 examinamos especificaes alternativas de tokens de Clite, incluindo
conjuntos regulares e autmatos de estados finitos determinsticos. Este ltimo fornece
um modelo que facilmente implementado, conforme demonstramos com uma imple-
mentao parcial para a parte lxica da gramtica de Clite.
A seguir, desenvolvemos um analisador descendente recursivo para uma pequena
parte da sintaxe concreta de Clite. Para fazer isso, desenvolvemos alguns algoritmos au-
xiliares. Depois mostramos como converter uma gramtica EBNF para um analisador
descendente recursivo.
Este captulo no pretende substituir um curso sobre construo de compiladores.
Contudo, til para as escolas que no oferecem tal curso e para aqueles alunos que no

57
58 Captulo 3 Anlise Lxica e Sinttica

tenham feito tal curso. Alm disso, ao aprendermos como construir um analisador des-
cendente recursivo, teremos um algoritmo muito til em nosso repertrio, especialmente
para pequenas linguagens. Por exemplo, algumas das solues dos exerccios neste
texto usam a tcnica de descrio de dados, utilizando uma gramtica simples e depois
construindo uma representao interna para os dados por meio do uso de um analisador
descendente recursivo (veja o Exerccio 3.21).

3 .1 A H IE R AR Q U IA DE CH O M SK Y
Chomsky (1957) definiu quatro classes de gramticas, cada uma correspondendo a
uma classe nica de linguagem e mquina terica. Da mais simples para a mais complexa,
estes tipos de gramtica so:
Gramticas regulares.
Gramticas livres de contexto (equivalentes a gramticas BNF).
Gramticas sensveis ao contexto.
Gramticas irrestritas.
Apenas as duas primeiras categorias so usadas em linguagens de programao; as
duas ltimas foram includas apenas para que todas fossem mencionadas. As gramticas
livres de contexto foram discutidas na Seo 2.1.
Lembre-se do que foi dito na Seo 2.1, que uma gramtica consiste de um conjunto
de produes, um conjunto de smbolos no-terminais N, um conjunto de smbolos ter-
minais T e um smbolo inicial S que deve ser no-terminal. Os smbolos terminais so o
alfabeto da linguagem sendo definida, enquanto os smbolos no-terminais representam
conceitos da linguagem. Usando a sintaxe concreta de Clite como exemplo, os smbolos
terminais incluem as palavras reservadas (por exemplo, while, if), literais (por exemplo,
542, 1.5, true), operadores etc. Os no-terminais representam conceitos de Clite como
uma Declarao, uma Expresso etc.
Na hierarquia de Chomsky, apenas a forma de uma produo muda de um tipo de
gramtica para outro. O conceito de terminais, no-terminais e o smbolo inicial perma-
necem os mesmos.
Gramticas regulares so a classe mais simples e equivalem s assim denominadas
expresses regulares e aos autmatos de estados finitos (ambos discutidos na Seo 3.2)
no seu poder expressivo. Uma gramtica regular direita (ou gramtica linear direita)
s pode ter produes na seguinte forma:

AB
A

em que  T* (uma string de zero ou mais terminais1) e B  N (um nico no-terminal).


Em outras palavras, cada alternativa de uma produo pode ter no mximo um no-ter-
minal e, caso exista, ele deve ser o smbolo mais direita. O no-terminal B pode ser o
mesmo no-terminal A, ou seja, a recurso direta permitida. Reescrevendo a produo
para Inteiro da Figura 2.7 (Clite) como uma gramtica regular direita, obtemos:

Inteiro 0 Inteiro | ... | 9 Inteiro |


0 | ... | 9

1. T* outro exemplo de uso da notao de estrela de Kleene.


3.1 A Hierarquia de Chomsky 59

ou o equivalente em EBNF:

Inteiro ( 0 | ... | 9 ) Inteiro |


0 | ... | 9

Uma alternativa define gramticas regulares esquerda (ou gramticas lineares


esquerda) de forma equivalente, exceto que o nico no-terminal em uma alternativa,
caso exista, deve ser o smbolo mais esquerda. Reescrevendo a produo para Inteiro da
Figura 2.7 (Clite) como uma gramtica regular esquerda, obtemos:

Inteiro Inteiro 0 | ... | Inteiro 9 |


0 | ... | 9

ou o equivalente em EBNF:

Inteiro Inteiro ( 0 | ... | 9 ) |


0 | ... | 9

Gramticas regulares e suas formas equivalentes de expresses regulares e autma-


tos de estado finito so usadas na construo de tokenizadores para converter fluxos de
caracteres em tokens (ou smbolos bsicos). Isto discutido em detalhes na Seo 3.2.
Gramticas regulares so consideravelmente menos poderosas do que gramticas
livres de contexto ou BNF. Por exemplo, um teorema bem conhecido que a linguagem

{anbn | n  1}

no uma linguagem regular, ou seja, a linguagem no pode ser gerada por uma gra-
mtica regular. Em uma gramtica de linguagem de programao como Clite, alguns
smbolos terminais vm em pares correspondentes: parnteses em expresses, colchetes em
listas de declaraes etc. Linguagens regulares no conseguem lidar com o caso mais simples
de balanceamento de parnteses, em que todos os parnteses esquerda precedem os
parnteses direita. Portanto, gramticas regulares so inadequadas para descrever apro-
priadamente a sintaxe de uma linguagem de programao.
Gramticas livres de contexto, na forma de gramticas BNF, j foram discutidas com
algum detalhe. Uma vantagem das gramticas livres de contexto que para uma classe
ampla de gramticas no ambguas h analisadores com base em tabelas (veja a Observa-
o na pgina 81). Gramticas livres de contexto correspondem a autmatos push-down,
que so discutidos na Seo 3.3.
Gramticas sensveis ao contexto possuem produes definidas da seguinte maneira
[Hopcroft e Ullman, 1979]:

de modo que ||  ||, em que ,  (N  T)*. Ou seja, diferentemente das gramticas


livres de contexto, o smbolo esquerda no se restringe a ser um nico no-terminal,
mas, em vez disso, pode consistir de uma string que contenha tanto smbolos terminais
quanto no-terminais. A restrio sobre gramticas sensveis ao contexto que o compri-
mento do lado direito de uma alternativa no pode ser menor do que o do lado esquerdo,
exceto que o smbolo inicial pode derivar a string vazia. Em uma derivao, a forma
seqencial no pode encolher de um passo de derivao para outro. Gramticas sensveis
ao contexto podem assegurar que os identificadores de um programa sejam declarados e
tenham o tipo apropriado para o contexto.
60 Captulo 3 Anlise Lxica e Sinttica

Do ponto de vista da definio de sintaxe e de semntica de uma linguagem de


programao, linguagens sensveis ao contexto (como as linguagens definidas por
uma gramtica G sensvel ao contexto) possuem muitas propriedades indesejadas;
por exemplo:
Dada uma string terminal e uma gramtica G, no possvel determinar se
 L(G), ou seja, se derivvel a partir de G. Nos nossos termos, no poss-
vel determinar se um programa vlido de acordo com G.
Dadas uma string terminal e uma gramtica G, no possvel determinar se L(G)
possui alguma string vlida. Em outras palavras, no possvel determinar se uma
gramtica sensvel ao contexto para Clite define algum programa Clite vlido.
Para os nossos propsitos, o termo indeterminvel significa que voc no pode escre-
ver um programa de computador que garantidamente pare (termine a execuo) para
todas as suas entradas e determine a soluo do problema. Por esse motivo, gramti-
cas sensveis ao contexto no so usadas no tratamento de sintaxes de linguagens de
programao.2
Gramticas irrestritas cessam as restries de comprimento do lado direito. Gram-
ticas irrestritas so equivalentes a mquinas de Turing ou, de forma equivalente, C/C++
integrais. Gramticas irrestritas podem calcular qualquer funo calculvel [Hopcroft e
Ullman, 1979]. Do ponto de vista da definio de sintaxe de uma linguagem de progra-
mao, as gramticas irrestritas sofrem das mesmas propriedades indesejveis das gram-
ticas sensveis ao contexto.

3 .2 A N LIS E LXICA
Conforme j observado na Seo 2.4, o propsito da fase da anlise lxica de um com-
pilador transformar o programa-fonte de uma seqncia de caracteres em uma seqncia
de tokens. No processo, os espaos em branco e os comentrios so descartados. Nesta se-
o, mostramos como um lexer projetado e implementado para a parte lxica da gramtica
Clite da Figura 2.7, ampliada pelas definies de espaos em branco e pelos comentrios.
Definio: Um token uma seqncia logicamente coesa de caracteres que repre-
sentam um nico smbolo.
Exemplos incluem um identificador, uma palavra reservada (como while), um literal
(como 3.1416), um operador (por exemplo, !=) e uma pontuao (por exemplo, ;).
Conforme discutido na Seo 2.4, historicamente o lexer uma fase de compilao
separada pelos seguintes motivos:

1 O projeto do analisador lxico baseado em um modelo de mquina muito mais


simples e rpido do que o modelo livre de contexto usado para anlise sinttica.
2 Para um compilador no-otimizador, aproximadamente 75% do tempo total de
compilao consumido na anlise lxica. Qualquer melhoria que faa com que
essa fase seja executada mais rapidamente possui um impacto comparvel no tem-
po total de compilao.
3 Antes da criao de ASCII, cada computador tinha seu prprio conjunto idiossin-
crtico de caracteres. Esconder este conjunto de caracteres no lexer fez com que
portar um compilador para uma nova mquina fosse muito mais fcil. Mesmo hoje,

2. Questes sensveis ao contexto como a resoluo de nomes e a verificao de tipos podem ser lidadas por uma
gramtica de atributos. Uma alternativa explorada no Captulo 8 so as semnticas denotacionais.
3.2 Anlise Lxica 61

vestgios desse problema ainda existem quando so usados os conjuntos de carac-


teres ASCII, Unicode e EBCDIC.
4 A conveno do final de linha tambm varia de um sistema operacional para outro. Por
exemplo, o Apple Macintosh usa o retorno de carro (ASCII cdigo 13) como caractere
de final de linha, Linux/Unix usa um caractere de nova linha (ASCII cdigo 10), e o
Microsoft Windows usa uma combinao retorno de carro/nova linha. Para muitas
linguagens modernas, mas no todas, esse problema manipulado pela biblioteca de
suporte de tempo de execuo da linguagem. No incio no era assim, de modo que a
dependncia do sistema operacional foi escondida dentro do analisador lxico.

O lxico de Clite um conjunto de tokens que so definidos pela gramtica e pelas


seguintes categorias gramaticais:
Identificadores (por exemplo: token)
Literais inteiros (por exemplo: 80)
Literais de ponto flutuante (por exemplo: 3.1415)
Literais de caracteres (por exemplo a)
Isso significa que, por exemplo, quando o analisador est esperando um identificador, ele
no se importa com qual especificamente encontrado, mas apenas que um seja encon-
trado. Isso muito diferente de uma palavra reservada como while que, quando vista no
incio de uma declarao, determina o tipo, a declarao e sua estrutura. Literais boleanos
so deixados de fora dessa lista porque apenas esses literais so palavras reservadas.
Precisamos adicionar lista anterior os espaos em branco, os comentrios, o in-
dicador de final de linha (necessrio para comentrios //) e um indicador de final de
arquivo. Estes itens acrescentados, exceto o ltimo, so descartados pelo lexer, j que o
analisador no precisa conhec-los. Assim, a lista completa de seqncias de caracteres
a serem reconhecidos :
Identificadores: uma letra seguida de uma ou mais letras e dgitos
Literais, incluindo:
literais inteiros: uma seqncia de um ou mais dgitos
literais de ponto flutuante: uma seqncia de um ou mais dgitos seguidos por
um ponto seguido por uma seqncia de um ou mais dgitos
literais de caracteres: um nico caractere imprimvel
Palavras-chave: bool char else false float if int main true while
Operadores: = || && == != < <= > >= + * / ! [ ]
Pontuao: ; , { } ( )
Espao em branco: caracteres de espao ou tabulao
Comentrios:
 de uma nica linha: // quaisquer caracteres de final de linha
Final de linha
Final de arquivo
Qualquer outro caractere ilegal.
Na Seo 3.2.1, uma forma alternativa de especificar o lxico de uma linguagem de
programao apresentada.
62 Captulo 3 Anlise Lxica e Sinttica

3.2.1 Expresses Regulares


Uma alternativa a gramticas regulares para especificao formal de uma lingua-
gem de nvel lxico a expresso regular. A linguagem de expresses regulares
resumida a seguir, usando sintaxe adaptada da famlia Lex (Mason et al., 1992) de
geradores lxicos:

Expresso Regular Significado


x Um caractere x (significa ele prprio)
\x Um caractere de escape, por exemplo, \n
{ nome } Uma referncia a um nome
M | N M ou N
M N M seguido de N (concatenao)
M* Zero ou mais ocorrncias de M
M+ Uma ou mais ocorrncias de M
M? Zero ou uma ocorrncia de M
[aeiou] O conjunto de vogais: a, e, i, o , u
[0-9] O conjunto de dgitos, 0 a 9
. Qualquer caractere (nico)

Observe que Lex usa \ para caracteres de escape, que de outra forma seriam inter-
pretados como metacaracteres (como chaves, \{ e \}), enquanto aqui usamos a fonte de
largura fixa para todos os no-metacaracteres.
Com essas convenes, agora redefinimos as diversas classes sintticas lxicas de
Clite da Figura 3.1. Observe que podem ser usados parnteses para agrupar elementos
de uma expresso regular.
As primeiras seis definies so auxiliares. A primeira definio indica que o conjunto de
caracteres imprimveis em ASCII consiste de todos os caracteres, indo do caractere de espao

Categoria Definio
qualquerCaractere [~]
letra [a-zA-Z]
dgito [0-9]
espaoembranco [\t]
eol \n
eof \004
palavra-chave bool | char | else | false | float | if | int | main |
true | while
identificador {letra}({letra} | {dgito})
litInteiro {dgito}+
litPontoFlutuante {dgito}\ .{dgito}
litCaractere {qualquerCaractere}
operador = | || | && | == | != | < | <= | > | >= | + | | * | / | ! | [ | ]
separador ;|,|{|}|(|)
comentrio //({qualquerCaractere} | {espaoembranco})*{eol}

| Figura 3.1 Expresses Regulares para Sintaxe Lxica Clite


3.2 Anlise Lxica 63

(cdigo ASCII 27) at o caractere de til (cdigo ASCII 126). A segunda linha define um
conjunto de letras, incluindo as maisculas e minsculas, enquanto a terceira linha define
o conjunto de dgitos. A seguir, os caracteres de espao em branco so definidos, excluindo
o final de linha. As duas definies auxiliares finais so os caracteres de final de linha e de
final de arquivo que utilizam convenes adotadas de Linux/Unix.
As linhas restantes, exceto a ltima, definem os diversos tokens: identificadores;
palavras-chave; literais de inteiros, ponto flutuante e caractere; operadores; e separadores
(ou pontuao). A ltima definio especifica um comentrio //, que pode incluir carac-
teres imprimveis e espao em branco, terminando com o final de linha (representado por
um caractere de nova linha).
Expresses regulares so uma ferramenta popular no projeto de linguagens porque
suportam facilmente a gerao automtica de analisadores lxicos, ou seja, em vez de
projetar manualmente e escrever cdigo para um analisador lxico, podem-se submeter
as expresses regulares diretamente a um gerador de analisador lxico. Dois geradores
comumente usados so da famlia Lex (Lex, Flex etc.) para gerar cdigo C/C++ e JLex
para gerar cdigo Java. Mais informaes sobre isso podem ser encontradas em diversos
textos sobre projeto de compiladores (Aho et al., 1986; Appel, 1998).
Apresentadas as definies anteriores do espao de possveis entradas, como projeta-
mos um programa para reconhecer essas seqncias? Exploramos esta questo a seguir.

3.2.2 Autmatos de Estados Finitos


Como j foi observado, h diversas formas equivalentes de se especificar o espao
de possveis entradas de um lexer: linguagem natural (como a mostrada anteriormen-
te), uma gramtica regular (preferencialmente regular direita) ou expresses regulares.
Tradutores dessas ltimas convertem-nas primeiro para um autmato de estado finito
determinstico (DFSA deterministic finite state automaton) e depois extraem um lexer
do DFSA.
Assim que se tiver adquirido a compreenso sobre como um DFSA, para reconhecer
tokens e outras seqncias de caracteres (como comentrios), construdo e convertido,
para cdigo, para seqncias simples de caracteres, raramente se ter de passar por esse
exerccio novamente. Todavia, em uma tentativa de evitar essa discusso de DFSAs, a pri-
meira edio deste texto apresentou um projeto ad hoc. As solues dos Exerccios 3.14 e
3.15 apresentam tipos de problemas nos quais o material desta seo deve sempre ser usado;
os autores encontraram compiladores nos quais estes problemas estavam incorretos.
Definio: Um autmato de estado finito possui:
1 Um conjunto de regras, representado por nodos em um grafo.
2 Um alfabeto de entrada, aumentado por um smbolo nico que representa o
final da entrada.
3 Uma funo de transio de estado, representada por arcos direcionados de
um nodo para outro, rotulados por um ou mais smbolos do alfabeto.
4 Um estado inicial nico.
5 Um conjunto de um ou mais estados finais (estados sem arestas de sada).
Um autmato de estado finito determinstico se, para cada estado e smbolo de entrada,
houver no mximo um arco de sada do estado rotulado com esse smbolo de entrada.
Por exemplo, um autmato de estado finito que reconhea identificadores mostrado
na Figura 3.2, em que S o estado inicial e F, o estado final. O smbolo l representa o
conjunto de letras, enquanto o smbolo d representa o conjunto de dgitos; $ o marcador
de final de entrada. O autmato da Figura 3.2 determinstico.
64 Captulo 3 Anlise Lxica e Sinttica

Figura 3.2 l,d

| Um Autmato de
Estado Finito para
Identificadores
S
l
I
$
F

Uma configurao de um autmato de estado finito consiste de um estado e a entrada


restante, terminada pelo smbolo especial de final de entrada. Um movimento significa
atravessar o arco que sai do estado que corresponde ao smbolo de entrada mais es-
querda, dessa forma, consumindo-o; se nenhum movimento estiver definido para o par
de smbolos estado-entrada, o autmato pra no erro, rejeitando a entrada. Uma entrada
aceita se, partindo do estado inicial, o autmato consumir todos os seus smbolos e parar
em um estado final.
Usando o autmato da Figura 3.2, os seguintes passos so dados para aceitar a entra-
da a2i$ comeando do estado S:
1 Mover-se para o estado I com entrada 2i$.
2 Mover-se para o estado I com entrada i$.
3 Mover-se para o estado I com entrada $.
4 Mover-se para o estado F sem entrada restante.
J que F um estado final, a2i , assim, aceito como um identificador vlido. For-
malmente, esta seqncia de passos escrita da seguinte forma:

(S, a2i$) | (I, 2i$)


| (I, i$)
| (I, $)
| (F, )

em que | denota um movimento. Assim, mostramos:

(S, a2i$) | * (F, )

ou seja, o DFSA aceita a string a2i$ em zero ou mais movimentos, terminando em um


estado final. Observe que | * outro uso da notao de estrela de Kleene.
Para facilitar o projeto de um DFSA a reconhecer a sintaxe lxica de Clite, as seguin-
tes convenes sero usadas:
Um smbolo terminador explcito aparece apenas para o programa-fonte de Clite
como um todo, em vez de para cada categoria lxica.
O smbolo l representa uma letra arbitrria e d, um dgito arbitrrio. Os smbolos
eoln e eof representam o final da linha e o final do arquivo, respectivamente. Todos
os outros smbolos representam a si prprios.
Um arco direcionado sem rtulo representa qualquer outro smbolo de entrada v-
lido, ou seja, o conjunto de todos os smbolos de entrada vlidos menos os rtulos
de arco para os outros arcos saindo do mesmo estado.
3.2 Anlise Lxica 65

O reconhecimento de tokens termina em um estado final (um sem arcos saindo).


O reconhecimento de no tokens possui arcos voltando para o estado inicial.
O reconhecimento do final de arquivo, representando o final do programa-fonte.

Finalmente, o autmato deve ser determinstico. Para assegurar isso, retiramos o re-
conhecimento de palavras-chave do DFSA, j que elas formam um subconjunto dos iden-
tificadores. Tambm analisamos todas as classes de tokens juntas que tenham um prefixo
comum. Por exemplo, tanto os literais int quanto float comeam com uma seqncia
de dgitos. Como outro exemplo, o smbolo de diviso e o de comentrio comeam ambos
com uma /, de modo que so analisados juntos.
O DFSA necessrio para Clite est esboado na Figura 3.3. Para que o diagrama
coubesse em uma pgina, muitos dos casos para pontuao simples e operadores foram
omitidos ( apresentado um de cada tipo). Por exemplo, o caso do caractere + aparece,
mas os casos para os outros tokens de um caractere (, * e assim por diante) so omitidos.
De forma semelhante, o caso da distino do operador de atribuio (=) do caso de igual-
dade (==) dado, mas no o menor que (<) versus menor ou igual (<=) e assim por diante.
Observe que as ocorrncias de espaos em branco ws, incluindo eoln e comentrios volta
para o estado inicial S. Os subdiagramas para literais de ponto flutuante e inteiros e iden-
tificadores so semelhantes Figura 3.2.
Tambm deve ser observado que este projeto ignora palavras reservadas, o que sim-
plifica bastante o DFSA. Em vez disso, assim que um identificador tiver sido reconhecido,
o cdigo que implementa o DFSA faz uma simples busca em uma tabela para distinguir
palavras reservadas de identificadores.
O analisador lxico (lexer) implementa este DFSA. Nessa implementao, o le-
xer supe que o analisador o chama cada vez que outro token necessrio. Assim, o
lexer precisa lembrar onde parou no consumo de caracteres-fontes de entrada cada
vez que for chamado.
Alm disso, a presena de laos no DFSA far com que o lexer avance um caractere
a mais na leitura da entrada. Por exemplo, analise a seguinte entrada-fonte:

se (a<b)

Na ausncia de espao em branco aps o a, o DFSA reconhecer o a como um Identi-


ficador somente aps avanar para o smbolo <. A prxima chamada no lexer avanar
imediatamente para o prximo caractere, a saber, b. H diversas solues possveis para
este problema:
1 Usar uma funo de leitura frente (look-ahead) para permitir o exame do prxi-
mo caractere sem consumi-lo. Em termos de tempo de execuo, este o projeto
menos eficiente.
2 Usar uma funo de envio de volta (push-back) de modo que um caractere possa
ser retornado para a entrada quando necessrio. Esta funo seria usada no reco-
nhecimento de strings de dgitos e identificadores, por exemplo, mas continua
pouco eficiente.
3 Adote a conveno de que sair do estado inicial no consome um caractere. Em
termos de tempo de execuo, este o projeto mais eficiente, j que cada caractere
examinado exatamente uma vez.
Nosso projeto DFSA para Clite usa a terceira alternativa, que necessita da introdu-
o de uma transio extra para avanar a entrada em diversos lugares. Por exemplo, no
caso em que um caractere de soma (+) visto saindo do estado inicial, uma transio sem
rtulo para um novo estado adicionada de modo a manter um caractere frente.
66 Captulo 3 Anlise Lxica e Sinttica

ws

/ eoln

l,d

, ,

& &

Figura 3.3 Projeto do DFSA do Analisador Lxico

| Aqui, ws representa caracteres de espao em branco; d, os dgitos; l, letras; eoln, final


de linha; eof, final de arquivo; e todos os outros caracteres representam a si prprios.
Um arco sem rtulo representa todos os outros caracteres.
3.2 Anlise Lxica 67

Observao
Variante da Funo de Pushback
Uma variante interessante do uso de uma funo de pushback para executar uma procura
frente ocorreu em diversos compiladores Pascal de ETH. Pascal permitia subfaixas na
forma x..y, na qual tanto x quanto y tinham de ser constantes inteiras ou literais inteiras.
Analise a subfaixa 5..12 na ausncia de espaos em branco. Para distingui-la do literal
de ponto flutuante 5.12, o compilador tinha de avanar para um segundo ponto. Na falta de
um recurso de pushback, o lexer substitua o segundo ponto com dois pontos (:). Testes
confirmaram que o compilador aceitava 5:12 como uma faixa vlida, embora fosse sin-
taxe lxica Pascal no permitida.

3.2.3 Do Projeto ao Cdigo


Tendo estas consideraes em mente, agora podemos implementar um DFSA para
Clite3. J que os casos tanto para espaos em branco quanto para comentrios voltam para o
estado inicial, em um nvel mais externo, a funo para obter o prximo token deveria ser
um lao do-while contendo um grande comando switch, conforme mostrado na Figura 3.4.
O lao repetido at que algo diferente do espao em branco ou de um comentrio
seja encontrado, quando ento o lao pode ser terminado por meio de um comando re-
turn. A varivel ch contendo o caractere de entrada corrente deve ser global, j que seu
valor deve ser lembrado de uma chamada do mtodo next at a prxima; a varivel ch
inicializada com um caractere de espao.
Para completar o processo de traduo do DFSA para o cdigo do lexer, as seguintes
regras devem ser seguidas:

1 O cdigo correspondente a atravessar um arco do n A para o B deve obter o pr-


ximo caractere, a menos que A seja o estado inicial.
(a) Se o rtulo x do arco for um nico caractere, deve haver um teste ch == x.
(b) Se o rtulo x do arco for um conjunto, deve haver um teste que implemente
efetivamente ch  x, ou seja, um teste para ver se ch faz parte do conjunto
desejado.

private chair ch = ;
public Token next ( ) {
do {
switch (ch) {
...
}
} while (true);
}

| Figura 3.4 Esboo da Rotina para Obter o Prximo Token

3. Estritamente falando, este passo poderia ser executado mecanicamente, como tem sido feito em muitas gera-
es de analisadores, ou seja, poderamos gerar tabelas representando o DFSA e usar um algoritmo orientado a
tabelas como sendo o lexer; verses iniciais de Lex usavam essa abordagem. O problema com ela que o lexer
resultante executado at 10 vezes mais lentamente do que quando o DFSA traduzido mo para o cdigo.
68 Captulo 3 Anlise Lxica e Sinttica

(c) Se o arco no tiver rtulo, deve haver um teste para garantir que ch no seja um
dos rtulos em algum dos outros arcos. (Se este for o nico arco saindo do estado,
nenhum teste necessrio.)
2 Um n com um arco para si prprio corresponde a um lao do-while, no qual a
condio corresponde ao rtulo do arco.

3 Caso contrrio, o movimento traduzido para um comando switch (ou if), com
cada arco sendo um case separado. Se um dos arcos no estiver rotulado, ele se
torna o case default. Caso contrrio, o case default um case de erro.
4 Uma seqncia de transies se torna uma seqncia de comandos traduzidos.
5 Um subdiagrama complexo traduzido colocando seus componentes dentro de
uma caixa, tornando-a efetivamente um n e ento traduzindo-se cada caixa usan-
do a estratgia outside-in.

... ...

Aplicar essas regras ao projeto do DFSA na Figura 3.3 resulta no cdigo dado na
Figura 3.5, novamente com os cases semelhantes omitidos. Para simplificar esse cdigo,
a rotina auxiliar nextChar traduz um caractere de final de linha para o de nova linha e um
de final de arquivo para um Control-D, usando convenes comuns Linux/Unix. Essas
suposies funcionam para Clite porque o programa-fonte restrito a caracteres imprim-
veis (no de controle, exceto apenas pelo caractere de tabulao).
Este esqueleto de cdigo usa diversos mtodos para executar sua tarefa. Algumas so
implementaes das definies de expresses regulares auxiliares, como isLetter:

private boolean isLetter (char c) {


return (c>=a && c<=z || c>=A && c<=Z);
}

Outros so mtodos que representam um cdigo comum. Por exemplo, a lgica bsica
para processar os caracteres de um identificador repetida para processar um nmero
inteiro e uma frao decimal:

private String concat(String set) {


StringBuffer r = new StringBuffer();
do {
r.append(ch);
ch = nextChar();
} while (set.index0f(ch) >= 0);
return r.toString( );
}
3.2 Anlise Lxica 69

public Token next( ) { // Retorna o prximo token


do {
if (isLetter(ch)) { // identificador ou palavra-chave
String spelling = concat(letters + digits);
return Token.keyword(spelling);
} else if (isDigit(ch)) { // literal inteiro ou de ponto flutuante
String number = concat(digits);
if (ch != .) // literal inteiro
return Token.mkIntLiteral(number);
number += concat(digits);
return Token.mkFloatLiteral(number);
} else switch (ch) {
case : case \t: case \r: case eolnCh:
ch = nextChar();
break;
case / : // diviso ou comentrio
ch = nextChar();
if (ch != /) return Token.divideTok;
// comentrio
do {
ch = nextChar();
} while (ch != eolnCh);
ch = nextChar();
break;
case \ : // caractere literal
char ch1 = nextChar();
nextChar(); // ler
ch = nextChar();
return Token.mkCharLiteral( + ch1);
case eofCh: return Token.eofTok;
case +: ch = nextChar();
return Token.plusTok;
...
case &: check(&); return Token.andTok;
case |: check(|); return Token.orTok;
case =:
return chkOpt(=, Token.assignTok,
Token.eqeqTok);
...
} // switch
} while (true);
}

| Figura 3.5 Mtodo Tokenizador do Lexer de Clite


70 Captulo 3 Anlise Lxica e Sinttica

Dois outros mtodos auxiliares so check e chkOpt. O primeiro deles, check,


usado para processar dois tokens de caracteres nos quais o primeiro caractere por si s
no um token vlido; um exemplo o operador de conjuno &&. O segundo, chkOpt,
processa dois tokens de caracteres nos quais o primeiro caractere, por si s, um token v-
lido. Se o seu argumento caractere no estiver presente, ele retorna o primeiro argumento
token; se estiver presente, retorna o segundo argumento token. Ele tambm avana pelo
fluxo de caracteres-fonte conforme necessrio. Neste caso, um exemplo o operador de
atribuio = versus o operador de igualdade ==.
Uma implementao completa das classes Lexer e Token para a sintaxe lxica apre-
sentada na Figura 2.7 pode ser baixada do site do livro. Embora esta implementao seja
feita em Java, pode ser facilmente traduzida para C, C++ ou Ada.
Para ilustrar a operao do Lexer, analise o seguinte programa Clite do exemplo:

// Um primeiro programa com


// duas linhas de comentrio
int main() {
char c;
int i;
c = h;
i = c + 3;
} // main

Para esse programa, o lexer produz a lista de tokens mostrada na Figura 3.6, excluindo o
token de final de arquivo. Na lista, a maioria dos tokens aparece como eles prprios. Qua-
tro tipos de tokens (Identificador, litInteiro, litPontoFlutuante e litCaractere) aparecem
com seu tipo de token e seu valor associado.

3 .3 A N LIS E S IN T T ICA
Definio: O propsito do analisador sinttico, ou analisador, construir uma
rvore de anlise que use como entrada o fluxo de tokens fornecido pelo lexer.
A sada do analisador geralmente uma rvore de sintaxe abstrata. A motivao para o
uso de uma rvore de sintaxe abstrata, em vez de uma rvore de anlise, foi discutida na
Seo 2.5.1.

int Identificador c
main =
( litCaractere h
) ;
{ Identificador i
char =
Identificador c Identificador c
; +
int litInteiro 3
Identificador i ;
; }

| Figura 3.6 Lista de Tokens de um Programa de Exemplo


3.3 Anlise Sinttica 71

Definio: Um analisador descendente recursivo um analisador no qual cada


no-terminal da gramtica convertido em uma funo que reconhece as entradas
derivveis desse no-terminal.
Para uma linguagem com uma gramtica grande como C/C++, Java ou Ada, um gerador de
analisador seria usado para gerar um analisador orientado a tabela (veja a Observao na
pgina 81). Entretanto, para linguagens com gramticas pequenas, prefervel um analisa-
dor descendente recursivo. Exemplos de tais linguagens incluem arquivos de configurao,
frmulas em planilhas eletrnicas e pequenas linguagens como a do Exerccio 3.21.
Como vimos na Seo 2.1, a gramtica de uma linguagem de programao define a
estrutura das diferentes partes de um programa como expresses aritmticas (por exem-
plo, x+2*y), declaraes de atribuio (por exemplo, z=2*x+y;), comandos de lao (por
exemplo, for (i=0; i<n; i++) a[i]=a[i] + 1;), definies de funes, declaraes
de variveis (por exemplo, int n ;) e at mesmo programas completos. A sintaxe EBNF de
uma linguagem fornece uma definio precisa na qual a gerao de uma rvore de anlise
a partir de um fluxo de tokens pode se basear.
Analise as categorias sintticas Atribuio e Expresso, que incluem todas as se-
qncias de Tokens que descrevem clculos aritmticos e atribuies do resultado a uma
varivel. Uma gramtica EBNF simples para essas categorias abreviadas da gramtica
Clite na Seo 2.3 mostrada na Figura 3.7. Esta gramtica servir como a gramtica de
exemplo para construir um analisador. Na gramtica, o smbolo terminal Literal denota
apenas um literal inteiro, ou seja, um nmero completo sem sinal.

3.3.1 Definies Preliminares


No preparo da construo de um analisador para esta gramtica de expresso, preci-
samos definir quatro conceitos preliminares: gramticas expandidas, Primeiro, anulvel
e grafo de dependncia esquerda.
Algoritmos que construam analisadores s vezes requerem que a gramtica esteja
em uma forma expandida. Nesta forma, um novo smbolo inicial S' introduzido com
uma nica regra, a saber:

S' S $

em que S o smbolo original e $ um novo smbolo terminal que representa o final da


informao. Por exemplo, na Seo 3.2, este terminal era o token de final de arquivo.
Outro conceito-chave usado na construo de analisadores o de conjunto Primeiro,
que contm todos os terminais que sejam smbolos mais esquerda derivveis de um
determinado smbolo, ou seja:

Primeiro(X)  {a  T | X * aw, w  (N  T)*}

Identificador  Expresso ;

|
Figura 3.7 Atribuio
Sintaxe Concreta Expresso Termo { opAdio Termo }
para Atribuies e
opAdio |
Expresses
Termo Fator {opMultiplicao Fator }
opMultiplicao *|/
Fator [opUnrio] Primrio
opUnrio |!
Primrio Identificador | Literal | ( Expresso )
72 Captulo 3 Anlise Lxica e Sinttica

Primeiro(X) pode ser lido como o conjunto de todos os smbolos terminais que podem
ocorrer como smbolos mais esquerda em uma derivao comeando em X. Quando X
for um smbolo terminal, segue-se que:

Primeiro(a)  {a}

Ou seja, o conjunto Primeiro(a) contm apenas o prprio a.


Quando for uma string arbitrria de terminais e no-terminais X1 ... XnU..., o con-
junto Primeiro() definido da seguinte forma:

Primeiro(X1...XnU...)  Primeiro(X1) ...Primeiro(Xn)  Primeiro(U)


em que X1,...,Xn so anulveis
e U no anulvel.

Um no-terminal anulvel se derivar a string vazia, ou seja:

A*

Uma vez que terminais no derivam algo alm de si prprios, no so anulveis.


Uma primeira observao na gramtica EBNF da Figura 3.7 sugere que nenhum dos
no-terminais seja anulvel, j que nenhuma das alternativas para qualquer no-terminal
vazia. Entretanto, este no o caso. Para encontrar o conjunto de no-terminais anulveis,
podemos usar o seguinte algoritmo [Knuth, 1971]:

int oldSize;
Set nullable = new Set();
do {
oldSize = nullable.size();
for (Production p : grammar.productions()) {
boolean allNull = true;
for (Symbol t : p.rule())
if (! nullable.contains(t))
allNull = false;
if (allNull)
nullable.add(p.nonterminal());
}
} while (nullable.size() > oldSize); // conjunto nullable cresceu

Basicamente, o algoritmo itera por todas as regras de produo da gramtica. Para cada
uma dessas produes p, se p for uma regra vazia ou se todos os smbolos da regra forem
anulveis, ento o no-terminal no lado esquerdo da produo adicionado ao conjunto
de smbolos anulveis. Este algoritmo seguramente parar, caso contrrio, cada iterao
de do while faria o conjunto nullable crescer em um smbolo no-terminal. Entretanto,
o conjunto de no-terminais em uma gramtica sempre finito.
Antes de aplicar o algoritmo gramtica da Figura 3.7, reescreveremos a gramtica,
aplicando as seguintes transformaes:
1 Coloque a gramtica em uma forma expandida usando $ para designar o final da
entrada.
2 Renomeie todos os no-terminais usando abreviaes de um ou dois caracteres
para facilitar o desenho dos grafos necessrios.
3.3 Anlise Sinttica 73

3 Abrevie o token Identificador como i e o Literal como l.


4 Substitua cada metaconstruo por um no-terminal conforme esboado no final
da Seo 2.2. Lembre-se de que, se A for um no-terminal e x, y e z forem seqn-
cias arbitrrias de terminais e no-terminais, ento qualquer regra EBNF que tiver
metachaves:

A x{y}z

pode ser reescrita de forma equivalente sem metachaves da seguinte forma:

A x A' z
A' | y A'

em que A' um novo no-terminal nico.

Por exemplo, aplicando essas transformaes gramtica na Figura 3.7 obtemos a gram-
tica da Figura 3.8. Nesta nova gramtica, os no-terminais E' e T' representam a iterao
nas produes originais Expresso e Termo, enquanto F' representa o opcional opUnrio
na produo original Fator.
Agora, se aplicarmos o algoritmo anulvel a esta nova gramtica na Figura 3.8, ob-
teremos o seguinte conjunto de no-terminais anulveis:

Passo Anulvel
1 E T F
2 E T F

O algoritmo descobre todos os no-terminais anulveis no primeiro passo, mas requer um


segundo passo para descobrir que no existem adicionais.
A seguir, definimos o grafo de dependncia esquerda de uma gramtica G. Cada
smbolo terminal ou no-terminal um n nesse grafo. Para cada produo da forma:

A U1 ... Un Xw

Figura 3.8 S A$

| Gramtica
de Expresso
Reescrita
A
E
E'



iE;
T E'
| AO T E'
AO |
T F T'
T' | MO F T'
MO *|/
F F' P
F' | UO
UO |!
P i | l | (E)
74 Captulo 3 Anlise Lxica e Sinttica

Tabela 3.1

|
No-terminal Primeiro
Conjunto Primeiro A i
da Gramtica E !i|(
de Expresso
E +
AO +
T i|(
T */
MO */
F ! i|(
F !
UO !
P i|(

desenhamos um arco de A a X se n  0 ou U1, ..., Un forem anulveis, ou seja, se no houver


nada esquerda de X ou se todos os smbolos esquerda de X forem anulveis. Aplicar esta
regra Figura 3.8 resulta no grafo de dependncia esquerda da Figura 3.9.
Calcular o conjunto Primeiro(A) a partir do grafo de dependncia esquerda agora
direto. Primeiro(A) consiste de todos os smbolos terminais no grafo de dependncia
esquerda que sejam atingveis a partir de A. Aplicar esse mtodo visualmente ao grafo
resulta na coluna rotulada Primeiro na Tabela 3.1.
Um algoritmo para calcular todos os ns que sejam atingveis a partir de um deter-
minado n pode ser esboado da seguinte maneira:

Set reachable(Node start) {


Set set = new Set( );
set.add(start);
int oldSize;
do {
oldSize = set.size();
for (Node a : set)
set.add(a.to( ));
} while (set.size() > oldSize);
return set;
}

3.3.2 Anlise Descendente Recursiva


Com esses conceitos iniciais definidos, agora estamos prontos para construir um anali-
sador a partir de uma gramtica EBNF. Existem diversos algoritmos diferentes para anlise,
e cada um possui suas vantagens e desvantagens (veja a Observao na pgina 81). O algo-
ritmo de anlise que apresentamos aqui chamado de analisador descendente recursivo4.

4. Formalmente, a anlise descendente recursiva uma variante da anlise LL(1), que se move da esquerda para
a direita no fluxo de entrada e precisa observar frente apenas um token para tomar qualquer deciso de anlise.
Essa caracterstica garantida pela gramtica em considerao. A maioria das linguagens modernas, como Java,
possui gramticas que permitem anlise eficiente.
3.3 Anlise Sinttica 75

$
S

A 

E E AO 

T T MO 

F F UO !

i l ( )

| Figura 3.9 Grafo de Dependncia Esquerda para a Gramtica de Expresso


76 Captulo 3 Anlise Lxica e Sinttica

O objetivo de qualquer analisador construir uma rvore de anlise a partir do fluxo


de tokens produzido pelo lexer. Analisadores descendentes recursivos possuem um mto-
do correspondente a cada no-terminal da gramtica. O trabalho de cada um desses mtodos
tem duas partes:
1 Reconhecer a seqncia mais longa de tokens derivvel dela no fluxo de entrada e
2 Construir uma subrvore de sintaxe abstrata e retornar um objeto da classe abstrata
que a raiz dessa subrvore.
Assim, precisamos de um algoritmo para converter regras de produo em cdigo. Se
o analisador no conseguir reconhecer a entrada, ou seja, construir implicitamente uma
rvore de anlise, ele ir parar com uma mensagem de erro que identifica o primeiro erro
encontrado.
Cada mtodo do analisador descendente recursivo corresponde a um smbolo no-
terminal A da gramtica. Esse mtodo construdo diretamente a partir de regra(s) de
gramtica EBNF da forma A , e o nome do mtodo A. Assim, usando a gramtica
da Figura 3.7 como exemplo, haver mtodos chamados atribuio, expresso, opAdio,
termo, opMult, fator, opUnrio e primrio. Supondo que A possua uma categoria sinttica
abstrata correspondente, o mtodo ter um tipo de retorno.
Como exemplo, construiremos um mtodo Java que analisa a entrada da classe sin-
ttica concreta Atribuio e gera uma rvore sinttica abstrata definida pela gramtica
da Figura 2.11. A regra de sintaxe concreta que governa o projeto desse mtodo (veja a
Figura 3.7) :
Atribuio Identificador  Expresso;
Primeiro, construmos um mtodo esqueleto apropriado:

private Assignment assignment ( ) {


...
return new Assignment(...);
}

Guiado pelo lado direito dessa regra de produo, este mtodo deve conter cdigo
que reconhea um Identificador, o operador =, uma Expresso e o separador ;, exatamente
nessa ordem.
Dois mtodos auxiliares geralmente so teis nesse processo: match e error. A cha-
mada a match(t) recupera o prximo token ou exibe uma mensagem de erro de sintaxe,
dependendo de o token corrente corresponder ou no ao token t esperado:

private String match (TokenType t) {


String value = token.value();
if (token.type().equals(t))
token = lexer.next();
else
error(t);
return value;
}
3.3 Anlise Sinttica 77

A chamada a error(t) exibe uma mensagem de erro na tela e encerra o processo


de anlise:

private void error(TokenType tok) {


System.err.println(Erro de sintaxe: esperando: + tok
+ ; encontrado: + token);
System.exit(1);
}

Na nossa implementao, um token consiste de duas partes: um tipo e um valor. O primeiro


determina a classe lxica do token (por exemplo, Identificador, Literal) e o valor a string em
questo (por exemplo, ch, 1). Para a maioria dos tokens, conhecer seu tipo suficiente.5
Usando esses mtodos auxiliares, a traduo da regra para Atribuio em um mtodo
continua da seguinte maneira:
1 Cada smbolo no lado direito da regra traduzido em seqncia, resultando em
uma seqncia de mais quatro comandos.
2 O primeiro smbolo a ser traduzido um terminal, de modo que a traduo deve
chamar o mtodo match, passando para ele um Identificador. Alm disso, a cate-
goria concreta Identificador corresponde categoria abstrata Varivel neste con-
texto, de forma que uma nova varivel local target deve ser declarada e iniciali-
zada com o valor retornado por match.
3 Uma traduo semelhante aplicada ao smbolo terminal =, mas, j que este termi-
nal no possui papel na sintaxe abstrata, nenhuma nova varivel local necessria.
4 O no-terminal Expresso traduzido para uma chamada ao mtodo expres-
sion, e o resultado atribudo a uma nova varivel local source correspondente
categoria abstrata Expresso.
5 Finalmente, o smbolo terminal ; pontuao, logo tratado como no passo 3
acima.
O mtodo resultante tem a seguinte forma:

private Atribuio atribuio ( ) {


// Atribuio --> Identificador = Expresso;
Varivel target = new Varivel(match(Token.Identifier));
match(Token.Assign);
Expresso source = expresso();
match(Token.Semicolon);
return new Atribuio(target, source);
}

Criticamente, este mtodo se baseia em um mtodo expression, que recebe o token


corrente (o que segue o operador =) e analisa o fluxo seguinte de tokens para derivar e
retornar uma rvore de anlise abstrata para Expresso. Dada a regra
Expresso Termo { opAdio Termo }

5. Entretanto, para o token Identificador e os literais inteiro, ponto flutuante e caractere, um valor associado
ao token, dando o modo de escrever do identificador, os dgitos que constituem um literal inteiro etc. No lexer,
h um token constante para todos, menos os tokens de literal e identificador; o ltimo deve ser criado dinamica-
mente para cada ocorrncia. Este projeto pode ser facilmente traduzido para Ada, C ou C++.
78 Captulo 3 Anlise Lxica e Sinttica

podemos construir o mtodo necessrio da seguinte forma:

1 O tipo de retorno estabelecido como Expresso e um comando return inserido.


2 O no-terminal Termo traduzido para uma chamada de mtodo cujo valor de
retorno do tipo Expresso.
3 A iterao traduzida para um lao while com o teste assegurando que o token cor-
rente pertence ao conjunto Primeiro(opAdio), podendo ser um sinal de mais (+) ou
de menos (). Usamos um mtodo auxiliar verificaOpAdio para executar o teste.
4 O corpo do lao a traduo dos smbolos opAdio Termo. Estritamente falando,
a traduo do smbolo opAdio deve resultar em uma chamada a um mtodo que
use um switch para determinar se o token corrente um sinal de mais ou de menos.
J que o teste do lao while j assegura isso, otimizamos manualmente o cdigo
para ser uma chamada a obter o prximo token.
5 O no-terminal Termo novamente traduzido para uma chamada de mtodo cujo
valor de retorno do tipo Expresso.

Devido iterao na produo, temos os mesmos problemas de leitura frente que


tivemos na Seo 3.2.3. As solues, exceto por serem aplicadas a tokens em vez de
caracteres, tambm so as mesmas: uma funo peek (de leitura frente), uma funo
push back (de envio de volta) ou manter sempre a leitura de um token frente. J que
preferimos a ltima alternativa, nossa traduo de produes a codificar supe que, quan-
do o mtodo expression for chamado, token j possui o valor do primeiro token a ser
reconhecido, e na sada a varivel token ficar com o valor do prximo token, seguindo
a regra de produo integralmente analisada. Esta converso seguida para cada mtodo
de anlise, exceto a regra de extenso.
O mtodo resultante mostrado na Figura 3.10. Este mtodo reflete o fato de que
uma Expresso possui pelo menos um Termo, e se o prximo token for um sinal de mais
ou de menos, ento deve ser seguido por outro Termo. J que pode haver qualquer nmero
de ocorrncias de uma opAdio e um Termo, a iterao EBNF traduzida para um lao.
No final, o mtodo expression reconhece a seqncia mais longa de Termos separados
por sinais de mais e de menos.
Esse mtodo tambm se baseia na existncia de um mtodo term, cuja construo
semelhante quela de expression. Os mtodos para as categorias sintticas remanescen-
tes Termo, Fator e Primrio para essa gramtica tambm aparecem na Figura 3.10.
Considere a anlise da entrada z=x+2*y; como uma Atribuio e a gerao da r-
vore de sintaxe abstrata mostrada na Figura 2.13. Primeiro, o mtodo assignment
chamado durante a varredura do Identificador z. Isso corresponde construo da raiz
de uma rvore de anlise abstrata Atribuio. O primeiro comando visa a reconhecer um
Identificador, que corresponde a construir a subrvore mais esquerda da Atribuio na
Figura 2.13. A seguir, o operador  reconhecido, mas no retido na rvore abstrata
porque no mais necessrio. O terceiro comando chama o mtodo expression, que cria
a subrvore abstrata Expresso para Atribuio. O mtodo expression busca um token
Identificador; assim ele chama term, que chama factor, que chama primary, que cor-
responde ao token. Tudo isso corresponde a desenhar a subrvore esquerda de Expresso.
Terminar a pilha de chamadas de primary, factor e term efetivamente conclui a criao
das subrvores remanescentes da Figura 2.13.
A Figura 3.11 resume um algoritmo chamado de algoritmo T, para traduzir uma produ-
o em cdigo. Devido a cada iterao de uma regra de produo ser traduzida para um lao
while, o analisador ficar no lao at ter avanado um token a mais. Este problema idntico
ao problema do lao no DFSA da Seo 3.2.2 e admite as mesmas solues. O algoritmo T
3.3 Anlise Sinttica 79

private Expresso expresso () {


// Expresso > Termo { opAtrib Termo }
Expresso e = term();
while (isAddOp()) {
Operator op = new Operator(match(token.type()));
Expresso term2 = term();
e = new Binary(op, e, term2);
}
return e;
}

private Expresso term () {


// Termo > Fator { opMult Fator }
Expresso e = factor();
while (isMultiplyOp()) {
Operator op = new Operator(match(token.type()));
Expresso term2 = factor();
e = new Binary(op, e, term2);
}
return e;
}

private Expresso factor() {


// Fator > [ opUnrio ] Primrio
if (isUnaryOp()) {
Operator op = new Operator(match(token.type()));
Expresso term = primary();
return new Unary(op, term);
}
else return primary();
}

private Expresso primary () {


// Primrio > Identificador | Literal | ( Expresso )
Expresso e = null;
if (token.type().equals(TokenType.Identifier)) {
e = new Variable(match(TokenType.Identifier));
} else if (isLiteral()) {
e = literal();
} else if (token.type().equals(TokenType.LeftParen)) {
token = lexer.next();
e = expression();
match(TokenType.RightParen);
} else error(Identificador | Literal | ();
return e;
}

| Figura 3.10 Analisador Descendente Recursivo para Expresses


80 Captulo 3 Anlise Lxica e Sinttica

1 Nomeie o mtodo A. Se A tiver uma categoria sinttica abstrata A' correspondente, faa
o tipo de retorno do mtodo ser A' e adicione um comando return new A' de modo
que assegure que todos os campos de A' sejam inicializados apropriadamente.
2 Se for o y terminal, verifique se o token corrente t uma instncia de y e ento
obtenha o prximo token. Caso contrrio, emita uma mensagem de erro e pare (o
token est errado). Se y corresponder a um campo na definio de sintaxe abstrata
de A', atribua seu valor a esse campo.
3 Se for um no-terminal B, chame o mtodo B e atribua o valor retornado por B
a uma varivel new na categoria sinttica abstrata B'.
4 Se for uma iterao com metachaves {'}, traduza para um lao while. O corpo
do lao while T aplicado recursivamente ao contedo da iterao '. Se t o
token corrente, o teste do lao t  Primeiro ('); como na Seo 3.2, existem
vrias formas para se implementar tais testes.
5 Se for um conjunto de alternativas 1|...|n , com ou sem metaparnteses, traduza
para um comando switch (ou uma seqncia de if/else-if) de acordo com o token t
corrente. Aplique recursivamente T a cada i , em que o teste do switch corresponde a
t  Primeiro(i ). Se nenhuma das alternativas for vazia, ento o case default deve ser
de que o token invlido (erro); caso contrrio, o case default vazio.
6 Se for uma escolha opcional com metacolchetes ['], pode ser reescrito como
('|) e traduzido usando a regra anterior.
7 Se estiver na forma de X1 ... Xn , aplique T recursivamente a cada Xi , resultando
na seqncia de comandos: T(X1); ...; T(Xn).

| Figura 3.11 O Algoritmo T para Implementar a Regra A no Mtodo A

supe que no reconhecimento de um no-terminal A, o mtodo A comea encontrando o


primeiro token que seja derivvel de A e termina encontrando o primeiro token que no
corresponda a uma string derivvel de A. O mtodo A corresponde assim a string mais
longa derivvel do no-terminal A.
O algoritmo T aplicado a todas as produes, exceto produo de extenso. Esta
ltima responsvel por:

1 Obter o primeiro token.


2 Chamar o mtodo correspondente ao smbolo inicial original da gramtica.
3 Verificar se todas as informaes (tokens) foram consumidas, mas no buscar um
token aps o final do arquivo (veja a Seo 3.2.3).

A anlise descendente recursiva requer que a gramtica no seja recursiva esquerda,


ou seja, no exista um A no-terminal tal que A + A ....6 Caso contrrio, o analisador
pode entrar em um lao recursivo infinito porque, sob alguma condio de entrada, o mto-
do A pode chamar a si prprio sem consumir alguma entrada; tendo feito isso uma vez, ele
continuar a faz-lo indefinidamente. Este outro motivo pelo qual gramticas EBNF so
preferidas, j que a recurso direta esquerda sempre pode ser substituda pela iterao.7

6. O no-terminal A deriva a si prprio como o smbolo mais esquerda, usando uma ou mais derivaes. Na
notao Kleene-star, um sinal de mais significa um ou mais do que o smbolo sua esquerda.
7. Felizmente, h uma verificao simples para a recurso esquerda, a saber, a presena de um ciclo no grafo
de dependncia esquerda. Isso significa que todos os no-terminais do ciclo so recursivos esquerda. Para a
nossa gramtica de exemplo, nenhum ciclo est presente na Figura 3.9.
3.3 Anlise Sinttica 81

Observao
Analisadores Orientados a Tabela
Para implementar um compilador ou tradutor para uma linguagem de programao como
C, C++, Java ou Ada, normalmente usado um gerador de analisador como o yacc/bison
ou o JavaCC, embora alguns compiladores C (Waite e Carter, 1993) usem um descen-
dente recursivo. A maioria dos sistemas geradores de analisadores produzem analisadores
orientados a tabela.
LL(1) um analisador top-down que basicamente uma verso orientada a tabela de
um analisador descendente recursivo, em que o 1 indica a quantidade de leituras frente,
ou seja, 1 token. Analisadores orientados a tabela so preferidos por gramticas grandes
porque o algoritmo fixo e apenas o tamanho da tabela cresce em funo do tamanho da
gramtica. O primeiro L em LL(1) indica que a entrada lida e processada da esquerda
para a direita, enquanto o segundo L indica que a rvore de anlise construda usando
a derivao mais esquerda.
Utilizando a gramtica da Figura 3.8 e numerando cada produo alternativa come-
ando do zero, obtemos a seguinte tabela de anlise LL(1):

N id lit + - * / ! ( ) ;
A 1
E 2 2 2
E 4 4 3 3
AO 5 6
T 7 7 7
T 8 8 9 9 8 8
MO 10 11
F 12 12 12
F 13 13 14 14 13
UO 15 16
P 17 18 19

O algoritmo de anlise LL(1) inicialmente coloca o lado direito da regra estendida


(por exemplo, A$) na pilha, de modo que A fique no topo. A seguir ele repete os passos
abaixo, em que X o topo corrente da pilha e a, o token de entrada corrente.

1 Se X e a forem ambos o smbolo final $: pare e aceite a entrada como sendo vlida.
2 Se X for um smbolo terminal: se X for igual a a, desempilhe X e obtenha o prxi-
mo token; caso contrrio, pare, porque ocorreu um erro.
3 Se X contiver um no-terminal: se a posio da tabela [X, a] estiver vazia, pare
porque ocorreu um erro; caso contrrio, ele deve conter um nmero de produo
p; empilhe os smbolos da produo da direita para a esquerda de modo que o
smbolo mais esquerda fique no topo.

O algoritmo de anlise mais amplamente usado o LR(1), que constri a rvore de


anlise de baixo para cima, em vez de constru-la de cima para baixo, como o caso do
LL(1). Por causa disso, um analisador LR(1) capaz de lidar com gramticas recursivas
esquerda, como a gramtica G1 da Seo 2.1.4. Alm disso, um teorema que cada
gramtica LL(1) tambm uma LR(1). Para mais informaes sobre analisadores LR(1),
consulte um texto sobre compiladores como (Aho et al., 1986).
82 Captulo 3 Anlise Lxica e Sinttica

3 . 4 R ES UM O
Este captulo apresentou um breve exame da hierarquia de Chomsky de linguagens
formais, demonstrando gramticas regulares como base para a construo de um anali-
sador lxico.
A seguir, mostramos um exame aprofundado do projeto e da implementao de um
analisador lxico para o Clite. Primeiramente, foram discutidos o conceito de token e
outras tarefas de um analisador lxico. Foram usadas expresses regulares como uma
forma alternativa de especificao de tokens. Para projetar manualmente um lexer, foram
apresentados autmatos de estados finitos determinsticos (DFSA). Finalmente, foi dada
a traduo de um DFSA para cdigo.
Na ltima seo deste captulo, a construo de um analisador descendente recur-
sivo foi demonstrada. Diversos algoritmos e algumas definies auxiliares foram apre-
sentados, incluindo a extenso de uma gramtica, o clculo do conjunto de no-terminais
anulveis e o clculo do conjunto de terminais mais esquerda derivveis de cada no-
terminal A (denominado Primeiro(A)). Finalmente, foi exibido um mtodo para traduzir
cada produo da forma A para um mtodo A para analisar .

E XE R C CI OS
3.1 Reescreva as produes para cada um dos seguintes no-terminais como gramticas regulares
direita: Identificador, Ponto Flutuante.

3.2 Reescreva as produes para cada um dos seguintes no-terminais como gramticas regulares
esquerda: Identificador, Ponto Flutuante.

3.3 Desenhe um DFSA que contenha apenas letras e dgitos, em que o identificador deva ter pelo menos
uma letra, mas que ela no precise ser o primeiro caractere. Dica: tudo esquerda da letra mais
esquerda deve ser um dgito.

3.4 Tente definir a linguagem {a n b n} usando uma gramtica regular. Discuta o porqu de isso talvez
no ser possvel.

3.5 Tente definir a linguagem {an bn} usando um DFSA. Discuta o porqu de isso talvez no ser possvel.

3.6 Determine por que linguagens mais antigas como Fortran e Cobol no diferenciam letras maiscu-
las de minsculas.

3.7 Que partes da linguagem PHP diferenciam letras maisculas de minsculas? Que partes no fazem
isso? Voc consegue encontrar um motivo para a separao?

3.8 Desenvolva um conjunto de testes completo para Lexer. Explique o propsito de cada caso
de teste.

3.9 Mostre as transies feitas usando o DFSA para identificadores apresentados na Seo 3.2.2 no
recebimento do seguinte: (a) a, (b) a2, (c) a2i, (d) abc.

3.10 Para nmeros de ponto flutuante em notao cientfica, apresente: (a) uma gramtica regular
direita; (b) uma expresso regular; (c) um DFSA. D exemplos de nmeros que sejam permitidos
e no permitidos.

3.11 Estenda o lexer para incluir nmeros de ponto flutuante em notao cientfica usando seu projeto do
exerccio anterior.
3.4 Resumo 83

3.12 Para nmeros em bases da forma base # nmero # (sem espao em branco interno) apresente: (a)
uma gramtica regular direita; (b) uma expresso regular; (c) um DFSA. A base deve ser expressa em
decimais; o nmero deve usar letras maisculas ou minsculas para representar dgitos maiores que 9. A
sua definio assegura consistncia do nmero com respeito base, por exemplo, 8#99# proibido?

3.13 Para comentrios no estilo da linguagem C /* ... */, d: (a) uma gramtica regular direita; (b)
uma expresso regular; (c) um DFSA.

3.14 Implemente no lexer seu projeto de comentrios no estilo da linguagem C /* ... */.

3.15 Projete e implemente uma parte de um lexer que lide com seqncias arbitrrias do seguinte: (a)
inteiros na forma: d + (em que d um dgito); (b) nmeros de ponto flutuante na forma: d +.d +;
(c) operador de subfaixa (...). Assim, a seguinte string consiste de trs tokens: 10.81, a saber, um
inteiro 10, o operador de subfaixa e outro inteiro 81.

3.16 Analise a seguinte gramtica:

S | a | (T)
T T, S | S

Aps estender a gramtica:


(a) Desenhe o grafo de dependncia esquerda.
(b) Calcule o conjunto Primeiro para cada no-terminal.

3.17 Acrescente semntica no analisador descendente recursivo que calcule o valor de cada expresso e
imprima esse valor. Para implementar variveis, restrinja-as a letras minsculas nicas e use uma
matriz com a varivel usada como ndice; inicialize a matriz com zero.

3.18 Melhore o analisador Clite encontrado no site do livro, adicionando um mtodo:

private boolean matches (int[] tok)

que retorne verdadeiro se token.type( ) corresponder a um dos tok[], e falso, caso contrrio.
O mtodo no deve avanar a leitura de tokens. Use esse mtodo para eliminar isAddOp( ), is-
MulOp( ) etc.

3.19 Uma linguagem pode no ter palavras reservadas? Ou seja, suponha que cada palavra reservada
(como if e for) fosse meramente um identificador predefinido, que o programador estivesse livre
para redefinir. Tal linguagem pode existir? Explique.

3.20 Adicione um mtodo display a cada uma das classes de sintaxe abstrata da Figura 2.11 de modo
que uma rvore de sintaxe abstrata para Atribuio possa ser exibida de maneira estruturada. Por
exemplo, a rvore de sintaxe abstrata para a Atribuio z  x  2* y; deveria ser exibida da
seguinte forma:


z

x
*
2
y
84 Captulo 3 Anlise Lxica e Sinttica

3.21 Projete uma sintaxe abstrata e implemente um analisador descendente recursivo para que as expres-
ses sejam simbolicamente diferenciadas. A gramtica uma gramtica polonesa prefixada:

Expr Op Expr Expr |Primria


Op ||*|/
Primrio Inteiro | Letra

na qual um Inteiro uma seqncia arbitrria de dgitos e uma Letra um nico caractere mins-
culo que representa uma varivel matemtica. Para simplificar a atribuio, voc pode supor que
a Expr livre de erros e que a Expr aparece em uma linha com tokens separados por um ou mais
espaos ou tabulaes. O programa deve exibir a rvore de sintaxe abstrata.
Nomes 4
O primeiro passo na direo da sabedoria chamar as coisas pelos nomes certos.
Provrbio chins

VISO GERAL DO CAPTULO

4.1 QUESTES SINTTICAS 86


4.2 VARIVEIS 88
4.3 ESCOPO 89
4.4 TABELA DE SMBOLOS 92
4.5 RESOLVENDO REFERNCIAS 93
4.6 ESCOPO DINMICO 94
4.7 VISIBILIDADE 95
4.8 SOBRECARGA 96
4.9 TEMPO DE VIDA 98
4.10 RESUMO 99
EXERCCIOS 99

A nomenclatura eficaz de variveis, funes, tipos, classes e outras entidades uma im-
portante habilidade de programao. Compreender os diversos usos e as implicaes de
se referenciar um nome em diferentes contextos, muitas vezes, no uma tarefa simples.
Lembre-se do que citamos no Captulo 1, que o termo ligao (binding) uma asso-
ciao entre uma entidade (como uma varivel) e uma propriedade (como seu valor).

85
86 Captulo 4 Nomes

Definio: Uma ligao esttica se a associao ocorrer antes da execuo.


Definio: Uma ligao dinmica se a associao ocorrer em tempo de execuo.
O momento em que uma ligao ocorre com um nome desempenha um papel fundamen-
tal. Por exemplo, o escopo esttico, no qual um nome ligado a uma declarao especfica
antes da execuo, deve ser familiar maioria dos programadores j que o mecanismo usado
por todas as principais linguagens para resolver referncias. Contudo, algumas linguagens
atrasam a resoluo do nome at o tempo de execuo. Para entendermos tanto o escopo est-
tico quanto o dinmico, examinamos a noo de uma tabela de smbolos e seu uso em tempo
de compilao e de execuo para implementar escopo esttico e dinmico, respectivamente.
Alm da resoluo de nomes, tambm consideramos a visibilidade destes. Em algu-
mas linguagens, a redeclarao dentro de um escopo de um nome pode esconder outras
instncias do mesmo nome. A sobrecarga permite que diferentes instncias do mesmo
nome de uma funo ou de um operador sejam resolvidas com base no nmero ou nos
tipos dos seus argumentos.
O tempo de vida do nome de uma varivel se refere ao intervalo de tempo durante o
qual a varivel fica alocada na memria, o que pode ocorrer esttica ou dinamicamente.
Neste ltimo caso, o tempo de vida de uma varivel deve ser a execuo dos comandos
pertencentes ao seu escopo. Algumas linguagens fornecem mecanismos para estender o
tempo de vida de tais variveis.
Iniciando neste captulo, usamos o termo linguagem como C para incluir todas as ver-
ses de C, C++ e Java e linguagem como Pascal para incluir tanto Pascal quanto Ada.

4 .1 Q U ES T ES S IN T T ICAS
Nomes so usados em programas para denotar muitas entidades diferentes: variveis,
tipos, funes etc. Nesta seo, analisamos a sintaxe bsica dos nomes. Outro termo para
nome Identificador.
Regras lxicas rgidas determinam como um nome pode ser construdo. Por exem-
plo, vimos no Captulo 2 que um Identificador Clite deve ser uma srie de letras e dgitos,
comeando com uma letra. Esta uma regra comum para criar nomes na maioria das
linguagens de programao.
As primeiras linguagens s usavam letras maisculas e colocavam limites estritos
sobre o comprimento de um identificador (veja a Observao). Linguagens posteriores,
como Pascal e Ada, continuaram a tradio de no diferenciar maisculas de minsculas, de
modo que qualquer um dos nomes alpha, Alpha ou alPha se referem mesma entidade.
Em contraste, nomes em linguagens como C diferenciam maisculas de minsculas, de
modo que, por exemplo, alpha e Alpha no so nomes da mesma entidade. A linguagem
de scripting web PHP diferencia parcialmente maisculas de minsculas, o que tambm
uma deciso de projeto questionvel.
Muitas linguagens permitem o uso de um ou mais caracteres especiais em identifica-
dores, mas algumas restringem seu uso; por exemplo, Cobol permite o caractere de hfen,
mas um identificador no pode comear ou terminar com um hfen. Outras, como as
linguagens como C, no apenas permitem um caractere especial (o sublinhado _), como
no restringem o seu uso.
Definio: A maioria das linguagens possui um conjunto predefinido de nomes
chamado palavras reservadas ou palavras-chave que possuem um significado
especial e no podem ser usados como identificadores.
Palavras reservadas incluem nomes que so teis na anlise de um programa por-
que identificam construes importantes. Nas linguagens como C, estas palavras-chave
incluem int, if e while.
87

Observao
Nomenclatura em Fortran
Fortran, a primeira linguagem de programao de alto nvel, originalmente usava apenas
letras maisculas simplesmente porque os dispositivos de entrada primrios (a mquina
de teclas) e as impressoras de linha s usavam maisculas. Fortran foi projetada e imple-
mentada para computadores IBM 709/704 que possuam um caractere de 6 bits (chamado
BCD) e uma palavra de 36 bits. Assim, os projetistas de Fortran limitaram o tamanho de
um identificador para um mximo de 6 caracteres, armazenados e justificados esquerda
com espaos de preenchimento, j que o 709/704 no tinha a habilidade de enderear
caracteres individuais.
Estas convenes foram padronizadas no Fortran 66 e 77 e mantidas inalteradas at
o Fortran 90. Todavia, naquele momento, a maioria dos computadores havia migrado
para ASCII (ou, no caso dos mainframes IBM, EBCDIC), que permitia caracteres em
letras minsculas. A conveno mais simples parecia ser, no caso do Fortran, no distin-
guir maisculas de minsculas em nomes. De outra forma, todas as palavras reservadas
teriam de ser digitadas em maisculas, para manter a compatibilidade para trs. Fortran
90 padronizou esta deciso.

Em algumas linguagens (por exemplo, Pascal e Scheme), alm de palavras reser-


vadas, existem identificadores predefinidos que possuem um significado especial; dife-
rentemente das palavras reservadas, o programador est livre para redefini-los. Nomes
de funes de bibliotecas geralmente caem nessa categoria. Tal caracterstica pode ser
uma bno ou uma maldio. Por um lado, isso ajuda a minimizar o nmero de pala-
vras reservadas de uma linguagem. Por outro lado, pode levar a programas confusos. Por
exemplo, analise o seguinte fragmento de programa vlido em Pascal, que redefine o
significado do identificador predefinido true:

program confuso;
const true = false;
begin
...
if (a < b) = true then
f(a)
else
g(b);
...
end.

Neste caso, o procedimento f chamado se a >= b, o oposto do que o cdigo parece


dizer. Permitir a redefinio de um identificador importante como true , dessa forma,
uma deciso ruim de projeto de linguagem.
Um projetista de linguagem deve decidir entre tornar identificadores predefinidos
reservados ou permitir que o programador redefina tais identificadores. Cobol usou a
primeira abordagem, com o resultado de que h centenas de palavras reservadas. A
maioria das linguagens modernas usa alguma forma da segunda abordagem, minimi-
zando assim o nmero de palavras reservadas. Linguagens como Pascal permitem que
os nomes de tipos bsicos como integer sejam redefinidos, enquanto as linguagens
como C os tornam reservados.
88 Captulo 4 Nomes

4 .2 VA R IV EIS
Um uso importante da nomenclatura dar nomes a variveis, que so fundamentais
na programao imperativa e orientada a objeto. Em tais linguagens, uma varivel um
nome para um local de memria (ou bloco de locais).
Definio: Uma varivel uma ligao de um nome com um endereo de mem-
ria. Alm disso, uma varivel possui um tipo, um valor e um tempo de vida.
Qualquer uma dessas ou outras ligaes pode ser esttica ou dinmica, criando in-
teressantes variaes entre as linguagens. Essas caractersticas de variveis tambm se
relacionam com a forma pela qual uma linguagem implementada. Por exemplo, se uma
linguagem requerer que todos os nomes de variveis sejam declarados antes de serem
referenciados em um programa, ento a fase de anlise semntica de um compilador deve
incluir uma lista de nomes declarados e seus tipos de dados associados; esta lista, muitas
vezes, denominada tabela de smbolos, como ser mostrado na Seo 4.4.
Os programadores usam variveis para dar nomes a locais de memria individuais
e agregados; pelo restante deste captulo, trataremos os dois como equivalentes. Uma
varivel de programa possui quatro ligaes bsicas:
Nome.
Endereo (que uma questo de implementao).
Tipo.
Valor.
Estes itens podem ser ligados esttica ou dinamicamente. Os diversos tipos de valores
que podem ser atribudos a variveis so discutidos no Captulo 5. O endereo de uma
varivel identifica de modo nico o local da memria onde o valor de uma varivel ar-
mazenado. O endereamento do valor de uma varivel tambm introduzido no Captulo
5, no qual a forte associao entre um ponteiro e um endereo estabelecida.
Algol 68 (van Wijngaarden, 1969) foi a primeira linguagem a distinguir claramente
entre o uso do nome de uma varivel para denotar seu endereo, o valor-l da varivel,
versus o uso do seu nome para denotar o prprio valor, o valor-r da varivel. Analise o
seguinte comando:

x = y + 1;

Tal comando deve ser lido: atribua ao endereo de memria denotado pela varivel
x o valor da expresso que a soma do valor da varivel y e um. Observe que, quando
usado como um valor no lado esquerdo (valor-l), o identificador da varivel x denota um
endereo; no entanto, se usado no lado direito (valor-r), o identificador da varivel y de-
nota o valor armazenado no endereo.
Algol 68 permitia que o programa distinguisse explicitamente o endereo de uma
varivel do seu valor. Uma varivel do tipo int foi denotada como um ref int, ou seja,
uma referncia a um inteiro. O que C++ chama de uma const (constante) foi denotado
como um int em Algol 68, conceitualmente o mesmo que int l.
ML [Ullman, 1998] tambm suporta tal desreferenciao explcita de uma varivel
em um programa. Por exemplo, a expresso ML:

x := !y + 1;
4.3 Escopo 89

usa o operador ! para converter a referncia y em um valor. A maioria das linguagens


no requer desreferenciao explcita para variveis comuns em comandos de atribuio.
Todavia, muitos compiladores usam uma representao de sintaxe abstrata aproximando
o comando ML anterior com operadores de desreferenciao explcita inseridos.
Entretanto, para variveis ponteiros, a desreferenciao explcita muitas vezes til.
C/C++ usam a estrela unria * para desreferenciar uma varivel ponteiro. Por exemplo,
no fragmento de cdigo:

int x, y;
int *p;
x = *p;
*p = y;

o terceiro comando atribui ao valor-l de x o valor do endereo de memria referenciado


por p, ou seja, o valor-r do valor-r de p. A ltima linha atribui ao local de memria re-
ferenciado por p o valor-r de y. Observe que o prprio valor de p, um local de memria,
permanece inalterado.

4 .3 E S CO P O
Os primeiros computadores tinham memrias muito limitadas em comparao aos
padres atuais, de alguns kilobytes (milhares de bytes) a algumas centenas de kilobytes.
medida que os programas tm crescido em tamanho, a coliso de nomes tem se tornado
uma questo cada vez mais importante. Para permitir que os programadores reusem o
mesmo identificador dentro de um programa, usado o conceito do escopo de um nome.
Definio: O escopo de um nome a coleo de comandos que podem acessar
essa ligao de nome.
Da mesma forma que com a maioria das questes, a ligao de um nome a um esco-
po pode ser esttica ou dinmica, com esta ltima sendo analisada na Seo 4.6.
Definio: No escopo esttico, o nome ligado a uma coleo de comandos de
acordo com sua posio no programa-fonte.
Assim, o escopo esttico pode ser executado em tempo de compilao e indepen-
dente da histria de execuo do programa. Devido ligao do nome poder ser determi-
nada por uma simples varredura do programa, o escopo esttico melhora a legibilidade e
permite um melhor nvel de verificao em tempo de compilao.
A maioria das linguagens modernas, incluindo C/C++, Java, Python e Ada, usa esco-
po esttico. Devido ao escopo esttico ser baseado na estrutura gramatical de um progra-
ma, s vezes chamado de escopo lxico. As estruturas gramaticais que constituem um
escopo variam conforme a linguagem; antes de examinarmos esta questo, definiremos
alguma terminologia.
Na maioria das linguagens, os escopos podem ser aninhados. Dois escopos diferen-
tes so disjuntos ou aninhados. Quando dois escopos so disjuntos, o mesmo nome pode
ser ligado a diferentes entidades sem qualquer interferncia.
O caso mais simples do que constitui um escopo consiste das primeiras verses de
Fortran, nas quais uma unidade de compilao consistia de um nico escopo, que no
podia ser aninhado. Uma unidade de compilao Fortran consistia do programa principal
ou de uma nica funo ou procedimento, e assim todos os escopos no possuam inter-
seco; nenhum podia ser aninhado.
Entretanto, de certo modo, essa uma viso simplista. Todas as linguagens su-
portam um escopo mais externo, no qual determinados mtodos ou bibliotecas exis-
tem e podem ser acessados; o que fica neste escopo e como as entidades podem ser
90 Captulo 4 Nomes

| Tabela 4.1 Algol C Java Ada


| O que Constitui um Escopo Pacote n/a n/a sim sim
Classe n/a n/a aninhado sim
Funo aninhado sim sim aninhado
Bloco aninhado aninhado aninhado aninhado
Lao For no no sim automtico

acessadas varia com a linguagem. Fortran e C exportam funes e procedimentos para


esse escopo mais externo.
Saindo de Fortran, uma unidade de compilao Algol 60 constitua um escopo dentro do
qual funes e procedimentos introduziam novos escopos e podiam ser aninhados. Alm disso,
um comando composto, delimitado com as palavras reservadas begin e end constitui um esco-
po, e estas podiam ser aninhadas tambm. Um comando composto com declaraes denomi-
nado um bloco em Algol, e tais declaraes podem incluir variveis ou funes. Entretanto, em
um escopo de Algol 60 todas as declaraes devem preceder todos os outros comandos.
Linguagens mais recentes tm incorporado algumas das convenes de escopo de
Algol 60. Por exemplo, linguagens como C seguem a tradio de Algol de permitir um
comando composto (como um bloco, exceto que delimitado por chaves em vez de begin
e end) que contenha novas declaraes e, assim, introduza um novo escopo. Contudo,
C/C++ no permitem funes aninhadas.
Uma tentativa de resumir a questo do que constitui um escopo para diversas lin-
guagens importantes apresentada na Tabela 4.1. Na tabela, a unidade de compilao
deixada de fora, j que sempre constitui um escopo. Alm disso, a tabela no para ser
definitiva, no sentido que nem todas as entidades gramaticais que constituem um escopo
para uma determinada linguagem aparecem na tabela. Em vez disso, ela deve ser vista
como uma tentativa de apresentar o quadro geral.
Analise a coluna da linguagem C; ela no suporta nem pacotes nem classes, de modo
que a questo no aplicvel (n/a). Funes constituem um escopo, mas no podem ser
aninhadas. Blocos (listas de comandos dentro de chaves) constituem um escopo e podem
ser aninhados. Finalmente, uma varivel de lao for no pode ser declarada como parte
deste comando for; em contraste, a varivel de lao for declarada automaticamente em
Ada e seu escopo se estende apenas ao cabealho e ao corpo do lao.
Definio: O escopo no qual um nome definido ou declarado chamado de seu
escopo de definio.
Definio: Uma referncia a um nome no-local se ocorrer em um escopo ani-
nhado do escopo de definio; caso contrrio, a referncia local.
Analise a funo simples de ordenao em C apresentada na Figura 4.1. O escopo
da varivel temporria t inclui as linhas 69, que tambm so o seu escopo de definio.
J que no h blocos aninhados dentro das linhas 69, todas as referncias a t so locais.
Assim, t s pode ser referenciada pelas declaraes que esto dentro das chaves nas
linhas 5 e 10. Este uso de um comando composto para declarar uma varivel melhora a
legibilidade do programa, j que o escopo da varivel limitado.
C tambm especifica que todas as declaraes devem preceder todos os outros ti-
pos de comandos em um bloco; assim, uma referncia pode ocorrer apenas aps o nome ser
declarado, ou seja, o cdigo no pode conter uma referncia forward a um nome de-
clarado. C++ e Java permitem que uma declarao aparea em qualquer lugar dentro
4.3 Escopo 91

1 void sort (float a[ ], int size) {


2 int i, j;
3 for (i = 0; i < size; i++)
4 for (j = i + 1; j < size; j++)
5 if (a[j] < ati]) {
6 float t;
7 t = a[i];
8 a[i] = a[j];
9 a[jj = t;
10 }
11 }

| Figura 4.1 Exemplo de Escopos em C

de um bloco, desde que a regra de nenhuma referncia forward seja violada. Uma refe-
rncia forward uma referncia a um nome que ocorre antes que esse nome tenha sido
declarado.
Continuando com o exemplo da Figura 4.1, o escopo para a e size so as linhas
210, ou seja, tudo entre as chaves nas linhas 1 e 11. De forma semelhante, o escopo
para i e j o mesmo bloco, porm referncias a um ou outro nome devem ocorrer aps
a declarao na linha 2. As referncias a i nas linhas 35 e a j nas linhas 45 tambm
so locais. Contudo, as referncias a estes nomes nas linhas 79 so no-locais, j que
as linhas 610 so um escopo aninhado dentro do escopo de definio para i e j. Esta
questo importante abordada mais integralmente na Seo 4.5.
Um tipo de escopo interessante o comando for, como em C++/Java, em que uma
nova varivel de controle pode ser declarada. Neste caso, o escopo dessa varivel fica
limitado ao corpo do lao:

for (int i = 0; i < 10; i++) {


System.out.println(i);
...
{
... i ... // referncia invlida a i

Aqui, a referncia a i seguindo o corpo do lao for invlida, j que o escopo da varivel
i limitado ao corpo do lao. Ada tambm suporta essa capacidade; contudo, em Ada a
varivel de controle do lao for implcita e automaticamente declarada.
O escopo de classe Java interessante porque uma exceo regra de referncia
forward. Na declarao de uma classe, as variveis e os mtodos de instncia podem ser
declarados em qualquer ordem conveniente. Embora a maioria dos textos sobre Java de-
clarem todas as variveis de instncia antes dos mtodos (seguindo a tradio Algol/Pas-
cal), alguns textos seguem a conveno oposta. O que mais importante que os mtodos
podem aparecer em qualquer ordem, independentemente de quais mtodos se referem a
outros mtodos; isso melhora bastante a capacidade de escrita.
At aqui, evitamos o problema de resolver referncias a nomes onde existam mlti-
plas declaraes do mesmo nome. Para abordarmos esta questo de forma apropriada, pri-
meiro examinamos o papel e a implementao da tabela de smbolos de um compilador.
92 Captulo 4 Nomes

4 .4 TA BE LA D E S M BO LO S
Uma das tarefas da fase de anlise semntica de um tradutor (veja a Seo 2.4)
construir uma tabela de smbolos com todos os nomes declarados e suas associaes.
Definio: Uma tabela de smbolos uma estrutura de dados mantida pelo tradu-
tor que permite a ele manter registro de cada nome declarado e suas associaes.
Pelo restante deste captulo, supomos que cada nome declarado seja nico dentro do
seu escopo local, ou seja, as nicas outras declaraes do mesmo nome ocorrem em um
escopo disjunto ou em um escopo aninhado. Esta suposio importante ser relatada na
Seo 4.8.
A estrutura de dados usada para um escopo local pode ser qualquer implementao
de um dicionrio ou conjunto, em que o nome a chave. Dado um nome, consultamos no
dicionrio a associao ligada a ele. Ele retorna uma associao apenas se o nome tiver
sido declarado no escopo corrente; caso contrrio, retorna um valor especial indicando
que nenhuma declarao de nomeassociao ocorreu no escopo local.1
Tudo o que precisamos agora um algoritmo para lidar com referncias no-locais
vlidas. O fato de que tais referncias podem ocorrer em um escopo que esteja aninhado
no escopo de definio sugere o uso de uma pilha de escopos, da seguinte forma:
1 Cada vez que se entra em um escopo, adicione um novo dicionrio ao topo da pilha.
2 Cada vez que se sair de um escopo, retire um dicionrio do topo da pilha.
3 Para cada nome declarado, gere uma associao apropriada e acrescente o par
nomeassociao ao dicionrio no topo da pilha.
4 Dada uma referncia a um nome, pesquise sobre este no dicionrio do topo da pilha.
(a) Se encontrado, retorne associao.
(b) Caso contrrio, repita o processo no prximo dicionrio da pilha at que o
nome seja encontrado.
(c) Se o nome no for encontrado em algum dicionrio da pilha, reporte um
erro.
Para o programa em C da Figura 4.1, a pilha de dicionrios na linha 7 teria o seguinte
contedo (de cima para baixo):
t, 6
j, 4, i, 3, size, 1, a, 1
sort, 1
em que o nome do par nomeassociao mostrado como um par ordenado, e a associa-
o mostrada apenas como o nmero da linha onde foi declarada, por motivo de sim-
plicidade. Observe que o nome sort est no escopo externo em relao aos parmetros e
ao corpo da funo. Dessa perspectiva, a referncia a t na linha 7 uma referncia local,
enquanto as referncias a a e i so no-locais.
Em contraste, a tabela de smbolos na linha 4 e novamente no incio da linha 11
apareceriam da seguinte forma (de cima para baixo):
j, 4, i, 3, size, 1, a, 1
sort, 1
O escopo contendo t no foi encontrado ainda na linha 4 e foi abandonado no final
da linha 10.

1. O leitor observador ter notado aqui a utilizao da regra de referncia forward.


4.5 Resolvendo Referncias 93

Para linguagens que usam escopo esttico, a tabela de smbolos construda durante
a fase de anlise semntica da compilao (veja a Seo 2.4) e dura todo o restante do
processo de compilao. A Seo 4.5 analisa o caso em que possam existir mltiplas
instncias de definies do mesmo nome.

4 .5 R E S OLV EN DO REFERN CIAS


O ambiente de referncias importante porque define a coleo de comandos nos
quais um nome pode ser validamente referenciado.
Definio: Para escopo esttico, o ambiente de referncia para um nome o seu
escopo definidor e todos os subescopos aninhados.
Usamos essa definio e o conhecimento de como uma tabela de smbolos funciona
para analisar escopos disjuntos e escopos aninhados que redefinam um nome dentro do
seu ambiente de referenciao.
A Figura 4.2 representa um exemplo tanto de escopos aninhados quanto de disjuntos
nos quais existem trs declaraes do nome i, duas de j e uma de todos os outros. As
tabelas de nomes individuais para cada escopo so as seguintes:
1 O escopo externo consiste de duas variveis declaradas na linha 1 mais as trs
funes: h, 1, i, 1, B, 2, A, 8, main, 14.
2 O escopo da funo B contm o parmetro mais as duas variveis da linha 3:
w, 2, j, 3, k, 3.
3 O escopo da funo A consiste dos dois parmetros da linha 8 mais a declarao da
linha 9: x, 8, y, 8, i, 9, j, 9.

1 int h, i;
2 void B(int w) {
3 int j, k;
4 i = 2*w;
5 w = w+1;
6 ...
7 }
8 void A (int x, int y) (
9 float i, j;
10 B(h);
11 i = 3;
12 ...
13 }
14 void main() {
15 int a, b;
16 h = 5; a = 3; b = 2;
17 A(a, b);
18 B(h);
19 ...
20 }

| Figura 4.2 Referncias em Escopos Disjuntos e Aninhados


94 Captulo 4 Nomes

4 O escopo da funo main consiste das duas variveis declaradas na linha.


15: a, 15 , b, 15.
Quando essas funes forem compiladas, a pilha da tabela de smbolos para cada
funo ter o seguinte contedo:

Funo Pilha de Tabelas de Smbolos


<w, 2>, <j, 3>, <k,3>
B
<h, 1>, <i, 1>, <B, 2>, <A, 8>, <main, 14>
<x, 8>, <y, 8>, <i, 9>, <j, 9>
A
<h, 1>, <i, 1>, <B, 2>, <A, 8>, <main, 14>
<a, 15>, <b, 15>
main
<h, 1>, <i, 1>, <B, 2>, <A, 8>, <main, 14>

Com essas informaes, podemos facilmente resolver as referncias no-locais ou defi-


nidas multiplamente:

Linha Referncia Declarao


4 i 1
10 h 1
11 i 9
16, 18 h 1

4 .6 E S COP O D IN M ICO
Em termos de tabelas de smbolos, o dicionrio para cada escopo pode ser constru-
do em tempo de compilao, porm o gerenciamento da pilha de escopos feito em tem-
po de execuo. Quando se entra em um escopo em tempo de execuo, seu dicionrio
empilhado no topo da tabela de smbolos e desempilhado quando se sai do escopo. A
resoluo de nomes executada em tempo de execuo em vez de em tempo de compila-
o. Fora isso, tudo ocorre da mesma forma.
Definio: No escopo dinmico, um nome associado sua declarao mais
recente com base no histrico de execuo do programa.
Usando novamente a Figura 4.2 como nosso exemplo e supondo escopo dinmico,
examinaremos a resoluo da referncia a i na linha 4. Suponha um histrico de chama-
das: main (linha 17) A (linha 10) B. A tabela de smbolos em tempo de execuo
tem o seguinte formato:

Funo Dicionrio
B <w, 2>, <j, 3>, <k, 3>
A <x, 8>, <y, 8>, <i, 9>, <j, 9>

main <a, 15>, <b, 15>


<h, 1>, <i, 1>, <B, 2>, <A, 8>, <main, 14>

A referncia a i na linha 4 resolvida com aquela declarada em A na linha 9, j que


este o primeiro i encontrado.
4.7 Visibilidade 95

Se, em vez disso, analisarmos o histrico de chamadas: main (linha 18) B, a tabe-
la de smbolos em tempo de execuo teria o seguinte formato:

Funo Dicionrio
B <w, 2>, <j, 3>, <k, 3>

main <a, 15>, <b, 15>


<h, 1>, <i, 1>, <B, 2>, <A, 8>, <main, 14>

A referncia a i agora resolvida com aquela declarada globalmente na linha 1.


O propsito dos exemplos deixar claro que a resoluo de nomes de referncias
no-locais usando escopo dinmico requer conhecimento do histrico em tempo de exe-
cuo do programa. Em especial, histricos diferentes de tempo de execuo podem re-
sultar em diferentes resolues de nomes para a mesma referncia.
Embora o escopo dinmico seja usado em verses iniciais de Lisp (antes de Com-
mon Lisp), APL, Snobol e Perl, ele provou ser especialmente propenso a erros e tem sido
freqentemente abandonado. O escopo dinmico no mais uma caracterstica comum de
linguagens modernas, exceto Common Lisp e Perl, que tambm suportam escopo esttico.
H diversos motivos para se preferir o escopo esttico. Primeiro, o escopo dinmico
compromete a capacidade de verificar-se estaticamente referncias a tipos de variveis
que no sejam locais, j que nem todas as variveis no-locais de um comando dentro
de uma funo chamada podem ser identificadas estaticamente. Em segundo lugar, todas
as variveis em uma funo que chama outras funes so visveis quelas funes cha-
madas (e, portanto, podem ser alteradas), o que tende a reduzir a confiabilidade de um
programa, de modo geral. Finalmente, o acesso a uma varivel no-local, seguindo-se
uma cadeia de ligaes dinmicas, tende a consumir mais tempo do que o acesso a uma
no-local em um ambiente de escopo esttico. Por esses motivos, o escopo dinmico ten-
de a no ser uma caracterstica de linguagens modernas de programao.
A prxima seo retorna ao escopo esttico para analisar a questo da visibilidade.

4 .7 V IS I BILID ADE
Conforme mostrado na Figura 4.2 da pgina 93, um nome redeclarado em um esco-
po aninhado efetivamente esconde a declarao mais externa. Isso acontece porque uma
referncia usa como padro a declarao mais interna.
Definio: Um nome visvel a uma referncia se a sua referenciao incluir essa
referncia e o nome no tiver sido redeclarado em um escopo mais interno.
Um nome , portanto, visvel por todo o seu ambiente de referenciao desde que
no seja escondido por uma redeclarao em algum subescopo aninhado. Algumas lin-
guagens fornecem um mecanismo para se referenciar um nome escondido. Em Java, por
exemplo, uma varivel de instncia de classe escondida, digamos x, pode ser referenciada
como this.x, em que this se refere ao objeto corrente. Analise o seguinte fragmento
de programa:

1 public class Student {


2 private String name;
3 public Student (String name, ...) {
4 this.name = name;
5 ...
6 }
7 }
96 Captulo 4 Nomes

procedure Main is
x : Integer;
procedure p1 is
x : Float;
procedure p2 is
begin
... x ...
end p2;
begin
... x ...
end p1;
procedure p3 is
begin
... x ...
end p3;
begin
... x ...
end main;

| Figura 4.3 Programa em Ada com Mltiplos Declarantes de um Nome

O parmetro name na linha 3 esconde a varivel declarada na linha 2; assim, a linha 4 usa
a notao this.name para se referir varivel escondida.
Ada permite que um nome seja qualificado explicitamente, possibilitando assim que
tanto uma varivel local quanto uma global com o mesmo nome seja referenciada dentro
do mesmo bloco. Analise o esqueleto do programa Ada mostrado na Figura 4.3. Aqui, ve-
mos duas variveis declaradas com o nome x, uma Integer e outra Float. As referncias
a x em p3 e em main acessam a varivel Integer, enquanto as referncias a x em p1 e
p2 acessam a varivel Float. Entretanto, qualquer uma das referncias em p1 ou p2 pode
ser mudada para main.x para acessar a varivel Integer. Esse recurso de Ada se estende
a blocos tambm, j que qualquer bloco Ada pode ser nomeado.

4 .8 S O BR ECAR G A
At agora, supomos que, em cada escopo de definio, no exista mais do que uma
declarao de um determinado nome. A capacidade de usar o mesmo nome para entidades
relacionadas pode melhorar tanto a legibilidade quando a facilidade de escrita de uma lin-
guagem, reduzindo a confuso de nomes. Por exemplo, ser capaz de print (imprimir)
uma varivel de qualquer tipo, como em Java, muito mais simples do que se lembrar de
usar WriteInt( ) para inteiros, WriteReal( ) para valores de ponto flutuante e assim
por diante, como em Modula.
Definio: A Sobrecarga usa o nmero ou tipo de argumento para distinguir entre
funes ou operadores com nomes idnticos.
Um exemplo a prtica antiga de se sobrecarregar as funes e os operadores arit-
mticos internos de uma linguagem. A maioria das linguagens sobrecarrega os operado-
res +, , * e / para denotar aritmtica de ponto flutuante ou inteira, dependendo dos tipos
4.8 Sobrecarga 97

de operandos. Em linguagens mais modernas, esta sobrecarga foi estendida para cobrir
todas as variantes de valores inteiros e de ponto flutuante (por exemplo, preciso simples
e dupla). Java tambm sobrecarrega o operador + para denotar a concatenao de strings
sempre que pelo menos um dos operandos for do tipo String. A sobrecarga tambm se
estende a funes internas. Em Fortran, os comandos de entrada e sada read e write
aceitam qualquer um dos tipos definidos pela linguagem.
Historicamente, nenhuma das principais linguagens iniciais estendia a sobrecarga
para operadores e funes definidas pelo usurio. Isso levou anomalia em Modula de
que diferentes nomes para funes de entrada e sada eram usados para tipos distintos.
Por exemplo, o nome Read( ) usado apenas para caracteres, ReadInt( ) para inteiros,
ReadString( ) para strings e ReadReal( ) para nmeros de ponto flutuante. Isso no
ocorreu em Pascal porque suas funes de entrada/sada eram internas, enquanto eram
rotinas comuns de bibliotecas externas em Modula.
Ada foi a primeira linguagem amplamente usada a estender a sobrecarga de opera-
dores a tipos e funes definidas pelo programador.2 Parecia natural para os projetistas de
Ada que, se algum fosse definir um novo tipo para implementar vetores matemticos, o
operador + deveria ser extensvel para denotar adio de vetores. Ada tambm permite a
sobrecarga com base no tipo de retorno, diferentemente de outras linguagens que supor-
tam sobrecarga.
C++ tambm permite que operadores aritmticos e mtodos sejam sobrecarrega-
dos, enquanto Java restringe a sobrecarga a mtodos. Java permite que nomes de m-
todos dentro da mesma classe sejam sobrecarregados, desde que o mtodo realmente
chamado possa ser determinado pelo nmero ou por tipos de parmetros. Um exemplo
ocorre na classe PrintStream, em que os mtodos print e println so intensamente
sobrecarregados:

public class PrintStream extends FilterOutputStream {


...
public void print(boolean b);
public void print(char c);
public void print(int i);
public void print(long l);
public void print(float f);
public void print(double d);
public void print(char[ ] s);
public void print(String s);
public void print(Object obj);
}

Java permite que tanto uma varivel de instncia quanto um mtodo tenham o mes-
mo nome, j que todas as referncias ao mtodo tm parnteses como sufixo:

public class Student {


private String name;
...
public String name ( ) { return name; }

2. Naquele tempo, isso foi um grande salto de f. O nico algoritmo conhecido para efetuar a resoluo de
nomes em funes e operadores sobrecarregados definidos pelo programador tinha complexidade de tempo
exponencial!
98 Captulo 4 Nomes

Observe que o mtodo name( ) contm uma referncia varivel de instncia name
e no h ambigidade nele. Na verdade, uma prtica comum na programao em Java
definir um mtodo de acesso em uma classe que retorne o valor da varivel de instncia
do mesmo nome, como este exemplo ilustra.

4 .9 TE MP O D E V IDA
As primeiras linguagens imperativas, a saber, Fortran e Cobol, usavam alocao
esttica tanto para variveis quanto para funes, ou seja, a memria a ser usada era
atribuda em tempo de compilao. No caso de funes, havia apenas uma nica rea de
memria tanto para argumentos quanto para o endereo de retorno; por causa disso, funes
recursivas no eram suportadas. Veja nos Captulos 9 e 10 uma discusso mais completa.
No caso de variveis, o trabalho de gerenciar memria era totalmente de responsa-
bilidade do programador, caso a memria de programa requerida excedesse a quantidade
disponvel. J que esta era uma ocorrncia comum, por conta da memria fsica limitada da
poca, muito do esforo de escrita de um programa era gasto no gerenciamento de memria.
Com o desenvolvimento de Algol veio a noo de que a memria para variveis,
incluindo parmetros de funes e endereos de retorno, deveria ser alocada e desalocada
em tempo de execuo, quando se entrasse ou sasse de um escopo, respectivamente.
Naquela poca, esta era uma idia radical, j que ningum sabia como implementar tal
esquema de gerenciamento de memria.
O gerenciamento bsico de memria usado por todas as linguagens modernas de
hoje fundamenta-se nesse conceito. O restante desta seo explora a implicao dessa
noo fundamental e mostra como diversas linguagens permitem que o tempo de vida de
uma varivel ou um objeto seja estendido. O Captulo 10 apresenta uma viso dessa ques-
to mais orientada a implementao. Na discusso aqui ignoramos a questo de alocao
dinmica de memria por meio de um comando new ou malloc (e sua desalocao), um
assunto explorado no Captulo 11.
Definio: O tempo de vida de uma varivel o intervalo de tempo durante o qual
a varivel tem um bloco de memria alocado para si.
A maior diferena na semntica de programas entre a alocao esttica de mem-
ria de Fortran e a alocao com base em escopo de Algol que, no primeiro caso, uma
varivel nunca esquece seu valor. Analise a varivel t da Figura 4.1 na pgina 91. Em
cada iterao do lao para j, qualquer memria que possa ser alocada para t tambm
desalocada. Para este exemplo em especial, a desalocao no um problema, j que t
no precisa lembrar seu valor.
Entretanto, analise o problema de escrever uma funo que conte o nmero de vezes
que foi chamada.

double Func( ) {
int count = 0;
...
}

A partir do ponto de vista do uso, apenas a funo precisa acessar o contador. Toda-
via, se a varivel contadora for declarada dentro da funo, o valor do contador perdido
cada vez que se sai da funo, sob a poltica de alocao de memria de Algol. A alter-
nativa incrementar o escopo da varivel contadora, com o nico objetivo de aumentar
seu tempo de vida.
4.10 Resumo 99

O restante desta seo analisa mecanismos de linguagens que, de alguma forma,


quebram a regra de escopo igual a tempo de vida, ao mesmo tempo em que a mantm
como caso-padro.
Por exemplo, um programa em Pascal consiste de uma nica unidade de compilao.
As variveis declaradas no nvel de unidade de compilao so efetivamente alocadas
estaticamente, no importa como sejam implementadas. Assim, tais variveis mantm
seus valores pela vida do programa.
C permite mltiplas unidades de compilao. As variveis declaradas no nvel de
unidade de compilao (por exemplo, h na Figura 4.2 na pgina 93) so todas efetiva-
mente alocadas estaticamente. Tais variveis retm sua memria e, assim, seus valores,
mesmo quando a execuo de um programa entra e sai da sua unidade de compilao.
A outra caracterstica adicionada por C a capacidade de declarar variveis como
sendo static. Quando usadas com uma varivel declarada dentro de uma funo, a vari-
vel alocada estaticamente. Se usada com uma varivel declarada em nvel de unidade de
compilao, no tem efeito sobre a alocao de memria da varivel, j que tais variveis
j usam alocao esttica.
Java tambm permite que variveis declaradas no nvel de classe usem o modifica-
dor static. Tais variveis ou objetos tm sua memria alocada estaticamente.

4 . 1 0 RE S UM O
Este captulo apresenta os conceitos-chave relacionados a nomes e suas ligaes em lin-
guagens de programao contemporneas. A ligao de um nome com uma varivel e seu tipo
desempenha um papel-chave no desenvolvimento de um sistema de tipos para uma linguagem.

E XE RC CI O S
4.1 Escolha uma das seguintes linguagens: Python, Modula, Ada, C# ou Perl. Aps consultar uma
referncia autorizada, discuta cada um dos seguintes requisitos para essa linguagem:
(a) Declarao antes do uso.
(b) Sobrecarga de operadores para tipos definidos pelo programador.
(c) Tempo de associao do tamanho de uma matriz.
(d) O que constitui um escopo.
(e) A localizao de uma declarao dentro de um escopo.

4.2 Aps consultar uma referncia autorizada sobre requisitos para uso de variveis globais em C,
responda s questes a seguir:
(a) Como elas podem ser acessadas em outras unidades de compilao?
(b) Como variveis globais podem ser escondidas de outras unidades de compilao?
(c) Por que voc esconderia variveis globais?

4.3 C e C++ distinguem declaraes e definies. Qual a distino? D um exemplo de cada uma.

4.4 Explique o uso de arquivos de cabealho em C e C++. Por que Java no usa arquivos de cabealho?

4.5 A maioria das linguagens de programao probe a redeclarao do mesmo nome de varivel dentro
do mesmo escopo.
(a) Explique por que o uso apenas de tipos no suficiente para distinguir tal duplicao.
(b) Qual a diferena entre esta restrio e a ausncia da mesma para funes sobrecarregadas?

4.6 Para a linguagem C, d trs exemplos de valores-r que no possam ser valores-l. D mais trs exem-
plos de valores-l. H valores-l que no possam ser valores-r? Explique.
Tipos 5
Tipos so o fermento da programao computacional; eles a tornam digervel.
Robin Milner

VISO GERAL DO CAPTULO

5.1 ERROS DE TIPOS 102


5.2 TIPAGEM ESTTICA E DINMICA 104
5.3 TIPOS BSICOS 105
5.4 TIPOS COMPOSTOS 112
5.5 TIPOS DE DADOS RECURSIVOS 123
5.6 FUNES COMO TIPOS 124
5.7 EQUIVALNCIA DE TIPOS 125
5.8 SUBTIPOS 126
5.9 POLIMORFISMO E GENRICOS 127
5.10 TIPOS DEFINIDOS PELO PROGRAMADOR 132
5.11 RESUMO 133
EXERCCIOS 133

101
102 Captulo 5 Tipos

Os tipos disponveis para um programador so muitos e variados. H um ncleo co-


mum de tipos bsicos que todas as linguagens suportam (inteiros, reais, boleanos e caracteres)
assim como diversos tipos compostos (ponteiros, matrizes, estruturas e funes). Este
captulo resume essas idias e seus usos em diversas linguagens contemporneas.
Definio: Um tipo um conjunto de valores e um conjunto de operaes sobre
esses valores.
Por exemplo, o tipo inteiro familiar possui valores ..., 2, 1, 0, 1, 2,..., e as
operaes , , *, , , ... sobre esses valores. O tipo boleano possui valores verda-
deiro e falso e operaes , e (e, ou e no, respectivamente) sobre esses valores.
Geralmente, um tipo computacional possui um nmero finito de valores, devido ao
tamanho fixo de memria alocada para armazen-los. Isso especialmente problemti-
co para tipos numricos. H excees, entretanto. Por exemplo, Smalltalk usa nmeros
fracionrios sem limites como padro e o tipo Haskell Integer representa inteiros sem
limites, o que permite que a aritmtica simule com mais preciso a faixa completa de
operaes matemticas. Java possui um recurso semelhante, que embutido na sua classe
BigInteger. Porm, esses tipos representam as excees.
De modo geral, contudo, o uso de um tamanho fixo de memria para armazenar
inteiros pode resultar em uma operao aritmtica que gere um valor fora da faixa vlida.
Tente calcular 20! (fatorial) usando um inteiro com tamanho de memria fixo; o valor
excede os limites de um inteiro de 64 bits.
Ainda mais problemtico o uso de nmeros de pontos flutuantes, com tamanho fixo
de memria, para aproximar nmeros racionais matemticos. Por exemplo, se usarmos
ponto flutuante binrio, o nmero 0,2 no exato em binrio; assim, calcular 5 0,2
no resulta exatamente 1. Pontos flutuantes computacionais so inconsistentes com seus
correspondentes matemticos.
Nas primeiras linguagens de alto nvel, Fortran, Algol e Cobol, todos os tipos de
linguagem eram internos. Com o decorrer do tempo, a inapropriao dessa situao tor-
nou-se bvia. Suponha, por exemplo, que o programador precisasse de um tipo color
para representar cores que voc pudesse exibir em um monitor. Geralmente, um inteiro de
determinado tamanho seria usado com alguns bits que representassem as cores vermelha,
verde e azul (RGB, sigla em ingls para red, green e blue). Com certeza, multiplicar duas
cores no faz sentido, mas usar uma representao inteira permite efetuar tal operao
sem significado. O propsito de tipos em linguagens de programao fornecer aos pro-
gramadores formas de definir convenientemente e efetivamente novos tipos para construir
programas que modelem melhor uma soluo.

5 .1 ER R O S D E T IP O S
Pelo padro, a representao de mquina de dados no carrega informaes explci-
tas relacionadas a tipos, ou seja, dados de mquina so apenas uma seqncia de bits, tor-
nando-os essencialmente sem tipo. Esses bits esto sujeitos a diferentes interpretaes.
Por exemplo, analise a seguinte string de 32 bits:
0100 0000 0101 1000 0000 0000 0000 0000
na qual adicionamos espaos por motivo de legibilidade. Esses 32 bits poderiam represen-
tar qualquer um dos seguintes:

1 O nmero de ponto flutuante 3,375.


2 O inteiro de 32 bits 1.079.508.992.
5.1 Erros de Tipos 103

3 Dois inteiros de 16 bits, 16472 e 0.


4 Quatro caracteres ASCII @ X NUL NUL.
Sem mais informaes, no h como saber qual interpretao est correta.
Uma das muitas dificuldades de programar linguagem no nvel de mquina (assem-
bly) ocorre pelo fato de a carga de trabalho para se registrar os tipos de dados das vari-
veis recair integralmente sobre o programador. Por exemplo, buscar incorretamente uma
quantidade de 32 bits que represente um inteiro e executar uma adio de ponto flutuante
no nvel de uma mquina sobre ele, basicamente cria lixo como resultado.
Definio: Um erro de tipo qualquer erro que surja porque uma operao
tentada sobre um tipo de dado para o qual ela no est definida.
Erros de tipos so problemas comuns em programao de linguagem assembly. Pro-
gramar em uma linguagem de nvel mais alto geralmente reduz erros de tipos, j que
compiladores e sistemas de tempo de execuo so projetados para identific-los.
Assim, a definio e a implementao apropriadas de tipos de dados no projeto de
linguagens de programao imensamente importante. O assim chamado sistema de tipo
pode fornecer uma base para a deteco precoce (em tempo de compilao) do uso incor-
reto de dados por um programa.
Definio: Um sistema de tipo uma definio precisa das associaes entre o
tipo de uma varivel, seus valores e as operaes possveis sobre esses valores.
A idia de um sistema de tipo mudou dramaticamente desde a emergncia da programa-
o no nvel de mquina e durante sua evoluo nas ltimas trs dcadas. Por exemplo, analise
a histria da linguagem de programao C, que foi desenvolvida no final da dcada de 1960,
para permitir portar o sistema operacional Unix entre diferentes arquiteturas de mquina. C foi
originalmente baseada na linguagem sem tipos BCPL. Contudo, enquanto C se desenvolvia
pelas dcadas de 1970 e 1980 do C de Kernighan e Ritchie para ANSI C e C++, adquiriu ca-
ractersticas de tipagem mais fortes e melhores. Entretanto, adicionar novas regras de tipagem
a uma linguagem no to simples quanto parece (Stroustrup, 1994, p. 20):

Durante a escrita e a depurao, adquiri um grande respeito pela expressividade do sis-


tema de tipo de Simula e pela capacidade do seu compilador de capturar erros de tipos.
Observei que erros de tipos quase invariavelmente refletiam um erro tolo de programa-
o ou uma falha conceitual no projeto. ... Em contraste, tinha descoberto que o sistema

Observao
O custo de um erro
Um dos axiomas da prtica de engenharia de software moderna que um erro no detec-
tado em uma fase de desenvolvimento custa, em mdia, uma ordem de mais magnitude
para ser corrigido na prxima fase. Por exemplo, suponha que um erro de tipo capturado
durante a fase de implementao custe $ 1 para ser corrigido. Se tal erro no fosse detec-
tado at a fase de teste, custaria em mdia $ 10 para ser corrigido. Alm disso, capturar o
mesmo erro aps a fase de teste custaria em torno de $ 100 para corrigi-lo. Por exemplo,
um erro no algoritmo de multiplicao de ponto flutuante em modelos iniciais do chip
Intel 386 custou $ 472 milhes para corrigi-lo, j que esse erro no foi detectado antes
que o chip chegasse ao mercado.
104 Captulo 5 Tipos

de tipos de Pascal era pior do que intil uma camisa-de-fora que causava mais pro-
blemas do que solues, forando-me a entortar meus projetos para adapt-los a um
artefato orientado a implementao.

5 .2 TI PAG EM ES T T ICA E DIN M ICA


Um sistema de tipo impe restries especficas, como o requisito de que os valores
usados em uma operao aritmtica devam ser numricos. Tais restries no podem ser ex-
pressas sintaticamente em EBNF. Para algumas linguagens (por exemplo, C), a verificao
de tipos executada em tempo de compilao, enquanto para outras (por exemplo, Perl) ela
realizada em tempo de execuo. Em qualquer caso, o sistema de tipo parte da semntica
da linguagem; semntica de tempo de compilao, no primeiro caso, e semntica de tempo de
execuo, no ltimo.1 Em Java, por exemplo, a maior parte da verificao de erros ocorre em
tempo de compilao, porm a converso (casting) verificada em tempo de execuo.
Algumas linguagens, como C e Ada, requerem que um nico tipo seja associado a
uma varivel quando essa for declarada, permanecendo associada por toda sua vida em
tempo de execuo. Isso permite que o tipo de valor de uma expresso seja determinado
em tempo de compilao.
Definio: Uma linguagem tipada estaticamente se os tipos de todas as vari-
veis so fixados quando so declaradas em tempo de compilao.
Outras linguagens, como Perl, Python e Scheme, permitem que o tipo de uma vari-
vel seja redefinido cada vez que um novo valor for atribudo a ela em tempo de execuo.2
Para implementar essa caracterstica, um indicador de tipo armazenado em tempo de
execuo com cada valor.
Definio: Uma linguagem tipada dinamicamente se o tipo de uma varivel
puder variar em tempo de execuo de acordo com o valor atribudo.
Lembre-se de que um erro de tipo qualquer erro que ocorra quando uma operao tentada
sobre um tipo de valor para o qual no est bem definida. Por exemplo, considere a expresso
em linguagem C, x+u.p, na qual u definido como a unio {int a; float p;}:

u.a = 1;
...
x = x + u.p;

Esse trecho de cdigo uma instncia de um erro de tipo permitido pelo fato de que
tanto valores int quanto float de u compartilham a mesma palavra de memria, que
inicializada com um valor int. Se x for um float, a expresso x+u.p faz com que um
valor int seja envolvido em uma adio de ponto flutuante sem ser convertido para uma
representao de ponto flutuante, gerando lixo como resultado.3
Definio: Uma linguagem de programao fortemente tipada se o seu sistema
de tipo permitir que todos os erros de tipos em um programa sejam detectados em
tempo de compilao ou em tempo de execuo.

1. Alguns textos se referem a qualquer erro detectado em tempo de compilao como sendo um erro de sintaxe.
Entretanto, o uso de tipos parte do significado (ou da semntica) de um programa.
2. Alguns autores se referem a tais linguagens como no tipadas ou sem tipo no sentido de que o tipo da varivel
no especificado no programa-fonte. Acreditamos que esse uso enganoso, preferindo reservar esses termos para
descrever linguagens, como linguagens assembly/de mquina, nas quais nem variveis nem valores so tipados.
3. Em alguns compiladores C, a situao piorada pelo fato de que um int ocupa 16 bits, enquanto um float tem
32 bits. Esse erro no pode, de modo geral, ser detectado em tempo de compilao nem em tempo de execuo.
5.3 Tipos Bsicos 105

O fato de uma linguagem ser esttica ou dinamicamente tipada no impede que


ela seja fortemente tipada. Por exemplo, Ada e Java so linguagens fortemente tipadas,
enquanto C e C++ no so. Linguagens dinamicamente tipadas como Scheme e Perl so
fortemente tipadas, j que a tipagem e a verificao dos valores que feita pelo inter-
pretador em tempo de execuo evitam erros de tipos no detectados. O exemplo dado
anteriormente expe um entre diversos problemas no sistema de tipo de C. A tipagem
forte geralmente promove programas mais confiveis e vista como uma virtude no
projeto de linguagens de programao.
As prximas duas sees examinam tipos internos. Tipos bsicos denotam valores
de dados que so atmicos ou indivisveis do ponto de vista do programador. Em contras-
te, tipos compostos ou no bsicos correspondem a dados que so compostos de outros
dados. Por exemplo, diversos tipos de listas, matrizes e conjuntos so tipos compostos.

5 .3 T IP O S BS ICO S
Os tipos bsicos de uma linguagem de programao geralmente correspondem aos
tipos de dados que esto disponveis em mquinas contemporneas. A Tabela 5.1 resume
os tipos bsicos que esto disponveis em C,4 Ada e Java.
Em um computador moderno de 32 bits, os requisitos de memria para esses tipos
so geralmente medidos com o uso das seguintes delineaes de unidades de memria:
Nibble: 4 bits contguos de memria; 1/2 byte
Byte: 8 bits contguos de memria
Half-Word: 16 bits contguos de memria 2 bytes
Word: 32 bits contguos de memria 4 bytes
Double word: 64 bits contguos de memria 8 bytes
Quad word: 128 bits contguos de memria 16 bytes
Historicamente, o tamanho de uma palavra (word) tem variado; alguns tamanhos de pa-
lavras incluam 8, 12, 16, 18, 24, 32, 36, 48 e 60 bits. Quando as mquinas de 64 bits
substiturem as de 32 bits, uma palavra deve se tornar associada a 64 bits.
Como exemplo, em Java a noo de um byte definida como um tipo explcito e um
byte Java pode representar qualquer um de 28 256 valores binrios distintos. Um short
Java ocupa meia palavra, um int ocupa uma palavra, e um valor long ocupa uma palavra
dupla. Java incomum pelo fato de a linguagem prescrever os tamanhos anteriores dos tipos

| Tabela 5.1 Tipos Bsicos em C, Ada e Java

Tipo C Ada Java


Byte byte

Inteiro short, int, long integer short, int, long

Nmero real float, double float, decimal float, double

Caractere char character char

Boleano boolean boolean

4. C++ suporta um tipo boleano, mas, nesse momento, poucos compiladores parecem suport-lo.
106 Captulo 5 Tipos

inteiros. A maioria das linguagens, incluindo C, C++ e Pascal, deixam para o implemen-
tador do compilador a tarefa de associar tipos a tamanhos de memria; em muitos casos,
os padres especificam tamanhos mnimos para cada tipo. Ada permite que o programa-
dor especifique a faixa necessria para cada varivel. Mas a faixa pode disparar um erro
de compilao se exceder o que o compilador suporta.
Devido ao fato de cada um desses tipos numricos ter tamanho finito, uma operao
aritmtica pode produzir um valor que esteja fora da sua faixa definida. Por exemplo,
analise a adio 2.147.483.647 1 usando aritmtica de complemento de dois de 32 bits;
a adio gera um valor fora da faixa permitida. Algumas mquinas (por exemplo, MIPS)
geram uma interrupo quando tal erro ocorre; entretanto, muitas, incluindo a Java Vir-
tual Machine, no (Arnold e Gosling, 1998, p. 358):

Se ocorrer overflow, ento o sinal do resultado no o mesmo da soma matemtica


dos dois valores do operando.

A falha em gerar uma interrupo ou uma exceo quando ocorre overflow um erro de
projeto que complica desnecessariamente a vida do programador.5
Em linguagens como C (usando sintaxe de estilo C), as seguintes operaes int so
funes que recebem dois argumentos int e retornam um valor int como resultado:

+, -, *, /, %
==, !=, <, <=, >, >=

O smbolo % denota a funo de mdulo que retorna o resto da diviso inteira. As demais
funes possuem comportamentos convencionais, exceto pela diviso, que em matemti-
ca normalmente produziria um nmero real como resultado.
Observe que linguagens como C, exceto Java, so incomuns para que os operadores
de igualdade e relacionais produzam um resultado int. Para permitir uma interpretao
consistente, o valor zero interpretado como falso e todos os outros so interpretados
como verdadeiro. Perl tambm usa essa conveno.
Na maioria das linguagens de programao, um nmero real um valor no integral
armazenado em formato de ponto flutuante em preciso simples (por exemplo, float de
C) ou dupla (por exemplo, double de C), dependendo da preciso e da faixa de valores
necessrias. Para mquinas atuais de 32 bits, um valor float requer uma palavra de me-
mria de 32 bits, enquanto um valor double requer uma palavra de 64 bits.
Valores de ponto flutuante em formato IEEE so representados pelo uso da notao
cientfica, na qual um valor representado como o produto de um nmero binrio que con-
tenha um nico dgito diferente de zero esquerda do ponto decimal por uma potncia apro-
priada de 2. Por exemplo, o nmero decimal 123,45 representado em notao cientfica
decimal como 1,2345 102. Computadores usam representao binria em vez de decimal
para armazenar nmeros, mas, alm disso, seguem os mesmos princpios. Computacio-
nalmente, um valor real armazenado como o produto de um valor real binrio contendo
um nico 1 esquerda do ponto binrio por uma potncia apropriada de 2. Mquinas mais
antigas no seguem o formato IEEE, mas usam alguma forma de notao cientfica.
Considere representar o nmero real decimal 3,375 em notao cientfica binria.
A converso para binrio feita separando-se o nmero nas suas partes inteira e decimal

5. O padro de bits 100...0 em aritmtica de complemento de dois poderia ser interpretado como valor indefi-
nido, em vez de um nmero negativo sem contraparte positiva. Tal projeto simplificaria a semntica de muitos
problemas de depurao (por exemplo, a identificao de variveis no inicializadas).
5.3 Tipos Bsicos 107

Preciso Simples:

s e m s sinal
e expoente
bit 0 1 8 9 31 m mantissa

Preciso Dupla:
s e m
bit 0 1 11 12 63

| Figura 5.1 Representao de Nmeros de Ponto Flutuante no padro IEEE 754

e convertendo cada parte separadamente para binrio. A parte inteira de 3,375 3, que em
binrio 112 . A parte fracionria 0,375 0,0112 , j que 0,375 0,25 0,125 22
23 0,0112 . Assim, a notao cientfica para 3,375 em binrio :
3,37510 11,0112 1,10112 21
Computadores modernos inventados aps 1980 usam o padro IEEE 754 para repre-
sentar valores em ponto flutuante (Goldberg, 1991). Esse padro est resumido na Figura
5.1. Um nmero de ponto flutuante de preciso simples normalizado possui:
Um bit de sinal com 0 denotando e 1 denotando .
Um expoente e de 8 bits denotando uma potncia de 2 e codificado como o ex-
poente verdadeiro 127 (todos bits um). O nmero 127 chamado de bias, e a
codificao chamada de notao excedente em 127.
Uma mantissa m de 32 bits denotando uma frao binria na faixa de 0 a 1 e
interpretada como o nmero 1.m. O 1 na frao comumente chamado de bit
escondido.
No conjunto, isso representa o nmero:
(1)s 1.m 2e127
Observe que o nmero 0,0 representado pelo uso do padro de zero bits. A verso de
preciso dupla permite mais bits para a frao binria e de expoente, aumentando, assim,
a faixa que pode ser representada bem como o nmero de dgitos significativos.
A preciso simples permite uma faixa de aproximadamente 1038 a 1038, enquanto
a preciso dupla permite uma faixa de 10308 a 10308. Nmeros de preciso simples tm
em torno de sete dgitos decimais significativos na mantissa, enquanto a preciso do n-
mero de preciso dupla tem 16 dgitos decimais significativos.
O padro IEEE 754 tambm fornece representaes nicas para nmeros incomuns,
como infinito (s 1s no expoente e tudo 0s na mantissa) e no um nmero (NaN) (s 1s no
expoente e no s 0s na mantissa). Nmeros de ponto flutuante desnormalizados tambm
so definidos para fornecer uma representao para nmeros que sejam menores do que o
menor valor normalizado (s 0s no expoente e qualquer mantissa diferente de zero).
Para continuar nosso exemplo, considere o nmero decimal 3,375. Ele possui um bit
de sinal 0, j que o nmero positivo. Uma vez que o expoente real 1, o expoente da
108 Captulo 5 Tipos

mquina 127 1 128 em decimais, ou 1000 0000 em binrio, j que o expoente


da mquina armazenado em notao excedente em 127. Finalmente, a mantissa o
valor aps o ponto binrio: 101 1000 0000 0000 0000 0000 em binrio de preciso
simples. Juntando o sinal, o expoente e a mantissa, obtemos:
0 1000 0000 101 1000 0000 0000 0000 0000
A maioria dos nmeros decimais exatos como 0,2 no tem representaes exatas em
ponto flutuante binrio. Assim, quando executamos determinadas operaes aritmticas
sobre eles, como 5 0,2, obtemos um resultado que no exatamente igual ao matemti-
co (5 0,2 1,0 nesse caso). Esse e outros problemas relacionados aritmtica de ponto
flutuante so discutidos com algum detalhe em (Hennessy e Patterson, 1998, Captulo 4)
e em (Goldberg, 1991).
Em C/C++, as seguintes operaes recebem dois operandos do tipo float:

+, , *, /
==, !=, <, <=, >, >=

Cada operao no primeiro grupo retorna um resultado float, e cada operao do segun-
do grupo retorna um int, que, conforme explicado anteriormente, geralmente interpre-
tado como um boleano.
Duas questes so levantadas por essa capacidade. Primeiro, a maioria dos smbolos
listados aqui so idnticos queles usados por operaes inteiras. Por exemplo, a adio
de dois inteiros denotada por + e calcula um resultado int, enquanto a adio de dois
nmeros de ponto flutuante tambm usa +, porm retorna um resultado float. Essa uma
instncia da noo mais geral de sobrecarga de operadores.
Definio: Um operador ou uma funo dito sobrecarregado quando seu signi-
ficado varia conforme os tipos dos seus operandos ou argumentos ou resultado.
A sobrecarga de operadores e funes internas (por exemplo, funes E/S) tem sido usada
desde os primeiros dias de Fortran. Exemplos mais interessantes de sobrecarga so dis-
cutidos no Captulo 4.
A segunda questo levantada aqui a de que as definies anteriores de operador de
ponto flutuante esto incompletas, ou seja, elas no incluem os casos em que um ou outro
operando int em vez de float. Isso chamado de aritmtica de modo misturado em
Fortran, e suas instncias para as operaes anteriores so resumidas da seguinte forma:

float (+, , *, /) int


int (+, , *, /) float
float (==. !=, <, <=, >, >=) int
int (==. !=, <, <=, >, >=) float

As duas primeiras produzem um resultado float, enquanto as duas ltimas produzem


um resultado int.
Por exemplo, considere a soma 2,5 1 envolvendo um float e um int. O resultado
mais apropriado para essa soma 3,5, que no pode ser representado como int; ele deve
ser float ou double. Para obter esse resultado, o valor int 1 deve ser convertido para
seu equivalente float antes de a soma ser calculada, j que no h operaes de modo
misturado no nvel de mquina.
5.3 Tipos Bsicos 109

Tabela 5.2 Tipo Bits

| O Valor 2 em
Diversas
Representaes
de Dados
char
short

int
float
0000 0000 0011 0010
0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0010
0100 0000 0000 0000 0000 0000 0000 0000

Converso de Tipos No nvel de mquina, as operaes aritmticas requerem que


ambos os operandos sejam do mesmo tipo (int, float ou double). Para simular uma
operao de modo misturado, um dos operandos deve ter seu valor convertido para o tipo
do outro. Tal converso chamada de converso de tipo, j que cria um padro de bits
diferente para representar o valor em um tipo diferente.
Como exemplo, considere as diversas representaes do valor 2 em Java apresentadas
na Tabela 5.2. Interpretar o caractere Unicode 2 como um inteiro de 16 bits daria o valor
inteiro 50 (veja a Tabela 5.3). As representaes inteiras de 16 e 32 bits so idnticas, exceto
pelos 16 zeros iniciais a mais na verso de 32 bits. Interpretar o nmero de ponto flutuante
2,0 como um inteiro daria o valor inteiro 230, um nmero muito grande. Assim, uma conver-
so de tipo muitas vezes necessria para garantir a interpretao apropriada de um valor
em preparao para executar uma operao aritmtica sobre esse valor.
Uma converso de tipo chamada de converso limitante, se o valor resultante per-
mitir menos bits do que o original (dessa forma, potencialmente perdendo informao).
Por exemplo, na converso de um float para um int, a parte fracionria aps o ponto
decimal ser perdida; se convertermos 2,4 para um int resultar no valor 2, com a perda
da frao 0,4.
Uma converso de tipo chamada de converso ampliadora se o resultado do
valor no requerer menos bits do que o original (geralmente sem perda de informao).
Assim, converter 2 para float resulta no valor 2,0, que matematicamente equivalen-
te. Usamos o termo geralmente porque h excees. Por exemplo, converter um inteiro
de 32 bits suficientemente grande em um nmero de ponto flutuante de 32 bits resulta
na perda dos 8 bits de ordem mais baixa do valor inteiro; assim, os dois valores s so
aproximadamente iguais.
As linguagens devem suportar converses de tipo implcitas apenas se forem do tipo
de ampliao. Assim, por exemplo, a expresso 2,5 1 permitida porque interpretada
como 2,5 + float(1), na qual a funo de converso float() considerada uma
converso ampliadora.
Historicamente, tanto os tipos de dados numricos quanto as strings tm causado
problemas por causa das converses de reduo. Problemas com strings tm resultado
principalmente do uso de strings de tamanho fixo.
De modo geral, as definies dos diversos tipos de converses de tipo implcitas
permitidas em uma linguagem de programao devem ser feitas com cuidado. Analise o
algoritmo da Figura 5.2, que define converses de tipos aritmticos em ANSI C (Kerni-
ghan e Ritchie, 1988, p. 198). Essas so regras confusas! Muitas vezes, elas no so bem
compreendidas pelos programadores e, s vezes, apresentam resultados surpreendentes.
Por exemplo, o efeito de algumas dessas regras remover o sinal de um valor com sinal.
Converses de reduo devem requerer que o programa chame explicitamente uma fun-
o de converso de tipo. Em qualquer conjunto de converses implcitas existe o perigo de
110 Captulo 5 Tipos

Se... ento converta...


algum operando for long double o outro para long double
algum operando for double o outro para double
algum operando for float o outro para float
algum operando for unsigned long int o outro para unsigned long int
os operandos forem long int e unsigned int
e long int puder representar unsigned int o unsigned int para long int
e long int no puder representar unsigned int ambos os operadores para
unsigned long int
um operando for long int o outro para long int
um operando for unsigned int o outro para unsigned int
| Figura 5.2 Regras de Converso Implcita de Tipos ANSI C

que o resultado seja inesperado, s vezes at para o projetista da linguagem. Um exemplo


clssico da linguagem PL/I :

declare (a) char (3);


...
a = 123;
a = a + 1;

em que o valor final de a so trs caracteres de espao, e no 124, como esperado. O


perigo aqui que, embora as regras de converso de tipos possam ser razoveis isolada-
mente, as formas pelas quais elas interagem nem sempre so bem compreendidas.
O termo coero de tipo usado para denotar uma alterao implcita de um valor de
um tipo para outro, que possa ou no envolver uma converso de tipo. No ltimo caso, o
valor permanece inalterado, apenas seu tipo mudou. Um exemplo de Java seria:

String s = abc;
...
Object obj = s;

Nesse caso, a string s convertida para um Object (objeto), mas o valor permanece o
mesmo, a saber, um objeto string.

Conjuntos de Caracteres Linguagens mais antigas, como C, usam o conjunto


de caracteres ASCII, que possui cdigos de caracteres acima de 127 no especificados.
A maioria dos sistemas operacionais modernos usa a chamada codificao Latin-1, que
especifica os cdigos acima de 127.
O conjunto de caracteres ASCII e seus cdigos de caracteres em base 8 (octal)6 so apre-
sentados na Tabela 5.3. Os cdigos de caracteres 000037 e 177 octal (031, 127 decimal)
no so imprimveis. Todos os cdigos 001037 podem ser gerados por meio de combina-
es de teclas de controle; por exemplo, n1 pode ser gerado por meio de Ctrl-J e o caractere

6. Mostrar os cdigos de caracteres usando hexadecimais poderia ter sido melhor, mas no preencheria hori-
zontalmente a pgina.
5.3 Tipos Bsicos 111

| Tabela 5.3 Conjunto de Caracteres ASCII


Octal 0 1 2 3 4 5 6 7
00x null soh stx etx eot enq ack bell
01x bs ht nl vt np cr so si
02x dle dc1 dc2 dc3 dc4 nak syn etb
03x can em sub esc fs gs rs us
04x  ! # $ % &
05x ( ) * + , . /
06x 0 1 2 3 4 5 6 7
07x 8 9 : ; < = > ?
10x @ A B C D E F G
11x H I J K L M N O
12x P Q R S T U V W
13x X Y Z [ \ ] ^ _
14x a b c d e f g
15x h i j k l m n o
16x p q r s t u v w
17x x y z { | } del

familiar de nova linha \n. Muitos dos nomes para os cdigos 001037 so abreviados;
por exemplo, bs para backspace, ht para tabulao horizontal, n1 para nova linha, cr
para retorno de carro etc. A partir dessa tabela vemos que o caractere 2 possui cdigo
octal 062 (decimal 50).
Um valor Java do tipo char usa o conjunto Unicode (Unicode Consortium, 2000)
UTF-16, que um padro de 16 bits para codificao de caracteres, em vez do padro mais
convencional de 7 bits ASCII.7 O padro Unicode define trs conjuntos de caracteres, UTF-8,
UTF-16 e UTF-32, de 8, 16 e 32 bits, respectivamente. O padro UTF-8 inclui ASCII,
sendo um subconjunto prprio de UTF-16, o qual, por sua vez, um subconjunto prprio
de UTF-32. O conjunto de caracteres Unicode UTF-16 fornece uma coleo rica de carac-
teres que permite a programas Java embutir um vocabulrio mais internacional de que seus
predecessores. Uma amostra da cobertura do conjunto de caracteres Unicode UTF-16 das
linguagens do mundo e outros conjuntos de caracteres apresentada na Tabela 5.4.
Como a tabela sugere, o padro Unicode inclui cdigos para conjuntos de caracteres
que se espalham pelas principais lnguas do mundo. Ele tambm inclui pontuao, sinais de
acentuao, smbolos matemticos e uma ampla gama de outros smbolos tcnicos. Fornece
cdigos para caracteres modificadores, como o til (~) e cdigos que so usados para codi-
ficar letras acentuadas (). O padro Unicode est constantemente sendo atualizado (veja
informaes mais detalhadas em www.unicode.org). Sua verso corrente define cdigos
para 49.194 caracteres dos alfabetos do mundo e outros conjuntos de smbolos.

7. ASCII o American Standard Code for Information Interchange, que um subconjunto do padro ISO 8859-1 de
cdigo de 8 bits, conhecido como o conjunto de caracteres Latin-1, que est embutido apropriadamente no conjunto
Unicode, conforme mostrado na Tabela 5.4.
112 Captulo 5 Tipos

| Tabela 5.4 Amostra do Conjunto de Caracteres Unicode UTF-16

Valor Unicode Valor Unicode


Caracteres Caracteres
(hex) (hex)
\u0000-\u007F Conjunto ASCII \u0F00-\u0FBF Tibetano
\u0100-\u024F Latim estendido \u10A0-\u10FF Georgiano
\u0300-\u036F acentuao \u2000-\u206FF pontuao
\u0300-\u036F Grego \u20A0-\u20CF smbolos monetrios
\u0400-\u04FF Cirlico \u2190-\u21FF setas
\u0530-\u058F Armnio \u2200-\u22FF smbolos matemticos
\u0590-\u05FF Hebreu \u2300-\u23FF smbolos tcnicos
\u0600-\u06FF rabe \u2440-\u245F OCR
\u0B80-\u0BFF Tmil \u25A0-\u25FF smbolos geomtricos
\u0C00-\u0C7F Telugu \u2700-\u27BF Dingbats
\u0C80-\u0CFF Kannada \u30A0-\u30FF Katakana
\u0E00-\u0E7F Tailands \u3200-\u32FF letras CJK
\u0E80-\u0EFF Laos \uAC00-\uD7A3 Hangul

5 .4 T IP O S CO M P O ST O S
Nas primeiras sees, discutimos tipos de dados que so construdos a partir dos
bsicos. Os tipos de dados construdos primrios incluem enumeraes, ponteiros, strings
de caracteres, matrizes, registros e unies (ou registros de invlucro variante).

5.4.1 Enumeraes
Outro tipo que bastante relacionado ao tipo inteiro a enumerao. Suportadas por
muitas linguagens, enumeraes fornecem um meio de atribuir nomes a uma srie de va-
lores inteiros. Isso permite que os nomes sejam usados nos programas no lugar dos seus
valores integrais associados, melhorando a legibilidade do texto do prprio programa.
C, C++, Java e Ada suportam enumeraes de maneiras semelhantes. Por exemplo,
a seguinte declarao C/C++:

enum dia {segunda, tera, quarta, quinta,


sexta, sbado, domingo};
enum dia meuDia = quarta;

define um tipo dia integral e uma varivel meuDia inicializada com o valor quarta. Em C/
C++ os valores desse tipo so tratados como se fossem os inteiros 0,..., 6 respectivamente,
de modo que a varivel meuDia pode ser tratada como se fosse uma varivel int comum.
Um tipo de enumerao em Java 1.5 mais poderoso do que a sua contrapartida em C,
C++ e Ada. Analise o fragmento de cdigo em Java da Figura 5.3. Esse cdigo define Dia
como um tipo enum, que uma subclasse da classe Java Enum. Os valores Segunda, Ter-
a,..., Domingo so ordenados implicitamente pela ordem de sua apario na definio,
como tambm acontece em C/C++ e Ada. Cada tipo enum de Java Comparable e Seriali-
zable e herda os mtodos da classe Java Object, como toString, que necessrio na ltima
linha da Figura 5.3. (Veja no Captulo 13 uma discusso mais completa sobre classes.)
5.4 Tipos Compostos 113

import java.io.*;
...
enum Dia {Segunda, Tera, Quarta, Quinta, Sexta, Sbado, Domingo};
...
for (Dia d : Dia.values()) {
System.out.println(d);
}

| Figura 5.3 Exemplo de enum em Java 1.5

A vantagem dos tipos de enumerao, de modo geral, que eles permitem que o
cdigo fique mais legvel. Se as enumeraes no estiverem disponveis (ou no usadas),
um tipo como dia deve ser simulado associando-se seus valores com os inteiros 06 e
usando atribuies e operadores inteiros. Na verso em Java, o uso de enum torna o c-
digo ainda mais confivel, j que toda a verificao de tipos extensiva de Java aplicada
naturalmente a todos os objetos enum em tempo de compilao.

5.4.2 Ponteiros
Um ponteiro um valor que representa uma referncia ou um endereo de memria.
Ele fornece um nvel de indireo na referenciao da memria que nenhum outro tipo de
dados possui. Ponteiros so usados comumente em C, C++, Ada e Perl.
C fornece duas operaes com ponteiros: o operador endereo de (unrio &) recebe
uma varivel como argumento e retorna o endereo dessa varivel, e o operador de desre-
ferenciao (unrio*) recebe uma referncia e produz o valor dessa referncia.
Para ilustrar o uso de ponteiros, analise a lista encadeada definida por um N em
C/C++:

struct N {
int chave;
struct N* prximo;
};
struct N* incio;

Aqui, um N um par de valores, um inteiro chave e um ponteiro prximo. Assim, N


uma estrutura de dados recursiva. O ponteiro incio se refere ao primeiro N da lista.
Um exemplo de uma lista encadeada mostrado na Figura 5.4.
Ponteiros so, muitas vezes, considerados a perdio do desenvolvimento de softwa-
re confivel, j que programas que usam ponteiros tendem a ter mais erros. Em especial,
algumas linguagens (por exemplo, C) requerem que o programador gerencie explicita-
mente os ponteiros tanto para alocar quanto para desalocar estruturas dinmicas de dados.
Entretanto, esse tipo de gerenciamento dinmico de memria to complexo que a maio-
ria dos programadores no o executa bem. Esse tpico mais explorado no Captulo 11.
Os ponteiros so especialmente problemticos em C porque referncias a matrizes
e ponteiros so consideradas equivalentes. Por exemplo, as duas funes apresentadas
na Figura 5.5 so semanticamente idnticas. Em especial, o uso de um ponteiro em uma
declarao de incremento dentro de um lao for da funo direita mais crtico do que
a sua contrapartida esquerda.
114 Captulo 5 Tipos

chave prximo
incio 1 3 5 7 Nulo

| Figura 5.4 Uma Lista Encadeada Simples em C

Apesar disso, o cdigo direita ilustra o relacionamento entre um ndice de matriz


e um ponteiro, ou seja, se a for uma matriz, ento a referncia a a mesma do endereo
de a[0]. Isso leva regra que se E1 ou E2 for um tipo ponteiro (por exemplo, seu valor
for um endereo), ento:

E1[E2] *((E1) (E2))

em que o unrio * o operador de desreferenciao. Por exemplo, usando a Figura 5.5, se


a for do tipo float[ ] e i for um int, ento:

a[i] = *(a + 1) = i[a]

Assim, C no faz distino efetiva entre um ndice i de matriz e um ponteiro referencian-


do o elemento de ndice i de uma matriz. Por motivo de clareza do programa, a indexao
de matrizes deve ser preferida, j que menos propensa a erros do que a aritmtica de
ponteiros.
Um exemplo clssico em C do uso de ponteiros para a indexao de matrizes a im-
plementao da funo strcpy apresentada na Figura 5.6. A decifrao desse cdigo se
baseia no conhecimento de que as strings p e q so matrizes de caracteres que terminam
em um caractere NUL, que interpretado como false. Nesse processo, o cdigo no
evita que a operao de cpia avance o limite da matriz p, o que ocorrer se o bloco de
armazenamento alocado para p for menor do que o comprimento da string q.
Embora C/C++ e Ada no tenham encontrado uma forma de remover ponteiros do
seu vocabulrio, outras linguagens, incluindo as linguagens funcionais e lgicas e al-
gumas orientadas a objetos, como Java, o fizeram. Java, linguagens funcionais como
Scheme e Haskell e a linguagem lgica Prolog fazem uso intenso de ponteiros, embora
eles no faam parte do vocabulrio dessas linguagens. Remover ponteiros explcitos de
uma linguagem no elimina a capacidade de realizar alocao e desalocao dinmica
de memria de estruturas complexas, como veremos no Captulo 14. Estudaremos o ge-
renciamento dinmico de memria no Captulo 11.

float sum(float a[ ], int n) float sum(float *a, int n)


{ {
int i; int i;
float s = 0.0; float s = 0.0;
for (i = 0; i < n;i++) for (i = 0; i < n; i++)
s += a[i]; s += *a++;
} }

| Figura 5.5 Equivalncia de Matrizes e Ponteiros em C/C++


5.4 Tipos Compostos 115

void strcpy (char* p, char* q) {


while (*p++ = *q++) ;
}

| Figura 5.6 A Funo strcpy

5.4.3 Matrizes e Listas


Matrizes so seqncias indexadas de valores; em uma linguagem estaticamente
tipada, todos os valores de uma matriz devem ser do mesmo tipo. Aqui est a sintaxe de
algumas declaraes exemplo de matrizes:

int A[10];
float C[4][3];
char S[40];

A varivel A uma matriz unidimensional de valores do tipo int, enquanto a varivel C


uma matriz bidimensional de valores do tipo float, composta de quatro linhas de trs en-
tradas cada. Embora algumas linguagens mais antigas (por exemplo, Fortran) restringissem
o nmero mximo de dimenses de uma matriz, a maioria das linguagens atuais no o faz.
Tecnicamente, linguagens como C restringem as matrizes a uma dimenso, o que
sugerido pela declarao anterior de C. Todavia, qualquer declarao de matriz unidimen-
sional pode ser estendida para produzir uma matriz de matrizes. Por exemplo, a matriz
C declarada anteriormente tecnicamente uma matriz de quatro elementos. Observe a
sintaxe estranha para se declarar o nmero de linhas e colunas em C; essa estranheza
mantida em C++ e Java tambm. Uma vantagem desse projeto que possvel criar uma
matriz no retangular bidimensional, ou seja, uma matriz bidimensional na qual cada
linha tenha um nmero diferente de colunas.
Todas as matrizes anteriores possuem limites estticos em cada dimenso. Muitas
linguagens permitem a criao de matrizes cujos tamanhos em cada dimenso podem
ser determinados em tempo de execuo. Por exemplo, em Java, todas as matrizes tm
seu tamanho estabelecido em tempo de execuo, mas, uma vez que foram criadas, seu
tamanho fica fixo.
O operador bsico de matrizes o de indexao, que referencia uma nica entrada
na matriz por intermdio de seu ndice. Exemplos incluem a[3] e c[i+1][j-1], usando
as matrizes declaradas anteriormente. Em linguagens de estilo C, o ndice estabelecido
para qualquer dimenso {0,..., n 1}, no qual n o tamanho dessa dimenso. Em
contraste, Fortran usa {1,..., n}, enquanto Pascal e Ada permitem a declarao explcita
dos limites inferior e superior de cada dimenso. Por exemplo, as declaraes anteriores
em Ada poderiam aparecer como:

a : array(-4..5) of INTEGER;
c : array(1..4, 0..2) of FLOAT;
s : array(1..40) of CHARACTER;

As matrizes de Pascal tm tamanho esttico, com limites estticos para cada dimenso.
Em tais linguagens, os limites da matriz esttica so parte do tipo dessa matriz, o que
(Stroustrup, 1994) chamou de camisa-de-fora.
Matrizes em Ada tambm possuem tamanho esttico, mas com um pouco mais de flexi-
bilidade na declarao do tamanho. Esse recurso chamado de sem restrio, o que significa
116 Captulo 5 Tipos

que a faixa de valores do ndice para uma matriz declarada determinada implicitamente
pelo nmero de elementos no seu valor inicial. Por exemplo:

a : array(INTEGER range <>) of INTEGER :=


(1 | 10 => 12, 2 .. 8 => 0, 9 => 33);

inicializa a matriz a com a faixa de ndices 1...10 e os valores iniciais 12, 0, 0,..., 0, 33, 12.
Linguagens como Pascal permitem que os ndices sejam de qualquer um dos tipos
integrais, incluindo int (de todos os tamanhos), char e enum. O tipo char, quando visto
atravs da sua codificao, pode ser tratado da mesma forma que outro tipo integral.
As linguagens Ada e Java verificam o valor de cada ndice da matriz para assegurar
que est na faixa vlida, embora em compiladores Ada a verificao possa requerer uma
opo de compilao. Nem C nem C++ obrigam a verificao em tempo de execuo, e
quase nenhum compilador C/C++ implementa a verificao de ndices, nem mesmo como
opo. Devido equivalncia de matrizes e ponteiros, no fica claro como a verificao de
ndices poderia ser implementada nessas linguagens (veja exemplos na Seo 5.4.2).

Questes de Implementao A fase semntica de um compilador constri um


bloco de informaes para cada matriz, incluindo (dependendo da linguagem): tipo do ele-
mento da matriz, tamanho do elemento da matriz, tipo do ndice para cada dimenso, faixa
ou tamanho dos ndices para cada dimenso. Em especial, essas informaes so necess-
rias para se calcularem os endereos de memria para expresses indexadas como A[i] e
para executar verificaes de faixa de ndices em tempo de execuo, caso seja necessrio.
Essas informaes so chamadas de descritor de vetor e podem existir apenas em tempo de
compilao ou parcialmente em tempo de execuo, dependendo da linguagem.
Como exemplo, analise a declarao de matriz em Java:

int [ ] A = new int(n);

Em tempo de execuo, cada referncia na forma a[i] deve ter um valor de ndice i entre
0 e n 1, e cada valor atribudo a a[i] deve ser do tipo int. J que o valor de i ou de n
no pode ser determinado antes do tempo de execuo, a parte de tempo de execuo do
descritor de vetor deve conter o tamanho da matriz. Na verdade, Java disponibiliza essas
informaes como a.length.
Em contrapartida, C e C++ no requerem verificao de ndices de matrizes em tem-
po de execuo. Transbordamentos de ndices em matrizes em C/C++ so uma fonte de
erros em tempo de execuo em grandes sistemas de software; muitas vezes, esses erros
aparecem com falhas de proteo de memria. Overflows de buffers so um tipo de
transbordamento de indexao que explorado por hackers para entrar em sistemas. As-
sim, linguagens fortemente tipadas como Java e Ada, que forneam verificao de faixas
de ndices, tm vantagens sobre C/C++ quanto confiabilidade de software.
Em Java, um descritor de vetor poderia conter o tamanho e do elemento, o tipo do
elemento, o nmero n de elementos e o endereo do primeiro elemento da matriz para
facilitar clculos de indexao. Por exemplo, o descritor de vetor e o layout da matriz A
quando n = 10 so mostrados na Figura 5.7, supondo que um int ocupe 4 bytes.
O endereo de memria do primeiro elemento, addr(a[0]), pode ser usado para cal-
cular o endereo de qualquer outro elemento addr(a[i]) com tamanho e de elemento, por
intermdio da seguinte frmula:

addr(a[i]) addr(a[0]) e i
5.4 Tipos Compostos 117

rea Esttica

vetor de informaes

...
4 int 10 A[0] A[1] A[9]
Tamanho (e) Tipo do Tamanho (n)
do elemento Elemento

| Figura 5.7 Alocao de Memria para a Matriz Unidimensional A

Por exemplo, o endereo de memria de a[2], supondo que o tamanho de um int seja
de 4 bytes, calculado como:

addr(a[2]) addr(a[0]) 4 2 addr(a[0]) 8

Esse clculo generalizado para matrizes de duas ou mais dimenses, estendendo


o descritor de vetor com informaes suficientes para permitir o clculo do endereo de
um elemento individual. Por exemplo, uma alocao esttica de memria para a matriz
bidimensional C anterior ilustrada na Figura 5.8.
Aqui, observamos que C possui quatro linhas e trs colunas, elementos individuais
so armazenados em locaes contguas de memria, uma linha por vez, e a matriz bidi-
mensional retangular (garantidamente pelo tamanho da alocao esttica). Isso cha-
mado de ordenao linha-coluna. O clculo do endereo de C[i][j], dados os valores
para os ndices i e j, pode ser definido da seguinte forma geral, considerando novamente
o nmero de unidades endereveis (bytes) que um elemento requer:

addr(C[i][j]) addr(C[0][0]) e (ni j)

Por exemplo, o endereo de C[2][1] calculado por:

addr(C[2][1]) addr(C[0][0]) 2 (3 2 1)
addr(C[0][0]) 14

Por motivos histricos, Fortran armazena os elementos de suas matrizes bidimensionais


na ordem coluna-linha, ou seja, todos os elementos da primeira coluna so armazenados em

rea Esttica
vetor de informaes

2 char 4 3 C[0][0] C[0][1] C[0][2] C[1][0] ... C[3][2]


Tamanho (e) Linhas
do Elemento (m)
Tipo do Colunas
Elemento (n)

| Figura 5.8 Alocao de Memria para a Matriz C na Ordem Linha-Coluna


118 Captulo 5 Tipos

rea Esttica

vetor de informaes

C[0][0] C[1][0] C[2][0] C[3][0] ... C[3][2]


2 char 4 3
Tamanho (e) Linhas
do Elemento (m)
Tipo do Colunas
Elemento (n)

| Figura 5.9 Alocao de Memria para a Matriz C na Ordem Coluna-Linha

locaes contguas de memria, depois, todos os elementos da segunda coluna, e assim por
diante. Por exemplo, se nossa matriz de exemplo C fosse declarada em um programa em
Fortran, seus elementos seriam alocados na memria conforme mostrado na Figura 5.9.
Com essa mudana, o clculo do endereo de C[i][j] agora se torna:

addr(C[i][j]) addr(C[0][0]) e (mj i)

Por exemplo, o endereo de C[2][1] muda para:

addr(C[2][1]) addr(C[0][0]) 2 (4 1 2)
addr(C[0][0]) 12

Em linguagens fortemente tipadas, a verificao de faixas para referncias de matri-


zes no pode, de modo geral, ocorrer at o tempo de execuo, j que os valores de ndices
i e j variam durante a execuo do programa. Para linguagens com indexao de origem
em 0, o seguinte cdigo de verificao de faixa deve ser executado para as matrizes a e c
declaradas anteriormente:
Para cada referncia A[i], verifique se 0 i n.
Para cada referncia C[i][j], verifique se 0 i m e 0 j n.
Em algumas situaes, possvel garantir em tempo de compilao que um ndice
invlido no possa ocorrer. Analise o seguinte lao em Java:

double sum = 0.0;


for (int i = 0; i < A.lenght; i++)
sum += A[i];

A faixa de valores do ndice i 0, ..., A.length 1, que precisamente a faixa de subs-


critos vlidos para a matriz A.
Em linguagens como Ada, que permitem indexao que comea com valores dife-
rentes de zero, o descritor de vetor deve conter informaes adicionais para permitir o
clculo efetivo de endereos e a verificao de faixa em tempo de execuo para refern-
cias como A[i] e C[i][j]. Essas informaes incluem o limite inferior (no zero) do
ndice em cada dimenso da matriz, assim como o tamanho dessa dimenso ou o limite
superior do seu ndice. Da mesma forma que Java, Ada disponibiliza essas informaes
para verificao em tempo de execuo.
Em algumas linguagens, todos os elementos de uma matriz so automaticamente pr-
inicializados quando so declarados; em outras, no. Por exemplo, Java segue a conveno de
que matrizes de nmeros (int, float, short, long e double) tm todos os seus elementos
5.4 Tipos Compostos 119

pr-inicializados com 0, matrizes de char so pr-inicializadas com \u0000, matrizes


de referncias a objetos so pr-inicializadas com null, e matrizes de valores boleanos
so pr-inicializadas com false.
Em algumas linguagens, o programador pode inicializar os elementos de uma matriz
quando essa matriz declarada. A seguinte declarao Java inicializa todos os seis nme-
ros inteiros da matriz 2 x 3 T com o valor inteiro 1:

int[][] T = {{1, 1, 1},{1, 1, 1}};

Listas e Fatias Muitas vezes, desejvel em programao examinar uma seo


inteira de uma matriz de uma s vez, como uma poro contgua de uma linha ou uma
coluna inteira de uma matriz bidimensional. Por exemplo, considere a seguinte decla-
rao Python:

b = [33, 55, hello, R2D2]

Aqui a matriz b (conhecida como uma lista em Python) heterognea, j que os


valores de diferentes entradas podem ter tipos diferentes. Listas em Python so indexadas
comeando em 0, de modo que a referncia a b[0] retorna o valor 33.
Uma fatia de uma lista em Python uma srie contgua de entradas, especificada
pelo seu ndice inicial e comprimento. Por exemplo, a fatia b[0:3] se refere sublista
[33, 55, hello]. Ada tambm suporta o fatiamento de matrizes.
Uma coleo rica de operaes que usam fatias de listas est disponvel em Python.
Por exemplo, os operadores e designam a concatenao e a atribuio de listas, res-
pectivamente. Assim, o seguinte dilogo8 em Python apresenta resultados interessantes:

>>> b
[33, 55, hello, R2D2]
>>> b[2:2] + 2*[C3PO, Luke]
[hello, R2D2, C3PO, Luke, C3PO, Luke]
>>> b[:0] = b
[33, 55, hello, R2D2, 33, 55, hello, R2D2]

A primeira operao constri uma lista concatenando duas, a segunda, contendo


duas cpias da lista [C3PO, Luke]. A segunda operao insere uma nova cpia da
lista b no incio da lista b, efetivamente duplicando seu comprimento.
Muitas oportunidades para usar listas dinmicas e suas operaes ocorrem na pro-
gramao funcional. Revisitaremos esse tpico com mais detalhes no Captulo 14, sobre
programao funcional, em que as listas so usadas extensivamente para resolver diversos
problemas de programao.

5.4.4 Strings
As strings so to fundamentais em programao que agora so suportadas direta-
mente pela maioria das linguagens de programao. Historicamente, nem Fortran nem
Algol 60 tinham algum suporte a strings; Cobol tinha strings de tamanho esttico, com
apenas um conjunto limitado de operaes. C, sendo uma linguagem relativamente
moderna, incomum, j que no suporta explicitamente strings de caracteres como um
tipo de dados distinto.

8. Aqui, os sinais >>> indicam um prompt do interpretador Python.


120 Captulo 5 Tipos

Em vez disso, strings em C so codificadas como matrizes de caracteres ASCII, um


caractere por byte, com um caractere NUL adicional (todos bits zero) no final. Por exem-
plo, para declarar a varivel string greeting e atribuir o valor hello, o programador C
escreveria:

char greeting[] = hello;

mas a string hello requer 6 bytes, um para cada um dos cinco caracteres mais um para
o caractere NUL, que marca o final da string. Alm do comprimento declarado da matriz
de strings de caracteres, todos os caracteres que seguem o caractere ASCII NUL na me-
mria sero valores desconhecidos ou indefinidos. Transformaes sobre esse valor so
efetuadas em um caractere por vez.
Assim, o projeto de strings em C propenso a erros porque:

1 O programador deve lembrar-se de alocar pelo menos 1 byte extra para o caractere
NUL.
2 A atribuio a = b em que a e b so strings errnea; em vez disso, voc deve
usar a funo de biblioteca strcpy. responsabilidade do programador assegurar
que o armazenamento alocado para a seja capaz de guardar todos os caracteres em
b, at, e incluindo, o caractere NUL.

Ada fornece strings de tamanho fixo, que, como C, so matrizes de caracteres. Entre-
tanto, diferentemente de C, uma string Ada de tamanho 6, por exemplo, sempre armazena
seis caracteres. Assim, a atribuio da string hello s pode ser feita em uma varivel
string de tamanho declarado 5, sem incorrer em erro em tempo de execuo.
Em contraste, uma string se comporta como um tipo interno em Java, Perl e Python,
capaz de armazenar um nmero ilimitado de caracteres. Nessas linguagens, uma varivel
string no declarada com um tamanho, como em C e Ada, nem o programador sabe (ou
se importa) como as strings so armazenadas em tempo de execuo. Aqui est a decla-
rao anterior reescrita em Java:

String greeting = hello;

Java, Perl e Python fornecem amplas bibliotecas de funes e operaes de strings, incluin-
do concatenao, converso de maisculas e minsculas, busca de padres e substrings.
Java e Python fornecem muitas operaes por intermdio de bibliotecas de classes.

5.4.5 Estruturas
Uma estrutura ou um registro um conjunto de elementos de tipos potencialmente di-
ferentes. Assim, estruturas so como listas em Python no sentido de que os tipos dos seus
elementos podem variar. Entretanto, elas so diferentes de matrizes e listas porque seus ele-
mentos possuem nomes distintos e so acessados por nome, em vez de serem acessados por
ndice. Os elementos individuais de uma estrutura so, muitas vezes, denominados campos.
As estruturas apareceram primeiro em Cobol e PL/I, aps serem omitidas tanto em
Fortran quanto em Algol 60. A forma de abstrao corrente de uma estrutura se originou
tanto em linguagens como Pascal quanto em linguagens de estilo C, embora sua sintaxe
concreta divirja. Java omite as estruturas completamente, tendo-as efetivamente agrupado
na noo mais geral de uma classe. C++ possui tanto estruturas quanto classes, conser-
vando estruturas para manter compatibilidade com C.
5.4 Tipos Compostos 121

Como exemplo, analise a seguinte estrutura e a declarao de uma varivel em C que


modela um funcionrio individual em uma organizao:

struct employeeType {
int id;
char name[26];
int age;
float salary;
char dept;
};
struct employeeType employee;

Cada campo dentro de uma estrutura pode ter um tipo de dado diferente. Nesse exemplo,
vemos que os tipos so int, char[], int, float e char. Entretanto, cada nome de cam-
po dentro de uma estrutura deve ser nico.
Campos individuais dentro de uma estrutura so geralmente alocados em um bloco
contguo. O nmero de bytes realmente alocados e a ordem dos campos alocados depen-
dem da mquina e do compilador. Por exemplo, alguns compiladores alocam os campos
na ordem reversa da declarao. Alm disso, algumas mquinas requerem, por exemplo,
que um float ou int de 32 bits seja alocado em um endereo que seja mltiplo de 4.
Assim, embora a alocao mnima para employeeType seja 39 bytes, sob as suposies
recm-mencionadas, a alocao requer 44 bytes. Programas que fazem suposies sobre
ordem de armazenamento e tamanho encontraro problemas de portabilidade.
A nica operao significativa sobre estruturas, alm da atribuio, o operador de
referncia estrutura (seleo de campos), denotado por um ponto. A referncia do ope-
rador de estrutura associa da esquerda para a direita; seu primeiro operando uma expres-
so que identifica a estrutura, e seu segundo operando um identificador id que identifica
um campo da estrutura. Como exemplo, a referncia employee.age seleciona o campo
age da varivel estrutura employee. Assim, uma declarao de atribuio como:

employee.age = 45;

vlida porque employee uma struct que contm o campo age do tipo int.
Em linguagens orientadas a objetos, uma classe pode ser vista como uma extenso
de uma estrutura, porque encapsula funes com variveis de instncias (veja o Captulo
13). As prprias variveis de instncia de uma classe so comparveis com os campos
de uma estrutura, ignorando, por enquanto, que algumas linguagens orientadas a objetos
podem limitar sua acessibilidade.

5.4.6 Registros Variantes e Unies


Anos atrs, a memria era escassa. Os programadores tinham de ser muito econmi-
cos com a forma pela qual definiam estruturas de matrizes e registros de modo que uma
quantidade mnima de armazenamento fosse desperdiada. Parte dessa economia poderia
ser realizada por intermdio de registros variantes, nos quais dois ou mais campos dife-
rentes compartilham o mesmo bloco de memria.
Fortran foi a primeira linguagem a suportar o conceito de registros variantes, na forma
de seu comando EQUIVALENCE. Esse comando acabou sendo um recurso muito inseguro,
j que os programadores tinham de manter o processamento das duas variveis separadas
122 Captulo 5 Tipos

por todo o tempo de execuo do programa. Por exemplo, se A e B forem definidas para
compartilhar o mesmo bloco de memria apenas por motivo de economia, todo o cdigo
que acessa A no pode sobrepor em tempo todo o cdigo que acessa B.
Linguagens posteriores, incluindo Pascal e Ada, definiram registros variantes de uma
forma mais restrita, o que levou a um estilo mais disciplinado de programao, embora
ainda suportasse a economia de memria permitida pelo comando EQUIVALENCE.
Apesar de os registros variantes fornecerem certo nvel de clareza nas definies de ti-
pos, conforme ilustrado anteriormente, eles parecem ser menos amplamente usados em apli-
caes modernas. Um motivo pode ser que a memria no claramente escassa como o era
na dcada de 1960, quando Fortran e Cobol eram as linguagens imperativas dominantes.
Em linguagens como C, um tipo unio realiza o objetivo de um registro variante,
j que permite duas ou mais formas de visualizao do mesmo local de armazenamento.
Analise a seguinte declarao de tipo em C/C++:

union myUnion {
int i;
float r;
};
union myUnion u;

Qualquer varivel do tipo myUnion, como u, permite duas formas de acesso aos mesmos
locais de armazenamento. Sob a variante u.i, o local visto como um inteiro, enquanto
sob a variante u.r o local visto como um valor de ponto flutuante. Na programao
de sistemas de software bsico (incluindo escrita de compiladores, partes de sistemas
operacionais, utilitrios como o grep), muitas vezes, tarefas como o acesso em nvel de
bit a nmeros de ponto flutuante so desejveis.
Todavia, o tipo de registro variante ou unio cria uma fraqueza no sistema de tipo
de uma linguagem. Usando o exemplo anterior, o mesmo padro de bits pode ser tratado
como um valor int ou float, o que cria precisamente o problema discutido na Seo 5.1.
Linguagens que tm essa falha nos seus sistemas de tipos incluem C, Pascal, C++ (por
causa da sua compatibilidade com C) e Modula.
Linguagens como Pascal tambm tm uma unio discriminada com rtulos, na qual
um campo rtulo declarado. O rtulo em Ada usado pelo compilador para registrar
o tipo da variante que est ativa no momento, resolvendo, assim, a falha citada anterior-
mente no sistema de tipo. Analise a seguinte verso em Ada do exemplo anterior, no qual
um campo rtulo b foi adicionado ao tipo de registro union:

type union(b : boolean) is


record
case b is
when true =>
i : integer;
when false =>
r : float;
end case;
end record;
5.5 Tipos de Dados Recursivos 123

tagged : union;
begin
tagged := (b => false, r => 3.375);
put(tagged.i);

A sintaxe de Ada requer que tanto a variante quanto o rtulo sejam atribudos em uma
declarao; qualquer tentativa de atribuir a variante ou o rtulo separadamente resulta em
um erro em tempo de compilao. A primeira declarao aps a declarao type configu-
ra o rtulo e atribui variante r um valor consistente com essa definio do rtulo.
Assim, Ada pode verificar cada referncia a uma variante em tempo de execuo
para assegurar que a referncia seja consistente com a configurao atual do rtulo. Ob-
serve que a ltima linha acima viola o sistema de tipo acessando o campo variante i do
registro sem verificar que seu uso consistente com a definio corrente do rtulo. Assim,
a referncia tagged.i gera um erro em tempo de execuo, j que o rtulo indica que o
valor corrente um nmero de ponto flutuante, no um inteiro. Substituir a referncia a i
na declarao put por r elimina o erro de tipo.
Os programadores, hoje, parecem no estar cientes do tipo unio e raramente usam
essa construo em programas. Entretanto, simular um tipo unio usando uma estrutura
comum como base para diversas realizaes concretas uma prtica popular. Analise o
seguinte exemplo em C++ que define uma estrutura Value, que armazena um nico valor
do tipo int, float, char ou bool:

struct Value {
// Value = int intValue | boolean boolValue |
// float floatValue | char charValue
enum Type type;
int intValue; boolean boolValue
float floatValue | char charValue
}

Essa struct imita a classe abstrata Value de Java que aparece na sintaxe abstrata de
Clite (veja a Seo 2.5.3). O enum type pode receber um de cinco valores indicando que
tipo de valor armazenado, ou indefinido, indicando que nenhum valor vlido armaze-
nado.

5 .5 TI P OS D E D ADO S RECU RSIV O S


Na Seo 5.4.2, vimos como os elementos de uma lista encadeada poderiam ser
definidos recursivamente em C, usando a estrutura Node como base. A recurso uma
ferramenta natural para situaes como essa, porque o nmeros de Nodes em uma lista
encadeada varia em tempo de execuo, mas a natureza de cada Node no.
Alm das estruturas, algumas linguagens de programao suportam a definio re-
cursiva de tipos de dados de uma forma mais genrica.9 Para ilustrar, analise as seguintes
definies de tipo em Haskell relacionadas sintaxe abstrata de Clite que foi introduzida
no Captulo 2.

9. Nesta seo, introduzimos tipos de dados recursivos usando Haskell; exploraremos sua utilidade com mais
detalhes no Captulo 14.
124 Captulo 5 Tipos

data Value = IntValue Integer | FloatValue Float |


BoolValue Bool | CharValue Char
deriving (Eq, Ord, Show)
data Expression = Var Variable | Lit Value |
Binary Op Expression Expression |
Unary Op Expression
deriving (Eq, Ord, Show)
type Variable = String
type Op = String
type State = [(Variable, Value)]

A primeira linha nessa definio um exemplo de definio de tipo algbrico em Haskell, no


qual o novo tipo Value definido como um valor Integer, um Float, um Bool ou um Char
(esses so os tipos bsicos em Haskell, assim como na maioria das outras linguagens).10
A segunda linha nessa definio semelhante; ela define recursivamente o tipo de
dado Expression como tendo uma entre quatro formas alternativas. Expression defi-
nida recursivamente, j que qualquer nmero de Expressions Clite pode ser aninhado.
As prximas duas linhas definem os tipos Variable e Op como tipos particulares do
tipo bsico String. A ltima linha define o tipo State como uma lista de pares, cada um
tendo uma Variable e um Value.
Com essas definies, diversas funes que analisam ou interpretam Expressions
abstratas em Clite podem ser escritas como transformaes sobre esse tipo de dado defi-
nido recursivamente. Esse exemplo particular ser mais explorado no Captulo 8, no qual
examinada a interpretao semntica de linguagens de programao, e na Seo 14.3.8,
na qual examinada a implementao de Clite em Haskell.

5 .6 F U N ES CO M O T IP O S
Em linguagens como Fortran, C/C++, Ada e Java, h muitos tipos de entidades que
no so cidads de primeira classe da linguagem. Um exemplo bvio uma estrutura
de controle como um comando if ou while, pelo fato de que uma instncia de tal comando
no pode ser atribuda como um valor a uma varivel e nem passada como um argumento.
Em muitas linguagens, as funes no so cidads de primeira classe.
A necessidade de se passar funes como argumentos surgiu no incio da histria de
Fortran. Um exemplo inclua rotinas de desenho que geravam grficos de funes como
y f(x). Outro exemplo comum inclui resolvedores de raiz para y f(x). Fortran permi-
tia funes como parmetros, mas no permitia nenhum tipo definido pelo programador,
incluindo funes. Assim, os compiladores Fortran no podiam verificar em tempo de
compilao se uma funo apropriada estava sendo passada.
Pascal, por outro lado, permitia tipos funo. Assim, um resolvedor de raiz geral
(como o mtodo de Newton) poderia declarar o tipo de argumento requerido:

function Newton(a, b: real; function f: real): real;

10. A clusula deriving (Eq, Ord, Show) declara que esse novo tipo herda a igualdade, a ordenao e as carac-
tersticas de exibio dos seus tipos componentes, permitindo-nos, assim, usar as funes de igualdade (),
ordenao () e exibio (show) em qualquer um de seus valores.
5.7 Equivalncia de Tipos 125

Os argumentos a e b so valores de x em ambos os lados da raiz. Tambm nos mostrado


que a funo f retorna um nmero real, mas o nmero de argumentos de f no especifi-
cado. Esse recurso foi amplamente omitido da maioria dos compiladores Pascal.
Um uso de uma classe de interface em Java resolve precisamente esse problema
(uma discusso mais completa sobre classes de interface ocorre na Seo 13.2.8). Tal
definio de interface a seguinte:

public interface RootSolvable {


double valueAt(double x);
}

Qualquer classe que implemente essa interface poderia ento ser passada como um
argumento para o nosso descobridor de raiz de Newton, cuja assinatura seria:

public double Newton(double a, double b, RootSolvable f);

Apesar dessa capacidade, as funes ainda no so cidads de primeira classe em Java.


Por exemplo, uma funo em Java no pode construir uma nova funo e retorn-la como
um valor. Em vez disso, ela est limitada a retornar apenas funes que j existem.
A mensagem-chave aqui a de que uma funo deve ser um tipo. A generalidade que
esse recurso traz programao significativa, como veremos no Captulo 14.

5 .7 EQ UIVALN CIA DE T IP O S
s vezes, precisamos saber quando dois tipos se equivalem. Por exemplo, na descri-
o de Pascal (Jensen e Wirth, 1975, p. 149), lemos:

O comando de atribuio serve para substituir o valor corrente de uma varivel por
um novo valor especificado como uma expresso. ... A varivel (ou a funo) e a
expresso devem ser de tipo idntico ...

Entretanto, o Pascal Report falha ao definir o termo tipo idntico. Na verdade, dois com-
piladores antigos ETH Pascal implementavam essa verificao usando diferentes defini-
es. importante definir esse termo de forma clara, para a implementao apropriada
tanto da atribuio quanto da passagem de parmetros para tipos compostos.11
Por exemplo, analise o seguinte exemplo em C:

struct complex {
float re, im;
};
struct polar {
float x, y;
};

11. Felizmente, o padro ANSI/ISO Pascal mais tarde corrigiu esse descuido e definiu a noo do mesmo tipo.
126 Captulo 5 Tipos

struct {
float re, im;
} a, b;
struct complex c, d;
struct polar e;
int f[5], g[10];

A questo : quais das variveis anteriores possuem o mesmo tipo?


Sob equivalncia de nome, dois tipos so o mesmo se tiverem o mesmo nome. No
exemplo anterior, as variveis c e d so, dessa forma, do mesmo tipo, porque comparti-
lham o mesmo nome de tipo struct complex. Entretanto, a e c no so do mesmo tipo,
j que tm diferentes nomes de tipo; na verdade, a uma struct annima. Pelo mesmo
motivo, d e e tambm no so do mesmo tipo.
A outra forma de equivalncia de tipo a equivalncia estrutural na qual dois tipos
so o mesmo se tiverem a mesma estrutura. Para tipos registro, ter a mesma estrutura
inclui o nmero e a ordem dos campos, assim como o nome e o tipo de cada campo. Sob
equivalncia estrutural, todos os a, b, c e d teriam o mesmo tipo. As variveis d e e teriam
tipos diferentes, j que os dois nomes de campos so diferentes (embora ambos tenham
dois campos de ponto flutuante).12
Analise a questo da equivalncia de tipos a partir da perspectiva de um compilador,
no qual os tipos so implementados como referncias a um registro variante rotulado. Se o
tipo tiver um nome, o nome e o tipo (ponteiro) so colocados em uma tabela de smbolos
de modo que o tipo pode ser recuperado pelo nome. De forma semelhante, uma varivel
e um ponteiro para seu tipo (mas no um nome de tipo) so inseridos em uma tabela de
smbolos. Assim, sob a equivalncia de nomes, dois tipos so o mesmo se os ponteiros
referenciarem a mesma entrada na tabela de smbolos. No exemplo anterior, a e b so o
mesmo tipo sob a equivalncia de nomes.
Ada usa a equivalncia de nomes para tipos, o que estende para matrizes e pontei-
ros. Na verdade, Ada probe sintaticamente a maioria das instncias de tipos annimos.
C usa a equivalncia de nomes para structs e unions e a equivalncia estrutural para
outros tipos construdos, incluindo matrizes e ponteiros. Assim, no exemplo anterior
em C, f e g so do mesmo tipo, j que ambas so uma matriz unidimensional de int;
o tamanho no importa.
Da mesma forma que Ada, Java requer que tanto classes quanto interfaces tenham
nomes. Assim, Java usa a equivalncia de nomes para determinar se duas classes so
a mesma. Ao usar um objeto no qual uma interface especfica requerida, Java requer
que a classe declarada do objeto implemente a interface; esse um requisito sinttico
e semntico.

5 .8 S U B TI PO S
Um subtipo pode ser visto como um tipo que possui determinadas restries coloca-
das sobre seus valores ou suas operaes.

12. No mundo real, os tipos nmero complexo e coordenadas polares so, na verdade, distintos nos seus com-
portamentos, o que d crdito a essa concluso.
5.9 Polimorfismo e Genricos 127

Em Ada, subtipos podem ser especificados diretamente para variveis, matrizes e


outras estruturas de dados. Por exemplo, analise as seguintes declaraes em Ada:

subtype um_a_dez is Integer range 1 .. 10;


type Dia is (Segunda, Tera, Quarta, Quinta,
Sexta, Sbado, Domingo);
subtype Fim_de_Semana is Dia range Sbado .. Domingo;
type Salrio is delta 0.01 digits 9
range 0.00 .. 9_999_999.99;
subtype Salrio_Autor is Salrio digits 5
faixa 0.00 .. 999.99;

Aqui, o subtipo um_a_dez restringe o tipo Integer, o subtipo Fim_de_Semana restringe


a faixa de valores do seu supertipo Dia, e o subtipo Salrio_Autor restringe a faixa
de valores do seu supertipo Salrio. Variveis podem ser declaradas para qualquer um
desses tipos ou subtipos, com verificao integral em tempo de compilao imposta pelas
suas respectivas restries.
Em Java, subtipos so realizados por meio da hierarquia de classes, ou seja, um objeto
s da classe S pode ser atribudo a um objeto t da clase T, t s, desde que S e T sejam da
mesma classe ou se S for uma subclasse de T. Executar tal atribuio no altera o tipo do
objeto atribudo a t em tempo de execuo. Entretanto, o compilador trata t como se fosse da
classe T, no sentido de que apenas os mtodos da classe T podem ser chamados. Assim, tal
atribuio um tipo de converso ampliadora porque nenhuma informao perdida.
Analise a classe Java Number e sua subclasse Integer definida em java.lang,
junto ao seu uso no seguinte trecho de programa em Java:

1 Integer i = new Integer (3);


2 ...
3 Number v = i;
4 ...
5 Integer x = (Integer) v;

A atribuio na linha 3 permitida porque um Integer uma subclasse da classe Num-


ber.13 A atribuio oposta na linha 5 requer uma converso para evitar um erro de tipo em
tempo de compilao. A converso verificada em tempo de execuo para determinar se
o objeto armazenado em v da classe Integer ou de uma de suas subclasses. Se Integer
no fosse uma subclasse de Number, a falha na converso seria detectvel em tempo de
compilao e relatada como um erro de tipo.

5 .9 P OL IM O R FISM O E GEN RICO S


De acordo com o dicionrio, o termo polimorfismo vem do grego e significa que tem
muitas formas. Em programao, polimorfismo um pouco diferente da idia de sobrecarga.
Definio: Uma funo ou uma operao polimrfica se puder ser aplicada a
qualquer um de diversos tipos relacionados e obtiver o mesmo resultado.

13. Tal atribuio perigosa em C++ porque informaes podem ser descartadas.
128 Captulo 5 Tipos

Uma vantagem do polimorfismo que ele permite a reutilizao de cdigo. Por


exemplo, a lgica de ordenao de uma matriz no deve necessariamente variar com o
tipo dos elementos de dados que estiverem sendo ordenados. Analise a seguinte rotina de
ordenao simples em Ada:

procedure sort (in out a : list) is


begin
for i in afirst .. alast-1 loop
for j in i+1 .. alast loop
if a(i) > a(j) then
begin t : integer := a(i);
a(i) := a(j);
a(j) := t;
end if;
end loop;
end loop;
end sort;

As nicas operaes que dependem do tipo so o operador de atribuio e a comparao


maior que. A atribuio s dependente do tipo porque necessita saber o tamanho de um
objeto. Mesmo em tipos de dados simples (veja a Seo 5.3), as comparaes so clara-
mente dependentes dos tipos.
Reconhecendo a necessidade de evitar redundncia e suportar polimorfismo, Ada intro-
duziu o conceito de genricos ou modelos. Uma funo ou um procedimento genrico um
modelo que pode ser instanciado em tempo de compilao com operadores e tipos concretos.
Em Ada, uma ou mais dessas rotinas pode ser reunida em um pacote e parametrizada tanto
por tipo de elemento quanto pelo operador de comparao, conforme mostrado na Figura
5.10. Procedimentos genricos como esse so exemplos de polimorfismo paramtrico.
Nesse caso, a ligao do argumento com um tipo deferida do tempo de programa-
o para o de compilao:

package integer_sort is new generic_sort( Integer, >);

Tal adiamento na ligao cria flexibilidade, j que o procedimento de ordenao precisa


ser escrito apenas uma vez, mas pode ser reutilizado para ordenar diferentes tipos de
dados. Com esse recurso, no ocorre nenhuma perda de verificao esttica de tipos; ele
apenas adiciona complexidade implementao da linguagem.
Analise um exemplo ligeiramente mais complicado, a saber, a implementao de uma
pilha na forma de uma lista encadeada de registros. Neste caso, a nica operao depen-
dente de tipo a atribuio, que s depende do tamanho do objeto que ser armazenado na
pilha. Aqui mostramos apenas a operao de empilhamento (push), novamente em Ada:

package stack_pck is
type stack is private;
procedure push (i : integer; in out s : stack);
...
5.9 Polimorfismo e Genricos 129

private
type stack is access node;
type node is record
val : integer;
next : stack;
end record;
end stack_pck;

package body stack_pck is


procedure push (i : integer; in out s : stack) is
begin
node n = new node;
n.val = i;
n.next = s;
s = n;
end push;
...
end stack_pck;

A pilha contm apenas dados do tipo inteiro. Todavia, da mesma forma que em qualquer
conjunto de estruturas de dados, outros alm do operador de atribuio, o cdigo da pilha
no depende do tipo dos dados armazenados na pilha.
Assim como em sort, Ada permite a parametrizao do tipo do elemento em
stack, conforme mostrado na Figura 5.11. O nico parmetro para esse modelo o
tipo dos dados sendo armazenados.
Ada, C++ e Java 1.5 fornecem genricos e modelos para esse propsito.14 O
mecanismo bsico de implementao em C++ pode ser pensado como uma macroex-
panso, como o dispositivo #define do pr-processador C. Primeiro, os parmetros
reais so substitudos textualmente pelos parmetros genricos, e o texto resultante
ento compilado.
Linguagens orientadas a objetos tambm suportam colees genricas (como uma
pilha) por meio de herana, na qual uma classe pode ser uma subclasse de outra. Es-
pecialmente em Java, cada classe uma subclasse da classe Object. Assim, todas as
classes collection em Java armazenam Objects ou uma subclasse de Object. Simpli-
fica mais ainda essa abordagem o fato de que cada varivel de objeto armazena uma
referncia ou um ponteiro para esse, em vez dele prprio. J que todos os ponteiros
so do mesmo tamanho, independentemente da classe de um objeto, a implementao
de uma classe collection no requer o uso de genricos. Uma implementao simples de
uma classe stack :

14. Modelos em Java 1.5 so implementados de forma diferente de genricos de Ada ou C++. Uma com-
parao adequada requer um conhecimento mais aprofundado de programao orientada a objetos, ento a
adiamos para o Captulo 13.
130 Captulo 5 Tipos

public class Stack {


private class Node {
Object val;
Node next;
Node(Object v, Node n) {
val = v; next = n;
}
}
private Node stack = null;

...

public void push(Object v) { stack = new Node(v, stack); }


}

generic
type element is private;
type list is array(natural range <>) of element;
with function >(a, b : element) return boolean;

package sort_pck is
procedure sort (in out a : list);
end sort_pck;

package sort_pck is
procedure sort (in out a : list) is
begin
for i in afirst .. alast - 1 loop
for j in i+1 .. alast loop
if a(i) > a(j) then
declare t : element;
begin
t := a(i);
a(i) := a(j);
a(j) := t;
end;
end if;
end loop;
end loop;
end sort;
end sort_pck;

| Figura 5.10 Ordenao Genrica em Ada


5.9 Polimorfismo e Genricos 131

generic
type element is private;

package stack_pck is
type stack is private;
procedure push (i : element; in out s : stack);
...

private
type stack is access node;
type stack is record
val : element;
next : stack;
end record;
end stack_pck;

package body stack_pck is


procedure push (i : element; in out s : stack) is
begin
node n = new node;
n.val = i;
n.next = s;
s = n;
end push;
...
end stack_pck;

| Figura 5.11 Uma Pilha Genrica em Ada

Observe o uso da classe interna Node, que , na verdade, apenas um registro com um
construtor. Uma discusso mais completa sobre herana apresentada no Captulo 13.
J vimos na Seo 5.4.6 um exemplo do uso de herana em uma subclasse para
anular um mtodo de uma classe me e fornecer assim a funcionalidade apropria-
da para a subclasse. Tais exemplos de polimorfismo puro so comuns em Java; um
exemplo comum criar um mtodo toString( ) em uma classe, anulando assim o
da classe Object.
Outra abordagem, novamente de Java, usa uma interface para criar uma funo
genrica. Embora uma interface possa ter subclasses, podemos por enquanto v-la
apenas como um conjunto de assinaturas de mtodos. Uma classe que implemente uma
interface obrigada a implementar todas as funes definidas pela interface.
132 Captulo 5 Tipos

Analise uma funo de ordenao genrica em Java:

public static void sort (Comparable[ ] a) {


for (int i = 0; i < a.lenght; i++)
for (int j = i+1; j < a.lenght; j++)
if (a[i].compareTo(a[j]) > 0) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
}

A lgica imita aquela do programa em Ada. H duas diferenas importantes. Primeiro, o


tipo do parmetro formal a um Comparable, que a interface:

public interface Comparable {


public abstract int compareTo(Object o);
}

Segundo, o operador maior que substitudo por uma chamada ao mtodo compareTo( ).
Assim, uma matriz de objetos de qualquer classe que implemente o mtodo
compareTo( ) pode ser usada na nossa ordenao genrica. O mtodo compareTo( )
deve retornar um nmero negativo, se o receptor da mensagem for menor que o
argumento, zero, se for igual, e um nmero positivo, se for maior.

5 .1 0 T IP O S D EFIN IDO S P ELO P RO GRAM ADO R


Lembre-se da definio de um tipo apresentada no incio do captulo: um tipo um
conjunto de valores e um conjunto de operaes sobre esses valores. Estruturas (discuti-
das na Seo 5.4.5) nos permitem definir uma representao para um novo tipo. Todavia,
historicamente, elas tinham dois problemas:

1 A representao no ficava escondida.


2 Nenhuma operao sobre o novo tipo poderia ser definida.
Uma tentativa de resolver esse problema foi o desenvolvimento de tipos abstratos
de dados, que permitiram que a representao fosse escondida. Tambm permitido que
o fornecedor do tipo defina operaes sobre o novo tipo. Tipos abstratos de dados so
discutidos na Seo 13.1.
A programao orientada a objetos (POO) fornece um meio poderoso para os pro-
gramadores definirem novos tipos. Devido ao fato de a POO ser um paradigma distinto,
discutiremos seus recursos de definio de tipos extensivamente no Captulo 13.
5.11 Resumo 133

5 . 1 1 RE S UM O
Este captulo apresenta os tipos que so suportados em diversas linguagens de pro-
gramao. Esses tipos incluem nmeros, valores lgicos, ponteiros, strings de caracteres,
matrizes, estruturas e funes. O relacionamento entre um tipo e sua representao de
mquina tambm foi discutido.
Diversas caractersticas incomuns de tipos foram apresentadas, incluindo o fatia-
mento de matrizes, o uso de ponteiros como ndices de matrizes, os conceitos de registros
variantes, a diferena entre alocao esttica e dinmica de matrizes e estruturas. Tipos de
dados recursivos, que so especialmente importantes na programao funcional, tambm
so introduzidos aqui.
Este captulo fornece, assim, fundamentos para os prximos dois captulos, que exa-
minam mais cuidadosamente o relacionamento entre variveis, seus tipos e seu apareci-
mento em expresses e funes que transformam seus valores. Esses relacionamentos so
cruciais para o entendimento dos recursos de verificao de tipos e o comportamento em
tempo de execuo das linguagens de programao.

E XE RC CI O S
5.1 Para a sua linguagem e seu compilador favoritos (outra que no seja Java), liste todos os tipos b-
sicos suportados e seus tamanhos em bytes.

5.2 Para a sua linguagem favorita que suporte um tipo boleano, responda s questes a seguir. Ele
ordenado? Ele pode ser convertido em um inteiro? Argumente com base em princpios o porqu de
isso ser til ou no.

5.3 Java no permite valores no boleanos em testes lgicos, diferentemente de outras linguagens do
como C. Argumente com base nos princpios o porqu de isso ser til ou no.

5.4 Escreva um programa para ler um valor de ponto flutuante de 32 bits, imprimir o valor e sua repre-
sentao em bits. Dica: use um tipo union.

5.5 Qual a representao em ponto flutuante de 32 bits para 0,2? 0,5? 0,3? 1,0?

5.6 Qual a representao em ponto flutuante de 32 bits para 0,1?

5.7 Escreva um programa para ler um valor inteiro de 32 bits, imprimir o valor e sua representao em
bits.

5.8 Qual a representao em inteiro de 32 bits para 0? 1? 1? 2? 2? 3? 3?

5.9 Qual a diferena entre uma mquina big-endian e uma little-endian? Mencione pelo menos uma arquite-
tura computacional de cada tipo. Como essas arquiteturas afetam os valores apresentados na Tabela 5.2?

5.10 Ada fornece dois operadores de resto. Qual a diferena entre eles? Para sua linguagem de mquina
favorita, que operador de resto elas implementam?

5.11 Escolha uma das seguintes linguagens: Pascal, Modula, Ada, C, C++, C#, Java ou outra linguagem apro-
vada pelo professor. Consulte uma fonte oficial. Escreva um relatrio que pesquise a definio da lingua-
gem quanto a converses de tipos consideradas inseguras, tanto em declaraes quanto em expresses.
134 Captulo 5 Tipos

5.12 Na sua linguagem favorita, use inteiros de 32 bits para calcular o fatorial N de 2, 3, .... Para qual
valor de N voc obtm um erro? Como o erro se manifesta?

5.13 Compare enums em Java 1.5 com aquelas em C/C++. Escreva um programa exemplo para cada
linguagem. Qual a diferena lingstica fundamental entre as duas?

5.14 Analise o trecho em C:

char a[20];
char b[20];

O comando de atribuio a = b gera um erro de compilao. Apresente pelo menos duas inter-
pretaes possveis para a atribuio e, para cada uma delas, corrija a declarao ou a atribuio
conforme for necessrio para que satisfaa interpretao.

5.15 Assim como Python, Ada tambm suporta a operao de fatiamento de matrizes.
(a) Compare essa operao com sua contraparte em Python discutida neste captulo.
(b) D trs exemplos realistas do uso de fatiamento de matrizes, em Ada ou Python.

5.16 Perl e Python suportam matrizes dinmicas. Discuta a implementao de matrizes dinmicas na sua
linguagem favorita.

5.17 Perl e Python suportam matrizes hash ou matrizes associativas. O que so elas? D um exemplo
realista de seus usos. Como voc simularia uma matriz associativa na sua linguagem favorita?

5.18 Analise as linguagens ML, Scheme e Python. Escreva um relatrio breve que compare e ilustre
como a noo de equivalncia de tipos usada em cada uma dessas linguagens.

5.19 Defina uma EBNF e uma sintaxe abstrata para adicionar estruturas de registros a Clite. A EBNF para
referenciar um elemento de uma estrutura deve usar a notao de ponto discutida neste captulo. As
sintaxes concreta e abstrata de Declarao, Expresso e Atribuio devem ser modificadas.

5.20 Usando suas definies do exerccio anterior, projete um algoritmo que determine se dois registros
so estruturalmente equivalentes.

5.21 Explore a definio de tipos de dados recursivos em ML. Reescreva o exemplo em Haskell mostra-
do neste captulo usando ML.

5.22 Aps consultar uma fonte oficial, escreva um relatrio breve que discuta o tratamento de equivaln-
cia de tipos em C, Perl ou Python.

5.23 Estenda a EBNF e a sintaxe abstrata de Clite para incluir estruturas ou registros. Implemente um
algoritmo que determine se dois registros so estruturalmente equivalentes.
Sistemas
de Tipos 6
Eu acabei convencido da necessidade de projetar notaes de programao de
modo a maximizar o nmero de erros que no podem ser cometidos ou, caso o sejam,
possam ser confiavelmente detectados em tempo de compilao.
C. A. R. Hoare

VISO GERAL DO CAPTULO

6.1 O SISTEMA DE TIPOS DE CLITE 137


6.2 CONVERSO IMPLCITA DE TIPOS 144
6.3 FORMALIZANDO O SISTEMA DE TIPOS DE CLITE 147
6.4 RESUMO 150
EXERCCIOS 151

A preocupao com a melhoria na confiabilidade de programas e na deteco inicial


de erros forou os projetistas a melhorar a capacidade de verificao de erros de lingua-
gens. Um bom sistema de tipos permite que os tipos de valores que ocorrem em cada
expresso de um programa sejam determinados antes que esta seja executada (em tempo
de compilao ou dinamicamente, durante a interpretao do programa), reduzindo assim
enormemente a chance da ocorrncia de erros relacionados a tipos. Este captulo explora
os elementos de sistemas de tipos e ilustra como um tipo pode ser projetado para a pe-
quena linguagem Clite.
Definio: A deteco de erros de tipos, em tempo de compilao ou de execu-
o, denominada verificao de tipos.

135
136 Captulo 6 Sistemas de Tipos

O sistema de tipos de uma linguagem de programao fornece um meio para se definir


novos tipos e determinar quando um tipo usado apropriadamente. Um sistema de tipos
pode ser definido formal ou informalmente. Neste captulo, desenvolvemos um sistema de
tipos informal para Clite e, a seguir, mostramos como um verificador esttico de tipos pode
ser implementado com o uso da sintaxe abstrata de Clite introduzida no Captulo 2.
Tambm discutimos um transformador de rvore de sintaxe abstrata para Clite, que
converte operadores sobrecarregados como  em operadores especficos de tipos. Isso inse-
re converso de tipos explcita onde for necessrio para assegurar que ambos os operandos
estejam em conformidade de tipos com relao queles requeridos pelo operador.
Lembre-se do que falamos no Captulo 2, que Clite possui quatro tipos bsicos: int,
float, bool e char. Possui referncias e declaraes de matrizes, mas no tem constru-
tores de tipos, struct, union e tipo ponteiro. Com essa simplificao, podemos definir
um sistema de tipos rigoroso para Clite que mostra como sua sintaxe abstrata permite uma
implementao direta do mesmo.
Clite uma linguagem simples. Um programa de Clite possui uma nica funo
chamada main, cujo corpo possui um nico escopo; no h escopos aninhados e nem
declaraes globais. Os limites das variveis em Clite so determinados estaticamente,
incluindo referncias e declaraes de matrizes,1 de modo que todos os tipos de erros po-
dem ser detectados em tempo de compilao. Alm disso, todas as converses implcitas
de tipos tambm so detectadas em tempo de compilao.
A fase semntica do compilador (veja a Figura 2.8) tambm deve verificar que:

1 Cada Identificador de varivel declarado deve ser nico, e no uma palavra-chave.


2 Cada varivel referenciada dentro de qualquer expresso do programa deve ter
sido declarada.

Formalmente, nenhuma dessas verificaes faz parte do sistema de tipos. Entretanto, as


incluiremos aqui como se fizessem.
A propsito, poderamos perguntar por que essas duas restries adicionadas no
esto de alguma forma definidas na sintaxe EBNF de Clite. Infelizmente, a natureza limi-
tada da EBNF impede que tais restries sensveis ao contexto sejam expressas, ou seja,
EBNF no suficientemente poderosa para expressar idias sintticas que dependem do
contexto no qual uma categoria sinttica (como Identificador) ocorre.2 Em vez disso,
devemos examinar a sintaxe abstrata de um programa e perguntar que tipo de algoritmo
pode ser projetado para assegurar que estas restries no sejam violadas.
Uma forma de implementar o sistema de tipos de uma linguagem escrever um
conjunto de funes boleanas que definam matematicamente regras como as anteriores.
Essa abordagem caracteriza a modelagem formal de um sistema de tipos, e tal modelo
para Clite apresentado na Seo 6.3.
Outra forma de implementar o sistema de tipos de uma linguagem declarar as re-
gras em uma linguagem altamente estilizada, junto a um algoritmo que implemente essas
declaraes. Para ilustrar essa abordagem, analise o programa simples em Clite na Figura
6.1 que calcula o fatorial de 8.
Neste programa, as variveis chamadas n, i e result declaradas no programa (linha 3)
devem ser nicas. A varivel n referenciada nas linhas 4 e 7 deve ser declarada; de forma
semelhante, a varivel i nas linhas 5, 7, 8 e 9 e a varivel result nas linhas 6 e 9 tambm

1. Veja no Captulo 11 outra discusso sobre alocao dinmica de matrizes.


2. Esta limitao de gramticas BNF geralmente exposta em um estudo de linguagens formais (do tipo
Chomsky) e seria adequada em um curso sobre teoria da computao ou compiladores.
6.1 O Sistema de Tipos de Clite 137

1 // calcular o fatorial do inteiro n


2 void main ( ) {
3 int n, i, result;
4 n = 8;
5 i = 1;
6 result = 1;
7 while (i < n) {
8 i = i + 1;
9 result = result * i;
10 }
11 }

| Figura 6.1 Um Pequeno Programa em Clite

devem ser declaradas. Assim, informalmente, podemos ver que o programa em Clite da
Figura 6.1 vlido quanto a essas duas regras acrescentadas.3

6 .1 O S IS T EM A DE T IP O S DE CLIT E
Como modelamos e implementamos verificaes de validade para programas em
Clite? Tal modelo requer um conjunto de funes de validao que represente regras
como todas as variveis declaradas possuem nomes nicos. Cada funo de validao
V pode retornar um valor boleano indicando se essa parte da rvore de sintaxe abstrata
do programa vlida quanto ao tipo e chamada sempre que sua respectiva regra precise
ser verificada, ou seja:

V : ClassedeSintaxeAbstrata B

Um sistema de verificao de tipos tambm se baseia em um mapa de tipos, que


uma funo que liga cada nome de varivel declarada com seu tipo. O mapa de tipos pode
ser extrado das Declaraes abstratas para o programa em Clite da seguinte maneira:

public static TypeMap typing (Declarations d) {


TypeMap map = new TypeMap();
for (Declaration di : d)
map.put (di.v, di.t);
return map;
}

Lembre-se de que a sintaxe abstrata para uma Declarao Clite :

Declarao  DeclVarivel | DeclMatriz


DeclVarivel  Varivel v ; Tipo t
DeclMatriz  Varivel v ; Tipo t; Inteiro tamanho

3. Novamente observamos que as restries adicionadas em identificadores no fazem, estritamente, parte de


um sistema de tipos.
138 Captulo 6 Sistemas de Tipos

Observao
Segurana em C/C++ /Java
Clite mais seguro do que C/C++, j que esta ltima permite converses implcitas
que reduzem o espao de armazenamento de uma varivel em um comando de atribuio
(Kernighan e Ritchie, 1988, p. 208):

O tipo de um comando de atribuio o tipo de seu operando esquerda... Na atri-


buio simples com , o valor da expresso substitui o valor do objeto referenciado
pelo valor-l... Ambos os operandos tm tipo aritmtico, em cujo caso o operando
direita convertido para o tipo do operando esquerda pela atribuio.

No h restries sobre essa atribuio, mesmo se houver converso limitante. Essa fra-
queza no sistema de tipos permite que informaes sejam perdidas sem aviso.
A converso de atribuio em Java para operandos numricos ou char segue um
conjunto de regras um pouco mais restritivo.
Se a expresso direita for do tipo byte, short, char, int, long ou float, seu valor
pode ser ampliado para um valor double equivalente.
Se for byte, short, char, int ou long, pode ser ampliado para um valor float.
Se for byte, short, char ou int, pode ser ampliado para um valor long.
Se for byte, short ou char, pode ser ampliado para um valor int.
Se for byte ou short, pode ser ampliado para um valor char.
Se for byte, pode ser ampliado para um valor short.
Uma converso com reduo pode ser usada em uma atribuio sob condies muito
limitadas; por exemplo, se a expresso direita for uma expresso constante e o valor da
expresso for representvel no tipo da varivel esquerda.
Tais problemas necessitaram do desenvolvimento da aplicao lint que examina
potenciais problemas, problemas de portabilidade e construes dbias em programas em C
(Kernighan e Pike, 1984).

Assim, dada uma lista d de Declaraes d[i], o mtodo typing retorna um TypeMap
Java cujas chaves so as variveis declaradas d[i].v e cujos valores so seus respectivos
tipos d[i].t.4, 5 Por exemplo, o programa da Figura 6.1 possui o seguinte TypeMap:

{ <n, int>, <i, int>, <result, int> }

Ainda nesta seo, definiremos cada uma das regras restantes no sistema de tipos
de Clite (mais as duas restries a identificadores acrescentadas), junto a uma discusso
sobre sua implementao.
Regra de Tipo 6.1 Todas as variveis referenciadas devem ser declaradas.

4. A lista d pode ser implementada como uma ArrayList Java, por exemplo, e um TypeMap pode ser imple-
mentado como uma extenso de um HashMap Java.
5. O size de uma DeclMatriz ignorado aqui, j que pode ser um valor determinado dinamicamente.
6.1 O Sistema de Tipos de Clite 139

A imposio dessa regra compartilhada entre as funes de verificao de tipo para


diferentes tipos de declaraes e expresses Clite, como veremos a seguir:
Regra de Tipo 6.2 Todas as variveis declaradas devem ter nomes nicos.
Esta regra pode ser imposta se verificarmos se cada par distinto de variveis em uma
lista de declaraes tem identificadores diferentes entre si. Em especial, ela aborda ape-
nas os requisitos de unicidade mtua para variveis. De modo geral, essa funo tambm
deve especificar que o tipo de cada varivel pertence ao conjunto de tipos disponveis na
linguagem (como {int, float, char bool} para Clite). Omitimos este requisito aqui
porque ele j foi imposto pela sintaxe de Clite.
Supondo que a categoria de sintaxe abstrata Declaraes seja implementada como
uma ArrayList em Java, o mtodo V para Declaraes pode ser especificado da se-
guinte maneira:6

public static void V (Declarations d) {


for (int i = 0; i<d.size() - 1; i++)
for (int j=i+1; j<d.size(); j++) {
Declaration di = d.get(i);
Declaration dj = d.get(j);
check( ! (di.v.equals(dj.v)),
duplicate declaration: + dj.v);
}
}

Por exemplo, as declaraes do programa na Figura 6.1 so nomeadas como n, i e re-


sult, que so nomes nicos entre si. Assim, o mtodo V para este conjunto especfico de
declaraes deve retornar normalmente (a condio check verdadeira para todos os
pares). A implementao da funo V em Java retorna void, em vez de parar a execuo
na primeira instncia de um par duplicado. Essa prtica se baseia na avaliao de curto
circuito do operador e, na qual:

a e b se no a ento falso seno b

Isso significa que, assim que uma verificao de validade falhar, o programa que esti-
ver sendo verificado continua a ser invlido apesar da verdade ou falsidade de verificaes
posteriores de validade.
Finalmente, observamos que esse mtodo no espelha exatamente a definio dada
para a unicidade entre nomes de variveis declaradas, ou seja, a regra sugere que todos
os pares distintos devem ser verificados (isto , d[i] versus d[j] e d[j] versus d[i]).
Omitimos essa redundncia computacional executando uma diagonalizao sobre o c-
digo do lao.
Para completar o sistema de tipos de Clite, um conjunto de funes V definido para
cada classe sinttica abstrata, incluindo a classe mais geral Programa. Um programa completo,

6. Na nossa implementao da funo V, o resultado boleano substitudo por uma chamada a uma funo
check, que gera uma exceo. Assim, se o verificador de tipos terminar de forma normal, o compilador pode
estar seguro de que a rvore de sintaxe abstrata vlida no que diz respeito s regras de tipos.
140 Captulo 6 Sistemas de Tipos

do ponto de vista da sintaxe abstrata, possui duas partes, uma srie de Declaraes e um Bloco:

class Program {
// Programa = Declaraes partedec ; Bloco corpo
Declaraes partedec;
Bloco corpo;
}

Regra de Tipo 6.3 Um Programa vlido em termos de tipos se suas Declara-


es partedec forem vlidas e seu Bloco corpo for vlido considerando-se o
mapa de tipos para essas Declaraes especficas.
J que essas duas partes so implementadas como classes Java, esta especificao fcil
de ser implementada.

public static void V (Program p) {


V (p.decpart);
V (p.body, typing (p.decpart));
}

Para o programa de exemplo da Figura 6.1, isso significa que Declaraes (n, i e result)
so vlidas e que o bloco que comea com a Atribuio n = 8; vlido. Observe que a
validade de tipos do corpo do programa pode ser verificada apenas em relao ao mapa
de tipos especfico que representa sua partedec.
Para garantir a validade de tipos de um Bloco devemos, claro, assegurar a validade
de tipos de cada um de seus comandos individuais. As regras que definem a validade de
um Comando so expressas na Regra de Tipo 6.4.
Regra de Tipo 6.4 Um Comando vlido em relao ao mapa de tipos do pro-
grama se satisfizer s seguintes restries:
1 Um Salto sempre vlido.
2 Uma Atribuio vlida, se todas as seguintes forem verdadeiras:
(a) Sua Varivel alvo est declarada.
(b) Sua Expresso origem vlida.
(c) Se o tipo da sua Varivel alvo for float, ento o tipo de sua Expresso
origem deve ser float ou int.7
(d) Caso contrrio, se o tipo de sua Varivel alvo for int, ento o tipo da sua
Expresso origem deve ser int ou char.
(e) Caso contrrio, o tipo da sua Varivel alvo deve ser o mesmo da sua
Expresso origem.
3 Um comando Condicional vlido se sua Expresso teste for vlida e tiver
tipo bool e tanto seus Blocos de Comandos ramificaoEnto quanto os de
ramificaoSeno forem vlidos.
4 Um Lao vlido se sua Expresso teste for vlida e tiver tipo bool e seu
Comando corpo for vlido.
5 Um Bloco vlido se todos os seus Comandos forem vlidos.

7. Estamos excluindo RefMatriz da definio, j que so deixadas como um exerccio.


6.1 O Sistema de Tipos de Clite 141

Referindo-se novamente ao nosso programa exemplo da Figura 6.1, observe que seu
Bloco abstrato contm quatro Comandos: trs Atribuies e um Lao. Assim, de acordo com
a Regra de Tipo 6.4, a validade desse programa depende da validade de cada um desses co-
mandos individuais e, recursivamente, da validade do outro Bloco que seja o corpo do prprio
Lao. Alm disso, para cada Atribuio como n = 8;, a Regra de Tipo 6.4 requer que:
A Varivel alvo n seja declarada;
A Expresso origem 8 seja vlida; e
O tipo de 8 seja int ou char.
Para completar essa atividade de verificao de tipos, devemos ento ter uma definio
clara do que queremos dizer por uma Expresso ser vlida.
Implementar as regras anteriores com base na sintaxe abstrata de Clite direto, con-
forme sugerido no esboo da Figura 6.2. L, o primeiro comando if espelha a Regra de
Tipo 6.4.1 sobre a validade de comandos Salto e a lgica do comando Atribuio segue
a Regra de Tipo 6.4.2.
O leitor observador notar o uso de diversas tcnicas de programao defensiva na
Figura 6.2. O primeiro comando verifica se Statement no o objeto Java null. Outra
verificao no final do mtodo garante que nenhum caso foi deixado de lado e, portanto,
o controle no deve chegar aqui.8
A seguir, especificamos a regra de validade para uma Expresso:
Regra de Tipo 6.5 A validade de uma Expresso definida com o uso do mapa
de tipos do programa e cada uma das subclasses da Expresso:
1 Um Valor vlido.
2 Uma Varivel vlida se seu id aparecer no mapa de tipos.9
3 Um Binrio vlido se todas as afirmaes seguintes forem verdadeiras:
(a) Suas Expresses termo1 e termo2 so vlidas.
(b) Se seu OpBinrio op for aritmtico (+, , *, /), ento ambas as suas Ex-
presses devem ser int ou float.
(c) Se op for relacional (==, !=, <, <=, >, >=), ento ambas as suas Expres-
ses devem ter o mesmo tipo.
(d) Se op for boleano (&&, ||), ento ambas as suas Expresses devem ser bool.
4 Um Unrio vlido se todas as afirmaes seguintes forem verdadeiras:
(a) Sua Expresso termo for vlida.
(b) Se o seu OpUnrio op for !, ento termo deve ser bool.
(c) Se op for , ento termo deve ser int ou float.
(d) Se op for a converso de tipo float( ) ou char( ), ento termo deve
ser int.
(e) Se op for a converso de tipo int( ), ento termo deve ser float ou char.
A implementao dessa funo V esboada na Figura 6.3. O trecho dado espelha as
regras 13 anteriores, com a implementao de expresses Unrio omitidas.
Finalmente, o tipo do resultado de uma Expresso deve ser definido para todas as
variaes.

8. Um programador orientado a objeto deve observar que os comandos if poderiam ter sido evitados se tivs-
semos usado uma abordagem mais orientada a objeto. Entretanto, neste ponto, estamos tentando implementar
precisamente o modelo formal da Seo 6.3, que, sendo matemtico, de natureza funcional.
9. Novamente, estamos excluindo as RefMatrizes desta definio; elas so deixadas como um exerccio.
142 Captulo 6 Sistemas de Tipos

public static void V (Statement s, TypeMap tm) {


if ( s == null )
throw new IllegalArgumentException(
Erro AST: declarao nula);
if (s instanceof Skip) return;
if (s instanceof Assignment) {
Assignment a = (Assignment)s;
check( tm.containsKey(a.target)
, alvo no definido na atribuio: +
a.target);
V(a.source, tm);
Type ttype = (Type)tm.get(a.target);
Type srctype = typeOf(a.source, tm);
if (ttype != srctype) {
if (ttype == Type.FLOAT)
check( srctype == Type.INT
, modo de atribuio misturado +
a.target);
else if (ttype == Type.INT)
check( srctype == Type.CHAR
, modo de atribuio misturado +
a.target);
else
check( false
, modo de atribuio misturado +
a.target);
}
return;
}
...

throw new IllegalArgumentException(


nunca deve chegar aqui);
}

| Figura 6.2 Funo de Validao V para Comando

Regra de Tipo 6.6 O tipo de resultado de toda Expresso determinado da


seguinte maneira:
1 Se a Expresso for um Valor, ento o tipo do seu resultado o tipo desse
Valor.
2 Se a Expresso for uma Varivel, ento o tipo do seu resultado o tipo dessa
Varivel.
3 Se a Expresso for um Binrio, ento:
(a) Se o Operador for aritmtico (, , *, /) ento o tipo do seu resultado o
tipo dos seus operandos. Por exemplo, a Expresso x+1 requer que x seja
int (j que 1 int), de modo que o tipo do seu resultado int.
6.1 O Sistema de Tipos de Clite 143

public static void V (Expression e, TypeMap tm) {


if (e instanceof Value)
return;
if (e instanceof Variable) {
Variable v = (Variable)e;
check( tm.containsKey(v)
, varivel no declarada: + v);
return;
}
if (e instanceof Binary) {
Binary b = (Binary) e;
Type typ1 = typeOf(b.term1, tm);
Type typ2 = typeOf(b.term2, tm);
V (b.term1, tm);
V (b.term2, tm);
if (b.op.ArithmeticOp( ))
check( typ1 == typ2 &&
(typ1 == Type.INT || typ1 == Type.FLOAT)
, erro de tipo para + b.op);
else if (b.op.RelationalOp( ))
check( typ1 == typ2 , erro de tipo para + b.op);
else if (b.op.BooleanOp( ))
check( typ1 == Type.BOOL && typ2 == Type.BOOL,
b.op + : operando no boleano);
else
throw new IllegalArgumentException(
nunca deve chegar aqui);
return;
}
...
throw new IllegalArgumentException(nunca deve chegar aqui);
}

| Figura 6.3 Funo de Validao V para Expresso

(b) Se o Operador for relacional (<, <=, >, >=, ==, !=) ou boleano
(&&, ||), ento o tipo do seu resultado bool.
4 Se a Expresso for um Unrio, ento:
(a) Se o operador for !, ento o tipo do seu resultado bool.
(b) Se o operador for , ento o tipo do seu resultado o tipo do seu operando.
(c) Se o operador for uma converso de tipo, ento o tipo do seu resultado
dado pela converso.
Como exemplo, os leitores devem ver que a Expresso result*i que a quarta
Atribuio do programa de exemplo em Clite da Figura 6.1 vlida, ou seja, pela Regra
6.5, result*i um Binrio, seu Operador aritmtico e ambos os seus operandos so
do tipo int (usando a Regra 6.6).
144 Captulo 6 Sistemas de Tipos

public static Type typeOf (Expression e, TypeMap tm) {


if (e instanceof Value) return ((Value)e).type;
if (e instanceof Variable) {
Variable v = (Variable)e;
check (tm.containsKey(v), varivel indefinida: + v);
return (Type) tm.get(v);
}
if (e instanceof Binary) {
Binary b = (Binary)e;
if (b.op.ArithmeticOp( ))
if (typeOf(b.term1,tm) == Type.FLOAT)
return (Type.FLOAT);
else return (Type.INT);
if (b.op.RelationalOp( ) || b.op.BoleanOp( ))
return (Type.BOOL);
}
if (e instanceof Unary) {
Unary u = (Unary)e;
if (u.op.NotOp( )) return (Type.BOOL);
else if (u.op.NegateOp( )) return typeOf(u.term,tm);
else if (u.op.intOp( )) return (Type.INT);
else if (u.op.floatOp( )) return (Type.FLOAT);
else if (u.op.charOp( )) return (Type.CHAR);
}
throw new IllegalArgumentException(
nunca deve chegar aqui);
}

| Figura 6.4 A Funo typeof

Uma implementao parcial da funo typeof, que implementa esta regra, dada
na Figura 6.4; novamente o cdigo para o tipo de uma expresso Unria est omitido. O
cdigo tenta espelhar fielmente a Regra de Tipo 6.6.

6 .2 CON VER S O IM P LCITA DE T IP O S


Conforme sugerido na Seo 6.1, Clite suporta algumas converses implcitas de
tipos em uma Atribuio, sendo que todas so converses ampliadoras. Todas as outras
converses requerem que o programa inclua uma funo de converso de tipo explcita,
que uma expresso Unria, j que ambos os operandos na implementao de uma ex-
presso Binria devem ter o mesmo tipo.
Na semntica de uma Atribuio, o valor da Expresso origem substitui o da Varivel
alvo. Se for necessrio, o tipo da origem deve ser convertido explicitamente, novamente
usando as converses ampliadoras especificadas na Regra 6.4.2 e governadas pelo tipo do
alvo. De outra maneira, os tipos do alvo e da origem devem ser o mesmo.
6.2 Converso Implcita de Tipos 145

Como exemplo de converso implcita de tipo, analise a seguinte rvore de sintaxe


abstrata:

i int()

Nesta rvore, estamos usando tipagem implcita com a primeira letra de cada varivel
indicando seu tipo; por exemplo, a varivel f do tipo float. A rvore considerada de
tipo vlido porque anotando-a com as seguintes informaes de tipo:


float


f
float int

i int()

int int

char

fora uma converso ampliadora implcita na declarao de atribuio, o que permitido


pela Regra de Tipo 6.4.2. Finalmente, transformamos a rvore de modo que todos os
operadores sejam tipados explicitamente e as converses implcitas se tornem explcitas:
146 Captulo 6 Sistemas de Tipos

f i2 f

int

i c2i

Aqui, c2i denota a converso de caractere para inteiro, int denota a subtrao de inteiro
e i2f, a converso de inteiro para ponto flutuante. Em um sistema real, o operador de
atribuio = seria substitudo por uma atribuio especfica de tipo, como float=.
Assim, a rvore de sintaxe abstrata transformada substitui todos os operadores so-
brecarregados por operadores especficos de tipo e insere converses de tipo explcitas no
lugar das implcitas onde quer que elas sejam necessrias.
As transformaes so implementadas como um conjunto de funes T (para trans-
formao) que convertem uma rvore de sintaxe abstrata e um mapa de tipos em outra
rvore de sintaxe abstrata com todas as converses de tipo implcitas removidas:

T : ClassedeSintaxeAbstrata  MapadeTipos ClassedeSintaxeAbstrata

A funo de transformao T para Programa constri um novo Programa que con-


siste das Declaraes originais10 e um corpo transformado (um Bloco):

public static Program T (Program p, Typemap tm) {


Block body = (Block)T(p.body, tm);
return new Program(p.decpart, body);
}

As transformaes-chave so as transformaes para os comandos Atribuio e Expresso.


A primeira deve inserir converses explcitas de int para float e de char para int, conforme

10. Nenhuma dessas transformaes necessria para a categoria Declaraes, j que ela no contm operadores.
6.3 Formalizando o Sistema de Tipos de Clite 147

if (s instanceof Assignment) {
Assignment a = (Assignment)s;
Variable target = a.target;
Expression src = T (a.source, tm);
Type ttype = (Type)tm.get(a.target);
Type srctype = StaticTypeCheck.typeOf(a.source, tm);
if (ttype == Type.FLOAT) {
if (srctype == Type.INT) {
src = new Unary(new Operator(Operator.I2F), src);
srctype = Type.FLOAT;
}
}
else if (ttype == Type.INT) {
if (srctype == Type.CHAR) {
src = new Unary(new Operator(Operator,C2I), src);
sctype = Type.INT;
}
}
StaticTypeCheck.check( ttype == srctype,
erro de tipo na atribuio a + target);
return new Assignment(target, src);
}

| Figura 6.5 Transformao de Atribuio

mostrado nas Figuras 6.5 e 6.6. Caso contrrio, as regras de verificao de tipo asseguram
que os tipos sejam os mesmos e, assim, nenhuma transformao adicional necessria.
Observe tambm na Figura 6.6 que no transformamos o operador de atribuio por tipo
porque no necessrio para o modelo semntico de tempo de execuo. Para a maioria
das mquinas, o operador de atribuio precisaria ser distinguido por tipo (pelo menos
pelo tamanho da memria necessria para o tipo), conforme discutido na Seo 5.3.
A maior parte do trabalho dos operadores de transformao por tipo realizada pela
funo T para uma Expresso, o que esboado na Figura 6.6. Para operadores binrios, um
mapa especfico de tipos usado para traduzir o operador. Por exemplo, em i int(c),
o tipo do operando int, de modo que o operador traduzido para int usando o mapa
de operadores inteiros intMap( ).
Para expresses Unria (no mostradas), o operador o principal regulador na determi-
nao da transformao do operador. Entretanto, para o unrio e a converso de tipo int( ),
so usados mapas de tipos dependentes de tipo, ou seja, o mapa de tipo inteiro intMap( )
usado para transformar uma converso int( ) sobre um caractere em um operador c2i.
A rvore de sintaxe abstrata transformada serve como base para a especificao e a
implementao de uma funo semntica de tempo de execuo no Captulo 8. Conforme
descrito aqui, essa funo assume que a rvore de sintaxe abstrata traduzida segura no
que diz respeito a tipos.

6 .3 FORMALIZANDO O SISTEMA DE TIPOS DE CLITE


A seguir esto as regras formais para verificao esttica de tipos em Clite; elas so es-
critas naturalmente como predicados. A discusso e as implementaes dessas regras como

148 Captulo 6 Sistemas de Tipos

public static Expression T (Expression e, TypeMap tm) {


if (e instanceof Value)
return e;
if (e instanceof Variable)
return e;
if (e instanceof Binary) {
Binary b = (Binary)e;
Type typ1 = StaticTypeCheck.tyeof(b.term1, tm);
Type typ2 = StaticTypeCheck.tyeof(b.term2, tm);
Expression t1 = T (b.term1, tm);
Expression t2 = T (b.term2, tm);
if (typ1 == Type.INT)
return new Binary(b.op.intMap(b.op.val), t1,t2);
else if (typ1 == Type.FLOAT)
return new Binary(b.op.floatMap(b.op.val), t1,t2);
else if (typ1 == Type.CHAR)
return new Binary(b.op.charMap(b.op.val), t1,t2);
else if (typ1 == Type.BOOL)
return new Binary(b.op.boolMap(b.op.val), t1,t2);
throw new IllegalArgumentException(
nunca deve chegar aqui);
}
...
throw new IllegalArgumentException(
nunca deve chegar aqui);
}

| Figura 6.6 Transformao de Expresso

uma coleo de mtodos Java j foram mostradas na Seo 6.1. Os leitores que no estive-
rem interessados na matemtica relacionada especificao do sistema de tipos podem
pular esta seo.11
O mapa de tipos de um Programa um conjunto de pares ordenados, com cada par
tendo uma Varivel e seu Tipo t declarado.

mt  {1, t1, 2, t2,..., n, tn}

A funo tipagem cria um MapadeTipo a partir de uma srie de Declaraes e cor-


responde Regra de Tipo 6.1. J que identificadores individuais so nicos, a unio do
conjunto assegura que todas as variveis e seus tipos declarados estejam representados.

tipagem: Declaraes MapadeTipo

tipagem(Declaraes d)   d ., d .t
i  {1,..., n}
i i

11. A validao de tipos de DeclMatrizes e RefMatrizes ignorada aqui, j que ignorada na Seo 6.2.
6.3 Formalizando o Sistema de Tipos de Clite 149

Uma srie de Declaraes vlida se seus nomes de variveis forem mutuamente


exclusivos (Regra de Tipo 6.2). Observe que a definio funcional verifica todos os pares
de identificadores.

V : Declaraes B
V(Declaraes d)  i, j  {1,..., n} (i  j di .  dj .)

Um programa vlido se suas Declaraes forem vlidas (V(p.partedec)) e seu


Bloco for vlido (V(p.corpo, tipagem (p.partedec)) para o MapadeTipo definido
para aquelas Declaraes (Regra de Tipo 6.3).

V : Programa B
V(Programa p)  V(p.partedec) V(p.corpo, tipagem(p.partedec))

A verificao de validade para um Comando depende do tipo deste comando; por


exemplo, um comando Salte sempre vlido, enquanto um comando Atribuio requer a
verificao tanto da varivel alvo quanto da expresso fonte. A funo a seguir equiva-
lente Regra de Tipo 6.4.

V : Assero  MapadeTipos B
V(Assero s, MapadeTipos mt)
 verdadeiro se s for um Salto
 s.alvo  mt V(s.fonte, mt)
((mt(s.alvo)  float tipoDe(s.fonte, mt)  {float, int})
(mt(s.alvo)  int tipoDe(s.fonte, mt)  {int, char})
(mt(s.alvo)  tipoDe(s.fonte, mt)
se s for uma Atribuio
 V(s.teste, mt) tipoDe(s.teste, mt)  boleano
V(s.ramificaoEnto, mt) V(s.ramificaoSeno, mt)
se s for uma Condicional
 V(s.teste, mt) tipoDe(s.teste, mt)  boleano
V(s.corpo, mt)
se s for um Lao
 V(b1, mt) V(b2, mt) ... (bn, mt)
se s for um Bloco  b1b2...bn
n0

Uma Expresso vlida se for um Valor, uma Varivel no mapa de tipos do programa
ou uma expresso Binria ou Unria cujos operandos satisfaam a restries adicionais.
A funo abaixo equivalente Regra de Tipo 6.5.

V : Expresso  MapadeTipo B
V(Expresso e, MapadeTipo mt)
 verdadeiro se e for um Valor
 e  mt se e for uma Varivel
 V (e.termo1, mt) V(e.termo2, mt)
tipoDe(e.termo1, mt)  {float, int} tipoDe(e.termo2, mt)  {float, int}
150 Captulo 6 Sistemas de Tipos

tipoDe(e.termo1, mt)  tipoDe(e.termo2, mt)



se e for um Binrio e.op  {OpAritmtico}
 V(e.termo1, mt) V(e.termo2, mt)
tipoDe(e.termo1, mt)  tipoDe(e.termo2, mt)
se e for um Binrio e.op  {OpRelacional}
 V(e.termo1, mt) V(e.termo2, mt)
tipoDe(e.termo1, mt)  boleano tipoDe(e.termo2, mt)  boleano
se e for um Binrio e.op  {OpBoleano}
 V(e.termo, mt)
((e.op  ! tipoDe(e.termo, mt)  boleano)
(e.op   tipoDe(e.termo, mt)   {float, int})
(e.op  float() tipoDe(e.termo, mt)   {int})
(e.op  char() tipoDe(e.termo, mt)   {int})
(e.op  int() tipoDe(e.termo, mt)   {float, char}))
se e for um Unrio
A funo auxiliar tipoDe define o tipo de uma Expresso, conforme determinado
pela Regra 6.6. Aqui, a expresso e.tipo denota o tipo de um valor ou de uma varivel e
no mapa de tipos.
tipoDe: Expresso  MapadeTipos Tipo
tipoDe(expresso e, MapadeTipos mt)
 e.tipo se e for um Valor
 e.tipo se e for uma Varivel e  mt
 tipoDe(e.termo1, mt) se e for um Binrio e.op  {OpAritmtico}
 boleano se e for um Binrio e.op  {OpBoleano, OpRelacional
 boleano se e for um Unrio e.op  !
 tipoDe(e.termo, mt) se e for um Unrio e.op  
 float se e for um Unrio e.op  float()
 char se e for um Unrio e.op  char()
 int se e for um Unrio e.op  int()
Isso completa a definio formal do sistema de tipos de Clite. Tanto as regras formais em
portugus quanto a implementao em Java mostrada na Seo 6.1 se baseiam nessa defini-
o. Ela ilustra como o sistema de tipos de uma linguagem pode ser definido de forma rigorosa
com o uso de ferramentas matemticas convencionais. Tal rigor assegura que o entendimento
informal e a implementao do sistema de tipos da linguagem sejam vlidas e consistentes.

6 . 4 R ES UM O
Com um sistema forte de tipos, muitos erros em tempo de execuo podem ser evita-
dos em tempo de compilao, ou pelo menos mais claramente compreendidos em tempo
de execuo. A pequena linguagem Clite fornece um bom meio para o desenvolvimento e
a implementao de um sistema de tipos completo para uma linguagem. O tratamento for-
mal de sistemas de tipos, usando predicados e funes matemticas, tambm ilustrado.
6.4 Resumo 151

E XE RC CI O S
6.1 Expanda a Regra de Tipo 6.2 para Declaraes de modo que defina o requisito de que o tipo de cada
varivel pertena a um pequeno conjunto de tipos disponveis {float, int, bool, char}. Use o mesmo
estilo de regras e sintaxe abstrata para Declaraes usado neste captulo.
6.2 Expanda o mtodo Java que implementa a funo V para Declaraes de modo que ele implemente
o requisito adicional declarado no Exerccio 6.1.
6.3 Argumente que o mtodo Java que implemente a funo V para Declaraes est correto, no senti-
do de que ele cobre todos os casos que a prpria Regra de Tipo cobre.
6.4 Para cada um dos seguintes comandos usando as regras de tipos implcitas da Seo 6.1, primeiro
desenhe a rvore sinttica abstrata no tipada e a seguir adicione informaes sobre tipos, conforme
feito na Seo 6.2. Se alguma rvore de sintaxe abstrata tipada for invlida quanto ao tipo, indique
claramente onde e por qu.
(a) f  3;
(b) f  3,5;
(c) i  2;
(d) i  2,5;
(e) f  c;
(f) i  c;
(g) i  i  1;
(h) i  i  c;
(i) f  f  i;
(j) if (b) i  1;
(k) if (f) i  1;
(l) if (f1  f2) f3  f4  f5;
6.5 Adicione informaes sobre o nmero da linha e coluna quelas exibidas pelos checks nas imple-
mentaes da funo V. Dica: para fazer isso, voc precisar modificar as classes Lexer, Token e
AbstractSintax.

6.6 Modifique a Regra de Tipo 6.5 para permitir converses implcitas de char para int e de int para
float. Modifique a funo V para expresses de modo a implementar as regras de tipo.

6.7 Adicione um comando put() a Clite, no qual put() recebe uma expresso como argumento e
grava o valor desta expresso em stdout. Para este novo comando:
(a) Defina seu EBNF e sua sintaxe abstrata;
(b) Modifique a Regra de Tipo 6.4 para definir as verificaes de validade necessrias; e
(c) Modifique a funo V para comandos de modo a implementar as regras de tipo.
6.8 Adicione as funes getInt() e getFloat() a Clite, onde getInt/GetFloat recebe o prxi-
mo valor de stdin e retorna o valor lido como um int/float, respectivamente. Para esta nova
expresso:
(a) Defina seu EBNF e sua sintaxe abstrata.
(b) Modifique a Regra de Tipo 6.5 para definir as verificaes de tipo necessrias.
(c) Modifique a funo V para expresses de modo a implementar as regras de tipo.
6.9 Modifique a classe transformadora de tipos de modo que as converses implcitas de tipos do Exer-
ccio 6.6 sejam convertidas em explcitas.
6.10 Modifique as funes V matemticas da Seo 6.3 de modo que as converses implcitas do Exer-
ccio 6.6 sejam includas.
152 Captulo 6 Sistemas de Tipos

6.11 Modifique a Regra de Tipo 6.5 de modo que ela verifique a validade de uma RefMatriz (veja a
Figura 2.14).
6.12 Modifique a Regra de Tipo 6.6 de modo que ela calcule o tipo de uma RefMatriz (veja a Figura 2.14).
6.13 Implemente a verificao de tipos para referncias de matrizes modificando os mtodos da funo
V para Comando e Expresso e o mtodo Tipo De.
Semntica 7
Ishmael: Com certeza isso deve ter algum significado.
Herman Melville, Moby Dick

VISO GERAL DO CAPTULO

7.1 MOTIVAO 154


7.2 SEMNTICA DE EXPRESSES 155
7.3 O ESTADO DO PROGRAMA 160
7.4 SEMNTICA DE ATRIBUIO 162
7.5 SEMNTICA DE CONTROLE DE FLUXO 164
7.6 SEMNTICA DE ENTRADA/SADA 169
7.7 SEMNTICA DE MANIPULAO DE EXCEES 179
7.8 RESUMO 194
EXERCCIOS 194

Nos primeiros tempos do projeto de linguagens, era possvel escrever um programa


sintaticamente correto em uma determinada linguagem que se comportaria de forma di-
ferente ao ser executado com a mesma entrada em plataformas diferentes. Essa situao
surgiu porque o comportamento em tempo de execuo da linguagem (semntica) no era
definido com preciso suficiente. Compiladores diferentes traduziam um programa para
verses de linguagem de mquina no equivalentes.

153
154 Captulo 7 Semntica

Desde aquela poca, os projetistas de linguagens perceberam gradualmente que um


tratamento rigoroso da semntica to importante quanto um tratamento formal da sin-
taxe. Uma semntica rigorosa deve assegurar que determinado programa signifique a
mesma coisa, independentemente da plataforma em tempo de execuo.
Um meio-termo, ao se lidar com casos em que diferentes implementaes possam
produzir diferentes resultados, identificaria aqueles casos na definio e na especificao
da linguagem cujos significados so deixados para a implementao. Isso ocorre em C,
por exemplo, para a ordem na avaliao dos argumentos de funes.

7 .1 M OT IVAO
As principais motivaes para se definir com preciso uma linguagem de programa-
o so:
1 Fornecer aos programadores uma definio oficial do significado de todas as cons-
trues da linguagem.1
2 Fornecer aos escritores de compiladores uma definio oficial do significado de
todas as construes, evitando, assim, diferenas nas implementaes.
3 Fornecer uma base para a padronizao da linguagem.2
Uma linguagem de programao bem definida apenas quando sua semntica, assim
como sua sintaxe e seu sistema de tipos, estiver integralmente definida. Lembre-se do
Captulo 2, que diz que a semntica de uma linguagem de programao uma definio
precisa do significado de qualquer programa que seja correto sintaticamente e tambm
quanto aos seus tipos. Trs abordagens diferentes tm sido usadas amplamente para defi-
nir a semntica de uma linguagem de programao.
A idia mais direta do significado de um programa o que acontece quando o pro-
grama compilado pelo compilador C e executado na mquina M. No final da dcada de
1950, Fortran foi definido originalmente dessa forma, usando o compilador Fortran em
um computador IBM 709. Uma caracterizao precisa dessa idia chamada de semnti-
ca operacional.3 Uma segunda forma de ver o significado de um programa axiomatizar
o significado de cada comando da linguagem. Assim, dada essa especificao formal do
que um programa deve fazer, pode-se provar com rigor que o programa executa o que
foi especificado usando um argumento lgico sistemtico. Essa abordagem chamada
semntica axiomtica e usada no Captulo 18.
Uma terceira forma de visualizar a semntica definir o significado de cada tipo de
comando que ocorre na sintaxe (abstrata) como uma funo matemtica de transformao
de estados. Assim, o significado de um programa pode ser expresso como um conjunto
de funes que atuam sobre o estado do programa. Essa abordagem chamada semntica
denotacional e usada no Captulo 8.
Cada uma dessas abordagens semntica tem vantagens e desvantagens. A semn-
tica operacional possui a vantagem de representar o significado de um programa dire-
tamente no cdigo de uma mquina real (ou simulada). Contudo, essa tambm uma
potencial fraqueza, j que definir a semntica de uma linguagem de programao com
base em alguma determinada arquitetura, real ou abstrata, limita a utilidade dessa defi-
nio para escritores de compiladores e programadores que trabalham com arquiteturas

1. Pessoas bastante versadas nas nuances de uma linguagem so, muitas vezes, chamadas de advogados
da linguagem.
2. PL/I foi a primeira linguagem na qual um modelo preciso serviu como base para seu padro ANSI/ISO.
3. Tecnicamente, h dois tipos de semnticas operacionais, chamadas de tradicional e estruturada (s vezes,
chamadas de semntica natural).
7.2 Semntica de Expresses 155

diferentes. Alm disso, a mquina virtual na qual as instrues so executadas tambm


precisa de uma descrio semntica, o que adiciona complexidade e pode levar a defini-
es circulares.
A semntica axiomtica especialmente til na explorao de propriedades for-
mais de programas. Programadores que precisarem escrever programas que sejam
verificavelmente corretos a partir de um conjunto preciso de especificaes esto es-
pecialmente bem servidos por esse estilo de semntica. A utilidade da semntica axio-
mtica ilustrada no Captulo 18.
A semntica denotacional valiosa porque seu estilo funcional traz a definio da
semntica de uma linguagem a um nvel alto de preciso matemtica. Por meio dela, os
projetistas de linguagens obtm uma definio funcional do significado de cada constru-
o da linguagem que independente de qualquer arquitetura de mquina. O modelo de-
notacional tambm valioso porque nos permite explorar ativamente conceitos de projeto
de linguagens usando uma abordagem experimental.
Este captulo apresenta um enfoque semntico operacional informal, de modo que
pode abordar no apenas a semntica bsica das atribuies, das expresses e do fluxo
de controle, mas tambm as questes mais amplas de semntica que envolvem recursos
como manipulao de excees e entrada e sada.

7 .2 S EM N T ICA DE EXP RESS ES


Em linguagens de programao, a avaliao de expresses fundamentalmente im-
portante. Nesta seo, exploramos a semntica de expresses, incluindo operadores e sua
associatividade e precedncia, o papel de diferentes ordens de avaliao e a importncia
da preciso.
Em matemtica, expresses so puras no sentido de que no tm efeitos colaterais.
Assim, a ordem em que subexpresses so avaliadas no importante. Algumas lingua-
gens de programao funcionais espelham essa pureza, j que probem os efeitos colate-
rais. O Captulo 14 explora essa idia mais integralmente.

7.2.1 Notao
Analise a rvore de expresso da Figura 7.1. Seguindo a notao matemtica con-
vencional, a maioria das linguagens de programao usa notao interfixada para opera-
es binrias, ou seja, cada operador binrio escrito entre seus operandos. Na notao
interfixada, a expresso da Figura 7.1 seria escrita como uma expresso completamente
parametrizada como:

(a + b) (c * d)

Supondo a associatividade e a precedncia normais dos operadores +, - e *, a ex-


presso tambm poderia ser escrita como:

a + b c * d

| Figura 7.1 Exemplo de rvore


de Expresso

a b c d
156 Captulo 7 Semntica

Isso traz tona uma questo importante. A associatividade e a precedncia de opera-


dores, muitas vezes, variam de uma linguagem para outra, como veremos na Seo 7.2.2.
Mesmo em uma nica linguagem ou famlia de linguagens, a tabela de precedncia pode
ser bastante grande (veja, por exemplo, a Tabela 2.4). Na maioria dos casos, entretanto, os
parnteses podem ser inseridos para se obter a estrutura semntica desejada.
Na ausncia de regras de precedncia e associatividade, a notao interfixada ine-
rentemente ambgua. Atribuir associatividade e precedncia aos operadores uma forma
de eliminar a ambigidade.
Uma forma alternativa de eliminar ambigidade semntica usar uma notao po-
lonesa. A semntica dessa notao inerentemente no-ambgua. Na notao polonesa
prefixada, um operador binrio sempre escrito na frente dos seus dois operandos. Por
exemplo, a expresso da Figura 7.1 em notao prefixada seria escrita:

+ a b * c d

Dada uma rvore de expresso, sua notao prefixada pode ser gerada usando-se
uma caminhada prefixada (s vezes chamada de travessia em pr-ordem) da rvore.
Esse algoritmo pode ser expresso da seguinte forma, comeando com o n raiz:
1 Gerar o valor do n.
2 Visitar a subrvore esquerda.
3 Visitar a subrvore direita.
Outra variante chamada notao polonesa ps-fixada, na qual um operador sempre
segue seus operandos. A expresso da Figura 7.1 em notao ps-fixada seria escrita:

a b + c d *

A notao ps-fixada usada nas linguagens Fortran e Postscript (uma linguagem


de controle de impresso).
Dada uma rvore de expresso, a expresso em notao ps-fixada pode ser gerada
usando-se uma caminhada ps-fixada (travessia em ps-ordem) pela rvore. Esse algorit-
mo pode ser expresso da seguinte maneira, comeando novamente na raiz:
1 Visitar a subrvore esquerda.
2 Visitar a subrvore direita.
3 Gerar o valor do n.
Uma das limitaes da notao polonesa que o mesmo smbolo no pode ser
usado em uma operao com diferentes nmeros de argumentos. Por exemplo, em ma-
temtica comum, o smbolo usado tanto para o sinal de menos unrio quanto para
o binrio. Uma soluo escolher um smbolo diferente para o sinal de menos unrio,
como o til (~).
A notao de prefixo de Cambridge evita esse problema fazendo com que um ope-
rador sempre preceda seus operandos e colocando a expresso inteira entre parnteses. A
expresso da Figura 7.1 em notao de prefixo de Cambridge seria escrita:

( (+ a b) (* c d) )
7.2 Semntica de Expresses 157

|
Tabela 7.1 Linguagem + * / Unrio ** == != etc.

Associatividade de C (e similares) esquerda direita esquerda


Operadores por Ada esquerda nenhuma nenhuma nenhuma
Linguagem
Fortran esquerda direita direita esquerda

Uma vantagem da notao de prefixo de Cambridge que operadores como + e - se


tornam naturalmente n-rios. A expresso:

a + b + c + d

pode ento ser escrita:

(+ a b c d)

Isso tambm permite interpretaes naturais para (+), que significa 0, e (+a), que significa a.
A notao de prefixo de Cambridge usada por Lisp e por Scheme (veja a Seo 14.2).

7.2.2 Associatividade e Precedncia


A maioria das linguagens usa notao interfixada para expresses que envolvem
operadores unrios e binrios. Elas tambm seguem as convenes matemticas comuns
para associatividade e precedncia sempre que possvel. Assim, a semntica refletida na
rvore de expresso da Figura 7.1 pode geralmente ser obtida da seguinte forma:

a + b c * d

A Tabela 7.1 mostra a associatividade de alguns operadores comuns para algumas


linguagens.4 O cabealho da coluna dos operadores relacionais usa a representao em C.
Para os operadores de adio e multiplicao comuns, existe uma concordncia nas lingua-
gens mostradas. Entretanto, para o sinal de menos unrio e a negao boleana, linguagens
do tipo C e Fortran os tornam associativos pela direita, enquanto Ada os torna no-as-
sociativos.
Linguagens do tipo de C no possuem operador de exponenciao (** em Fortran
e Ada). Fortran o faz associativo pela direita, consistente com a matemtica. Ada torna a
exponenciao no-associativa, forando, assim, o programador a deixar clara tal expres-
so pelo uso de parnteses.
J discutimos os problemas de associatividade de expresses relacionais, mas vale a
pena repetir aqui. Linguagens do tipo de C tornam os operadores relacionais associativos
pela esquerda. Todavia, a expresso a < x < b no equivalente a:

a < x && x < b

como se poderia esperar. Em vez disso, em C e C++ equivalente a:

se (a < ) 1 < b ento 0 < b

4. A representao dos operadores relacionais varia muito em cada linguagem, sendo que a representao de
no igual a que mais varia.
158 Captulo 7 Semntica

|
Tabela 7.2 Operadores C (e similares) Ada Fortran

Precedncia de Unrio 7 3 3
Operadores por ** 5 5
Linguagem
*/ 6 4 4
+ 5 3 3
= = ! = 4 2 2
< <= > >= 3 2 2
no unrio 7 2 2
e lgico 2 1 1
ou lgico 1 1 1

que no o que se espera. Tornar os operadores relacionais no-associativos, como Ada


faz, uma deciso de projeto claramente superior.
A Tabela 7.2 mostra a precedncia de diversos operadores por linguagem e mostra
que h uma concordncia geral quanto ordenao relativa de operadores aritmticos
binrios, que os operadores relacionais tm menor precedncia do que os aritmticos e
que os operadores boleanos binrios tm a menor precedncia.
Entretanto, h um grande desacordo em relao colocao dos operadores un-
rios nessa hierarquia. Finalmente, linguagens do tipo de C dividem os operadores de
igualdade (igual e no igual) dos outros quatro operadores relacionais assim como dos
operadores lgicos.
Nem todas as linguagens usam convenes matemticas comuns para definir a pre-
cedncia de operadores. Por exemplo, a linguagem Smalltalk (veja a Seo 13.3) usa
avaliao direta da esquerda para a direita, atribuindo um nico nvel de precedncia e
associatividade pela esquerda a todos os operadores. APL usa avaliao direta da direita
para a esquerda.

7.2.3 Avaliao de Curto-Circuito


Seguindo a conveno matemtica, os primeiros compiladores avaliavam uma ex-
presso boleana como A e B primeiro avaliando A e B separadamente. A avaliao de
curto-circuito avalia uma expresso boleana da esquerda para a direita e pra assim que a
verdade da expresso puder ser determinada.
Assim, uma definio de curto-circuito de A e B :

if (! A) false else B

De forma semelhante, uma definio de curto-circuito de A ou B :

if (A) true else B

Diferentemente da matemtica, B pode ser indefinido nesses casos, j que pode no


ser avaliado.
7.2 Semntica de Expresses 159

Um exemplo clssico ocorre na pesquisa de uma lista encadeada, com o ponteiro


inicial head, para encontrar determinado valor de chave. Usando a sintaxe Java para esse
trecho de cdigo, podemos escrever:

Node p = head;
while (p != null && p.info != key)
p = p.next;
if (p == null) // not in list
...
else // found it
...

O teste p.info != key gerar uma exceo quando p for null. Entretanto, sob avalia-
o de curto-circuito, o teste p.info != key no ser executado sempre que p == null.
A vantagem que o programador pode escrever cdigo menor e mais claro aprovei-
tando a avaliao de curto-circuito. Sem ela, o lao anterior pode ser reescrito por meio
de um comando break:

while (p != null) {
if (p.info) == key
break;
p = p.next;
}

Evitar o comando break requer a introduo de uma varivel boleana:

boolean found = false;


while (p != null && ! found) {
if (p.info == key)
found = true;
else
p = p.next;
}

A introduo de uma varivel boleana torna o cdigo mais difcil de ser entendido com
relao verso original com curto-circuito.
Uma desvantagem de expresses de curto-circuito que este quebra a lei comutati-
va. No verdade que a && b seja a mesma coisa que b && a. Quando a avaliada como
falsa e b como indefinido, a && b falso, enquanto b && a indefinido.

7.2.4 O Significado de uma Expresso


Em uma linguagem de programao, o significado de uma expresso deve depender
apenas dos valores das suas subexpresses e do significado do seu operador. Para simplifi-
car, supomos aqui que a expresso tenha sido verificada estaticamente quanto aos tipos.
Uma complicao que na matemtica no h nmero maior ou menor. Os computa-
dores e suas linguagens de programao suportam dois tipos de nmeros (em diversas preci-
ses): nmeros inteiros e de ponto flutuante. Ambos possuem um valor mximo/mnimo.
160 Captulo 7 Semntica

Devido ao fato de qualquer representao ser finita, a lei associativa no vale para a
aritmtica computacional. Em especial:

(a b) c a (b c)

no verdadeiro para todos a, b e c. Para aritmtica de nmeros inteiros, se a for o maior


inteiro positivo possvel, b 3 e c 5, ento o lado esquerdo da igualdade avaliado
como um erro (overflow de inteiros), enquanto o lado direito avaliado como o maior
nmero inteiro menos 2.
Outro problema ocorre se alguma subexpresso tiver um efeito colateral. Por exem-
plo, analise a expresso a + b , na qual a e b so valores de ponto flutuante e + denota
uma adio de ponto flutuante. O significado da expresso, ou seja, seu valor, deve de-
pender apenas dos valores de a e b. Em especial, se tanto a quanto b forem subexpresses
que envolvem operadores e funes, a ordem da avaliao de a e b no deve afetar o valor
da expresso.
Analise os operadores ++ e na linguagem C, que podem ser problemticos quan-
do ocorrem dentro de uma expresso maior. Por exemplo, aps os seguintes comandos
serem executados:

i = 2; b = 2; c = 5;
a = b * i++ + c * i;

qual o valor resultante de a? Na verdade, linguagens como C definem a expresso anterior


como semanticamente indefinida, j que a pode ser 14 (incremente i aps sua segunda
referncia) ou 19 (incremente i antes da sua segunda referncia). As duas interpretaes
dependem de qual das duas subexpresses b * i++ e c * i for avaliada primeiro.
Na maioria das linguagens, subexpresses em subrvores separadas podem ser ava-
liadas em qualquer ordem. Se o significado do programa depender da ordem, ento esse
programa semanticamente indefinido. Um compilador no precisa, pelo projeto da lin-
guagem, detectar tais erros de semntica.

7 .3 O E S TAD O D O P RO GRAM A
Para que se entenda perfeitamente a semntica, o conceito de um estado de progra-
ma fundamental.
Definio: O estado de um programa a associao de todos os objetos ativos
com seus valores correntes.
O estado possui dois mapas: (1) a associao de objetos ativos com locais especficos
na memria e (2) a associao de locais ativos na memria com seus valores correntes.
O comando corrente (parte de uma rvore de sintaxe abstrata) a ser executado em um
programa interpretado de forma relativa ao seu estado corrente. As etapas individuais
que ocorrem durante a execuo de um programa podem ser vistas como uma srie de
transformaes de estados.
Por exemplo, suponha que tenhamos variveis i e j com valores 13 e 1 em algum
momento durante a execuo de um programa. Suponha que os locais de memria sejam
numerados serialmente comeando em 0 e que as variveis i e j estejam associadas aos
locais de memria 154 e 155 nesse momento. Ento o estado representado por essa con-
figurao pode ser expresso da seguinte forma:

ambiente {i,154,  j,155}


memria {0, undef , ..., 154,13, 155, 1, ...}
7.3 O Estado do Programa 161

1 // calcula o fatorial de n
2 void main ( ) {
3 int n, i, f;
4 n = 3;
5 i = 1;
6 f = 1;
7 while (i < n) {
8 i = i + 1;
9 f = f * i;
10 }
11 }

| Figura 7.2 Programa Fatorial

O valor especial undef usado para denotar o valor de um endereo de memria que
esteja correntemente no definido (ainda no recebeu um valor).
Assim, o estado de um programa o produto dos seus objetos ativos, seus locais de
memria e os valores associados. Ele pode ser visto como a composio funcional dos
dois mapas ambiente e memria como: estado memria ambiente. Esse tratamento
formal ser visto no Captulo 9.5
Para os propsitos deste captulo, conveniente representar o estado de um progra-
ma de uma forma mais simplificada. Essa representao retira os endereos de memria
e simplesmente define o estado do programa como um mapeamento das variveis decla-
radas para seus valores atribudos correntemente em algum momento durante a execuo
do programa.
O estado como uma janela de observao em um ambiente de desenvolvimento
integrado (IDE). Ele est sempre associado a determinado comando no programa-fonte,
e mostra para cada varivel do programa seu valor corrente.
Antes que um programa em C/C++ comece a ser executado, cada varivel declarada
possui o valor undef consistente com seu tipo. Qualquer operao sobre um valor indefi-
nido torna o programa semanticamente indefinido.
Como exemplo, analise o programa da Figura 7.2, que calcula o fatorial de 3. A Ta-
bela 7.3 mostra o rastreamento da execuo desse programa em um formato que pode ser
produzido pela maioria dos depuradores. A primeira linha mostra o estado do programa
antes que o comando 4 (o primeiro comando executvel) tenha sido executado. Em vez de
mostrar o estado aqui como um conjunto de pares varivel-valor, a tabela usa a exibio
familiar em colunas rotuladas. Observe que h trs estados separados associados ao teste
do lao while no comando 7.
Uma caracterstica que o rastreamento destaca que uma atribuio modifica o
estado do programa. Em comparao, j que o teste do lao while no tem efeitos
colaterais, ele no pode modificar o estado do programa. Por outro lado, o teste do lao
while determina se a seguir ser executado o corpo do lao (linhas 8 e 9) ou o comando
aps este (linha 11).

5. Nos Captulos 10 e 11, ser importante representar o ambiente de uma forma mais realista, permitindo-nos
caracterizar com preciso os papis da pilha de tempo de execuo e o heap. Isso nos permite lidar de maneira
eficaz com chamadas de procedimentos, passagem de parmetros, recurso e assim por diante.
162 Captulo 7 Semntica

|
Tabela 7.3 Antes do Variveis
Passo
Comando n i f
Rastreamento do
Programa Fatorial 1 4 indefinido indefinido indefinido
2 5 3 indefinido indefinido
3 6 3 1 indefinido
4 7 3 1 1
5 8 3 1 1
6 9 3 2 1
7 7 3 2 2
8 8 3 2 2
9 9 3 3 2
10 7 3 3 6
11 11 3 3 6

7 .4 S E M NT ICA DE AT RIBU IO
A declarao de atribuio fundamental para o paradigma imperativo e sua ra-
mificao mais nova, o paradigma orientado a objetos. Nesta seo, examinamos trs
questes relacionadas atribuio:
1 Atribuio mltipla
2 Comandos de atribuio versus expresses de atribuio
3 Semntica de cpia versus semntica de referncia em atribuies
Lembre-se da sintaxe abstrata para uma atribuio em Clite, apresentada no Captulo 2:
Atribuio Varivel-alvo; Expresso origem
A semntica de uma atribuio a seguinte:
A expresso origem avaliada no seu estado corrente, que resulta um valor, o qual
substitui o valor da varivel-alvo, que resulta um novo estado.
Por toda esta seo, usamos os termos varivel-alvo e expresso origem com essa inter-
pretao semntica especfica.

7.4.1 Atribuio Mltipla


Muitas linguagens suportam mltiplas variveis-alvo para uma nica expresso ori-
gem. Um uso comum de tal construo para inicializar duas ou mais variveis com o
mesmo valor, como em:

a = b = c = 0;

Este comando de atribuio inicializa todas as trs variveis com zero.


7.4 Semntica de Atribuio 163

7.4.2 Comandos de Atribuio versus Expresses de


Atribuio
Mesmo com uma atribuio com mltiplos alvos, na maioria das linguagens uma
atribuio um comando. Da mesma forma que qualquer outro comando, ela no pode
ser colocada dentro de uma expresso.
Todavia, em linguagens como C, uma atribuio uma expresso em vez de um
comando. Assim, o operador de atribuio um pouco diferente de qualquer outro opera-
dor, como , etc. Em C, a seguinte construo:

if (a = 0) ... else ...

tanto sinttica quanto semanticamente significativa.6 Isso resulta no valor zero sen-
do atribudo varivel a, e a parte else do if sendo executada a seguir, j que zero
interpretado como falso. Em C, o valor de uma expresso de atribuio o da ex-
presso origem.
H muitos usos prticos para a expresso de atribuio em C e C++:
Copiar uma string: por exemplo, while (*p++ = *q++) ...
Ler caracteres at o final do arquivo: por exemplo, while (ch = getc(arquivo)) ...
Percorrer uma lista encadeada: por exemplo, while (p = p->proximo) ...
Entretanto, compensando essa convenincia est o erro comum apresentado ante-
riormente de digitar-se inadvertidamente o operador de atribuio (=) em vez da relao
de iguais (==). Por esse motivo, Java discordou de C/C++ e requereu que todos esses
testes fossem do tipo boolean.

7.4.3 Semntica de Cpia versus Semntica de


Referncia
A maioria das linguagens imperativas incluindo C e C++ usam a semntica de cpia
para atribuies, na qual uma cpia do valor da expresso origem atribuda varivel-
alvo. Em comparao, muitas linguagens orientadas a objetos, incluindo Java, usam a
semntica de referncia para atribuies de objetos, na qual uma referncia (ponteiro)
para o valor da expresso origem atribuda varivel-alvo.
Por exemplo, sob a semntica de cpia, a atribuio:

a = b;

deixa a e b com duas cpias do mesmo valor. Alteraes em a ou b no tm efeito sobre


o outro.
Sob a semntica da referncia, a mesma atribuio deixa a e b apontando para o
mesmo objeto, que o valor original de b. Assim, alterar o valor (estado interno) de a ou
b ter o efeito colateral de alterar o outro, ou seja, a atribuio anterior torna as variveis
a e b em dois aliases do mesmo objeto.

6. Embora o programador presumivelmente tenha desejado testar se a varivel a igual a zero. Os compilado-
res, muitas vezes, emitem um aviso para alertar o programador para o fato de que este pode ter pretendido usar
== em vez de =.
164 Captulo 7 Semntica

Um exemplo mais ilustrativo o mtodo add (Tucker e Nonnan, 2002, p. 191):

public void add (Object word, Object number) {


Vector set = (Vector) dict.get(word);
if (set == null) { // vetor vazio
set = new Vector( );
dict.put(word, set);
}
if (allowDupl || !set.contains(number))
set.addElement(number);
}

O primeiro comando atribui a set o valor da entrada com a chave word na tabela hash
dict. Se essa chave no estiver na tabela hash, set recebe o valor null, em cujo caso
um novo set criado e colocado na tabela hash com a chave word. Em ambos os casos,
o ltimo comando do mtodo adiciona o inteiro Number ao set da tabela hash. Sob a se-
mntica de cpia, esse ltimo comando no atualizaria a tabela hash apropriadamente.

7 .5 S EM N T ICA DE CO N T RO LE DE FLU XO
De acordo com o Teorema de Bhm-Jacopini (Bhm e Jacopini, 1966), uma linguagem
completa em relao a Turing se, alm do comando de adio, possuir as seguintes estrutu-
ras de controle de fluxo: seqncia, um comando condicional e um comando de lao.
Definio: Uma linguagem de programao completa quanto a Turing se os
seus programas forem capazes de computar qualquer funo calculvel.
Cada uma dessas estruturas ser discutida nas sees a seguir. Chamadas a funes/m-
todos e retornos so discutidos no Captulo 9.
Qualquer outro tipo de comando aumenta a automao ou a convenincia de uma
linguagem de programao e sua expressividade, mas, em ltima instncia, no o seu
poder computacional.

7.5.1 Seqncia
A capacidade de escrever uma seqncia de comandos em uma linguagem to
natural e bsica que raramente discutida. No caso mais simples, uma seqncia possui
dois comandos justapostos:

c1c2

A semntica de tal seqncia intuitiva. Na ausncia de alguma forma de comando


de desvio, primeiro c1 executado, e em seguida, c2.
Como vimos na Tabela 7.3, o estado de sada de c1 o de entrada de c2.
Comandos de desvio incluem: return, break, continue e o comando irrestrito
goto. Esse ltimo comando e seus problemas associados so discutidos na Seo 7.5.4.
Os outros so formas restritas de comandos goto.
O comando return usado para terminar imediatamente uma funo ou um mto-
do e discutido mais integralmente no Captulo 9. Os comandos break e continue so
usados principalmente em laos e so discutidos na Seo 7.5.3.
7.5 Semntica de Controle de Fluxo 165

7.5.2 Condicionais
O comando condicional usado para selecionar caminhos alternativos durante a
execuo de um programa. Na maioria das linguagens, ele apresenta duas variaes: o
comando if bsico e o comando case (ou switch).
Conforme vimos no Captulo 2, o comando if (usando a sintaxe de C) apresenta
duas variaes:
ComandoIf if ( Expresso ) Comando1 [ else Comando2 ]
que so, muitas vezes, chamadas de if-then e if-then-else. A semntica de um ComandoIf
pode ser definida da seguinte forma:
Se o valor da Expresso for verdadeiro, o significado o estado resultante da ava-
liao do Comando1 no estado corrente. Caso contrrio, o significado o estado
resultante da avaliao do Comando2 no estado corrente.
Lembre-se do Captulo 2, que diz que if-then pode ser visto como um if-then-else com um
comando de salto inserido na parte do else. Assim, a existncia de um comando if-then
apenas uma abreviao conveniente.
Um exemplo de um if-then-else o seguinte trecho de cdigo (em sintaxe C) para
calcular o mximo entre dois nmeros:

if (a > b)
z = a;
else
z = b;

Uma questo interessante como especificar que o Comando1 ou o Comando2 pos-


suem mais de um nico comando. Duas abordagens gerais tm sido usadas.
A abordagem de Algol 60 limitar tanto a parte do then quanto a do else em um
nico comando. J que isso claramente inconveniente, Algol 60 tambm usou um meca-
nismo de agrupamento que permitiu que uma seqncia de comandos fosse considerada
sintaticamente como um nico comando. Algol 60 usou begin e end para denotar tal
seqncia, que chamada de comando composto. Linguagens como C e Perl seguem essa
tradio, mas usam uma chave esquerda para begin e uma chave direita para end.
Algol 68 via o Comando1 e o Comando2 como naturalmente compostos, precisando
apenas de uma palavra-chave para terminar o Comando2 (se existir) ou o Comando1 (se
o Comando2 no existir). Algol 68 introduziu a conveno de usar a palavra-chave inicial
escrita ao contrrio. Assim, um comando if era terminado com um fi. Ada segue esta con-
veno, mas usa duas palavras-chave para terminar, a saber, endif. Python tambm segue
essa tradio, mas usa indentao para o Comando1 e o Comando2.
Para simplificar o fechamento de comandos if aninhados, linguagens que seguem a
conveno do Algol 68 muitas vezes tm uma construo elseif nos seus comandos if.
Usando Ada, por exemplo, a sintaxe de um comando if :

ComandoIf if Expresso then Comandos


{elsif Expresso then Comandos}
[else Comando ]
end if ;
166 Captulo 7 Semntica

A ortografia da palavra-chave elseif varia de acordo com a linguagem; at Perl,


que segue a tradio de C, possui um elsif. O comando switch (ou case) comeou como
uma tabela de saltos e foi implementado como um goto ou jump. Os cases individuais no
tinham de estar fisicamente adjacentes, mas poderiam estar dispersos por todo o cdigo.
Um exemplo em Fortran :

goto (100, 200, 300, 400), i

Se a varivel i tiver valor igual a 1, o prximo comando executado o rotulado como 100,
se tiver valor igual a 2, ento 200, e assim por diante. Se o seu valor no estiver dentro da
faixa 1 a 4, ento o comando goto pulado e o controle passa para o prximo comando.
Devido a essa tradio e implementao associada, a maioria das linguagens limita a
varivel de controle do comando switch ao tipo inteiro (incluindo caracteres nicos). Se
os cases excederem uma pequena faixa de valores, muitos compiladores implementam
um switch usando um if. Um exemplo de um comando switch em linguagens C aparece
na classe tokenizer na Figura 3.5. Uma deciso infeliz de projeto para comandos switch
em linguagens do tipo de C o requisito de que cada case termine com uma declarao
break explcita para evitar que um case caia acidentalmente no prximo. Isso se provou
muito propenso a erros; assim, lamentvel que linguagens mais novas como Java e C#
tenham continuado com esse projeto.
Em linguagens distintas de C e suas variantes, como Ada, existe um break implcito no
final de cada case. Alm disso, Ada tambm permite que o rtulo de um case seja uma subfai-
xa inteira de valores. Assim, o comando switch da Figura 3.5 apareceria em Ada como:

case ch is
when A .. Z | a .. z => -- identifier
...
when 0 .. 9 => -- numeric literal
...
when
...
when others => -- default
...
end case;

Observe que em Java os primeiros dois cases so escritos de forma mais conveniente
como comandos if. Observe tambm que Ada usa -- para comear um comentrio e end
case para terminar o comando case.

7.5.3 Laos
O lao com teste no incio adequado para todas as necessidades de laos. Em lin-
guagens do tipo de C, esse lao escrito como
ComandoWhile while (Expresso) Comando
e chamado de lao while. Seu significado resumido a seguir:
A Expresso avaliada. Se ela for verdadeira, o Comando executado e o Lao
executado novamente. Caso contrrio, o lao termina.
7.5 Semntica de Controle de Fluxo 167

Em comparao, o lao com teste no final em linguagens do tipo de C aparece sintatica-


mente da seguinte forma:
ComandoDoWhile do Comando while ( Expresso )
Seu significado ligeiramente diferente daquele do lao com teste no incio:
O Comando executado uma vez e ento a Expresso avaliada. Se a Expresso
for verdadeira, o Comando executado novamente, e assim por diante. Caso con-
trrio, o lao termina.
Tal construo , dessa forma, semanticamente equivalente a:
{ Comando while ( Expresso ) Comando }
Assim, o lao com teste no final conveniente, porm redundante.
Ainda mais conveniente o lao com contador. Um exemplo de C para somar os
elementos de uma matriz est a seguir:

sum = 0.0;
for (i = 0; i < SIZE; i++)
sum += a[i];

Na linguagem C, a varivel i teria de ser declarada no incio do bloco no qual o cdigo


aparece. Linguagens mais novas como C++, Java e C# permitem que a redeclarao de
i ocorra como parte da inicializao do lao. Nesse caso, o escopo de i limitado ao
prprio comando for. Da mesma forma que com o lao com teste no final, o lao com
contador tambm conveniente, porm redundante.
Ainda mais convenientes, so os laos for-each, que usam alguma forma de ite-
rador para atravessar uma estrutura de dados, acessando cada elemento da estrutura
de dados uma vez.
Definio: Um iterador qualquer conjunto finito de valores sobre os quais um
lao pode ser repetido.
Como exemplo, analise a impresso das chaves e os valores de uma matriz associativa (ou
tabela hash). Na linguagem Perl, isso poderia ser escrito:

foreach $key (sort keys %list) {


print $key, \t, $list{$key}, \n;
}

Construes de laos for-each aparecem em Python, Java (desde a verso 1.5) e C#.
Muitas linguagens fornecem duas formas limitadas de comandos goto para tornar
o controle do lao mais flexvel. O primeiro desses comandos denominado comando
break em linguagens do tipo de C. O propsito do comando break o de permitir que
um lao seja concludo de qualquer lugar do corpo do lao.
Um uso natural de tal construo ocorre na exibio de menus ou caixas de dilogo
e para se obter a resposta do usurio. Um esboo de tal cdigo na linguagem C :
168 Captulo 7 Semntica

while (1) {
resp = displayMenu( );
if (resp == QUIT)
break;
...
}

A outra forma limitada de desvio denominada comando continue em linguagens do


tipo C. Seu propsito o de abortar a iterao corrente de um lao e comear a prxima.
O comando continue pode ser pensado como um desvio para o final do lao.
Um exemplo do uso de um comando continue o seguinte trecho em Java. Esse
lao est lendo um arquivo de configurao, uma linha por vez. Cada linha processada
pela informao que contm. Entretanto, linhas em branco e linhas que comeam com um
sinal de cerquilha (#) so ignoradas.

while (true) {
String line = infile.readLine( );
if (line == null) // final de arquivo
break;
line = line.trim( );
if (line.length == 0) continue;
if (line.startsWith(#)) continue;
...
}

7.5.4 A Polmica do GoTo


A controvrsia do comando goto comeou com uma carta de Edsger Dijkstra para
o editor da Communications of the ACM (Dijkstra, 1968b) intitulada GoTo Considerado
Prejudicial. Com essa carta, a revoluo da programao estruturada estava lanada.
De modo geral, a programao estruturada se referia ao projeto de programas a
partir de princpios bsicos, por intermdio de apenas trs estruturas de controle bsi-
cas: a seqncia, o comando condicional e o lao while. Em especial, a programao
estruturada defendia um banimento do uso de qualquer forma de comando goto, j que
era desnecessrio e encorajava a escrita de cdigo incompreensvel. Bhm e Jacopini
(Bhm e Jacopini, 1966) mostraram que o comando goto era logicamente desnecess-
rio. Desde ento, algumas linguagens (como Ada e Java) excluram o comando goto
dos seus vocabulrios.
Na dcada de 1960, a principal ferramenta de projeto de programas era o fluxograma
(veja um exemplo na Figura 12.1). O uso de fluxogramas para o projeto de programas
encorajava o assim chamado cdigo espaguete, ou seja, cdigo cuja estrutura estava toda
entrelaada. A presena de estruturas lgicas equivalentes a laos e comandos condicio-
nais era, muitas vezes, difcil de ser discernida. Um aspecto da programao estruturada
era tornar a estrutura lgica dos programas mais bvia, eliminando o cdigo espaguete.
Analise o fragmento de programa em Fortran da Figura 7.3, que soma os valores no-ne-
gativos de um conjunto de nmeros. Um contador dos nmeros somados tambm calculado.7

7. Em Fortran, o comando continue usado para marcar o final de um lao ou qualquer outro ponto de desvio,
diferentemente do seu uso em linguagens do tipo de C.
7.6 Semntica de Entrada/Sada 169

i = 0
ct = 0
sum = 0.0
100 if (i .ge. n) goto 300
i = i + 1
if (a(i) .lt. 0) goto 100
ct = ct+ 1
sum = sum + a(i)
goto 100
300 continue

| Figura 7.3 Exemplo de Cdigo Espaguete em Fortran

Uma leitura cuidadosa deve nos convencer de que o programa funciona, mas a estrutura de
controle no bvia. Um lao while teria fornecido uma soluo mais simples e legvel.
O desenvolvimento no final da dcada de 1960 das linguagens C e Pascal estimulou o
desenvolvimento da programao estruturada. Diferentemente de Fortran 66, elas tinham
estruturas de lao e condicionais necessrias, incluindo os laos com teste no incio e com
teste no final, laos for, comandos if-then-else e comandos switch/case, junto ao comando
composto que permitia o aninhamento dessas construes. C tambm incluiu formas res-
tritas de comandos goto, englobando os comandos return, break e continue, conforme
discutido anteriormente.
Entretanto, Dijkstra e Wirth estavam interessados principalmente em substituir o
fluxograma por mtodos melhores de projeto de programas que facilitassem a demons-
trao da correo de programas (veja o Captulo 18). O texto de Wirth (Wirth, 1973) e o
relatrio de Dijkstra (Dijkstra, 1972) foram extremamente influentes quanto a isso.
De modo geral, a revoluo na programao estruturada foi um sucesso. Os progra-
madores abandonaram o comando irrestrito goto em favor de construes explcitas de
laos e condies. extremamente raro atualmente se encontrar um comando goto in-
condicional, embora seu uso ainda seja ocasionalmente defendido (Rubin, 1987). Embora
a programao estruturada esteja firmemente estabelecida como metodologia de projeto
dentro do paradigma da programao imperativa, sua utilidade menos bvia no mundo
orientado a objetos.

7 .6 S EM N T ICA DE EN T RADA/SADA
Toda programao prtica necessita do domnio das tcnicas de recuperao de
dados de uma fonte permanente remota e do armazenamento de dados em um destino
permanente remoto. Coletivamente, essas tcnicas so chamadas de entrada/sada, ou
apenas E/S. As fontes e os destinos para as operaes de entrada e de sada so denomi-
nados arquivos. A mdia em que os arquivos so armazenados muito variada e inclui,
por exemplo, o teclado, o monitor, um disco magntico ou um pen-drive. Nesta seo,
revisamos algumas das caractersticas semnticas que se relacionam com a programao
de E/S em diversas linguagens.
No decorrer dos anos, os projetistas de linguagens tm usado abordagens muito dife-
rentes para a incorporao de recursos de entrada/sada nas suas linguagens. Os projetis-
tas de Algol, por exemplo, viam a E/S como sendo to dependente da implementao que
omitiram sua definio da especificao Algol 60. Projetistas de linguagens posteriores,
entretanto, foram forados a incluir especificaes de E/S, geralmente escolhendo inclu-
las nas suas bibliotecas de funes-padro.
170 Captulo 7 Semntica

7.6.1 Conceitos Bsicos


Antes que um programa possa comear a acessar um arquivo, este deve ser localiza-
do e aberto. Uma vez aberto, as informaes de um arquivo podem ser acessadas de uma
entre duas maneiras: seqencialmente ou aleatoriamente. Um arquivo acessado seqen-
cialmente um arquivo no qual cada entidade armazenada na mesma ordem em que
lida/gravada pelo programa. Um arquivo acessado aleatoriamente um arquivo cujas
entidades podem ser lidas/gravadas em alguma ordem no-serial, de modo que uma ni-
ca entidade possa ser lida/gravada zero, uma vez ou qualquer nmero de vezes enquanto
o arquivo estiver aberto.
Quando um programa termina de ler/gravar entidades em um arquivo, este fe-
chado. Dessa forma, o programa abandona o controle para que o arquivo possa ser
acessado por outros programas posteriormente. Geralmente, nenhum arquivo aberto
por mais de um programa por vez.8
As entidades em um arquivo acessado seqencialmente podem ser recuperadas/gra-
vadas por um programa como um fluxo de caracteres ou como uma srie de registros de
tamanho fixo, cada um dos quais possuindo a mesma estrutura ou formato dos outros.
Entidades em arquivos acessados aleatoriamente so geralmente lidas e gravadas como
registros de tamanho fixo.

Arquivos-padro Um requisito fundamental na programao a capacidade de se


ler dados de um teclado e exibi-los na tela. Essas duas mdias so identificadas como arqui-
vos-padro em linguagens do tipo de C. Essas linguagens tambm fornecem um arquivo
de erro-padro, em que as mensagens de erro que ocorrerem durante a execuo do progra-
ma so armazenadas serialmente. Java identifica esses trs arquivos-padro como System.
in, System.out e System.err, respectivamente. Essa conveno adaptada de C, na qual
esses arquivos so chamados stdin, stdout e stderr, respectivamente.
Java, C++ e Ada requerem que o programa mencione explicitamente o nome dos
pacotes de E/S (java.io, iostream e Ada.Text_IO, respectivamente) antes que possa
abrir algum desses arquivos para leitura ou gravao. Um programa em Fortran, C++ ou
Ada pode acessar dados usando E/S formatada ou no-formatada.
A Figura 7.4 mostra um exemplo de programa em C++ que l valores inteiros para
uma matriz a usando a entrada-padro, e depois exibe os valores em uma lista usando a
sada-padro.

#include <iostream.h>
main()
{
int i, a[8];
cout << Enter a series of eight integers: \n;
for (i=0; i<8; i++)
cin >> a[i];
for (i=0; i<8; i++)
cout << a[i] << \n;
}

| Figura 7.4 Exemplo de Uso de E/S-Padro em C++

8. H excees, como no caso de um banco de dados bancrios que acessado simultaneamente por programas
que servem a clientes em diferentes caixas automticos (ATMs).
7.6 Semntica de Entrada/Sada 171

Java 1.5 suporta a leitura de um valor numrico do teclado em um estilo no-forma-


tado, por meio da classe Java.util.Scanner. Essa classe analisa um fluxo de entrada
em uma srie de tokens medida em que ele lido. A conveno para fazer isso usar o
mtodo nextInt, que encontra o prximo token, o converte para int e o retorna.

Scanner reader = new Scanner(System.in);


int i = reader.nextInt();

Verses mais antigas de Java requeriam que a entrada passasse por um InputStreamReader
e um BufferedReader e depois fosse analisada antes que pudesse ser atribuda a uma
varivel int, ou seja, o seguinte cdigo seria necessrio:

BufferedReader br = new BufferedReader(


new InputStreamReader(System.in));
String str = br.readLine();
int i = Integer.parseInt(str);

Fluxos de Entrada/Sada Como padro, as linguagens suportam uma entrada


e uma sada seqenciais em um estilo no-formatado, ou seja, as informaes so li-
das/gravadas como um fluxo de caracteres, do qual valores de tokens individuais so
distinguidos, convertidos para o tipo apropriado e atribudos a variveis na ordem em que
estas so listadas no comando de leitura ou gravao. Esse estilo de programao de E/S
especialmente til para programadores iniciantes, conforme ilustrado na Figura 7.4.
A entrada/sada no-formatada em Fortran denominada direcionada a listas, o
que anlogo a um fluxo de E/S em C++ ou Java. A aparncia de um comando READ
ou WRITE em Fortran usando a E/S-padro :

READ (*, *) variable-list


WRITE (*, *) expression-list

A seguir, encontra-se um programa em Fortran que imita o cdigo em C++ da Figura 7.4.

INTEGER :: i, a(8)
WRITE (*, *) Enter a series of eight integers:
READ (*, *) a
WRITE (*, *) a

As referncias a a nos comandos READ e WRITE designam laos na forma de (a(i),


i=1,8). Tal referncia pode ser usada sempre que o lao envolver todos os elementos
da matriz.
Em Java, a biblioteca java.io fornece muitos tipos diferentes de fluxos para a en-
trada/sada seqencial, incluindo fluxos de arquivo, fluxos de pipes, fluxos de memria
e fluxos de filtro. Um fluxo de arquivo usado para transferir dados para o sistema de
arquivos nativo. Um fluxo de pipe faz com que os dados sejam enviados diretamente
de um fluxo para outro. Isso permite que um programa evite armazenar dados em es-
truturas de dados intermedirias. Fluxos de memria so usados para transferir dados
entre matrizes e o programa, enquanto fluxos de filtro fornecem suporte a Java para a
formatao da entrada e da sada.
172 Captulo 7 Semntica

Fluxos de E/S em Java tambm suportam um mtodo mark, que identifica o local cor-
rente de um fluxo. Se um mtodo reset for chamado posteriormente, o programa continuar
lendo no local identificado no fluxo. Fluxos de leitura possuem mtodos read que obtm
um caractere do fluxo, armazenam toda a entrada disponvel em uma matriz de caracteres ou
armazenam a entrada em uma parte especificada de uma matriz de caracteres. Fluxos de gra-
vao suportam mtodos write semelhantes para gravar em fluxos e usam um codificador de
caracteres para converter dados entre o formato de caractere e o de byte. Leitores e gravadores
de matrizes so usados para executar operaes de E/S em blocos inteiros de memria.
Java tambm possui convenes para serializar objetos usando fluxos. A serializa-
o um processo pelo qual os objetos podem ser arquivados para instanciao posterior
no mesmo programa ou para transmisso entre computadores por meio de sockets. Por
intermdio da converso de valores buscados do fluxo de entrada para serializao de obje-
tos, sejam como strings ou como o tipo original do objeto, possvel recuperar os objetos
e recri-los posteriormente na sua forma original.
A classe Java StreamTokenizer fornece quatro ferramentas que so teis para diver-
sas atividades de tokenizao: nextToken, TT_EOF, sval e nval. O mtodo nextToken
varre o fluxo serialmente, comeando na posio corrente e pulando os espaos em branco
at localizar e retornar o prximo token que encontrar. TT_EOF uma constante especial
que indica que o final do arquivo foi encontrado. Os valores sval e nval contm o valor do
prximo token analisado como uma String e como um double, respectivamente.
Os procedimentos de Ada para abrir, fechar e acessar arquivos seqenciais espelham
aqueles de C++ e Java. Em Ada, Open associa um arquivo definido pelo programa com
um arquivo externo que j existe, enquanto Create cria um novo arquivo externo. Close
termina com a associao entre o programa e o arquivo, e Delete termina com essa asso-
ciao e tambm apaga o arquivo externo.
Para um programa em Ada determinar a situao corrente de um arquivo, so forne-
cidas as funes Is_Open, Name, Form e Mode. Is_Open retorna um valor boleano que
descreve se o arquivo est aberto ou no, enquanto Name retorna o nome associado ao
arquivo. Form retorna informaes especficas de sistema operacional sobre o arquivo,
como privilgios de leitura/gravao e a quantidade de espao em disco alocada inicial-
mente para ele. Mode informa se o arquivo do tipo In_File, Out_File ou Inout_File.
Este ltimo modo reservado para arquivos de acesso aleatrios, que so discutidos mais
adiante nesta seo.

E/S Seqencial Formatada E/S formatada fornecida em C, Fortran, Java 1.5 e


Ada. Entretanto, ela possui uma forma diferente em cada uma dessas linguagens. A seguir
est um resumo sobre isso.
Em C, o comando fopen fornece o nome de arquivo e as permisses sob as quais
ele deve ser aberto. A leitura e a gravao em um arquivo (uma vez aberto) podem ser
executadas por meio das funes de biblioteca fscanf e fprintf. Aqui est um exemplo
em C que l informaes para uma matriz a partir de um arquivo chamado input.asc.

FILE* input;
input = fopen(input.asc, r);
for (i=0; i<8; i++)
fscanf(input, %d, &a[i]);

Aqui, o %d no segundo parmetro uma dica de que a funo fscanf deve encontrar um
nmero inteiro (em contraste com %f para um float ou %s para uma string), pulando espaos
7.6 Semntica de Entrada/Sada 173

em branco nesse nterim. Esse um processo hbrido entre E/S formatada e no-formata-
da: no-formatada porque quantidades arbitrrias de espaos em branco (espaos, novas
linhas etc.) podem ocorrer entre valores adjacentes, e formatada porque os valores lidos
devem ser do tipo especificado.
Alm de fscanf e fprintf, C fornece as funes getc e putc, que recebem e en-
viam um nico caractere, e fgets e fputs, que enviam e recebem uma linha inteira de
caracteres de/para um arquivo para/de uma varivel string. Todas as funes de entrada
entre essas retornam EOF quando o final do arquivo atingido.
Em Fortran, E/S formatada mais rigorosa do que em C, j que o espao em branco
no pulado automaticamente entre entidades de entrada. O formato de um comando
READ ou WRITE em Fortran :

READ (file_unit, format_expression) variable-list


WRITE (file_unit, format_expression) expression-list

Se file_unit for um asterisco, supe-se a entrada ou a sada padro. Caso contrrio,


file_unit um inteiro que identifica um arquivo diferente do buffer de entrada-padro,
em cujo caso um comando OPEN deve preceder o comando READ:

OPEN (UNIT=file_unit, FILE=f, STATUS=OLD, &


ACTION=READ,IOSTAT=s)

Aqui, as seguintes informaes so fornecidas:


file_unit conecta o arquivo aos comandos READ subseqentes.

O nome de arquivo f o nome externo do arquivo na mdia em que ele reside


permanentemente.
O arquivo deve ser OLD, indicando que j existe.
Opcionalmente, a varivel s de valor inteiro do tipo IOSTAT pode ser configurada para
indicar o status da operao de e/s (ou seja, que o arquivo foi aberto com sucesso).
O comando FORMAT permite a programas em Fortran controlarem o local e a apa-
rncia de valores de dados individuais em um fluxo de entrada e de sada. Muitos tipos
diferentes de descritores de formatos esto disponveis para esse propsito. Os quatro
descritores seguintes so usados com freqncia:
In indica um inteiro na posio n.

Fn.d indica um valor de ponto flutuante na posio n, com d dgitos direita do


ponto decimal.
An indica um valor de string de n caracteres.

nX indica um salto de n caracteres.


Na impresso, a primeira posio de cada linha de sada deve conter um caractere de
controle que defina o espaamento vertical para essa linha (espao simples, espao duplo
etc.).9 Aqui est uma variante em Fortran do cdigo C anterior para inicializar a matriz a
a partir do arquivo de entrada input.asc.

9. Este um remanescente do tempo em que Fortran produzia sada principalmente para impressoras de linhas,
que podiam imprimir exatamente 132 caracteres por linha, e cada caractere tinha uma fonte fixa (como a fonte
courier de hoje).
174 Captulo 7 Semntica

OPEN (UNIT=3, FILE=input.asc, STATUS=OLD, &


ACTION=READ, IOSTAT=ioerror)
IF (ioerror == 0) READ (3, 110) a
110 FORMAT ( 8I5 )

Aqui, o comando FORMAT diz que cada valor lido e armazenado em a recuperado do
arquivo como um inteiro de 5 dgitos, existindo oito desses.
Uma string de caracteres pode ser colocada na expresso do FORMATO para incluir
informaes adicionais na sada. O comando a seguir exibe uma mensagem seguida pelos
oito valores de a no arquivo de sada padro; cada valor ocupa cinco posies decimais
na linha.

WRITE (*, 120) a


120 FORMAT (Contents of array a are: , 8I5

Observe o espao em branco no incio da mensagem, que indica que essa linha de sada
tem um nico espao.
Antes da verso 1.5, Java usava FilterOutputStreams e FilterInputStreams
para gerenciar sua formatao de E/S. As verses bsicas dessas classes fornecem
outro fluxo de entrada/sada por meio do qual os programadores poderiam criar sub-
classes de fluxos de filtros para definir seus prprios fluxos de filtros. Esse mtodo ,
no mnimo, incmodo, e leva a inconsistncias entre diferentes aplicaes em Java
para formatao de E/S.
O lanamento de Java 1.5 inclui a classe java.util.Formatter, que contm m-
todos de formatao de sada semelhantes queles encontrados em C. Esse recurso pode
levar a convenes de formatao mais padronizadas e fceis para programas em Java.
Por exemplo, o seguinte cdigo Java 1.5 exibe uma string e um inteiro de 3 dgitos:

System.out.printf(%s %3d, name, age);

Em Ada, a biblioteca Test_IO fornece dois procedimentos altamente sobrecarrega-


dos, Get e Put, que permitem aos programas especificar entrada e sada formatadas para
diversos tipos de valores. Por exemplo, aqui esto algumas variantes do procedimento
Get para a entrada de valores em Character, Integer e String, respectivamente.

procedure Get (File: in File_Type; Item: out Character);


procedure Get (File: in File_Type; Item: out Integer);
procedure Get (File: in File_Type; Item: out String);

Aqui, o parmetro out designa uma varivel na qual um valor character/string/integer


deve ser armazenado.
Cada um desses valores possui um parmetro opcional Width, que permite ao pro-
grama especificar o nmero de caracteres a serem lidos de um arquivo antes que ele
encontre um terminador de linha. Isso semelhante conveno de Fortran, que define o
comprimento de um campo que contm um valor de dado. Se Width=0 for especificado,
o programa tentar ler tantos caracteres quanto puder interpretar como sendo do tipo
apropriado. Por exemplo, suponha que i seja uma varivel do tipo Integer e:
7.6 Semntica de Entrada/Sada 175

myFile: File_type;
Get (myFile, Item => i, Width => 0);

seja executado sobre o fluxo de entrada:

213a345

Isso recuperar o valor 213 da entrada e o atribuir a i.


A E/S formatada em Ada permite que o programa especifique o nmero de dgitos
antes e depois do decimal, o nmero de dgitos exponenciais a serem includos e a base (bi-
nria, hexadecimal ou octal) a usar para exibir o valor. Por exemplo, o seguinte comando:

Put (myFile, Item => 1024, Base => B);

exibe

10000000000

que o inteiro 1.024 em notao binria.


Conclumos esta seo observando que, embora a E/S seqencial formatada e no-for-
matada seja uma atividade de programao razoavelmente direta e central, seus detalhes e
suas idiossincrasias de programao variam bastante entre diferentes linguagens.

7.6.2 Arquivos de Acesso Seqencial


A E/S seqencial possui duas limitaes srias quando so processados arquivos
grandes. A primeira que nem todas as aplicaes acessam as informaes em um ar-
quivo na mesma seqncia na qual elas esto armazenadas. Em segundo lugar, j que
registros individuais em um arquivo seqencial so armazenados adjacentemente uns aos
outros, qualquer tentativa de substituir um registro por um registro maior arrisca sobres-
crever a parte inicial do prximo registro na seqncia.
Arquivos de acesso aleatrio (ou acesso direto) permitem o processamento no se-
qencial de informaes em um arquivo. Tais arquivos ocorrem em diretrios e em outras
aplicaes que precisem evitar o processamento de todos os registros iniciais serialmente
apenas para encontrar ou atualizar uma nica entrada.

Trilha
Brao/cabea

Cilindro

Setores

(a) (b)

| Figura 7.5 A Estrutura de Dispositivos de Armazenamento de Discos


176 Captulo 7 Semntica

Dispositivos de armazenamento em disco so projetados para lidar com o acesso


aleatrio a arquivos. A Figura 7.5a mostra a estrutura de tal dispositivo. Um disco possui
mltiplos pratos, cada um fornece diversas trilhas circulares concntricas. Cada trilha
possui o mesmo nmero de setores de tamanho fixo (Figura 7.5.b). Uma srie de ca-
beas de leitura/gravao, uma por superfcie de prato, fica posicionada para ler/gravar
informaes em um nico cilindro antes que o brao as mova para outro cilindro. Esse
movimento de brao chamado busca.
Em programas em C, uma struct pode ser usada para definir um registro indivi-
dual em uma aplicao de acesso aleatrio a arquivos. Como vimos no Captulo 5, uma
struct simplesmente um rtulo para um conjunto de variveis com tipos diferentes.10
Aqui est um exemplo de uma struct em C que define um registro de dados de uma
aplicao de fluxo de fluidos, os quais esto organizados em uma matriz com nRows x
nCols de registros desse tipo, armazenados em um arquivo de acesso aleatrio.

typedef struct grid_t {


float nRows, nCols;
int noData;
int dir;
} grid_entry;

As bibliotecas C padro contm os seguintes mtodos para acessar dados aleatoriamente:


fseek, ftell, rewind, fgetpos e fsetpos. Estes mtodos permitem a programas em
C buscar um registro especfico, informar qual registro ser lido ou gravado no momento,
retornar ao incio do arquivo, informar a posio corrente do arquivo e redefinir uma nova
posio corrente do arquivo, respectivamente.
Ao acessar aleatoriamente um registro em um arquivo, o comando fseek usado
primeiro para reposicionar o brao de leitura/gravao no cilindro em que o registro est
localizado. Um comando fseek tem a seguinte forma:

fseek(FILE *stream, long offset, int origin)

Aqui, offset o deslocamento em bytes do prximo registro a partir da origem do arqui-


vo, em que a distncia para se reposicionar o brao de leitura/gravao pode ser calculada.
A funo sizeof de C retorna o nmero de bytes de que uma struct necessitar
quando armazenada em um arquivo, e assim o parmetro offset para fseek, para buscar
o registro de ndice n no arquivo, pode ser escrito como (n-1) * sizeof(struct).
O parmetro origin pode ter um entre trs valores constantes, definindo a posio
corrente (SEEK_CUR) no arquivo, o incio (SEEK_SET) do arquivo ou o final (SEEK_
END) deste. O programa ento busca os dados movendo-se de uma origin dada at um
nmero offset de bytes em preparao para a prxima operao de fscanf ou fprintf
nesse arquivo.
A Figura 7.6 mostra uma aplicao de acesso aleatrio em GIS, no qual uma posio
na direo NORTHWEST a partir da posio corrente no arquivo GIS.data recuperada
de um modo no-seqencial. Aqui, x e y definem as coordenadas da posio corrente,
cujas informaes esto armazenadas na struct grid_entry definida anteriormente.
A funo fseek aqui reposiciona o arquivo no incio daquela posio que est na linha
e na coluna anteriores (ou seja, o par x-1 e y-1 designa NORTHWEST). A grade completa
de posies armazenada no disco com as linhas, identificando a maior ordem (semelhante

10. Ada possui o tipo record, que serve para o mesmo propsito da struct em C.
7.6 Semntica de Entrada/Sada 177

FILE* input;
input = fopen(GIS.data, r);
...
x = /x column of entry */;
y = /* row of entry */;
/* seek to the dir variable NORTHWEST of grid_entry */
fseek(input, sizeof(grid_entry) * ((y-2) * nCols
+ (x-1)) + 2 * sizeof(float) + sizeof(int), SEEK_SET);
fscanf(input, %d, &temp);
if (temp == SOUTHEAST)
return true;
...

| Figura 7.6 Segmento de Cdigo em C que Acessa Aleatoriamente uma Posio em uma Grade

ao esquema de armazenamento para matrizes discutido no Captulo 5). Assim, o clculo


offset = sizeof(grid_entry)*((y-2)*nCols+(x-1)) identifica o incio dos dados
referentes a essa posio no arquivo, e o valor incremental 2*sizeof(float)+sizeof
(int) indica a terceira posio aps essa posio corrente. Assim, a funo fscan que se
segue ao comando fseesk na Figura 7.6 recupera o valor dir para a posio na direo
NORTHWEST a partir da posio corrente.
Em Fortran, os programadores usam uma clusula RECL e ACCESS = DIRECT no
comando OPEN para especificar que o arquivo ser acessado aleatoriamente. RECL
usado para especificar o comprimento de um registro individual no arquivo que estiver
sendo aberto. Em seguida, cada comando READ ou WRITE que acesse um registro no
arquivo deve especificar o nmero do registro, usando o parmetro REC para especificar
a posio do registro dentro do arquivo.
A seguir, est um par de comandos em FORTRAN que simulam os comandos
fopen, fseek e fscanf da Figura 7.6, supondo que x e y representem a coluna e a linha
da posio corrente.

OPEN(7, ACCESS= DIRECT, RECL=16)


READ(7, REC = (y-1)*nCols + x) nCols, nRows, noData, dir

O comprimento do registro representado aqui supe que cada valor REAL e INTEGER
ocupa 4 bytes de memria, de modo que o equivalente em FORTRAN a toda struct
grid_entry em C ocupa 16 bytes.11
Observamos finalmente que a soluo em FORTRAN executa automaticamente uma
busca dentro do comando READ, no qual o nmero do registro da entrada procurado
especificado para facilitar a busca, ou seja, a posio do registro no arquivo calculada
pelo sistema como rec*RECL.

7.6.3 Semntica da Manipulao de Erros de E/S


A maioria dos erros em tempo de execuo em situaes prticas de programao ocor-
re durante operaes de entrada e sada. A expresso muito comum lixo para dentro, lixo

11. Lembre-se de que a indexao de matrizes em FORTRAN comea em 1 (em vez de em 0), de modo que o
clculo do nmero relativo do registro ligeiramente diferente daquele da Figura 7.6.
178 Captulo 7 Semntica

para fora (a abreviao em ingls GIGO) foi cunhada para denotar que, se um pro-
grama tiver recebido entrada de dados ruins, provavelmente ter produzido resultados
ruins. Todavia, linguagens mais antigas forneciam poucas ferramentas de programao
para capturar erros de E/S. Linguagens mais recentes incorporam ferramentas sofistica-
das para suportar a deteco e a correo de erros de E/S durante a execuo. Assim, a
expresso GIGO tem menos probabilidade de caracterizar programas escritos em lingua-
gens modernas, assumindo que esses programas utilizam efetivamente os dispositivos de
manipulao de erros disponveis.
Nesta seo, resumimos os recursos de manipulao de erros de E/S de verses re-
centes de Fortran, C++ e Ada. Esse tratamento fornece uma introduo conveniente para
a Seo 7.7, que cobre o tpico mais geral de semntica de manipulao de erros.
A Figura 7.7 mostra um pequeno programa em Fortran que tenta abrir e ler um valor
inteiro de um arquivo e imprime a sada dessa operao na tela. O veculo de Fortran
para a manipulao de erros de E/S a clusula IOSTAT, em que uma varivel inteira
designada para monitorar o status de uma operao OPEN, READ ou WRITE. Quando
usado com um comando OPEN, o valor 0 significa que o arquivo foi aberto com sucesso;
caso contrrio, um inteiro positivo atribudo a ela.
Quando um comando READ ou WRITE encontra erros de formatao, condies de
final de arquivo ou de final de registro, o programa abortar, a menos que uma clusula
IOSTAT esteja presente. Nesse caso, a varivel IOSTAT recebe um valor negativo, se o
final de um arquivo for encontrado, um valor positivo, se ocorrer um erro de entrada, ou
zero, se no ocorrer nem o final de arquivo nem o erro de entrada.
A partir de Fortran, projetos de linguagens mais recentes suportam a manipulao de
erros de E/S por meio do uso de mecanismos de exceo. Esses mecanismos suportam
no apenas a manipulao de eventos inesperados de E/S como tambm a manipulao de
uma diversidade de outros eventos inesperados em tempo de execuo. Tratamos do uso da
manipulao de excees para erros de E/S no restante desta seo, deixando um tratamento
mais amplo da manipulao de excees para outros tipos de eventos na Seo 7.7.

PROGRAM main
INTEGER :: error
OPEN(UNIT=7,FILE=myData,STATUS=OLD,IOSTAT=ioerror)
IF (ioerror==0) THEN
WRITE (*, (1X,A)) opened without errors
READ (7, *, IOSTAT=ioerror) InValue
IF (error < 0)
WRITE (*, (1X,A,I3)) End of file : ,ioerror
ELSE IF (ioerror > 0)
WRITE (*, (1X,A,I3)) error on READ: ,ioerror
ELSE WRITE (*, (1X, I3,A)) InValue, was read
END IF
CLOSE (UNIT=7)
ELSE IF (ioerror > 0) THEN
WRITE (*, (1X,A,I4,A)) error on OPEN ,ioerror, &
: file does not exist
END IF
END PROGRAM main

| Figura 7.7 Exemplo de Manipulao de Erros de E/S em Fortran


7.7 Semntica de Manipulao de Excees 179

| Tabela 7.4 Excees de E/S Predefinidas em Ada

Exceo Significado

Status_Error Tentativa de leitura ou gravao em um arquivo que no esteja aberto


Tentativa de leitura de um arquivo de sada ou gravao em um arquivo
Mode_Error
de entrada
Name_Error Uso de um nome de arquivo inapropriado em um comando de abertura
Tentativa de enviar a sada para um dispositivo de entrada (por exemplo,
Use_Error
o teclado)
Device_Error Mal funcionamento de hardware
End_Error Tentativa de leitura aps o final do arquivo
Data_Error O valor lido incompatvel com o tipo da varivel-alvo
Layout_Error Tentativa de referenciar o nmero da coluna ou da linha alm do limite

C++, Ada e Java tm mecanismos abrangentes de manipulao de excees, en-


quanto Fortran, Pascal e C no tm. O mecanismo de manipulao de excees de C++
preenche a lacuna entre uma definio de classe e seu cliente, ou seja, embora a classe
contenha informaes detalhadas sobre quais tipos de erros podem ocorrer, ela no sabe
onde tais erros podem ocorrer apenas o programa cliente o sabe.
Ada suporta uma quantidade de excees predefinidas de E/S e outros tipos. Da mesma
forma que C++, Ada tambm permite aos programadores definir suas prprias excees. En-
tretanto, Ada no manipula excees por padro, de forma que, caso uma exceo seja gerada,
o programa terminar e exibir informaes sobre o tipo e a localizao da exceo.
A Tabela 7.4 lista as excees predefinidas em Ada, com uma breve descrio de
cada. Por exemplo, o cdigo de manipulao de excees em Ada da Figura 7.8 simula
um pouco do comportamento do cdigo em Fortran da Figura 7.7.
Esses exemplos em C++ e Ada ilustram o progresso que ocorreu na manipulao
de erros de E/S desde o projeto de Fortran. Especificamente, a manipulao de erros por
meio do mecanismo de excees de uma linguagem pode resultar em um cdigo mais
limpo e um conjunto mais poderoso de alternativas para o programa responder a erros de
E/S. Essa flexibilidade se estende questo mais geral de manipulao de excees que
ocorrem em outras partes do programa alm da E/S, conforme discutimos na Seo 7.7.

7 .7 SEMNTICA DE MANIPULAO DE EXCEES


A robustez tem se tornado criticamente importante medida que os computadores
tm sido embarcados em dispositivos de tempo real como motores de automveis, siste-
mas de controle de aeronaves e assim por diante.
Definio: Aplicaes so robustas quando continuam a operar sob todas as
situaes de erro presumveis.
Um computador que auxilia na operao de um automvel ou no vo de uma aeronave
no pode parar simplesmente porque ocorreu uma condio excepcional.
A manipulao de excees fornece um meio para um programa lidar com erros
inesperados em tempo de execuo, E/S e outros tipos. Geralmente essencial para um
programa poder responder a erros em tempo de execuo quando eles ocorrem, em vez
de deix-los causar o encerramento do programa prematuramente.
Definio: Uma exceo uma condio de erro que ocorre a partir de uma ope-
rao que no pode ser resolvida por si s.
180 Captulo 7 Semntica

with Ada.Text_IO, Ada.Integer_IO;


use Ada.Text_IO, Ada.Integer_IO;
procedure main is
begin
error: Integer;
inValue: Integer;
myData: File_Type;
Open (Unit7, In_File, myData);
Get (Unit7, inValue);
Put (Opened file without errors);
New_Line;
Put (Value read is: ); Put (inValue);
exception
when Name_error =>
Put (Error on open: improper file name);
when Data_error =>
Put (Error on read: invalid integer);
end;
end main;

| Figura 7.8 Exemplo de Manipulao de Erro de E/S em Ada

Excees podem ocorrer em vrios nveis de abstrao. No nvel de hardware, ex-


cees incluem operaes ilegais como diviso por zero e referncias ilegais memria
como falhas de segmentao e erros de barramento. No nvel de linguagem de programa-
o, excees podem ser causadas por eventos como um ndice de matriz fora dos limites,
uma tentativa de leitura de um valor do tipo errado ou uma tentativa de acesso a um objeto
na pilha usando um ponteiro nulo. Excees de nveis mais altos tambm so teis para
tipos especficos de estruturas de dados, como a tentativa de execuo de uma operao
de desempilhamento de uma pilha vazia.
Durante muito tempo, nenhuma linguagem com amplo uso forneceu um mecanismo
de excees verdadeiramente geral e usvel. Algumas linguagens (como Cobol e PL/I)
forneciam recursos limitados de exceo. O dispositivo de excees de Cobol era bastan-
te limitado, mas PL/I estendeu bastante a idia de manipulao de excees.
PL/I forneceu uma longa lista de excees predefinidas, assim como a capacidade de
um programa definir excees especficas de aplicaes. Os projetistas de Ada estenderam
a abordagem de PL/I, ao mesmo tempo em que incorporaram as idias de manipulao
de excees das linguagens experimentais CLU (Liskov e Snyder, 1979; Liskov e outros,
1981) e Mesa (Mitchell et al., 1979). Linguagens modernas (como C++, Ada e Java) agora
incorporam um extenso mecanismo de excees para facilitar aplicaes robustas.
Gerar uma exceo sinalizar que a condio que ela representa ocorreu. No caso
da diviso por zero, a exceo gerada pelo hardware. Capturar uma exceo significa
transferir o controle para um manipulador de excees, o qual define a resposta que o pro-
grama recebe quando a exceo ocorre. No caso de diviso por zero, o controle pode ser
transferido pela interrupo de hardware para uma rotina de manipulao de interrupes,
que poderia exibir uma mensagem e terminar o programa.
7.7 Semntica de Manipulao de Excees 181

Embora exista um modelo semntico formal de excees baseado em semntica


denotacional (Alves-Foss (ed.), 1999), matematicamente muito complexo para ser til
neste estudo. Na Seo 7.7.1, damos um tratamento mais conceitual de semntica de ma-
nipulao de excees, tanto em linguagens de programao que no possuem recursos
de excees gerais quanto naquelas que os tm.

7.7.1 Estratgias e Questes de Projeto


Como os programadores lidam com situaes excepcionais em linguagens como
Pascal, C e Fortran, que no possuem um mecanismo de excees? Geralmente eles in-
ventam uma conveno para lidar com a exceo que permite ao programa continuar de
forma elegante quando ela ocorre.
Outra tcnica comum definir uma funo que retorne um valor incomum ou ilegal
quando ocorrer uma falha em tempo de execuo. Por exemplo, durante a pesquisa de
determinado valor em uma matriz, se esse valor for encontrado, geralmente o ndice deste
ser retornado. Contudo, se o valor no estiver na matriz, ento (no caso de matrizes inde-
xadas em 0) o valor 1 pode ser retornado, o que indica que o valor no foi encontrado.
Entretanto, um mecanismo definido pelo programador, muitas vezes, um substi-
tuto fraco para um mecanismo de excees interno. Analise, por exemplo, o seguinte
fragmento de cdigo simples em Pascal que l uma srie de nmeros de um arquivo e
calcula sua soma:

sum := 0.0;
while not eof(file) do
begin
read(file, number);
sum := sum + number;
end;

Diversas excees possveis impedem que esse cdigo seja robusto. Primeiro, se houver
espao em branco aps o ltimo nmero, o teste de final de arquivo retornar falso, mas
o read pular esse espao em branco e gerar um erro fatal quando o final do arquivo for
atingido. Outro tipo de exceo pode ocorrer se um valor de entrada no for um nmero
vlido; por exemplo, se o valor contiver um caractere que no for dgito. Isso tambm
gera um erro fatal. Esses tipos de erros so difceis de contornar via programao.
Linguagens modernas incorporam estratgias em nvel de programao nas suas bi-
bliotecas de classe e de funo. Por exemplo, quando o mtodo Java indexOf usado
para pesquisar um Vetor, retorna 1 quando a pesquisa malsucedida.
Definies similares ocorrem em muitas outras bibliotecas de funes Java. Ao
recuperar um valor associado a determinada chave em uma tabela hash, Java retorna
um objeto null se a chave no for encontrada. De forma semelhante, ao ler linhas de um
BufferedReader usando o mtodo readLine, um objeto null retornado quando o
final do arquivo for atingido. Essa abordagem tambm usada nas bibliotecas C padro.
Outra abordagem programao de excees adicionar um parmetro de erro (ou
retornar um cdigo de erro para uma funo void). Um exemplo do Turbo Pascal o da
funo val, que converte uma representao em string de um nmero para binrio:

val(numberString, int, error);


182 Captulo 7 Semntica

Se a varivel numberString contiver o valor 123 antes da chamada a val, ento o int
resultante possui o valor 123, e error possui o valor 0. Se, em vez disso, numberString
contiver o valor 1x3, ento int indefinido e error possui o valor 2, que a posio na
string em que o caractere ilegal aparece.
Se tais situaes so verdadeiramente excees ou apenas parte do processamento
normal de tabelas e arquivos uma questo a ser debatida. Em Java, essas situaes no
so consideradas excees, enquanto em Ada, por exemplo, espera-se continuar a ler at
que seja encontrado um final de arquivo para disparar essa exceo.
Uma explicao possvel para a diferena nas filosofias de manipulao de excees
entre Ada e Java que, em Java, as excees so objetos. Assim, criar uma exceo en-
volve uma alocao de heap que deve acabar sofrendo a coleta de lixo aps a exceo ter
sido manipulada. Entretanto, uma exceo em Ada um tipo primitivo interno da lingua-
gem, de modo que o custo de gerar e manipular excees menor do que em Java.
Quando uma exceo disparada, o fluxo de controle transferido diretamente para
um mecanismo de manipulao de excees. Duas questes importantes surgem ao se
projetar um mecanismo efetivo de manipulao de excees:
1 Como cada manipulador associado a cada exceo possvel de ser gerada?
2 Um manipulador de exceo deve continuar a executar o cdigo que gera essa
exceo?
Para abordar a primeira questo, excees em nvel de hardware so definidas para
corresponder a interrupes especficas de hardware. Existe basicamente um manipulador
de interrupes distinto para cada tipo de interrupo. Em uma aplicao grande, tal or-
ganizao menos til do que permitir que diferentes partes de uma aplicao respondam
diferentemente a determinada exceo quando ela ocorrer.
Ada, C++ e Java permitem que um manipulador de excees seja associado a um
bloco de cdigo. Tal bloco pode ser um mtodo ou uma funo inteira ou simplesmente
um grupo de comandos dentro de uma construo maior, como uma seqncia de coman-
dos, uma condio ou um lao.
Se nenhum manipulador de excees aparecer dentro da funo em que a exceo
for gerada, a pesquisa de um manipulador ser propagada pela pilha de chamadas at a ro-
tina que a chamou. Se a chamada ao mtodo ou funo estiver dentro de um bloco com
um manipulador para essa exceo, ento ela capturada aqui. Caso contrrio, a pesquisa
continua pela pilha de chamadas at o primeiro manipulador de excees apropriado.
Ao abordar a segunda questo, duas abordagens alternativas foram usadas, denomi-
nadas modelo de prosseguimento e modelo de finalizao, e esto resumidas de forma
ilustrada na Figura 7.9 (usando sintaxe Java).

Figura 7.9 ...

| Modelos de Manipulao de
Excees Prosseguimento
versus Finalizao
try {
...
chamada ao mtodo
... gera
} catch (. . .) { exceo
prosseguimento ...
} catch (. . .) {
...
finalizao } // final do try
...
7.7 Semntica de Manipulao de Excees 183

Manipuladores de interrupo em um sistema operacional implementam o modelo


de recomeo, ou seja, aps lidar com a interrupo, o manipulador fica livre para pros-
seguir com o cdigo que gerou a exceo. PL/I usa o modelo de prosseguimento. Meca-
nismos de manipulao de excees em Ada, C++ e Java, entretanto, usam o modelo de
finalizao; o cdigo que sinaliza a exceo no pode ser continuado.
Programadores obtm o efeito do modelo de finalizao executando um comando
return ou goto no-local dentro do manipulador de excees. De forma semelhante,
a programao cuidadosa em Ada, C++ e Java pode ser usada para simular o modelo
de prosseguimento.
De modo geral, o modelo de prosseguimento mais complexo de se implementar, j
que deve restaurar o estado inteiro do programa antes de a exceo ter sido gerada. Isso
pode incluir voltar pela pilha de tempo de execuo e restaurar os valores de determina-
das variveis. O modelo de finalizao, claro, no tem tanta complexidade.

7.7.2 Manipulao de Excees em Ada, C++ e Java


Um programa em Ada pode conter cdigo que responda a excees quando elas
ocorrerem. Quando uma exceo ocorre, o sistema de tempo de execuo procura um
manipulador de excees que corresponda exceo gerada. Se ele no encontrar um,
a rotina corrente termina e passa a exceo para a rotina que a chamou. Esse processo
continua at que encontre um manipulador que lide com a exceo ou termine a rotina
principal, em cujo caso o programa termina.
Se o sistema encontrar um manipulador para a exceo, ele executa o cdigo no
manipulador e ento termina a rotina corrente. Se uma exceo for gerada na parte de-
clarativa de uma rotina, Ada abandona os processamentos posteriores dessa declarao.
O mesmo acontece com excees geradas dentro de um manipulador de excees, elimi-
nando, assim, o perigo de laos infinitos na manipulao de excees.
Um programa em Ada pode gerar uma exceo explicitamente com o comando
raise. Esta exceo pode ser uma exceo predefinida em Ada ou definida no prprio
programa. Todavia, j que excees em Ada so definidas de forma geral, uma exceo
mais especfica definida pelo programa, muitas vezes, mais til. Dentro de um manipu-
lador, a exceo raise with no condition pode ser empregada para propagar a exce-
o para um nvel mais alto na seqncia de chamadas. Isso pode ser til para assegurar a
recuperao elegante em todos os nveis, quando uma exceo for fatal para o programa.
Excees em C++ e Java so definidas como classes. Essas duas linguagens contm
dispositivos semelhantes para definir, gerar e capturar uma exceo.
C++ usa as seguintes palavras-chave para manipulao de excees: try, throw e
catch. A sintaxe de um comando de manipulao de excees :

try {
// cdigo que pode gerar uma exceo
}
catch ( // tipo da exceo)
{
//cdigo a ser executado quando esse tipo de exceo ocorrer
}
184 Captulo 7 Semntica

A semntica de um manipulador de excees em C++ bastante direta. Primeiro, o c-


digo dentro do bloco try executado normalmente. Se nenhuma exceo ocorrer, o bloco
catch pulado. Se uma exceo ocorrer (por exemplo, se uma funo throw for executada
para essa exceo), o cdigo do bloco catch executado imediatamente e o controle no
retorna para o comando em que a exceo foi gerada.
A seguir est um exemplo em C++, em que o programa pode capturar uma instn-
cia de um ndice de matriz fora dos limites declarados e exibir uma mensagem quando
isso ocorrer.

#include <iostream.h>
int main () {
char A[10];
cin n;
try
{
for (int i=0; i<n; i++)
{
if (i>9) throw array index out of range;
A[i]=getchar();
}
}
catch (char * s)
{
cout << Exception: << s << endl;
}
return 0;
}

Assim que a exceo capturada, o programa executa o bloco do catch e retorna 0. De


modo geral, o parmetro para catch pode ser de qualquer tipo vlido. Alm disso, catch
pode ser sobrecarregado de modo que possa receber diferentes tipos como parmetro.
Neste caso, o bloco catch executado o que corresponde exceo gerada. Se uma
exceo no for capturada por nenhum comando catch, o programa terminar e exibir
uma mensagem de erro de Trmino anormal.
A biblioteca padro de C++ predefine algumas excees que podem ser capturadas
quando ocorrerem dentro de um bloco try. As excees a seguir so subclasses da classe
std::exception:

Exceo C++ Gerada Quando:

bad_alloc O armazenamento no heap no puder ser alocado para um comando new


bad_cast Uma converso no puder ser completada
bad_exception Uma exceo no corresponde a algum catch

bad_typeid Um tipo no pode ser determinado por typeid


logic_error Um erro de programao (por exemplo, ndice fora do limite) ocorre
runtime_error Uma circunstncia fora do controle do programa ocorre
7.7 Semntica de Manipulao de Excees 185

Figura 7.10
|
Throwable
Hierarquia de Classes
de Exceo em Java Error Exception

RuntimeException (excees
verificadas)

(excees
no-verificadas)

J que Java uma linguagem mais nova do que C++, seu suporte a excees mais
bem desenvolvido. A Figura 7.10 mostra a classe Exception de Java e algumas de suas
classes relacionadas.
Algumas excees em Java so predefinidas dentro da hierarquia de classes. Todas
as excees em Java so subclasses de Throwable e esto divididas em trs categorias:
1 Erros na mquina virtual (Error e suas subclasses);
2 Excees em tempo de execuo (RuntimeException e suas subclasses), como o
acesso a um objeto nulo ou um ndice ilegal;
3 Todas as outras.
As duas ltimas categorias so subclasses da classe Exception e, por conveno, tm
a palavra Exception como parte do seu nome. De forma semelhante, erros na mquina
virtual, por conveno, tm a palavra error como parte do seu nome.12
As excees, em contraste com os erros na mquina virtual, esto divididas em
verificadas e no-verificadas. Formalmente, uma exceo verificada uma instncia
da classe Exception ou uma subclasse que no seja instncia de RuntimeException.
Excees verificadas so excees em nvel de aplicao. As bibliotecas de classe Java
contm uma ampla variedade de excees, incluindo excees de E/S, excees de rede
e assim por diante.
Uma exceo no-verificada uma instncia da classe RuntimeException ou uma
de suas subclasses. Java requer que excees verificadas sejam capturadas por um mani-
pulador de excees, enquanto um manipulador de excees opcional para uma exceo
no-verificada. Exemplos de excees no-verificadas incluem ndices fora de limites de
matrizes ou substrings, acesso por meio de um objeto nulo e diviso por zero.
Uma exceo definida pelo programador em Java um objeto da classe Exception
ou uma de suas subclasses; assim, uma exceo definida pelo programador pode ser ve-
rificada ou no-verificada. Uma exceo pode conter uma mensagem arbitrria que tenha
informaes. Assim, a classe Exception possui dois construtores, cujas assinaturas so:

public Exception( );
public Exception(String s);

Assim, cria-se uma nova exceo por meio de uma subclasse de Exception ou de uma de
suas subclasses, conforme mostrado a seguir.

12. Excees em Java do tipo Error geralmente no so a principal preocupao do programador; elas incluem
erros de mquina virtual tais como falta de memria, overflow de pilha de tempo de execuo e assim por diante.
186 Captulo 7 Semntica

class StackUnderflowException extends Exception {


public StackUnderflowException() {super(); }
public StackUnderflowException(String s) {super(s); }
}

Um uso tpico de uma exceo definida pelo programador seria:

if (stack == null)
throw new StackUnderflowException();

Objetos que constituem excees (em oposio a subclasses) raramente recebem


nomes, j que seu nico propsito serem gerados. Subclasses de Exception podem ter
informaes adicionais ou fornecer comportamento especializado. Todavia, na maioria
dos casos, elas existem apenas para fornecer novas subclasses de excees. A subclasse
mais comum a IOException e suas subclasses no pacote java.io.
Um mtodo em Java necessrio para especificar por meio de uma clusula throws
no seu cabealho qualquer exceo verificada que no capture a si prpria. Um exemplo
o mtodo readLine de um BufferedReader:

public String readLine( ) throws IOException;

Por meio disso, o compilador Java pode assegurar que cada exceo verificada seja cap-
turada e manipulada pela aplicao.
Excees so capturadas de duas formas. Uma chamada a um mtodo que potencial-
mente gere uma exceo deve aparecer em um comando try ou o mtodo que contenha
a chamada deve declarar quais excees ele gera. Analise o cdigo a seguir que, dado
um filename, tenta abrir esse arquivo para entrada:

try { BufferedReader infile = new BufferedReader(


new FileReader(filename));
... // processar arquivo
} catch (FileNotFoundException e) {
System.out.printIn(File not found: + filename);
System.exit(1);
} // try

Um comando catch em Java semelhante a um cabealho de mtodo, pois seu ar-


gumento formal especifica o tipo (ou supertipo) da exceo que ele est capturando, que
pode ser usado como qualquer outro objeto.13 Nesse caso, se a exceo for gerada, uma
mensagem ser impressa e a aplicao ser concluda por meio de uma sada diferente de
zero (erro). Se no houver sada, o fluxo de controle continuar com o prximo comando
aps o try; ou seja, Java usar o modelo de finalizao para manipular excees. A forma
geral de um comando try em Java :

13. Essa semelhana limitada, entretanto, j que um catch no possui tipo de retorno ou modificadores de
visibilidade.
7.7 Semntica de Manipulao de Excees 187

try {
// cdigo que pode dispar um ou mais tipos de excees
...
} catch (Exception type e) {
...
} catch (Exception type e) {
...
} finally
...
}

Se uma exceo for gerada, cada clusula catch avaliada em ordem at que ocorra
uma correspondncia. A seguir, o bloco associado clusula catch executado. Cada
try pode ter tantas clusulas catch quanto desejado. Observe que um nico try pode
ser usado para capturar mltiplas excees por meio de um grupo de comandos, desde
que cada possvel exceo verificada seja capturada. Uma forma simples de simular um
catch genrico para cada exceo verificada usar catch (Exceo) como a ltima
(ou nica!) clusula catch.
A clusula finally, se estiver presente, sempre ser executada, seja uma exceo
gerada ou no. No possvel evitar a execuo do cdigo na clusula finally. Essa
clusula, muitas vezes, usada para fechar arquivos que haviam sido abertos e processa-
dos dentro do seu comando try.
A outra alternativa para se usar um try em chamadas a mtodos que geram excees
fazer o prprio mtodo que a contm propagar a exceo por meio de uma clusula throws:

public void readFile (BufferedReader infile)


throws IOException
String line;
while ((line = infile.readLine()) != null) {
... // processar linha
}
}

Essa rotina l linhas por meio do mtodo readLine da classe BufferedReader, que pode
gerar uma IOException. Como a chamada a readLine no aparece em um comando
try, o mtodo readFile deve declarar que no trata a exceo.
Observe que, no final do arquivo, o mtodo readLine retorna um valor nulo, em
vez de gerar uma exceo de final de arquivo. Em contraste, outros mtodos de leitura
em Java geram uma exceo no final do arquivo, assim como Ada.
Excees em Java so executadas pelo comando throw:

throw new Exception( );

Neste ponto, o fluxo normal de controle concludo, e o controle transferido para o


catch correspondente do try mais prximo. Se no houver um, o mtodo chamado pro-
paga a exceo e o processo continua pela pilha de tempo de execuo (para obter mais
detalhes sobre a dinmica da pilha de tempo de execuo, veja o Captulo 10).
188 Captulo 7 Semntica

Observe que a exceo gerada anteriormente annima, no sentido de que nenhuma


referncia a ela guardada pelo nome. J que o fluxo de controle no continua aps esse
ponto, geralmente no serve a nenhum propsito manter uma referncia exceo.
Normalmente, a exceo gerada seria uma das subclasses de Exception, talvez at
mesmo com uma mensagem anexada:

throw new StackUnderflowException(pop on an empty stack);

O principal propsito de se criar novas excees dar um nome a uma categoria que
seja significativa para a aplicao e agrupar excees por categoria. Um exemplo dessa
ltima IOException, que a classe-me de todas as excees de entrada/sada.

Exemplo: Manipulando a Entrada de Forma Robusta Excees que cau-


sam o trmino de um processo de entrada em Java ocorrem com freqncia em aplicaes
que usam um argumento de linha de comando para identificar um arquivo de entrada.
Um mtodo main tpico para manipular tal cenrio mostrado na Figura 7.11.
Quando o usurio fornece um nome de arquivo vlido em tempo de execuo, esse
arquivo aberto e processado normalmente. Todavia, diversos outros eventos podem
ocorrer. Por exemplo, o usurio poderia esquecer de fornecer um argumento ou ento
poderia fornecer um argumento que fosse um nome de arquivo invlido ou o nome de
um arquivo que no pudesse ser lido pela aplicao. O programa projetado para res-
ponder a qualquer um desses eventos.
Se nenhum argumento for fornecido na linha de comando, ento a referncia a
arg[0] gera uma ArrayIndexOutOfBoundsException, j que o ndice 0 excede o ta-
manho real da matriz. Normalmente, a indexao no precisa aparecer em um comando
try; fazer isso, nesse caso, permite que a aplicao fornea uma mensagem de erro es-
pecfica da aplicao, em vez da mensagem de erro da linguagem Java. Essa ltima seria
presumivelmente ininteligvel para o usurio.
Em resposta, o catch captura uma ArrayIndexOutOfBoundsException, que a
classe-me da exceo realmente gerada. Alm da relativa conciso do nome da classe-me,
parece haver pouca razo, nesse caso, para se preferir uma em relao outra. Um uso desse

public static void main(String[] arg) {


BufferedReader rdr = null;
try {
rdr = new BufferedReader(new FileReader(arg[0]));
} catch (IndexOutOfBoundsException e) {
System.err.printIn(Missing argument);
displayUsage( );
System.exit(1);
} catch (FileNotFoundException e) {
System.err.printIn(Cannot open file: + arg[0]);
System.exit(1);
}
process(rdr);
}

| Figura 7.11 Manipulao Robusta de Entradas em Java


7.7 Semntica de Manipulao de Excees 189

recurso se aplica para listar todas as excees especficas primeiro e depois usar uma clas-
se-me (por exemplo, Exception) efetivamente para capturar todas as outras excees.
Outro evento que pode ocorrer o arquivo no poder ser aberto para leitura. Isso
pode ser causado por diversos tipos de situaes, conforme explicado anteriormente. Em
qualquer um desses casos, a classe FileReader gera uma FileNotFoundException.
Nesse caso, a exceo capturada no mtodo main, e uma mensagem de erro apropria-
da exibida. Observe que, nesse exemplo, cada catch termina com uma sada de erro
diferente de zero.
A soluo dada anteriormente possui outro aspecto notvel, ou seja, a chamada ao
mtodo process no pode ser includa no bloco try. Se fosse, qualquer exceo de li-
mite de ndice no capturada no mtodo process resultaria na mensagem Argumento
ausente, o que seria enganoso. Manter a chamada a process fora do bloco try-catch
requer declarar a varivel BufferedReader fora do bloco try-catch e inicializ-la ex-
plicitamente como null.

Exemplo: Verificando Entrada Invlida pelo Teclado Neste exemplo,


exploramos como o modelo de prosseguimento de manipulao de excees pode ser
simulado em Java com o uso de um lao. Esse exemplo tipifica aplicaes que solicitam
ao usurio uma entrada, e ocorre um erro de teclado. Uma resposta apropriada simples
notificar o usurio do erro e solicitar que ele tente novamente.
Antes da verso 1.5, Java no fornecia um mtodo read sobrecarregado para os
diferentes tipos primitivos de dados, de modo que fossem convertidos da representao
em texto para, por exemplo, um inteiro em sua representao interna (binrio de 32 bits
para inteiros), como o que ocorre para print.14 Em vez disso, uma representao em
string do nmero lida e convertida para (por exemplo) um int. Supondo que um objeto
BufferedReader in tenha sido criado com o uso do cdigo:

BufferedReader in = new BufferedReader(


new InputStreamReader(System.in));

ento o cdigo necessrio apresentado na Figura 7.12.

while (true) {
try {
System.out.print (Enter number : );
number = Integer.parseInt(in.readLine ( ));
break;
} catch (NumberFormatException e) {
System.out.printIn (Invalid number, please reenter.);
} catch (IOException e) {
System.out.printIn(Input error, please reenter.);
} // try
} // while

| Figura 7.12 Exemplo do Modelo de Prosseguimento

14. Lembre-se da discusso anterior sobre a E/S formatada em Java 1.5.


190 Captulo 7 Semntica

Se um nmero vlido for digitado, nenhuma exceo ser gerada e o lao ser con-
cludo normalmente. Contudo, se um nmero invlido for digitado, uma exceo ser ge-
rada, uma mensagem de erro ser impressa e o lao ser repetido, solicitando novamente
um nmero ao usurio. Nesse cdigo, o mtodo readLine pode gerar uma IOException,
que deve ser capturada pelo comando try.
O exemplo de entrada invlida usa um lao potencialmente infinito para contornar o
modelo de finalizao usado por Java para manipular excees. O lao aqui s pode ser
concludo se nenhuma exceo for gerada. Assim, o uso do lao permite ao programador
obter efetivamente um modelo de prosseguimento de manipulao de excees.

Exemplo: Definindo uma Exceo Especfica de Aplicao Ao desen-


volver um mtodo ou uma classe reusvel, pode haver erros que o desenvolvedor da clas-
se gostaria que o programa cliente manipulasse. A maioria das linguagens que fornecem
manipulao de excees permite ao programador criar tais excees especficas para a
aplicao e ger-las sob as condies apropriadas. Estas geralmente representam condi-
es das quais o programa cliente pode querer se recuperar.
Um exemplo tpico ocorre no desenvolvimento de uma classe de pilha. Suponha
que temos a classe StackUnderflowException apresentada no incio da Figura 7.13.
Quando a pilha estiver vazia, uma operao pop no pode ser definida. Assim, o cdigo
tpico para pop em uma classe Stack de ints usando uma implementao de matriz
apresentada no restante da Figura 7.13.
O cdigo declara a matriz stack com alguma capacidade mxima; a varivel top
registra o topo corrente da pilha. StackUnderflowException declarada como uma
subclasse da classe Exception e se baseia nessa classe-me para seus mtodos.
O mtodo pop simples: primeiro, ele decrementa top e ento retorna o valor de
stack[top] usando o valor decrementado de top. Entretanto, essa operao no segu-
ra; se o valor original de top for menor ou igual a zero, uma ArrayIndexOutOfBounds-
Exception ser gerada.

class StackUnderflowException extends Exception {


public StackUnderflowException( ) { super(); }
public StackUnderflowException(String s){ super(s);}
}
...
class Stack {
private int stack[];
private int top = 0;
...
public int pop() throws StackUnderflowException {
if (top <= 0)
throw new StackUnderflowException(pop on empty stack);
return stack[--top];
}
...
}

| Figura 7.13 Definindo e Usando uma Exceo Especfica de Aplicao


7.7 Semntica de Manipulao de Excees 191

Entretanto, quem chama esse mtodo no deve ter de lidar com a exceo. Assim,
em vez disso, uma StackUnderflowException criada e gerada, conforme necessrio,
pelo mtodo pop. O cabealho desse mtodo deve listar qualquer exceo que ele possa
gerar em uma clusula throws. Assim, qualquer chamada ao mtodo pop deve capturar
explicitamente essa exceo.
Como esse exemplo ilustra, criar uma nova exceo um recurso simples, porm
poderoso, em Java.

7.7.3 Excees e Asseres


Excees e manipuladores de erros de E/S so dois dispositivos distintos que os
programadores podem usar para responder a eventos inesperados em tempo de execu-
o. Um terceiro dispositivo, que especialmente til durante o desenvolvimento de pro-
gramas, a assim chamada assero,15 um predicado inserido em algum ponto de um
programa que descreve um estado que as variveis ativas devem satisfazer sempre que
esse ponto for atingido durante a execuo. Se esse estado no for satisfeito, a assero
dita como tendo falhado, e a execuo pode ser interrompida. Assim, asseres so
um pouco semelhantes a excees no sentido de que podem definir e responder a eventos
inesperados em tempo de execuo.
A linguagem Eiffel foi a primeira a incorporar integralmente asseres. Verses pos-
teriores de Java tambm incluem asseres. Nesta seo, examinamos o mecanismo de
Java (adicionado na verso 1.4) para definir e manipular asseres. Tambm examinamos
o desafio de implementar um mecanismo de asseres em verses anteriores de Java.
De modo geral, as asseres so teis na verificao de programas porque podem repre-
sentar as assim chamadas precondies, ps-condies e invariantes dos laos. Um tra-
tamento cuidadoso no uso de asseres na verificao de programas o foco do Captulo 18.
Informalmente, a precondio de uma funo uma restrio sobre os valores dos
seus argumentos que deve ser satisfeita para que a funo execute sua tarefa. De forma
semelhante, uma ps-condio um tipo de promessa de que, se a chamada fornecer
os argumentos que satisfaam precondio, o resultado satisfar a ps-condio. Um
invariante do lao uma expresso sobre o que deve ser verdadeiro antes (e depois) de
cada repetio do corpo de um lao. Todas essas trs devem ser escritas na forma de uma
expresso boleana em linguagens como Java, C++ e Ada.
Por exemplo, analise o clculo de nmero de Fibonacci de ndice n mostrado na
Figura 7.14 que uma variante em Java do programa em C++ da Figura 10.5. Esse pro-
grama l um inteiro como um argumento de linha de comando e relata um problema, se
esse inteiro for negativo. Conforme mostrado aqui, a precondio da funo fibonacci
que o valor inteiro de n fornecido por qualquer chamada deve ser no-negativo, e sua
ps-condio uma promessa de retorno do nmero de Fibonacci de ndice n para esse
determinado valor de n.
A sintaxe EBNF do comando de assero de Java definida a seguir:
ComandoAssero assert expressoBoleana [ : expressoString ];
Aqui, a expressoString opcional define a mensagem que ser exibida sempre que a expresso-
Boleana for false quando o ComandoAssero for atingido em tempo de execuo.
Semanticamente, um comandoAssero interromper a execuo do programa se
sua expressoBoleana for false quando for atingido durante a execuo do programa,
em cujo caso sua expressoString tambm exibida. Se sua expressoBoleana for true, a

15. O termo est definido formalmente no Captulo 18.


192 Captulo 7 Semntica

public class assertTest {


public static int fibonacci (int n) {
int fib0, fib1, temp, k;
// precondition: n >= 0
assert n >= 0 : Invalid argument for fibonacci ;
fib0 = 0; fib1 = 1; k = n;
while (k > 0) {
temp = fib0;
fib0 = fib1;
fib1 = fib0 + temp;
k = k - 1;
}
// postcondition: fib0 is the nth Fibonacci number.
return fib0;
}
public static void main (String[] args) {
int answer;
answer = fibonacci(Integer.parseInt(args[0]));
}
}

| Figura 7.14 Programa em Java 1.4 com um ComandoAssero

mensagem do comando de assero no ser impressa e a execuo do programa conti-


nuar normalmente.
Em Java, um ComandoAssero pode ser colocado em qualquer lugar entre os co-
mandos executveis de um mtodo, mas no entre as declaraes.16 Para o nosso exem-
plo, o programa passar a seguinte mensagem de erro, se o argumento para fibonacci
for negativo:

Exception in thread main java.lang.AssertionError: Invalid


argument for fibonacci
at assertTest.fibonacci(assertTest.java:4)
at assertTest.main(assertTest.java:16)

Em verses de Java anteriores verso 1.4, podemos implementar a verificao de


asseres definindo uma classe AssertException (Fowler, 2000) como um tipo especial
de exceo.

class AssertException extends RuntimeException {


public AssertException( ) { super( ); }
public AssertException(String s) { super(s); }
}

16. Para permitir asseres em um programa em Java 1.4 ou 1.5, a execuo requer a opo -enableassertions
(abreviada -ea). Para desabilitar a verificao em tempo de execuo, usada a opo -disableassertions
(abreviada -da).
7.7 Semntica de Manipulao de Excees 193

Para se evitar ter de adicionar muitos comandos try-catch, tornamos AssertException


uma subclasse de RuntimeException. Logicamente, uma assero como um ndice
fora dos limites: esperamos que ela ocorra apenas se o programador tiver cometido
algum erro. Assim, devemos ser capazes de fazer asseres sem precisarmos incluir
comandos try-catch.
A seguir, analisamos a prpria classe Assert. J que no existem dados para serem
armazenados dentro de um objeto assero, optamos por tornar a classe uma simples bi-
blioteca de mtodos estticos. Um mtodo claramente necessrio serve para declarar que
determinada condio boleana verdadeira.
O cdigo para esse mtodo direto; se a condio for falsa, o mtodo gera uma
AssertException. Outro mtodo til declara que a execuo no deve atingir esse ponto;
a implementao sempre gera uma exceo. Finalmente, inclumos uma constante bolea-
na que, se desejado, pode ser usada para otimizar asseres fora do cdigo do programa
cliente. Nossa classe Assert apresentada na Figura 7.15.
Como exemplo do uso dessa classe, analise a classe de pilha revisada apresentada
a seguir:

class Stack {
int stack[];
int top = 0;
...
public boolean empty() { return top <=; }
public int pop() {
Assert.isTrue(!empty());
return stack[--top];
}
...
}

Nesta verso, a responsabilidade de verificar se uma pilha no est vazia antes de chamar
uma operao pop do programa que usa a classe Stack. Portanto, o programador me-
ramente declara que a pilha no est vazia como parte da operao pop. No caso de uma
exceo de tempo de execuo ser disparada pela assero, isso seria considerado uma falha
no cdigo cliente.
Observe que, j que o mtodo pop gera uma RuntimeException, uma clusu-
la throws no necessria no cabealho do mtodo. A filosofia usada aqui bastante

class Assert {
static public final boolean ON = true;
static public void isTrue(boolean b) {
if (!b) {
throw new AssertException(Assertion failed); }
}
static public void shouldNeverReachHere() {
throw new AssertException(Should never reach here);
}
}

| Figura 7.15 Uma Classe Assert


194 Captulo 7 Semntica

diferente da usada anteriormente. Neste caso, o desenvolvedor da classe impe certas restri-
es aos clientes da classe, e essas restries so reforadas por meio do uso de asseres.
Se um programa cliente da nossa classe pilha executasse logicamente a seguinte
seqncia de cdigo:

Stack stack = new Stack();


stack.pop();

uma exceo de tempo de execuo seria gerada, resultando em uma mensagem como a
seguinte:

AssertException: Assertion failed


at Assert.isTrue(Assert.java.4)
at Stack.pop(Stack.java.13)
at ...

Testes adequados deveriam eliminar a possibilidade de tais excees de assero


serem disparadas. Inseri-las no cdigo serve parcialmente para documentar suposies
feitas a respeito do estado de um mtodo ou objeto. Nossa classe de assero permite que
tais suposies sejam declaradas explicitamente, auxiliando assim na deteco de erros
sutis em tempo de execuo.

7 . 8 R ES UM O
Este captulo introduz a semntica de linguagens de programao a partir de um
ponto de vista informal. Uma abordagem operacional da semntica usada, destacando
os recursos semnticos de diversas linguagens populares. As semnticas de expresses,
atribuies, condies, ramificaes e laos so analisadas, junto quelas de manipulao
de excees e entrada/sada.
Esse tratamento enfoca o comportamento de programas em tempo de execuo. Em
captulos posteriores, analisaremos a implementao de semnticas de linguagens assim
como suas definies operacionais e denotacionais.

E X E RC CI OS
7.1 Analise a seguinte seqncia de comandos C/C++, que so sintaticamente vlidos, mas que no tm
interpretao semntica razovel (supondo que i e j tenham sido declarados como variveis int):

j = 0;
i = 3/j;
for (i=1; i>1; i++)
i--;

Como essas situaes so manipuladas quando executadas pelo seu sistema C/C++?
7.2 D outros tipos de comandos C/C++ (alm daqueles ilustrados na questo anterior) que sejam vli-
dos sintaticamente, mas cujo significado no possa ser definido razoavelmente na semntica de uma
linguagem de programao.
7.8 Resumo 195

7.3 (a) Como Java define a idia numrica de infinito? (Voc deve examinar a Java Language Speci-
fication (Gosling et al., 1996) para obter detalhes.)
(b) Analisando as especificaes na Java Language Definition (Arnold e Gosling, 1998), como
voc pode explicar em linguagem simples o significado do comando i 3/j; para todos os
valores possveis de j, incluindo 0?
7.4 Analise a expresso x + y/2 na linguagem C. Quantos significados diferentes essa expresso
possui, dependendo dos tipos de x e y ?
7.5 Reescreva o programa de fatorial da Figura 7.2 em Java de modo que o valor inicial de n seja lido
como entrada e o valor final de f seja impresso na tela. Agora execute o programa para os valores
2, 3, ... at que o valor de f ultrapasse o tamanho de um valor int. Qual o valor de n para que isso
ocorra? Como esse erro tratado por Java?
7.6 Para seu compilador C/C++, verifique se a converso do inteiro 257 para um char resulta em
um erro.
7.7 Para o seu compilador/interpretador Java, verifique se a converso do inteiro 65.537 em um char
resulta em um erro.
7.8 Tanto para C/C++ quanto para Java, escreva um programa que calcule 230 65.534. A seguir, con-
verta esse inteiro (ou long int) para ponto flutuante, adicionando 1,0. Depois converta o nmero
de ponto flutuante de volta para um inteiro e subtraia 1. Voc obtm de volta o mesmo inteiro com
o qual comeou? No use ponto flutuante de preciso dupla.
7.9 Sugira uma estratgia geral pela qual as convenes de E/S formatada das diversas linguagens (For-
tran, C, Ada e Java) poderiam se tornar mais unificadas, ou seja, voc pode projetar uma expresso
de formato generalizado para a qual uma expresso formatada em cada uma dessas linguagens
poderia ser mapeada de forma que preservasse seu significado? Quais so os desafios para a criao
de tal mapeamento?
7.10 Responda questo anterior novamente, mas para E/S no-formatada.
7.11 Analise o fragmento de programa em C da Figura 7.6, que ilustra o acesso aleatrio.
(a) Esse um cdigo C++ vlido? Por que sim ou por que no?
(b) Escreva um pequeno programa em C ou C++ que crie um arquivo de acesso aleatrio GIS.
data de tamanho razovel, que possa ser usado como entrada por esse cdigo.
(c) Complete esse programa de forma que ele navegue pela grade de posies, comeando pelo
canto superior esquerdo (NOROESTE) e exiba cada par de coordenadas x-y que encontrar em
cada passo.
7.12 Analise os erros de E/S em Ada resumidos na Tabela 7.4. Usando referncias da Internet, determine
se um conjunto semelhante de erros de E/S definido para C. E quanto a C++?
7.13 Usando referncias da Internet, examine os modelos de manipulao de erros de C++ e Ada mais
detalhadamente.
(a) Como o modelo de C++ para a manipulao de excees difere do modelo de Java discutido
neste captulo?
(b) Como o modelo de Ada para a manipulao de excees difere do modelo de Java?
7.14 Modifique o exemplo de entrada numrica invlida da Seo 7.7.2 de modo que o usurio tenha no
mximo trs tentativas para informar um inteiro vlido.
7.15 Incorpore o exemplo de entrada numrica invlida da Seo 7.7.2 em um programa que leia uma se-
qncia de nmeros do usurio at que um nmero negativo seja informado, e depois calcule a soma e a
196 Captulo 7 Semntica

mdia da seqncia de nmeros. Verifique se a aplicao se comporta corretamente na presena de


entrada invlida de nmeros.
7.16 Analise a classe stack da Seo 7.7.2.
(a) Estenda essa classe com um construtor apropriado e um mtodo principal para testar a stack.
(b) Estenda essa classe com um mtodo de empilhamento que gere uma exceo de overflow se o
mtodo for usado em uma pilha cheia.
(c) Estenda a classe de stack da Seo 7.7.2 com um construtor apropriado e um mtodo principal
para testar a pilha usando o comando assert de Java 1.4.
7.17 Estenda a classe de stack da Seo 7.7.2 com um mtodo de empilhamento que gere uma exceo
de overflow de pilha, se o mtodo for usado em uma pilha cheia.
Interpretao
Semntica 8
Para entender um programa voc deve se tornar tanto a mquina quanto o programa.
Atribudo a Alan Perlis

VISO GERAL DO CAPTULO

8.1 TRANSFORMAES DE ESTADOS


E FUNES PARCIAIS 198
8.2 A SEMNTICA DE CLITE 199
8.3 SEMNTICA COM TIPAGEM DINMICA 210
8.4 UM TRATAMENTO FORMAL DE SEMNTICA 214
8.5 RESUMO 222
EXERCCIOS 222

Este captulo aborda tanto a semntica operacional quanto a denotacional. Ele come-
a descrevendo o estado de uma computao com bastante cuidado, fornecendo, assim,
uma base para a discusso detalhada da semntica de Clite nas Sees 8.1 e 8.2.
As Sees 8.2 e 8.3 abordam a semntica operacional. Elas discutem os detalhes
da semntica de Clite, com tipagem esttica e dinmica, respectivamente, usando um
estilo informal, suplementado por partes de cdigo em Java. Um interpretador comple-
to para Clite fornece uma ferramenta de testes pronta para explorar em detalhes essas
idias semnticas.

197
198 Captulo 8 Interpretao Semntica

A Seo 8.4, que opcional, faz um exame mais formal de semntica denotacional so-
bre o mesmo tpico. Esse tpico fornece informaes sobre o rigor matemtico necessrio
para expor completamente a semntica de uma linguagem de programao completa.

8 .1 TR A NS FO R M A ES DE ESTADO S
E F U N ES PARCIAIS
As etapas individuais que ocorrem durante a execuo de um programa podem ser
modeladas como uma srie de funes de transformao de estados. Lembre-se do que
foi citado no Captulo 7, que o estado de um programa o conjunto de todos os objetos
ativos e seus valores correntes.
Definio: A semntica denotacional de uma linguagem define os significados
de elementos abstratos dessa linguagem como um conjunto de funes de trans-
formao de estados.
Para especificar a semntica de uma linguagem, primeiro precisamos ter como base
um pequeno conjunto de entidades matemticas e suas propriedades. Por exemplo, os n-
meros inteiros e reais, os caracteres e os boleanos e suas propriedades definem uma base
para o estudo da semntica de Clite.
Essas suposies matemticas so amenizadas pelas restries de computadores
reais, conforme discutido no Captulo 5. Os inteiros, por exemplo, constituem um dom-
nio semntico para linguagens de programao, mas o tipo int em linguagens como C
no inclui a faixa completa de valores inteiros.
Definio: Um domnio de semntica um conjunto de valores cujas proprieda-
des e operaes so bem compreendidas independentemente, nas quais as regras
que definem a semntica de uma linguagem podem ser baseadas.
O segundo elemento de que necessitamos um modelo preciso de um estado, in-
cluindo os valores que possam ser armazenados l; isso foi introduzido no Captulo 7.
Para a linguagem simples Clite, cujo ambiente esttico, o estado pode ser representado
como um conjunto de pares varivel-valor, ou seja:

estado {var1, val1, var2, val2, ..., varm, valm}

Aqui, cada vari denota uma varivel, e cada vali denota seu valor atribudo correntemente.
Expandiremos essa definio de estado nos Captulos 10 e 11, quando ser importan-
te representar o ambiente de uma forma mais dinmica. L, um ambiente dinmico nos
permite caracterizar precisamente as idias de heap e a pilha de tempo de execuo e, as-
sim, lidar de forma eficaz com chamadas a procedimentos, criao de objetos, passagem
de parmetros, recurso e assim por diante.
O terceiro elemento de que precisamos a idia de que as funes de transformao
de estados na definio de semntica so necessariamente funes parciais.
Definio: Uma funo parcial uma funo que no est bem definida para
todos os valores possveis de seu domnio (estado de entrada).
Isso significa que determinadas construes de programas em determinados estados
no podem ter representaes de significados, embora essas construes sejam vlidas
quanto sua sintaxe e s suas caractersticas de tipos.
Por exemplo, considere o seguinte trecho (traioeiro) de programa em Clite:
8.2 A Semntica de Clite 199

i = 1;
while (i  0)
;

Supondo que i seja uma varivel int, esse trecho no tem estado final, j que o valor
de i nunca muda e o lao se repete infinitamente.
O significado de uma Expresso Clite tambm uma funo parcial. Por exem-
plo, a diviso por zero indefinida para muitos computadores e muitas linguagens de
programao. De forma semelhante, adicionar 1 ao maior valor int de uma mquina
no pode ser definido semanticamente, j que o resultado um valor fora do domnio
semntico da mquina.

8 .2 A S EM N T ICA DE CLIT E
Com essa introduo, podemos definir o significado de um programa abstrato em
Clite1 como um conjunto de funes S (de significado) que transformam o estado do pro-
grama. Essas funes definem individualmente o significado de Programa e cada tipo de
comando que ocorre na sintaxe abstrata: Salto, Bloco, Condicional, Lao, Atribuio.
Essas funes so implementadas como uma classe Semantics de Java que, combi-
nada com a classe AbstractSintax, forma um interpretador para Clite. Tal interpretador
valioso para o projetista de linguagem porque:
Pode ser usado para testar a validade das definies de semntica;
Pode revelar as vantagens e as desvantagens que ocorrem entre definies alterna-
tivas de semntica.
Entretanto, um interpretador Java para Clite, para ser completamente vlido, deve supor
que o prprio Java tenha sido completamente definido. De fato, este quase o caso uma
definio formal de Java foi proposta em artigos de pesquisas recentes. Leitores interes-
sados devem se reportar a Alves-Foss (ed.), 1999, para obter mais informaes.

8.2.1 O Significado de um Programa


Suponha que Estado represente o conjunto de todos os estados do programa. Ento o
significado S de um Programa abstrato em Clite definido pelas trs funes a seguir:
S: Programa Estado
S: Comando Estado Estado
S: Expresso Estado Valor
Isso quer dizer que o significado de um Programa uma funo que produz um Esta-
do. De forma semelhante, o significado de um Comando uma funo que, dado um
Estado corrente, produz um novo Estado. Finalmente, o significado de uma Expresso
uma funo que, dado um Estado corrente, produz um Valor.
Observe que o significado de uma Expresso normalmente um Valor. Isso faz
sentido intuitivamente, j que uma expresso normalmente um Valor constante (por
exemplo, 1), uma Varivel (por exemplo, n), um Binrio (por exemplo, i 1), um
Unrio (por exemplo, x) ou alguma combinao deles. O Valor de uma Varivel seu
valor no estado corrente.

1. A semntica de matrizes em Clite omitida dessa discusso, j que matrizes requerem um modelo dinmico
de memria (veja o Captulo 11).
200 Captulo 8 Interpretao Semntica

J que a sintaxe abstrata de um programa em Clite uma estrutura de rvore cuja raiz
o elemento abstrato Programa, o significado de um programa em Clite pode ser definido
pela aplicao de uma srie de funes rvore. Lembre-se do que foi citado no Captulo
2, que a sintaxe abstrata de um Programa :

Programa Declaraes partedec; Bloco corpo

Regra de Significado 8.1 O significado de um Programa definido como sen-


do o significado do seu corpo quando dado um estado inicial que consista das
variveis da partedec, cada uma inicializada com um valor indefinido corres-
pondente ao seu tipo declarado.
A regra define o significado de um programa usando as definies de sintaxe abstrata
dos constituintes do Programa. Assim, essa regra diz que o significado de um programa
o significado do corpo do programa com o estado inicial em que todas as variveis de-
claradas tenham valores indefinidos do tipo apropriado. medida que o significado do
programa se desdobra, esse estado inicial mudar quando forem atribudos e reatribudos
valores s variveis, conforme mostrado, por exemplo, na Tabela 7.3.
Esse estilo funcional para definir o significado de um programa especialmente
direto de se implementar em Java. Uma implementao a seguinte:

State M (Program p) {
return M (p.body, initialState(p.decpart));
}

O mtodo initialState( ) constri um estado guardando cada varivel declarada ini-


cializada com o valor undef apropriado para o seu tipo.
J que um estado um conjunto de pares chave-valor nicos, implementado natu-
ralmente com uma tabela hash, usando a classe HashMap de Java:

public class State extends Hashmap { ... }

Lembrando que Declaraes implementada como uma ArrayList de Java e a sintaxe


abstrata de Declarao definida:

Declarao Varivel v; Tipo t;

A implementao necessria de initialState :

State initialState (Declarations d) {


State state = new State();
for (Declaration decl : d)
state.put(decl.v, Value.mkValue(decl.t));
return state;
}

Os pares varivel-valor so colocados na tabela hash usando o mtodo put de HashMap.


O mtodo Value.mkValue, dado o tipo, retorna um valor indefinido do tipo apropriado.
8.2 A Semntica de Clite 201

8.2.2 Semntica de Comandos


O significado de um comando abstrato uma funo de transformao de estado
da forma:

S: Comando Estado Estado

H cinco tipos de comandos em Clite, identificados na sintaxe abstrata como:

Comando Salto | Atribuio | Condicional | Lao | Bloco

Assim, o significado de qualquer Comando em Clite do tipo particular do comando


que represente. Uma implementao consiste em um mtodo com um comando if para
cada tipo:

State M (Statement s, State state) {


if (s instanceof Skip) return M(Skip)s, state);
if (s instanceof Assignment)
return M((Assignment)s, state);
if (s instanceof Conditional)
return M((Conditional)s, state);
if (s instanceof Loop) return M(Loop)s, state);
if (s instanceof Block) return M(Block)s, state);
throw new IllegalArgumentException(
nunca deve chegar aqui);
}

Observe nosso uso do comando throw como uma tcnica de programao defensiva para cap-
turar tanto erros lgicos no modelo semntico quanto erros na implementao do modelo.

Salto Um comando Salto em Clite no altera o estado, de forma que seu significado
simples.
Regra de Significado 8.2 O significado de um comando Salto uma funo
identidade do estado, ou seja, o estado permanece inalterado.
A implementao da semntica de um Salto tambm simples; ele retorna o Estado
que foi passado:

State M (Skip s, State state) {


return state;
}

Embora um comando Salto no faa nada, ainda assim desempenha um papel impor-
tante na programao. Por exemplo, C e C++ permitem efeitos colaterais em testes de lao,
de modo que o corpo de um lao pode ser um Salto. Para entender melhor, veja a Figura 5.6.
Outro uso de um comando Salto em Clite que o analisador converte todos os comandos
se-ento na sintaxe concreta para comandos se-ento-seno na sintaxe abstrata, inse-
rindo um Salto como a ramificao do seno de um comando abstrato Condicional.
202 Captulo 8 Interpretao Semntica

Atribuio A sintaxe abstrata de uma Atribuio em Clite possui as seguintes partes:

Atribuio Varivel alvo; Expresso origem

Informalmente, o significado de uma Atribuio avalia a expresso no estado corrente e


produz um novo valor para a varivel alvo no estado de sada.
Regra de Significado 8.3 O significado de um comando de atribuio o resul-
tado de substituir o valor da Varivel alvo pelo valor da Expresso origem no
estado corrente.
O significado de uma Atribuio supe as restries de tipos discutidas no Captulo 6.
Lembre-se do seguinte exemplo da Figura 7.2:

f = f * i;

J que Clite fortemente tipada, essa Atribuio vlida, quanto ao tipo, quando a fun-
o seguinte de significado for aplicada, ou seja, o tipo do resultado da expresso, com
certeza, o mesmo da varivel alvo. Assim, erros de tipo em tempo de execuo (como a
atribuio de um valor boleano a um alvo de ponto flutuante) no podem ocorrer.
A Tabela 7.3 mostra que, quando o comando 4 executado, o valor inteiro 3 substitui
o valor undef para a varivel n. Os valores das outras variveis so inalterados por essa
Atribuio.
A implementao de uma Atribuio usa o mtodo chamado onion( )2 para substi-
tuir o valor da varivel na implementao da tabela hash do Estado.

State M (Assignment a, State state) {


return state.onion(a.target, M (a.source, state));
}

Basicamente, o mtodo onion executa um put para substituir o valor da Varivel alvo
na tabela hash (estado) pelo valor da Expresso origem.

Condicional Um Condicional em Clite tem trs partes:

Condicional Expresso teste; Comando ramificao_ento, ramificao_seno

No caso de a ramificao_seno ser omitida na sintaxe concreta, o Condicional possui


um comando Salto no seu lugar.
Regra de Significado 8.4 O significado de um Condicional depende do resulta-
do (verdadeiro ou falso) do seu teste boleano no estado corrente. Se o teste
for verdadeiro, ento o significado da Condio o significado do Comando
ramificao_ento; caso contrrio, o significado do Comando ramifica-
o_seno.

2. O motivo desse nome estranho explicado na Seo 8.4, que define a matemtica e o modelo formal rela-
cionado implementao.
8.2 A Semntica de Clite 203

Analise o seguinte cdigo para calcular o maior entre dois nmeros a e b:

if (a b)
max = a;
else
max = b;

Se for verdade que a b, ento o significado do Condicional definido como o significado


de max = a. Caso contrrio, porque max = b. Assim, independentemente do resultado do
teste a b, o estado da sada reflete uma atribuio a max com o valor de a ou de b.
A implementao imita a regra anterior:

State M (Conditional c, State state) {


if (M(c.test, state).boolValue( ))
return M (c.thenbranch, state);
else
return M (c.elsebranch, state);
}

O teste boleano do Condicional avaliado no estado corrente. Se o teste for verda-


deiro, o significado do Condicional o estado retornado pela ramificao_ento; caso
contrrio, o significado do estado retornado pela ramificao_seno.

Lao O Lao de Clite possui um teste e um corpo:

Lao Expresso teste; Comando corpo

Regra de Significado 8.5 Se o teste for falso, o significado (estado da sada) de um


Lao o mesmo do estado de entrada. Caso contrrio, o significado o resultado da
aplicao dessa regra novamente ao significado do seu corpo no estado corrente.
Essa uma definio recursiva. Intuitivamente, um Lao especifica a repetio do
corpo zero ou mais vezes, e cada iterao comea com o estado que resulta do anterior.
Analise novamente o lao (comandos 710) no programa de fatorial da Figura 7.2.
Na primeira vez em que esse lao executado, o estado do programa

{n, 3, i, 1, f, 1}

(veja a Tabela 7.3). Assim, esse estado se torna o estado de entrada para o corpo do lao,
efetivamente os comandos 89, cujo estado de sada

{n, 3, i, 2, f, 2}

Esse estado se torna o estado de entrada para uma funo recursiva de significado para o
lao. O processo se repete assim at o estado

{n, 3, i, 3, f, 6}

o que resulta da terceira iterao do lao retornando o estado de entrada como seu estado
de sada, j que i n falso. Neste ponto, a recurso termina com esse estado como o
estado de sada do significado do Lao.
204 Captulo 8 Interpretao Semntica

A implementao do significado de um Lao uma codificao direta da Regra 8.5:

State M (Loop l, State state) {


if (M (l.test, state).boolValue( ))
return M(l, M (l.body, state));
else return state;
}

Observe que, se o Lao nunca terminar, a recurso tambm no termina (j que nunca
alcana a clusula seno). Isso reflete o fato de que a semntica de um programa uma
funo parcial.

Bloco Abstratamente, um Bloco em Clite apenas uma seqncia de comandos a se-


rem executados na ordem em que ocorrem.

Bloco Comando*

Regra de Significado 8.6 O significado de um Bloco o significado agrega-


do dos seus comandos quando aplicados ao estado corrente. Se um Bloco no
tiver comandos, o estado no alterado. Caso contrrio, o estado resultante
do significado do primeiro Comando no Bloco se torna a base para a defini-
o do significado do resto do bloco.
Essa tambm uma definio recursiva. Por exemplo, analise o Bloco que consiste
nos comandos 89 da Figura 7.2 com estado inicial:

{n, 3, i, 2, f, 2}

O estado de sada do Bloco deve primeiro determinar o estado de sada da Atribuio


i = i + 1, que :

{n, 3, i, 3, f, 2}

Esse estado de sada se torna o estado de entrada da Atribuio f = f * 1, cujo estado


de sada :

{n, 3, i, 3, f, 6}

Esse ltimo estado se torna o estado de sada do Bloco.


A implementao:

State M (Block b, State state) {


for (Statement s : b.members)
state = M (s, state);
return state;
}

usa um lao para atravessar os Comandos do Bloco, e usa o estado de sada de um Coman-
do como estado de entrada para o prximo.
Isso completa as definies dos diversos tipos de comandos em Clite. Agora passa-
mos para a definio do significado de uma expresso.
8.2 A Semntica de Clite 205

8.2.3 Semntica das Expresses


O significado de uma Expresso complicado. De um lado, a principal tarefa de uma
Expresso produzir um valor para ser atribudo a uma varivel ou ser usado como teste
em uma Condio ou Lao. De outro lado, as Expresses podem ter os assim chamados
efeitos colaterais:
Definio: Um efeito colateral ocorre durante a avaliao de uma expresso se,
alm de retornar um valor, a expresso alterar o estado do programa.
Devido ao tratamento de efeitos colaterais ser um tpico importante na semntica de lin-
guagens, ns o discutiremos integralmente na Seo 8.2.4.
Assim, por enquanto, o significado de uma Expresso em Clite um Valor:

S: Expresso Estado Valor

Uma Expresso sempre avaliada no estado corrente porque pode conter variveis cujos
Valores podem ter mudado em estados anteriores.
Lembre-se de que uma Expresso em Clite possui a seguinte sintaxe abstrata:
Expresso Varivel | Valor | Binrio | Unrio
Binrio OpBinrio op; Expresso termo1, termo2
Unrio OpUnrio op; Expresso termo
Varivel String id
Valor ValorInt | ValorBool | ValorFloat | ValorChar
em que as expresses Binrio e Unrio envolvem operadores binrios e unrios e seus
operandos, respectivamente.3 O significado de uma Expresso ento definido com base
nas suas quatro formas alternativas:
Regra de Significado 8.7 O significado de uma Expresso em um estado um
Valor definido da seguinte maneira:
1 Se a Expresso for um Valor, ento seu significado o do prprio Valor. Um
exemplo de Valor o inteiro 3.
2 Se a Expresso for uma Varivel, ento seu significado o Valor da Varivel
no estado corrente. Na Tabela 7.3, o significado da varivel i no estado

{n, 3, i, 2, f, 2}


2.
3 Se a Expresso for um Binrio, ento o significado de cada um de seus ope-
randos termo1 e termo2 ser determinado primeiro. Ento a Regra 8.8 de-
termina o significado da expresso aplicando o Operador op aos Valores desses
dois operandos.
4 Se a Expresso for um Unrio, ento o significado do seu operando termo
determinado. A Regra 8.9 determina ento o significado da expresso apli-
cando o Operador op ao Valor do operando.

3. Para mantermos o foco nas questes principais, supomos, na discusso a seguir, que todos os operadores em
Clite, exceto a atribuio, tenham um tipo especfico, de modo que no h sobrecarga de operadores (veja o
Captulo 4) e a adio de inteiros diferente da adio de pontos flutuantes . As regras de transformao de
estados tambm supem que transformaes e alguns tipos primitivos sejam predefinidos e constituam, assim,
um domnio de semntica para Clite.
206 Captulo 8 Interpretao Semntica

Essa regra possui uma implementao direta:

Value M (Expression e, State state) {


if (e instanceof Value)
return (Value)e;
if (e instanceof Variable)
return (Value)(state.get(e));
if (e instanceof Binary) {
Binary b = (Binary)e;
return applyBinary (b.op,
M(b.term1, state), M(b.term2, state));
}
if (e instanceof Unary) {
Unary u = (Unary)e;
return applyUnary(u.op, M(u.term, state));
}
throw new IllegalArgumentException(
nunca deve chegar aqui);
}

Na implementao, o significado de um Valor ou de uma Varivel exatamente confor-


me foi especificado na regra anterior. Para uma expresso Binria, ambos os operadores
so avaliados no estado corrente e ento o mtodo applyBinary chamado para aplicar
o operador op aos valores dos dois operandos. A explicao da avaliao de uma expres-
so Unria semelhante.
Analise, por exemplo, a avaliao da Expresso origem do comando 8 da Figura 7.2:

i = i + 1;

com o estado de entrada mostrado no passo 5 da Tabela 7.3. O valor de i 1 (Regra


4.7.2), e o valor de 1 1 (Regra 4.7.2) Assim, o valor de i + 1 2 (Regras 4.7.3 e 4.8).
Lembre-se do que vimos no Captulo 6, que operadores concretos sobrecarregados
em Clite eram transformados em operadores abstratos e especficos de tipo. Por exemplo,
os operadores aritmticos binrios , , * e / eram convertidos para operadores especfi-
cos de tipo int+, int-, int*, int/ para operandos int e float+, float-, float*
e float/ para operandos float. Alm disso, quaisquer converses de tipos necessrias
eram inseridas na rvore de sintaxe abstrata. Assim, um float+, por exemplo, garante
que ambos os seus operandos sejam do tipo float.
Regra de Significado 8.8 O significado de uma expresso Binria um Valor
definido da seguinte maneira:
1 Se o operando termo1 ou termo2 estiver indefinido, o programa no tem
significado.
2 Se o operador for um aritmtico inteiro, ento um int+, int-, int* execu-
tam adio, subtrao ou multiplicao sobre seus operandos inteiros, resul-
tando em um inteiro. Se o operador for int/, ento o resultado o mesmo
da diviso matemtica com truncagem da parte fracionria. Por exemplo, um
valor matemtico de 3,9 truncado na direo do zero, resultando em um valor
int 3. De forma semelhante, um resultado matemtico de 3,9 truncado
para 3.
8.2 A Semntica de Clite 207

3 Se o operador for de ponto flutuante, ento a aritmtica de ponto flutuante


usando o padro IEEE executada sobre os operandos float, revertendo em
um resultado do mesmo tipo.
4 Se o operador for relacional, ento os operandos so comparados com o re-
sultado verdadeiro ou falso. O resultado o mesmo resultado matemtico
correspondente, exceto que falso verdadeiro.
5 Se o operador for boleano, ento:
(a) O operador && interpretado como:
a && b  se a ento b seno falso

(b) O operador || interpretado como:


a || b  se a ento verdadeiro seno b

A implementao a seguir mostra apenas os operadores aritmticos inteiros sobre operan-


dos inteiros. Os casos restantes constituiriam um comando switch gigante.

Value applyBinary (Operator op, Value v1, Value v2) {


StaticTypeCheck.check( ! v1.isUndef( ) &&
! v2.isUndef( ), reference to undef value);
if (op.val.equals(Operator.INT_PLUS))
return new IntValue(
v1.intValue( ) + v2.intValue( ));
if (op.val.equals(Operator.INT_MINUS))
return new IntValue(
v1.intValue( ) - v2.intValue( ));
if (op.val.equals(Operator.INT_TIMES))
return new IntValue(
v1.intValue( ) * v2.intValue( ));
if (op.val.equals(Operator.INT_DIV))
return new IntValue(
v1.intValue( ) / v2.intValue( ));
...
}

Assim, a implementao completa imitaria o que a mquina real faz, exceto pelo fato de
os computadores reais no filtrarem variveis no inicializadas como o nosso modelo
deve fazer.
Nesse cdigo, cada operador inteiro possui um operador Java correspondente apli-
cado aos operandos inteiros, produzindo um novo Valor inteiro, que o resultado. Nosso
interpretador detecta uma tentativa de interpretar um caractere ou um valor de ponto flutuante
como um inteiro, o que seria um erro de tipo na implementao. C++, e assim Clite, define
falso como menor que verdadeiro, o que pode ser implementado em Java mapeando o
valor falso para 0 e o verdadeiro para 1. A no ser a nossa implementao de operadores
relacionais sobre operandos boleanos, nosso verificador de tipos no permite que boleanos
sejam interpretados como valores inteiros, o que um distanciamento de C/C++.
Regra de Significado 8.9 O significado de uma expresso Unria um Valor
definido da seguinte maneira:
1 Se o operando termo for indefinido, o significado da expresso indefinido.
208 Captulo 8 Interpretao Semntica

2 Se o operador for ! (no), ento o operando boleano invertido.


3 Se o operador for int-/float-, ento o sinal do inteiro/ponto flutuante
invertido.
4 Se o operador for i2f, ento o operando inteiro convertido para ponto flu-
tuante. Por exemplo, o valor 2 seria convertido para 2,0.
5 Se o operador for c2i, ento o operando char convertido para inteiro usan-
do o cdigo ASCII do caractere. Efetivamente, exceto pela adio de bits com
valor zero, o valor no alterado, exceto pelo seu tipo.
6 Se o operador for i2c, ento o operador inteiro convertido para caracte-
re. Se o operando inteiro for maior que 255 ou menor que 0, o significado da
expresso indefinido. Se a converso for bem-sucedida, ento, exceto pela
perda de zeros esquerda, o padro de bits do valor o mesmo, apenas o
tipo alterado.
7 Se o operador for f2i, ento o valor de ponto flutuante convertido para
inteiro, descartando-se as posies aps o ponto decimal, ou seja, ele
truncado na direo do zero. Por exemplo, o valor 2,9 convertido para 2,
e 2,9 convertido para 2. Se a parte inteira do valor de ponto flutuante
for grande demais para ser armazenada em um inteiro, o significado da
expresso indefinido.

A implementao segue da regra anterior:

Value applyUnary (Operator op, Value v) {


StaticTypeCheck.check( ! v.isUndef( ),
reference to undef value);
if (op.val.equals(Operator.NOT))
return new BoolValue(!v.boolValue( ));
else if (op.val.equals(Operator.INT_NEG))
return new IntValue(-v.intValue( ));
else if (op.val.equals(Operator.FLOAT_NEG))
return new FloatValue(-v.floatValue( ));
else if (op.val.equals(Operator.I2F))
return new FloatValue((float)(v.intValue( )));
else if (op.val.equals(Operator.F2I))
return new IntValue((int)(v.floatValue( )));
else if (op.val.equals(Operator.C2I))
return new IntValue((int)(v.charValue( )));
else if (op.val.equals(Operator.I2C))
return new CharValue((char)(v.intValue( )));
throw new IllegalArgumentException(
nunca deve chegar aqui);
}

Diferentemente da aplicao de applyBinary, todos os casos so mostrados.


Isso completa a definio e a implementao da semntica de tempo de execuo de
Clite. Uma descrio formal da semntica apresentada na Seo 8.4.
8.2 A Semntica de Clite 209

8.2.4 Expresses com Efeitos Colaterais


Alm de retornar um valor, uma expresso tem um efeito colateral sobre o estado
da computao. Por exemplo, o aparecimento de uma chamada de funo f(x) em uma
expresso como f(x) + x parece incua sob circunstncias normais. Todavia, se essa
chamada alterar o valor de x no estado em que ele avaliado, o valor retornado ser dife-
rente, dependendo da ordem em que os dois operandos forem avaliados, da esquerda para
a direita ou da direita para a esquerda.
Observe na Regra de Significado 8.7 que a ordem de avaliao de operandos em um
Binrio no especificada. Entretanto, o cdigo Java que acompanha essa definio clara-
mente avalia os operandos da esquerda para a direita, j que a chamada de funo Java:

applyBinary(b.op, M(b.term1, state), M(b, term2, state)

avalia seus argumentos da esquerda para a direita.


Efeitos colaterais tambm podem alterar o comportamento normal dos comandos de
Condicional e Lao. Por exemplo, suponha que a chamada de funo g(x) produza um
resultado boleano e tambm tenha um efeito colateral sobre x. Ento o seguinte:

if (g(x))
y = x;

pode ou no atribuir a y um valor anterior de x quando g(x) for verdadeira.


Uma forma simples de lidar semanticamente com efeitos colaterais em uma linguagem
simplesmente proibi-los. Todavia, isso teria conseqncias srias em linguagens como C, em
que mesmo expresses como x++ dentro de uma expresso maior no seriam permitidas.
Outra forma de lidar com efeitos colaterais expandir a definio do significado
de uma expresso de modo que ela produza tanto um Valor quanto um Estado. A seguir est
uma reviso da Regra de Significado 8.7 que permite que uma Expresso tenha um efeito
colateral alterador de estado.

S : Expresso Estado Valor Estado

Regra de Significado 8.10 O significado de uma Expresso em um estado um


Valor e um Estado, definido da seguinte maneira:
1 Se a Expresso for um Valor, ento seu significado o significado do prprio
Valor e o estado corrente no alterado.
2 Se a Expresso for uma Varivel, ento seu significado o Valor da Varivel
no seu estado corrente, o qual permanece inalterado.
3 Se a Expresso for um Binrio, o significado do termo1 no estado corrente
determinado primeiro, dando um valor 1 e um estado s1. A seguir, o significado
do termo2 no estado s1 determinado, dando um valor 2 e um estado s2. A
seguir, a Regra 8.8 determina o significado da expresso aplicando o Operador
op aos valores resultantes 1 e 2 no estado s2, e o estado resultante s2.
4 Se a Expresso for Unria, ento o significado do seu operando termo no
estado corrente determinado, dando o valor 1 e estado s1. Ento a Regra
8.9 determina o significado da expresso, aplicando o Operador op ao Valor
do operando, e o estado resultante s1.
210 Captulo 8 Interpretao Semntica

Observe que o terceiro passo nessa regra define a avaliao da esquerda para a direi-
ta. Alm disso, o terceiro e o quarto passos supem que os operadores binrios e unrios
na linguagem no tenham eles prprios efeitos colaterais. Se tiverem, ento as Regras de
Significado 8.8 e 8.9 precisariam ser expandidas de modo que esses operadores tambm
produzam um par valor-estado.
Finalmente, observe que a introduo de efeitos colaterais dessa forma ter impacto
at mesmo sobre o significado de comandos Condicional e Lao. Por exemplo, quando
a avaliao de uma Expresso teste de um Lao tiver um efeito colateral, os valores ou
algumas variveis podem ser alteradas antes que seu corpo comece a ser interpretado.

8 .3 S EM N T ICA CO M T IPAGEM DIN M ICA


Aqui analisaremos a semntica de uma linguagem tipada dinamicamente para exa-
minar a diferena entre linguagens tipadas esttica e dinamicamente de forma mais com-
pleta. Usamos o mesmo mtodo de apresentao da Seo 8.2.
Muitas linguagens tipadas dinamicamente diferentes poderiam servir como veculo
para este estudo, incluindo JavaScript, Perl, Python e Scheme. Contudo, preferimos usar
uma verso modificada de Clite para essa discusso, porque ela familiar e suficiente
para ilustrar os principais pontos por trs da tipagem dinmica.
Chamamos essa verso de CliteD, j que usa o mesmo analisador lxico, o analisador
e a sintaxe abstrata que foram usados para Clite. Nosso modelo semntico para CliteD
interpreta a sintaxe abstrata diretamente, sem pr-processamento com um verificador ou
transformador de tipos.
Analise o programa para clculo do fatorial apresentado na Figura 8.1. Ele se parece
muito com o da Figura 7.2, exceto pela ausncia de declaraes. Alm de eliminar decla-
raes, que outros recursos CliteD deveria ter?
Por exemplo, Perl possui operadores distintos para comparaes de strings versus
numricos. Em Perl, 210 verdadeiro usando um teste numrico, mas 2 lt 10 falso,
j que o teste uma comparao de strings, ou seja, 2 lt 10 o teste realmente
executado. Em comparao, Python sobrecarrega os operadores como C/C++; para ope-
radores sobrecarregados como comparaes, se um operando for uma string, o outro
tambm deve s-lo. Na especificao da semntica de CliteD, usaremos bastante Python
como nosso modelo.
Nesta apresentao, discutimos apenas as partes da semntica de CliteD que diferem
da semntica de Clite. A sintaxe abstrata de um Programa em CliteD :

Programa Bloco corpo

1 int main ( ) {
2 n = 3;
3 i = 1;
4 f = 1.0;
5 while (i n) {
6 i = i + 1
7 f = f * float(i);
8 }
9 }

| Figura 8.1 Programa Fatorial em CliteD


8.3 Semntica com Tipagem Dinmica 211

A primeira diferena entre os dois modelos semnticos no significado de um progra-


ma. Lembre-se da Regra 8.1, em que a interpretao de um Programa comeava com um
Estado no qual todas as variveis declaradas eram inicializadas com o valor especial undef.
Aqui no h variveis declaradas, de modo que o Programa comea com um Estado vazio.
Regra de Significado 8.11 O significado de um Programa definido como o de
seu corpo quando dado um estado inicial vazio.
Usando essa regra, a Tabela 8.1 mostra um rastro da execuo do programa fatorial
da Figura 8.1. Observe que o estado no passo 1 vazio. medida que os comandos de
atribuio so executados, novas variveis alvos so adicionadas ao estado. O tipo da
varivel o tipo do seu valor no estado corrente. Por exemplo, no passo 4, as variveis n
e i tm tipo inteiro, enquanto a varivel f de ponto flutuante.
Lembre-se de que, para linguagens tipadas dinamicamente, nenhuma verificao de
tipo executada no programa. Apesar disso, o significado de uma Expresso nas duas lin-
guagens (Regra 8.7) quase idntico. Por exemplo, o significado de um Valor, j que a fun-
o S deve retornar um Valor, esse prprio Valor. Na definio do significado de uma Va-
rivel em CliteD, insistimos que a Varivel tem um Valor. Aqui est a definio completa:
Regra de Significado 8.12 O significado de uma Expresso em um estado um
Valor definido da seguinte maneira:
1 Se a Expresso for um Valor, ento seu significado o significado do prprio
Valor.
2 Se a Expresso for uma Varivel, ento seu significado o significado da Va-
rivel no estado corrente. Se a Varivel no estiver definida no estado corrente,
o programa semanticamente sem significado. Por exemplo, na Tabela 8.1, a va-
rivel f no passo 2 no ocorre no estado corrente; portanto, uma referncia
a f no comando 4 seria um erro.
3 Se a Expresso for um Binrio, ento o significado de ambos os seus operan-
dos termo1 e termo2 so determinados. Ento a Regra 8.13 determina o sig-
nificado da Expresso, aplicando o Operador op aos Valores dos operandos.

Tabela 8.1 Passo Comando Estado

| Rastro do
Programa
Fatorial
1
2
3
3
4
5
<n, 3>
<n, 3>, <i, 1>
4 6 <n, 3>, <i, 1>, <f, 1,0>
5 7 <n, 3>, <i, 1>, <f, 1,0>
6 8 <n, 3>, <i, 2>, <f, 1,0>
7 6 <n, 3>, <i, 2>, <f, 2,0>
8 7 <n, 3>, <i, 2>, <f, 2,0>
9 8 <n, 3>, <i, 3>, <f, 2,0>
10 6 <n, 3>, <i, 3>, <f, 6,0>
11 10 <n, 3>, <i, 3>, <f, 6,0>
212 Captulo 8 Interpretao Semntica

4 Se a Expresso for Unria, ento o significado do seu operando termo


determinado. Portanto, a Regra 8.14 determina o significado da Expresso,
aplicando o Operador op ao Valor do operando.

Esta regra tem uma implementao direta:

Value M (Expression e, State sigma) {


if (e instanceof Value)
return (Value)e;
if (e instanceof Variable) {
StaticTypeCheck.check( sigma.containsKey(e),
reference to undefined variable);
return (Value)(sigma.get(e));
}
if (e instanceof Binary) {
Binary b = (Binary)e;
return applyBinary (b.op,
M(b.term1, sigma), M(b.term2, sigma));
}
if (e instanceof Unary) {
Unary u = (Unary)e;
return applyUnary (u.op, M(u.term, sigma));
}
throw new IllegalArgumentException(
nunca deve chegar aqui);
}

Nessa implementao, os significados de um Binrio e de um Unrio so deferidos para


os mtodos applyBinary e applyUnary, respectivamente. Para o significado de uma
Varivel em CliteD, o interpretador semntico verifica se o Estado corrente contm a
varivel quando for referenciada em uma Expresso.
Para um Binrio, ambos os operandos so avaliados no estado corrente e ento o m-
todo applyBinary chamado para aplicar o operador op aos valores dos dois operandos.
A explicao da avaliao da expresso Unrio semelhante.
Lembre-se do que foi dito no Captulo 6, que os operadores sobrecarregados em
Clite so transformados em operadores dependentes de tipos. No existe tal fase para
uma linguagem tipada dinamicamente. Em vez disso, os operadores permanecem sobre-
carregados na sintaxe abstrata, da mesma forma que foram construdos pelo analisador.
Assim, a verificao de se ambos os operandos de um operador binrio so do mesmo tipo
e compatveis com o operador deve ser executada pela regra de significado (por exemplo,
em tempo de execuo).
Regra de Significado 8.13 O significado de um Binrio um Valor, definido da
seguinte forma:
1 Se o operador for aritmtico, ento:
(a) Se algum operando for do tipo inteiro, o outro deve ser desse tipo tambm. Se
os operadores forem um , ou *, o resultado a soma, a diferena ou o
produto dos operandos. Se o operador for /, o resultado o quociente mate-
mtico com truncagem na direo do zero. Por exemplo, o quociente 3,9
truncado para o valor int 3.
8.3 Semntica com Tipagem Dinmica 213

(b) Se algum operando for do tipo ponto flutuante, o outro deve ser do mesmo
tipo. A aritmtica de ponto flutuante usando o padro IEEE executada
sobre operadores float, resultando em float.
2 Se o operador for relacional, ento os operandos so comparados com um
resultado de verdadeiro ou falso. O resultado o mesmo resultado mate-
mtico correspondente. Seguindo C/C++, falso verdadeiro.
3 Se o operador for boleano, ento:
(a) O significado de && : se a ento b seno falso
(b) O significado de || : se a ento verdadeiro seno b
em que tanto a quanto b devem ser do tipo boleano.
4 Qualquer outra combinao operador/tipo no permitida.
A seguir est uma implementao da semntica aritmtica inteira em CliteD:

Value applyBinary (Operator op, Value v1, Value v2) {


StaticTypeCheck.check( v1.type( ) == v2.type( ),
mismatched types);
if (op.ArithmeticOp( )) {
if (v1.type( ) == Type.INT) {
if (op.val.equals(Operator.PLUS))
return new IntValue(
v1.intValue( ) + v2.intvalue( ));
if (op.val.equals(Operator.MINUS))
return new IntValue(
v1.intValue( ) - v2.intvalue( ));
if (op.val.equals(Operator.TIMES))
return new IntValue(
v1.intValue( ) * v2.intvalue( ));
if (op.val.equals(Operator.DIV))
return new IntValue(
v1.intValue( ) / v2.intvalue( ));
}
...
}

Observe o teste no tipo do operando v1 esquerda para determinar se ele um inteiro. Ape-
nas os primeiros casos so mostrados do que basicamente um comando case gigante.
Para cada operador inteiro, o operador Java correspondente aplicado aos operandos
inteiros, e ento um novo Valor inteiro construdo contendo o resultado. Nosso inter-
pretador detecta uma tentativa de interpretar um caractere ou um valor de ponto flutuante
como um inteiro, o que seria um erro de tipo na implementao.
Regra de Significado 8.14 O significado de um Unrio um Valor definido da
seguinte maneira:
1 Se o operador for ! (no), ento o operando boleano invertido.
2 Se o operador for unrio , ento o operando inteiro/ponto flutuante tem seu
sinal invertido.
3 Se o operador for float, ento o operando inteiro convertido para ponto
flutuante. Por exemplo, o valor 2 seria convertido para 2,0.
214 Captulo 8 Interpretao Semntica

4 Se o operador for int, ento:


(a) Um operando char convertido para inteiro, por meio do uso do cdigo
ASCII do caractere. Efetivamente, exceto pela adio de bits zero es-
querda, o valor inalterado, exceto pelo seu tipo.
(b) Um valor float convertido em inteiro descartando a parte fracionria.
Por exemplo, o valor 2,9 para 2. Se a parte inteira do valor de ponto
flutuante for grande demais para ser armazenada como um int, o resul-
tado indefinido.
5 Se o operador for char, ento o operando inteiro convertido para caractere. Se o
operando inteiro for maior que 255 ou menor que zero, o resultado indefinido.
Se a converso for bem-sucedida, ento, exceto pela perda de zeros esquerda,
o padro de bits do valor o mesmo; apenas o tipo alterado.
6 Qualquer outra combinao operador/tipo no permitida.
A implementao desta regra tambm direta:

...
else if (op.val.equals(Operator.FLOAT))
return new FloatValue((float)(v.intValue( )));
else if (op.val.equals(Operator.INT))
return new IntValue((int)(v.intValue( )));
else if (op.val.equals(Operator.INT))
return new IntValue((int)(v.intValue( )));
else if (op.val.equals(Operator.CHAR))
return new CharValue((char)(v.intValue( )));
throw new IllegalArgumentException(
should never reach here);
...

Da mesma forma que a implementao de applyBinary, apenas alguns casos so mos-


trados do que basicamente um comando case gigante.

 8 .4 U M T RATAM EN T O FO RM AL DE SEM N T I CA
Esta seo apresenta a semntica de Clite pelo uso de um estilo denotacional
formal. Ela espelha as regras de significado para comandos e expresses apresentadas
na Seo 8.2.
O propsito desta apresentao mostrar como a semntica de uma linguagem de
programao pode ser definida com preciso matemtica. Tal definio pode convencer
projetistas e implementadores de linguagens de que uma linguagem bem definida. A boa
definio uma caracterstica essencial para linguagens de programao, especialmente
quelas cujos programas devem se provar corretos (veja o Captulo 18).
Leitores que decidam estudar esta seo devem estar familiarizados com os tpicos
matemticos necessrios que so revistos no Apndice B.

8.4.1 Estados e Transformao de Estados


Na determinao do significado de uma varivel na Seo 8.2, tivemos de acessar
o valor da varivel no seu estado corrente. Assim como com os tipos no Captulo 5, uma
varivel e seu valor so modelados como um par ordenado, por exemplo, x, 3.
Assim, um estado um conjunto de pares ordenados, em que o primeiro elemento
o nome da varivel e o segundo seu valor. Por exemplo, a expresso a seguir descreve o
8.4 Um Tratamento Formal de Semntica 215

estado do nosso programa fatorial, que corresponde linha 8 da Tabela 7.3:

estado {n, 3, i, 2,  f, 2}

Para este determinado estado, podemos recuperar o valor de uma varivel, digamos f, escre-
vendo a expresso estado( f ), que apresenta o valor 2. J que os nomes de variveis so nicos,
um estado, como um conjunto de pares ordenados, equivalente a uma funo. Assim, a ex-
presso estado() denota a funo que recupera o valor da varivel v do estado corrente.
Uma transformao de estado ocorre sempre que uma atribuio for interpretada em
um programa imperativo ou orientado a objeto. Por exemplo, analise a seguinte atribuio:

i = i + 1;

cujo efeito alterar o estado anterior da seguinte maneira:

estado {n, 3, i, 3,  f, 2}

Em Clite, a varivel alvo sempre ocorre no estado. Por outro lado, uma atribuio em
CliteD, como:

f = 1;

pode ter o efeito de transformar o estado de:

estado {n, 3, i, 1}

para o estado:

estado {n, 3, i, 1,  f, 1}

Esse ltimo pode ser modelado com o uso da unio comum de conjuntos. Entretanto, usar
unio de conjuntos para o primeiro criaria o estado:

estado {n, 3, i, 3, i, 2,  f, 2}

o que claramente incorreto.


Transformaes de estado que representem ambos os tipos de atribuies podem
ser representadas matematicamente por uma funo especial denominada unio de
substituio, representada pelo smbolo . Essa funo semelhante unio comum
de conjuntos, exceto quando altera o valor de uma varivel que j esteja presente no
estado corrente.
Definio: A unio de substituio de X e Y, escrita X Y, o resultado da subs-
tituio em X de todos os pares x,  cujo primeiro membro corresponda a um par
x, w de Y por x, w, e depois adicionam-se a X todos os pares restantes em Y.
Por exemplo, suponha que estado1 {x, 1, y, 2, z, 3} e estado2 {y, 9, w, 4}.
Ento, estado1 estado2 {x, 1, y, 9, z, 3, w, 4}.
Outra forma de visualizar a unio de substituio por meio da juno natural des-
ses dois conjuntos. A juno natural estado1 estado2 o conjunto de todos os pares de
estado1 e estado2 que tenham o mesmo primeiro membro. Por exemplo,

{x, 1, y, 2, z, 3} {y, 9, w, 4} {y, 2, y, 9}

Lembre-se de que a diferena de conjunto, estado1 (estado1 estado2), remove


efetivamente cada par de estado1 cujo primeiro membro seja idntico ao primeiro mem-
216 Captulo 8 Interpretao Semntica

bro de algum par no estado2. Agora a unio de substituio pode ser definida em termos
do seu conjunto de diferenas da seguinte maneira:

estado1 estado2 (estado1 (estado1 estado2))  estado2

Os leitores podem ter observado que o operador um modelo formal e genera-


lizado da operao familiar de atribuio na programao imperativa. Ele desempenha,
assim, um papel central na semntica formal de linguagens imperativas e orientadas a
objeto, como C/C++, Ada e Java. Ilustraremos essa caracterstica a seguir.

8.4.2 Semntica Denotacional de um Programa


Lembre-se da Seo 8.2, em que a funo de significado S um mapeamento:
S: Programa Estado
S: Comando Estado Estado
S: Expresso Estado Valor
Isso quer dizer que o significado de um Programa uma funo que produz um Estado
de um Programa. De forma semelhante, o significado de um Comando uma funo que
produz um Estado desse Comando e um Estado. Finalmente, o significado de uma Ex-
presso uma funo que, dada uma expresso e um Estado, produz um Valor.4
Lembre-se tambm do que foi citado na Seo 8.2, que essas funes so funes par-
ciais, j que no so bem definidas por todos os membros do seu domnio. Assim, as repre-
sentaes abstratas de certas construes de programas em determinados estados no tm re-
presentaes finitas de significados, embora essas construes sejam sintaticamente vlidas.
J que a sintaxe abstrata de um programa em Clite uma estrutura de rvore cuja raiz
o elemento abstrato Programa, o significado de um programa Clite pode ser definido como
uma srie de funes, a primeira das quais define o significado de Programa. Os leitores de-
vem perceber essa funo como uma redeclarao matemtica da Regra de Significado 8.1.
S: Programa Estado
S (Programa p) S (p.corpo, EstadoInicial (p.partedec))
A primeira linha dessa definio um prottipo da funo S, enquanto a segunda linha
define a prpria funo, usando as definies de sintaxe abstrata dos constituintes diretos
do Programa:
Programa Declaraes partedec; Bloco corpo
Declaraes Declarao*
Declarao Varivel v; Tipo t
Assim, essa definio funcional diz que o significado de um programa o significado do
corpo do programa com o estado inicial produzido pela funo EstadoInicial aplicada
lista de declaraes.
A funo EstadoInicial, dada uma lista de declaraes, produz:

{ 1, undef t1,..., m, undef tm}

Isso significa que todas as variveis declaradas do programa so armazenadas no estado


inicial com seus valores configurados como o valor especial undef apropriado para cada tipo.

4. Para simplificar, essa definio exclui a questo de efeitos colaterais abordada na Seo 8.2.4.
8.4 Um Tratamento Formal de Semntica 217

8.4.3 Semntica Denotacional dos Comandos


Lembre-se de que um Comando em Clite definido na sintaxe abstrata como:
Comando Salto | Bloco | Atribuio | Condio | Lao
Salto
Bloco Comando*
Atribuio Varivel alvo ; Expresso origem
Condicional Expresso teste ; Comando ramificao_ento, ramificao_seno
Lao Expresso teste; Comando corpo
O significado de um comando , assim, o do tipo determinado do comando que ele
representa:
S : Comando Estado Estado
S (Comando d, Estado estado) S ((Salto)d, estado) se d for um Salto
S ((Atribuio)d, estado) se d for uma Atribuio
S ((Condicional)d, estado) se d for um Condicional
S ((Lao)d, estado) se d for um Lao
S ((Bloco)d, estado) se d for um Bloco

Salto O significado de um comando Salto efetivamente a funo identidade, j que


no altera o estado corrente da computao (lembre-se da Regra de Significado 8.2).

S (Salto p, Estado estado) estado

Atribuio O significado de uma Atribuio pode ser expresso como uma unio de
substituio (lembre-se da Regra de Significado 8.3):
S : Atribuio Estado Estado
S (Atribuio a, Estado estado) estado {a.alvo, S(a.origem, estado)}
Essa uma maneira formal de definir o estado que resulta da transformao do esta-
do corrente em um novo estado que difira do antigo apenas pelo par cujo primeiro mem-
bro a Varivel alvo a.target da Atribuio. O segundo membro desse par o valor da
origem Expresso a.origem da Atribuio. O significado dessa Expresso ser definido
formalmente por outra funo S na Seo 8.4.4.
Analise o exemplo da Figura 8.2 e suponha que o estado corrente seja:
estado {x, 2, y, 3,z, 75}
Intuitivamente, esperamos que o significado da expresso origem neste estado, ou
S (a.origem, estado) S (x 2 * y, {x, 2, y, 3, z, 75})
o Valor 4. Assim,
S (z x 2 * y, {x, 2, y, 3, z, 75}) {x, 2, y, 3, z, 75}
{z, 4}
{x, 2, y, 3, z, 4}
o que completa a transformao de estado para essa atribuio.
218 Captulo 8 Interpretao Semntica

Atribuio

Varivel Expresso
z

Binrio

Operador Varivel Binrio


x

Operador Valor Varivel


* 2 y

| Figura 8.2 Sintaxe Abstrata para a Atribuio z = x + 2 * y;

Condicional O significado de um Condicional o significado de uma ou outra


de suas ramificaes, dependendo de o teste ser verdadeiro ou falso (veja a Regra de
Significado 8.4).

S (Condio c, Estado estado) S (c.ramificao_ento, estado) se S (c.teste, estado)


for verdadeiro
S (c.ramificao_seno, estado) caso contrrio

Analise o cdigo a seguir para calcular o maior entre dois nmeros a e b:

if (a b)
max = a;
else
max = b;

Dado o estado estado {a, 3, b, 1}, o significado dessa condio apresenta a seguinte
interpretao:

S (se (a b) max a; seno max b;, estado)


S (max a;, estado) se S (a b, estado) for verdadeiro
S (max b;, estado) caso contrrio

Usando a definio de S(Expresso, estado), vemos que S(a b, estado) verdadei-


ro nesse estado em particular, de modo que o significado desse Condicional o mesmo
daquele da primeira Atribuio, ou S(max a;, estado). Considerando o que sabemos
sobre o significado de Atribuies, vemos que o estado final se torna:

estado {a, 3, b, 1, max, 3}


8.4 Um Tratamento Formal de Semntica 219

Bloco O significado de um Bloco a funo identidade (se no tiver comandos) ou o


significado do resto do Bloco aplicado ao novo estado obtido pela computao do signifi-
cado do primeiro comando do Bloco.

S (Bloco b, Estado, estado) estado se b { }


S (bn, S (..., S (b1, estado)...)) se b {b1b2...bn}

Essa uma maneira formal de expressar a Regra de Significado 8.6.


Por exemplo, analise o Bloco da Figura 7.2. Aqui est um resumo do seu significado
para estado {i, 2,f, 2,n, 3}, conforme definido pela funo anterior.

S ({i i 1; f f * i;}, estado)


S ({f f * i;}, S (i i 1;, {i, 2, f, 2, n, 3}))
S ({}, S (f f * i;, S (i i 1;, {i, 2, f, 2, n, 3})))
S ({}, S (f f * i; ,{i, 3, f, 2, n, 3}))
S ({}, {i, 3, f, 6, n, 3})
{i, 3, f, 6, n, 3}

As primeiras duas linhas e a ltima nessa interpretao revelam o significado de


um Bloco, enquanto as linhas restantes aplicam as duas atribuies ao estado original na
ordem em que aparecem no Bloco. Isso ilustra que podemos explicar formalmente o que
acontece quando alguma seqncia de comandos executada.

Lao O significado de um Lao a funo identidade (se o teste boleano no for


verdadeiro) ou o significado do mesmo lao quando aplicado ao estado resultante da exe-
cuo do seu corpo uma vez (veja a Regra de Significado 8.5).

S (Lao l, Estado estado) S (l, S (l.corpo, estado)) se S (l.teste, estado) verdadeiro


estado caso contrrio

Por exemplo, considere o Lao da Figura 7.2. Est claro que os dois comandos que
precedem o lao neste exemplo formam estado {i, 1, f, 1, n, 3}. Lembrando o que
sabemos sobre o significado do bloco {i i 1; f f * i;} do pargrafo anterior, pode-
mos comear a anlise como segue:

S (enquanto (in){i i 1; f f * i;}, estado)


S (enquanto (in){i i 1; f f*i;}, S ({i i 1;f f * i;},{i,1,f,1,n, 3}))

Isso significa que interpretamos o corpo uma vez no determinado estado e ento
passamos o estado resultante de volta para a funo S para o prprio Lao. Disso resulta
a seguinte srie de transformaes de estados:

S (enquanto (in){i i 1; f f * i;}, estado)


S (enquanto (in){i i 1; f f * i;}, S ({i i 1;f f * i;}, {i,1, f,1, n,3}))
S (enquanto (in){i i 1; f f * i;}, S ({i i 1;f f * i;}, {i, 2, f, 2, n, 3}))
S (enquanto (in){i i 1;f f * i;}, S ({i i 1; f f * i;}, {i, 3, f, 6, n, 3}))
{i, 3,f, 6,n, 3}
220 Captulo 8 Interpretao Semntica

Para cada uma das trs linhas intermedirias, a funo S para Bloco, Atribuio e Ex-
presso usada para transformar o estado que mostrado na linha abaixo dela. A ltima
linha resulta do teste i n se tornando falso, em cujo caso o estado inalterado. Esta
funo , claro, uma funo parcial, j que um Lao pode ser escrito de forma a nunca
parar. Nesse caso, deve ficar claro que a ltima linha em interpretaes como a anterior
nunca ser alcanada.

8.4.4 Semntica Denotacional de Expresses


Lembre-se de que uma Expresso em Clite possui a seguinte sintaxe abstrata:

Expresso Varivel | Valor | Binrio | Unrio


Binrio OpBinrio op; Expresso termo1, termo2
Unrio OpUnrio op; Expresso termo
Varivel String id
Valor ValorInt | ValorBool | ValorFloat | ValorChar

O significado de uma Expresso em um Estado depende do seu tipo:

S : Expresso Estado Valor


S (Expresso e, Estado estado)
e se e for um Valor
estado(e) se e for uma Varivel
ApplyBinary(e.op, S (e.termo1, estado), S (e.termo2, estado)) se e for um Binrio
ApplyUnary(e.op, S(e.termo, estado) se e for um Unrio

Esta funo uma formalizao da Regra de Significado 8.7.


A funo ApplyBinary calcula um Valor quando recebe um Operador binrio e dois
Valores que sejam seus operandos (veja a Regra de Significado 8.8).

ApplyBinary : Operador Valor Valor Valor


ApplyBinary(Operador op, Valor v1, Valor v2)
1 2 se op int+
1 2 se op int-
1 2 se op int*
floor 1
...
(| |)
2 sign (1 2) se op int/

Esta funo supe que os operadores int+, int-,... carregam seus significados cos-
tumeiros no domnio semntico dos inteiros I, nmeros de ponto flutuante F, strings S e
boleanos B.5 Ela tambm supe que os tipos de 1 e 2 devam ser os mesmos e concordar
com o tipo requerido pelo operador. Assim como na Seo 8.2.3, mostramos apenas os
primeiros casos de uma definio semntica bastante grande, dado o nmero de operado-
res distintos.
A funo ApplyUnary calcula um Valor quando recebe um Operador unrio e um
Valor que seu operando (veja a Regra de Significado 8.9).

5. Observe que a definio do operador int/ uma forma matemtica de especificar o quociente inteiro de 1
e 2 (a implementao Java mais simples de expressar).
8.4 Um Tratamento Formal de Semntica 221

ApplyUnary : Operador Valor Valor


ApplyUnary (Operador op, Valor )  se op !
 se op int-
 se op float-
...

em que o tipo de  deve concordar com o tipo requerido pelo operador. Assim como na
Seo 8.2.3, mostramos apenas os primeiros casos de uma definio de semntica bastan-
te grande, dado o nmero de operadores distintos.
Para ilustrar como essas funes atuam, analise novamente a expresso x+2*y cuja
sintaxe abstrata mostrada na Figura 8.2. Suponha que essa expresso seja avaliada no
estado estado {x, 2, y, 3, z, 75}, ou seja, queremos usar essas definies funcio-
nais para mostrar que:

S (x 2 * y, {x, 2, y, 3, z, 75} 4

J que esta expresso um Binrio, temos:

S (x 2 * y, {x, 2, y, 3, z, 75} ApplyBinary(, A, B)


em que A S (x,{x, 2, y, 3, z, 75})
e B S (2 * y,{x, 2, y, 3, z, 75})

Agora o significado de A o valor de x no estado {x, 2, y, 3, z, 75}, ou 2, j que x
uma Varivel. O significado do Binrio B, entretanto, vem de outra aplicao da funo
S, que :

S (2 * y, {x, 2, y, 3, z, 75} ApplyBinary(*, C, D)


em que C S (2, {x, 2, y, 3, z, 75})
e D S (y, {x, 2, y, 3, z, 75})

Aqui, o significado de C 2, j que 2 um Valor e o significado de D 3, j que y uma


Varivel no estado {x, 2, y, 3, z, 75}.
Com essas informaes, a definio de ApplyBinary nos d o significado de 2 * y:

S (2 * y, {x, 2, y, 3, z, 75}) ApplyBinary (*, 2, 3)


6

Assim, o significado da nossa expresso original se revela o seguinte:

S (x 2 * y, {x, 2, y, 3, z, 75}) ApplyBinary (, 2, 6)


4

Observe que este exemplo usa apenas as funes ApplyBinary e S definidas anterior-
mente, junto s propriedades matemticas da aritmtica de nmeros inteiros, para derivar
esse resultado. Deve ficar claro para os leitores que os significados de Expresses mais
complexas com operaes binrias e operandos inteiros so definidos integralmente por
essas funes.
Isso completa nosso tratamento formal da semntica para a linguagem Clite. Uma im-
plementao completa em Java da sintaxe Clite, sistema de tipos e semntica podem ser en-
contrados no site do livro indicado no Prefcio. Essas ferramentas podem ser usadas para
exercitar as funes discutidas neste captulo com diversos exemplos de programas em Clite.
222 Captulo 8 Interpretao Semntica

8.4.5 Limites de Modelos Semnticos Formais


A semntica formal difcil de aplicar a algumas construes das linguagens, como
transferncias de controle (os comandos goto, break e continue), gerao e captura de
excees e operaes dinmicas de memria relacionadas com chamadas de funes e
criao de objetos.
Em semntica denotacional, transferncias de controle so manipuladas por um dis-
positivo conhecido como uma continuao (veja, por exemplo, Tennet, 1981), cuja co-
bertura est alm do escopo deste texto.
Um recurso de linguagem que correntemente no pode ser definido por um modelo
semntico denotacional a concorrncia. Em semntica denotacional, o significado de um
Programa um estado. J que significado uma funo, dado o mesmo estado de entrada
e o mesmo programa, o resultado sempre o mesmo estado de sada. Entretanto, o signi-
ficado de um programa concorrente pode ser um estado ligeiramente diferente cada vez
que o programa for executado.

8 . 5 RE S UM O
Este captulo introduz a idia de semntica, apresentando uma descrio semntica
completa de Clite, primeiro em um estilo operacional e depois em um estilo denotacional
formal. As vantagens e as limitaes de modelos semnticos formais para linguagens de
programao tambm so resumidas.
Muitos dos exerccios pedem que voc execute ou modifique o interpretador Clite
para explorar diversos recursos semnticos. Esse interpretador pode ser baixado do site
do livro mencionado no Prefcio.

E XE R C CI OS
8.1 Discuta a incluso de operaes e ao Clite. Suponha que elas sejam permitidas apenas
como comandos isolados.
(a) Discuta as alteraes necessrias para o verificador de tipos e o transformador de tipos.
(b) Acrescente novas regras para comandos que definam a semntica desses operadores.
(c) Modifique o interpretador CliteD para implementar esses novos comandos.
(d) Discuta por que no adicionamos esses operadores a expresses em geral. Dica: pense em como
isso altera o interpretador.
8.2 Adicione comandos de E/S cin/cout no estilo de C++ a Clite:
(a) Definindo sua sintaxe concreta,
(b) Definindo sua sintaxe abstrata,
(c) Modificando o lexer e o analisador para reconhec-los e
(d) Adicionando esses comandos ao interpretador CliteD.
8.3 Discuta as vantagens e as desvantagens de no permitir comandos de atribuio na forma b = a,
em que a tenha o valor undef. Que alteraes voc teria de fazer nas regras de semntica e no inter-
pretador de Clite para impor essa mudana?
8.4 Adicione o operador % ao Clite:
(a) Definindo sua sintaxe concreta,
(b) Definindo sua sintaxe abstrata,
(c) Modificando o lexer e o analisador para reconhec-lo e
(d) Adicionando-o ao interpretador CliteD.
8.5 Resumo 223

8.5 Modifique a definio dos operadores && e || de modo que no usem avaliao de curto-circuito.
Apresente as novas regras semnticas. Discuta as modificaes necessrias para a implementao.
8.6 Modifique o interpretador Clite de modo que o estado corrente seja exibido imediatamente antes de
cada comando ser executado. Crdito adicional: inclua o nmero do comando na exibio.
8.7 Modifique o interpretador Clite de modo que o trmino do programa devido a um erro identifique
em qual linha esse erro ocorreu.
8.8 Usando a Tabela 7.3 como guia, construa um rastro de execuo para o programa Clite:

1 void main ( ) {
2 int i, a, z;
3 i = 5;
4 a = 2;
5 z = 1;
6 while (i  0) {
7 if (i - 1 / 2 * 2 == 1)
8 z = z * a;
9 i = i / 2;
10 a = a * a;
11 }
12 }

8.9 Para o programa do Exerccio 8.8:


(a) Apresente a sintaxe abstrata do comando 8 como ela produzida pelo analisador.
(b) Apresente a sintaxe abstrata do comando 8 como ela produzida pelo transformador de tipos.
(c) De acordo com as regras de semntica da Seo 8.2.3, explique precisamente como o valor da
expresso no comando 8 calculado.
8.10 Modifique o interpretador Clite de modo que overflows de inteiros (conforme visto no Exerccio 7.5)
interrompam o interpretador com uma mensagem de erro apropriada. Demonstre que sua modificao
funciona no programa fatorial.
8.11 Modifique o interpretador Clite de modo que as expresses com efeitos colaterais sejam suportadas.
Use as definies da Seo 8.2.4 como guia.
8.12 Usando a Tabela 7.3 como guia, construa um rastro de execuo para o programa CliteD:

1 void main ( ) {
2 i = 5;
3 a = 2.0;
4 z = 1.0;
5 while (i  0) {
6 if (i - 1 / 2 * 2 == 1)
7 z = z * a;
8 i = i / 2;
9 a = a * a;
10 }
11 }
224 Captulo 8 Interpretao Semntica

8.13 Para o programa do Exerccio 8.12, apresente:


(a) A sintaxe abstrata do comando 7 como ela produzida pelo transformador de tipos.
(b) De acordo com as regras de semntica da Seo 8.2.3, explique precisamente como o valor
da expresso calculado.
8.14 Modifique o interpretador CliteD de modo que converses implcitas de inteiro para ponto flutuante
sejam permitidas.
8.15 Suponha que estado1 {x, 1, y, 2, z, 3}, estado2 {y, 5} e estado3 {w, 1}. Quais so os
resultados das seguintes operaes? Observao: o smbolo denota o mtodo onion introduzido
na Seo 8.4.
(a) estado1 estado2
(b) estado1 estado3
(c) estado2 estado3
(d)  estado2
(e) estado1 estado3
(f) estado2 estado3
(g) (estado1 (estado1 estado3)  estado3)
8.16 Mostre como o significado de cada uma das expresses a seguir e os estados dados so derivados
das regras de semntica apresentadas na Seo 8.2.3. (A sintaxe abstrata de cada uma dessas expres-
ses foi desenvolvida como um exerccio no Captulo 2.)
(a) S ((z 2) * y, {x, 2, y, 3, z, 75})
(b) S (2 * x 3 / y 4,{x, 2, y, 3, z, 75})
(c) S (1, {x, 2, y, 3, z, 75})
8.17 Mostre todas as etapas da derivao do significado do seguinte comando de atribuio, quando
executado em um determinado estado, usando as regras de semntica apresentadas na Seo 8.2.3.

S (z 2 * x 3 / y 4,{x, 6, y, 12, z, 75})

8.18 Complete as regras de semntica para operadores aritmticos, boleanos e lgicos para Clite que
foram introduzidos na Seo 8.2.3.

 8.19 Modifique as funes semnticas denotacionais para comandos e expresses conforme for necess-
rio para no permitir uma atribuio b = a, na qual a possui o valor undef.

 8.20 Modifique as funes semnticas denotacionais para expresses de modo que os efeitos colaterais
sejam suportados. Use as definies da Seo 8.2.4 como guia.

 8.21 Apresente uma funo semntica denotacional para os operadores ++ e --, conforme discutido
no Exerccio 8.1.

 8.22 Apresente uma funo semntica denotacional para os operadores cin e cout conforme discutido
no Exerccio 8.2.
Funes 9
melhor fazer cem funes operarem sobre uma estrutura de dados do que dez
funes sobre dez estruturas de dados.
Alan Perlis

VISO GERAL DO CAPTULO

9.1 TERMINOLOGIA BSICA 226


9.2 CHAMADA E RETORNO DE FUNES 226
9.3 PARMETROS 227
9.4 MECANISMOS DE PASSAGEM DE PARMETROS 229
9.5 REGISTROS DE ATIVAO 236
9.6 FUNES RECURSIVAS 237
9.7 PILHA DE TEMPO DE EXECUO 238
9.8 RESUMO 240
EXERCCIOS 241

Funes so elementos fundamentais em toda linguagem de programao, j que so


ferramentas essenciais para abstrao em programao. Em linguagens diferentes, as fun-
es so conhecidas variavelmente como procedimentos, sub-rotinas, subprogramas ou
mtodos, e possuem diversas caractersticas em comum, assim como algumas diferenas
importantes nas mais variadas linguagens.

225
226 Captulo 9 Funes

O comportamento das funes altamente relacionado com o gerenciamento din-


mico de memria. As caractersticas de uma estrutura de memria fundamental, a pilha,
ajudam a compreendermos melhor como a memria organizada para implementar fun-
es, especialmente funes recursivas.
Este captulo discute os elementos-chave de funes em linguagens de programao,
incluindo chamada e retorno, parmetros e argumentos e recurso. A implementao de
funes discutida no Captulo 10.

9 .1 T ER M IN O LO GIA BSICA
Fortran e Ada fazem distino entre funes que retornam um valor e aquelas que no
retornam. As primeiras so denominadas funes, e as ltimas, sub-rotinas em Fortran e
procedimentos em Ada. Linguagens do tipo de C no fazem tal distino; ambas so chama-
das de funes. Em C++ e Java, um mtodo uma funo declarada dentro de uma classe.
Alguns tm argumentado que funes deveriam ser como funes matemticas. Elas
no deveriam ter efeitos colaterais e seu nico valor de retorno deveria ser o valor da
prpria funo. Chamadas para funes, como em matemtica, aparecem como parte de
uma expresso. Um exemplo de Fortran a frmula para calcular uma soluo x para uma
equao quadrtica, ax2 bx c:

x= (b*b sqrt(4*a*c))/(2*a)

Nesse caso, a funo sqrt calcula a raiz quadrada do seu argumento e no possui efeitos
colaterais.
Alguns tambm tm argumentado que sub-rotinas e procedimentos no devem retor-
nar um valor, exceto modificando um ou mais dos seus argumentos. Uma sub-rotina ou
um procedimento tambm pode ler ou gravar sada ou modificar uma ou mais variveis
globais, que so os efeitos colaterais. Em linguagens tipo C, um procedimento uma fun-
o com um tipo de retorno void. Em contraste, Fortran e Ada tm uma sintaxe distinta
para funes e procedimentos.
Chamadas a procedimentos aparecem como declaraes separadas em vez de como
partes de uma expresso. Mesmo em C, uma chamada a funo pode aparecer como uma
declarao separada, em cujo caso seu tipo de retorno normalmente void. Um exemplo
de uma chamada C aparece a seguir:

strcpy(s1, s2);

Essa chamada copia uma string de caracteres de s1 para s2.


Deixando os argumentos anteriores parte, a maioria das linguagens orientadas a
objeto e imperativas no tentam impor a regra do efeito colateral em funes. Mantendo-
se a conveno de C, usaremos o termo funo para denotar tanto procedimentos quanto
funes.

9 .2 CHA MAD A E RET O RN O DE FU N ES


O programa na Figura 9.1, primeiramente introduzido no Captulo 4, pode ilustrar
a dinmica de chamada e o retorno de funes. Nesse programa, main inicialmente
chamado e as variveis globais h e i esto disponveis para ele, junto s variveis locais
a e b. Aps as trs primeiras declaraes de atribuio em main terem sido executadas, as
variveis h, a e b tm valor 5, 3 e 2, respectivamente.
9.3 Parmetros 227

int h, i;
void B(int w) {
int j, k;
i = 2*w;
w = w+1;
}
void A(int x, int y) {
bool i, j;
B(h);
}
int main( ) {
int a, b;
h = 5; a = 3; b = 2;
A(a, b);
}

| Figura 9.1 Exemplo de Programa C/C++

Quando A chamada por main, seus parmetros x e y obtm os valores 3 e 2, e A


tambm tem acesso varivel global h. As variveis locais de A, i e j tambm so aces-
sveis nesse ponto, assim como a varivel global h. Entretanto, a varivel global i no
acessvel a A, conforme destacado na discusso sobre escopos no Captulo 4.
Quando B chamada por A, o parmetro w se torna acessvel (com o valor 5), assim
como as variveis locais j e k e as globais h e i. Dessa forma, a primeira atribuio dentro
de B atribui o valor 10 para a varivel global i.
O controle retorna de uma funo chamada de uma entre duas formas. Para uma
funo ou um procedimento void, o controle retorna quando o final do corpo da funo
atingido ou quando uma declarao return encontrada. Para uma funo no-void, o
controle retorna quando uma declarao return encontrada. Nesse caso, a declarao
return tem uma expresso que designa o valor a ser retornado.
Funes em Pascal designam um resultado retornado por meio de uma declarao
de atribuio cuja varivel-alvo identicamente o nome da prpria funo. Funes
em Haskell designam o resultado gravando uma expresso. Muitos exemplos do estilo
Haskell de escrita de funes aparecem no Captulo 14.

9 .3 PA R M ET R O S
Funes derivam sua grande utilidade tanto em matemtica quanto em linguagens
de programao pela sua capacidade de receber parmetros. Por exemplo, uma funo de
raiz quadrada no seria muito til se no pudesse receber um argumento que especificasse
o valor cuja raiz quadrada devesse ser calculada.
Definio: Uma expresso que aparece em uma chamada de funo ou em um
procedimento denominada argumento.
228 Captulo 9 Funes

Definio: Um identificador que aparece em uma declarao de funo ou em um


procedimento denominado parmetro.
A tradio Algol/Pascal usar o termo parmetro real para uma expresso na cha-
mada e parmetro formal para um parmetro em uma declarao de funo. A tradio C/
Java usar os termos argumento para o primeiro e parmetro para o segundo. Alguns tex-
tos usam parmetros como sinnimo de argumento. Este texto segue a tradio C/Java.
Seguindo a conveno matemtica, a maioria das linguagens de programao asso-
cia argumentos na chamada de funes com parmetros na declarao de funes pela
posio. Assim, na Figura 9.1, a chamada em main para a funo A:

A(a, b)

associa o argumento a ao parmetro x e o argumento b ao parmetro y, e nunca o contrrio.


Exceto para nomes de funes sobrecarregadas (veja o Captulo 4), os argumentos
devem corresponder em nmero e tipo. Excees a essa regra ocorrem nas linguagens
Perl, Ada e Smalltalk.
Perl no permite a declarao de parmetros em um cabealho de funo. Em vez
disso, ela disponibiliza os parmetros em uma matriz chamada @_. Os parmetros podem
ser acessados por posio usando essa matriz. Essa conveno foi adaptada do script do
shell Unix. Perl discutida na Seo 12.7.
Como alternativas correspondncia estrita por posio, Ada fornece dois mecanis-
mos. No primeiro, os parmetros podem ser declarados com valores-padro:

function sum(list : realist;


length : integer := listlength) return real is
...

Nesse exemplo, se o segundo parmetro for omitido, ele passa para o comprimento
declarado do primeiro parmetro. Assim que um parmetro declarado com um valor-
padro, todos os parmetros depois dele tambm devem ter valores-padro. Essa caracte-
rstica desejvel porque ajuda a reduzir o nmero de argumentos em uma chamada. Um
nmero de funes de bibliotecas cientficas em linguagens anteriores, principalmente
Fortran, tinha contadores de parmetros de dgito duplo.
O outro mecanismo fornecido por Ada permite que os argumentos tenham uma ordem
diferente da usada pelos nomes dos parmetros na chamada. Na Figura 9.1, a chamada de A
em main pode ser escrita de qualquer uma das seguintes formas:

A(a, b);
A(a, y => b);
A(x => a, y => b);
A(y => b, x => a);

Esse mecanismo mais til quando os nomes de parmetros forem identificadores


com significado.
Smalltalk (veja a Seo 13.3) fornece um mecanismo interessante. Uma chamada do
mtodo at:put em uma matriz a escrita:

a at: i put: x + y
9.4 Mecanismos de Passagem de Parmetros 229

Supondo que dois-pontos seja um identificador vlido, o equivalente em Java seria:

a.at:put:(i, x+y); // significando que a[i] = x+y

Falando estritamente, isso no se desvia da regra de correspondncia por posio, j que


o mtodo put:at: pode no existir ou pode ter uma semntica diferente.

9.4 MECANISMOS DE PASSAGEM DE PARMETROS


Os valores associados aos parmetros durante a vida de uma funo chamada so
determinados com base em como esses argumentos so passados para a funo pela cha-
mada. H cinco formas principais de passar um argumento para uma funo:
1 Por valor,
2 Por referncia,
3 Por resultado-valor,
4 Por resultado e
5 Por nome.
Entre esses, os dois primeiros so os mais proeminentes.

9.4.1 Passagem por Valor


A passagem por valor (s vezes chamada de avaliao vida) geralmente ocorre
quando um argumento fornece um valor de entrada para uma funo chamada.
Definio: Passar um argumento por valor significa que o valor do argumento
calculado no tempo da chamada e copiado para o parmetro correspondente.
Usando o programa do exemplo da Figura 9.1, a passagem por valor ilustrada na
Figura 9.2, tanto na chamada A(a,b) de main (parte (b)) quanto na chamada B(x) de
A (parte (c)). Nessas chamadas, cada um dos argumentos a, b e x passado por valor.
Observe que o rastro fornecido mostra valores 3 e 2 para os parmetros x e y de A. Agora
todas as referncias a x e y de dentro de A usam essas cpias dos valores de argumentos
a e b, de modo que seus valores originais so efetivamente protegidos contra gravao
durante a chamada.
Assim, a seguinte funo de troca C/C++ no trocar os valores dos argumentos
associados aos parmetros a e b:

void swap (int a, int b) {


int temp = a;
a = b;
b = temp;
}

Embora os valores dos parmetros a e b sejam de fato trocados, esses valores no so


copiados para os argumentos. Assim, a passagem por valor , muitas vezes, chamada de
mecanismo de cpia.
230 Captulo 9 Funes

rea
Esttica Main A B

h no definido a 3
(a)
i no definido b 2

h 5 a 3 x 3 (b)
i no definido b 2 y 2
i no definido
j no definido

h 5 a 3 x 3 w 5
i 10 b 2 y 2 j no definido (c)
i no definido k no definido
j no definido

| Figura 9.2 Passando Argumentos por Valor

Em C, uma funo de troca pode ser escrita com o uso de ponteiros, da seguinte
forma:

void swap(int *a, int *b) {


int temp = *a
*a = *b;
*b = temp;
}

Nesse caso, os valores dos parmetros so ponteiros para outras variveis. Considere a
chamada:

swap(&x, &y);

Os parmetros a e b contm os endereos &x e &y, respectivamente. Devido ao fato de os


ponteiros serem explicitamente desreferenciados, como no exemplo

*a = *b;

os valores dos argumentos x e y so trocados.


Assim como em C, todos os argumentos em Java so passados por valor. O va-
lor de uma varivel para os assim chamados tipos de referncia (matrizes e objetos de
classes) na verdade um endereo em vez de um valor comum (como seria o caso para
int, float, char etc.). Argumentos que sejam tipos de referncia so passados por inter-
mdio de cpias de suas referncias (endereos) para o parmetro em vez do seu valor.
9.4 Mecanismos de Passagem de Parmetros 231

Assim, a seguinte funo de troca em Java no substituir os valores dos argumentos


associados aos parmetros a e b:

void swap(Object a, Object b) {


Object temp = a;
a = b;
b = temp;
}

O raciocnio basicamente o mesmo que em C/C++.


Entretanto, a seguinte funo swap em Java trocar os valores das entradas de ndi-
ces i e j na matriz associada ao parmetro A.

void swap(Object[ ] A, int i, int j) {


Object temp = A[i]
A[i] = A[j];
A[j] = temp;
}

9.4.2 Passagem por Referncia


A passagem por referncia ocorre quando um argumento fornece uma varivel de
sada qual a funo chamada pode atribuir um resultado.
Definio: Passar um argumento por referncia (ou por endereo) significa que
o endereo de memria do argumento copiado para o parmetro correspon-
dente, de modo que o parmetro se torna uma referncia (ponteiro) indireta ao
argumento real.
Assim, todas as atribuies ao parmetro formal dentro da vida da chamada afetam
diretamente o valor do argumento. Por exemplo, suponha que o programa da Figura 9.1
tivesse o argumento para o parmetro w de B passado por referncia em vez de por valor.
Agora, a atribuio

w = w+1;

dentro de B ir incrementar diretamente o valor de h, em vez de uma cpia desse valor, dentro
da varivel w de B.
Uma forma de se entender a passagem por referncia visualizando como um com-
pilador poderia traduzir o programa da Figura 9.1 para C, conforme mostrado na Figura
9.3. Nesse exemplo, o endereo de h, a saber, &h, passado para a varivel ponteiro *w.
Dentro da funo B, todas as ocorrncias de w so desreferenciadas explicitamente como
*w. Assim, na passagem por referncia o argumento h potencialmente alterado por qual-
quer atribuio ao parmetro w. Devido desreferenciao na verso compilada, no
possvel mudar para onde w aponta. Um rastro da verso compilada do programa
apresentado na Figura 9.4.
Em linguagens que suportam mecanismos de passagem de mltiplos parmetros,
a distino entre um parmetro por valor e um de referncia deve se tornar explcita
dentro da declarao da funo. Por exemplo, em C++, usado um & para indicar um
parmetro por referncia e a sua ausncia indica um parmetro por valor. Ou seja, se
232 Captulo 9 Funes

int h, i;
void B(int* w) {
int j, k;
i = 2*(*w);
*w = *w+1;
...
}
void A(int* x, int* y) {
bool i, j;
B(&h);
...
}
int main( ) {
int a, b;
h = 5; a = 3; b = 2;
A(&a, &b);
...
}

| Figura 9.3 Exemplo de Passagem por Referncia

rea
Esttica Main A B

h no definido a 3
(a)
i no definido b 2

h 5 a 3 x 3 (b)
i no definido b 2 y 2
i no definido
j no definido

h 5 a 3 x 3 w endereo(h)
i 10 b 2 y 2 j no definido (c)
i no definido k no definido
j no definido

| Figura 9.4 Exemplo de Passagem de um Argumento por Referncia


9.4 Mecanismos de Passagem de Parmetros 233

a funo B da Figura 9.1 fosse escrita em C++, sua primeira linha seria alterada da seguin-
te maneira, para indicar um parmetro por referncia:

void B(int& w) {

e a chamada de A permaneceria B(h).


Essa declarao de w como um parmetro por referncia em B requer que o argumen-
to em qualquer chamada tenha um endereo, ou seja, uma varivel, matriz ou estrutura,
por exemplo, mas no uma expresso com operadores ou um literal.
Os argumentos so passados por referncia nos casos em que o valor real do argumento
deva mudar durante a vida da chamada. A passagem por referncia tambm preferida
quando o argumento passado ocupa muita memria, como uma matriz grande. Nesses ca-
sos, o overhead de espao e tempo necessrio para criar uma cpia completa do objeto, se
ele fosse passado por valor, evitado pela cpia de um nico endereo para o parmetro.
A maior desvantagem da passagem por referncia que os efeitos colaterais sobre o
valor original do argumento passado podem no ser desejados por quem realizou a chama-
da. Por exemplo, se a chamada de B por A com argumento h no quiser que o valor de h seja
alterado durante a vida dessa chamada, deve ser evitada a passagem de h por referncia.
Fortran usa exclusivamente a passagem por referncia, o que causou alguns efeitos
colaterais interessantes em verses mais antigas de Fortran. Por exemplo, a sub-rotina
sub de Fortran, que adiciona 1 ao seu parmetro p, junto chamada call sub(1), po-
deria deixar o valor da constante 1 alterado para 2 por todo o resto da execuo do
programa. Compiladores Fortran modernos avaliam cada parmetro real que no pode
ser interpretado como um valor l e o atribuem a uma varivel temporria; o endereo da
temporria ento passado para o parmetro.

9.4.3 Passagem por Resultado-valor e Resultado


A passagem por resultado-valor usada quando um argumento fornece tanto entra-
da para quanto sada da funo chamada.
Definio: Um argumento passado por resultado-valor implementado por meio
da cpia do valor do argumento para o parmetro no incio da chamada e depois
por meio da cpia do resultado calculado de volta para o argumento correspon-
dente no final da chamada.
Por ser um mecanismo copy-in-copy-out, a passagem por resultado-valor combina a
passagem por resultado com a passagem por valor.
Definio: Um argumento passado por resultado implementado pela cpia do
valor final, calculado para o parmetro de sada, para o argumento no final da vida
da chamada.
Alguns compiladores Fortran passam variveis no-matrizes usando a passagem por
resultado-valor. As passagens por referncia e por resultado-valor so equivalentes na
falta de aliasing.
Definio: Aliasing ocorre quando, dentro de uma funo ou de um procedimen-
to, a mesma localizao de memria pode ser acessada com o uso de diferentes
nomes.
Exemplos de aliasing incluem:
Passar uma varivel como parmetro e referenci-la como uma global dentro da
mesma funo.
Passar uma varivel como argumento duas ou mais vezes com o uso de qualquer
mecanismo de passagem de parmetro que no seja por valor.
234 Captulo 9 Funes

Ter duas ou mais referncias (por exemplo, ponteiros) para a mesma localizao.
Como uma comparao da passagem por referncia versus passagem por resultado-
valor, analise as seguintes funes semelhantes em C++ e Ada:

void f (int& x, int& y)


{
x = x + 1;
y = y + 1;
}

procedure f (x, y: in out Integer) is


begin
x := x + 1;
y := y + 1;
end f;

A chamada f(a, b) possui o efeito, em ambos os casos, de incrementar tanto a quanto


b por um. Entretanto, a chamada f(a, a) deixar o valor a priori de a incrementado por
2 no caso de C++ (passagem por referncia) e por 1 (passagem por valor) no caso de Ada.1
Entretanto, assim como em muitas outras questes, os padres da maioria das lin-
guagens especificam que tais programas so invlidos. Embora casos simples como o
recm-citado possam ser detectados por muitos compiladores, o problema de aliasing ,
de modo geral, intratvel.

9.4.4 Passagem por Nome


A passagem por nome um exemplo de associao tardia, pelo fato de a expresso
na chamada no ser avaliada at que seja usada pela funo.
Definio: Um argumento passado por nome se comporta como se fosse subs-
titudo textualmente para cada ocorrncia do parmetro.
Uma viso de implementao da passagem por nome uma macroexpanso, efetiva-
mente o mecanismo #define de C/C++, mas implementada para cada chamada, em vez
de em tempo de compilao, como usado por #define.
A passagem por nome foi usada originalmente em Algol 60. Em casos simples, era
um mecanismo ineficiente para se obter chamada por referncia. Infelizmente, para Algol
60 isso tinha diversas conseqncias no previstas.
Uma dessas conseqncias era a incapacidade de escrever um procedimento com-
pletamente geral de troca:

procedure swap (a, b);


integer a, b;
begin integer t;
t := a;
a := b;
b := t
end;

1. Tecnicamente, um parmetro in out Ada pode ser implementado como passagem por referncia ou por
resultado-valor. Nesse exemplo, estamos supondo que a implementao seja resultado-valor.
9.4 Mecanismos de Passagem de Parmetros 235

A chamada swap(a[i], i) troca os dois valores, mas a chamada swap(i, a[i]) no


troca, porque a segunda declarao de atribuio muda o valor de i.
Ainda mais interessante o dispositivo de Jensen (Knuth, 1967). Analise a seguinte
funo de soma:

procedure sigma (a, j, m) : integer;


integer a, j, m;
begin integer s;
s := 0; j := 1;
while (j <= m) do
begin
s := s + sigma;
j := j + 1;
end;
sigma := s;
end;

primeira vista, parece ser uma forma custosa de se calcular a quantidade m a.


Analise, entretanto, cada uma das seguintes chamadas:

s := sigma (z, i, n);


s := sigma (x[i], i, n);
s := sigma (x [i] * y[i], i, n);

Essas chamadas calculam, respectivamente:


nxz
x1 +...+ xn
x1 x y1 +...+ xn x yn
Com o advento da programao estruturada (veja o Captulo 7, Seo 7.5.4), um
prmio foi colocado sobre a escrita de cdigo limpo e facilmente compreensvel. O dispo-
sitivo de Jensen forneceu evidncia clara de que as funes que usam passagem por nome
no poderiam ser totalmente compreendidas, independentemente de chamadas sobre es-
sas funes. Nenhum dos sucessores de Algol usou a passagem por nome.
Entretanto, a associao tardia pode ser implementada com o uso de um mecanismo
chamado lazy evaluation, que muito importante em linguagens funcionais. Essas idias
so discutidas no Captulo 14.

9.4.5 Passagem de Parmetros em Ada


Entre todas as linguagens discutidas neste livro, Ada a nica que no especifica
como parmetros so passados, mas, sim, como so usados: in, out ou in-out. Em Ada um
parmetro in um parmetro que referenciado, mas no modificado. Como tal, podem
ser usadas tanto a passagem por valor quanto a por referncia, dependendo do tamanho
do argumento.
Em contraste, um parmetro out um parmetro cujo valor deve ser estabelecido
pela rotina chamada antes que possa ser referenciado. Em especial, a rotina chamada deve
atribuir um valor a um parmetro out antes de referenci-lo ou retornar. Outra forma de
visualizar um parmetro out o fato de sua entrada nunca ser referenciada. Um parmetro
out pode ser implementado usando chamada por resultado ou por referncia, dependendo
do seu tamanho.
236 Captulo 9 Funes

Finalmente, um parmetro in-out um parmetro cujo valor pode ser referenciado


antes que seja atribudo e ser reatribudo antes de retornar. Parmetros in-out podem ser
implementados com o uso da passagem por resultado-valor para tipos primitivos de dados
ou passagem por referncia para matrizes e registros.
Como esperado, o padro da linguagem Ada especifica que um programa no v-
lido se o mecanismo de implementao real alterar o resultado do clculo. Ada a nica
linguagem discutida aqui que especifica o uso de um parmetro, em vez do mecanismo
de implementao.

9 .5 R E G IS T R O S DE AT IVAO
Para implementar uma chamada de funo, a noo de um registro de ativao se
tornou amplamente aceita. Esta seo resume as idias bsicas, em preparao para um
tratamento mais completo da chamada de funo do Captulo 10.
Definio: Um registro de ativao o bloco de informao associado a uma
ativao de funo, incluindo os parmetros e as variveis locais da funo.
A estrutura bsica de um registro de ativao mostrada na Figura 9.5. Um registro
de ativao individual possui espao para:
Os parmetros e as variveis locais da funo chamada.
Um endereo de retorno.
Uma cpia dos registros salvos da funo que chama.
Valores temporrios.
O valor de retorno da funo chamada, se houver algum.
Um ponteiro de ligao esttica para o pai esttico da funo.
Um ponteiro de ligao dinmica para o registro de ativao da funo que chama.
A necessidade da maioria dos itens no registro de ativao deve ser bvia. Por exem-
plo, o endereo de retorno necessrio de modo que a funo chamada possa retornar
apropriadamente para seu solicitante. A ligao dinmica usada para facilitar o fluxo de
informaes entre a funo chamada e seu solicitante.

| deFigura 9.5 Estrutura de um Registro


Ativao de uma Funo Chamada
Ligao Esttica Ligao Dinmica

Parmetros
Variveis locais

Endereo de retorno

Registros salvos

Valores temporrios

Valores de retorno
9.6 Funes Recursivas 237

A ligao esttica geralmente s est presente naquelas linguagens que permitem


que uma funo seja aninhada estaticamente dentro de outra funo. Por exemplo, C no
precisa de uma ligao esttica, j que no permite tal aninhamento. Entretanto, Algol,
Pascal, Ada e Java requerem uma ligao esttica, j que nessas linguagens o solicitante
pode no ser o pai esttico da funo chamada. Sem a ligao esttica, a funo chamada
no consegue localizar o registro de ativao do seu pai esttico. Um exemplo da neces-
sidade de ligao esttica apresentado na Seo 9.7.
Em linguagens que no suportam funes recursivas (por exemplo, Fortran), os re-
gistros de ativao podem ser alocados estaticamente. Fazer isso muito eficiente no
tempo, mas no necessariamente no espao. A eficincia de tempo resulta do acesso dire-
to tanto s variveis locais quanto s globais. Entretanto, ineficiente em espao porque
espao alocado para funes que podem nunca ser chamadas.
Mais comumente, registros de ativao so alocados dinamicamente em uma pilha de
tempo de execuo quando uma funo chamada e desalocados quando o retorno de uma
funo executado. Em tais casos, o termo frame de pilha comumente usado. Um frame
de pilha um sinnimo de registro de ativao quando este ltimo alocado e desalocado
dinamicamente usando uma pilha. Na Seo 9.6, examinamos funes recursivas para
motivar a necessidade de uma pilha em tempo de execuo de registros de ativao.

9 .6 F U N ES RECU RSIVAS
Funes recursivas tm uma implementao natural usando uma pilha de tempo de
execuo que registra o status de todas as chamadas ativas associando um registro de ativa-
o a cada chamada. A maioria das linguagens atuais (C, C++, Java, Ada) suporta recur-
so, e de fato muitas linguagens tm a recurso como uma estrutura de controle central
(Scheme, Haskell, Prolog). Analise a seguinte funo recursiva em C++ que calcula o
fatorial de um inteiro no-negativo n:

int factorial (int n) {


// calcula o fatorial de n, dado n >= 0
if (n < 2)
return 1;
else
return n*factorial(n-1);
}

Suponha que alguma funo execute a chamada factorial(n), na qual n possui


valor igual a 3. Isso resultar em chamadas recursivas para calcular factorial(2) e
factorial(1), conforme apresentado na rvore de chamadas da Figura 9.6. J que a
chamada original usou uma varivel, cujo valor poderia ter sido lido do teclado, um com-
pilador no pode determinar quantos registros de ativao simultneos podem ser neces-
srios para uma determinada funo recursiva.
A soluo alocar dinamicamente (empilhar) registros de ativao em uma pilha
quando as funes forem chamadas e desaloc-los (desempilh-los) quando as funes
retornarem a seus solicitantes. A Figura 9.7a mostra um registro de ativao para a cha-
mada factorial(3) sendo alocado (empilhado) em uma pilha de tempo de execuo.
A parte do else inicia uma nova chamada, factorial(2), fazendo com que mais
um registro de ativao seja empilhado, conforme mostrado na Figura 9.7b. Nesse ponto,
o registro de ativao para a primeira chamada permanece na pilha esperando que a se-
gunda chamada seja completada.
238 Captulo 9 Funes

Figura 9.6 rvore de Chamada factorial(3)


| para factorial(3)

return 2

factorial(2)

return 1

factorial(1)

Entretanto, a segunda chamada inicia uma terceira, j que a condio n < 2 ainda no
est satisfeita, conforme mostrado na Figura 9.7c. Observe que os trs registros de ativa-
o agora na pilha guardam diferentes valores para o parmetro n, j que cada chamada
est efetivamente calculando um fatorial diferente.
Agora a terceira chamada retorna 1 para a segunda, que pode ento completar o cl-
culo 2*factorial(1) = 2 e retornar 2 para a chamada original, conforme mostrado na
Figura 9.7c e d. Nesse ponto, os dois registros do topo foram removidos (desempilhados)
da pilha e a chamada original retorna 3*factorial(2) = 6 para o solicitante original, e
seu registro de ativao desempilhado.
Este exemplo ilustra como o conceito geral de pilha de tempo de execuo um
modelo efetivo para funes recursivas. Cada chamada recursiva empilha um novo regis-
tro de ativao, e cada retorno desempilha esse registro de ativao quando retorna seu
resultado para o solicitante.

9 .7 P IL HA D E T EM P O DE EXECU O
Uma forma natural para modelar a semntica de chamada e retorno de fun-
es usar uma pilha para armazenar o registro de ativao de cada funo chamada.

Liga o para
o frame do
solicitante

n 3 n 3 n 3 n 3 n 3

n 2 n 2 n 2

n 1

(a) Primeira (b) Segunda (c) Terceira (d) Segunda (e) Primeira
chamada chamada chamada chamada chamada
retorna 1 retorna 2 * 1 = 2 retorna 3 * 2 = 6

| Figura 9.7 Atividade da Pilha para a Chamada Recursiva de factorial(3)


9.7 Pilha de Tempo de Execuo 239

Um push de um registro de ativao ocorre sempre que uma funo chamada, e um pop
tambm ocorre sempre que o controle retornar de uma funo chamada.
Definio: Uma pilha de tempo de execuo uma pilha de registros de ativao
usada para modelar a semntica de chamadas e retornos de funes.
Quando uma funo chamada retorna o controle solicitante, seu registro de ativa-
o removido da pilha. J que a funo chamada pode chamar outra funo (e geralmente
o faz), muitos registros de ativao podem estar ativos na pilha de tempo de execuo a
qualquer momento. O fato de que registros de ativao so removidos na ordem inversa
qual so empilhados fornece um modelo consistente com as idias fundamentais de
escopo e visibilidade.
Analise o programa C/C++ na Figura 9.1 cuja pilha de tempo de execuo pode ser
modelada conforme mostrado na Figura 9.8. Aqui, vemos esquerda o nico registro de
ativao no topo das globais h e i quando a funo main chamada. O desenho do meio
mostra a pilha quando a funo A chamada de main.
O desenho direita na Figura 9.8 mostra a pilha quando a funo B chamada por
A. Trs registros de ativao agora esto na pilha, refletindo que todas as trs funes
esto ativas. Cada registro de ativao contm uma ligao esttica para as globais h e i
e uma ligao dinmica de volta para o registro de ativao para a funo que o chamou.
Cada registro de ativao tambm contm seus prprios parmetros e as variveis locais
de funo.
Finalmente, considere um programa em Ada apresentado na Figura 9.9. Analise a
cadeia de chamadas: Quicksort, Sort, Sort, Swap. Mesmo sem desenhar explicitamente
a pilha de tempo de execuo, deve estar claro que a ligao dinmica inadequada para
localizar o pai esttico de Swap. Da a necessidade do ponteiro ligao esttica.
Java possui exatamente o mesmo problema com classes internas, que so comumen-
te usadas para manipuladores de eventos que precisam de acesso a variveis de instncia
de suas classes externas. Esses manipuladores de eventos podem ser chamados por algu-
ma ao do usurio, como o clique em um boto. Sem a ligao esttica, a pilha de tempo de
execuo precisaria ser pesquisada seqencialmente para localizar o pai esttico. Diver-
sos exemplos de tais manipuladores de eventos ocorrem na classe Controller da Seo
16.3.3, que discute a interface grfica em um Jogo da Velha.

h no definido h 5 h 5
rea esttica
i no definido i no definido i 10
Pilha
Frame a 3 a 3 a 3
para main b 2 b 2
b 2

Frame x 3 x 3
para A y 2 y 2
i no definido i no definido
j no definido j no definido

Frame w 5
para B j no definido
k no definido

| Figura 9.8 Pilha de Tempo de Registro de Ativao


240 Captulo 9 Funes

procedure Quicksort(List : in out Intarray;


Length: Integer := ListLength) is
procedure Swap(I, J : Integer) is
Temp : Integer;
begin
Temp := List(I);
List(I) := List(J);
List(J) := List(I);
end Swap;

procedure Sort (M, N : Integer) is


I, J, Key : Integer;
begin
if M < N then
I := M; J := N + 1;
Key := List(M);
loop
I := I + 1;
exit when List(I) >= Key
end loop;
loop
J := J 1;
exit when List(J) <= Key;
end loop;
Swap (I, J);
Sort (M, J-1);
Sort (J+1, N);
end if
end Swap;

begin
Sort(1, Length);
end Quicksort;

| Figura 9.9 Necessidade de um Ponteiro de Ligao Esttica

9 . 8 R ES UM O
Este captulo estuda o importante assunto de funes, seus mecanismos de passagem
de parmetros e registros de ativao. Uma pilha de tempo de execuo facilita o geren-
ciamento normal em tempo de execuo de variveis locais e a passagem de parmetros
para chamadas de funes, assim como para recurso. Seu comportamento dinmico
explorado mais inteiramente no Captulo 10, no qual discutimos uma implementao
completa de funes em Clite.
9.8 Resumo 241

E XE RC CI O S
9.1 Analise o seguinte programa em Clite:

void swap(int[] list, int i, int j) {


int temp = list[i];
list[i] = list[j];
list[j] = temp;
}
void main () {
int x[3] = {5, 2, 4};
swap(x, 1, 2);
}

Qual o valor final da matriz x para cada uma das seguintes suposies de passagem de parmetro?
(a) O argumento x passado por valor.
(b) O argumento x passado por referncia.
(c) O argumento x passado por resultado-valor.
9.2 Usando o software disponvel no site deste livro, exercite o interpretador Clite para o programa
functions.cpp. Quais so os valores das variveis globais h e i nesse programa quando cada
uma das funes main, A e B comeam a ser executadas?
9.3 No software disponvel no site deste livro, exercite o programa recFib.cpp.
(a) Qual o valor do parmetro n no programa recFib.cpp no registro de ativao mais acima da
pilha cada vez que a funo fibonacci chamada?
(b) Quantos registros de ativao na pilha so ativados para a chamada fibonacci(13)?
(c) Voc pode imaginar uma forma diferente de definir a funo fibonacci de modo que menos
registros de ativao na pilha sejam ativados?
9.4 O programa C/C++ a seguir resolve o problema das Torres de Hani para trs discos:

void moveTower (int disks, char start, char end,


char temp) {
if (disks == 1)
cout << Move a disk from << start <<
to << end << endl;
else {
moveTower (disks-1, start, temp, end);
// move um disco do incio para o fim
cout << Move a disk from << start <<
to << end << endl;
moveTower (disks-1, temp, end, start);
}
}
int main () {
int totalDisks;
totalDisks = 3;
moveTower(totalDisks, A, B, C);
}
242 Captulo 9 Funes

Usando seu compilador C++ favorito, rastreie os valores dos parmetros disks, start, end
e temp em todos os registros de ativao da pilha que sejam empilhados quando o programa
executado.
9.5 Modifique o programa da Figura 9.1 de modo que a funo B no tenha efeitos colaterais na va-
rivel global i. Em vez disso, B deve retornar um valor int, que o clculo de 2*w. Alm disso,
a chamada para B de A deve ser modificada de modo que esse resultado seja atribudo varivel
global i. A declarao da varivel global i dentro de A deve ser removida. Execute esse programa
modificado usando seu compilador C++ favorito e responda s seguintes questes:
(a) Descreva graficamente (desenhe) a subrvore abstrata que representa sua chamada modificada
para B de A.
(b) Descreva o registro de ativao que ocorre quando essa chamada interpretada.
(c) Esse programa modificado possui o mesmo efeito global do programa original da Figura 9.1. O
que teria acontecido se voc no tivesse removido a declarao da varivel local i de A?
9.6 A estratgia da pilha de tempo de execuo discutida neste captulo especialmente robusta para
suportar funes recursivas. Execute o interpretador Clite usando o programa gcd.cpp, que con-
tm as seguintes funes recursivas para calcular o maior divisor comum de dois inteiros x e y:

int rem(int x, int y) {


return x x/y*y;
}
int gcd(int x, int y) {
if (y==0) return x;
else return gcd(y, rem(x, y));
}

Quais registros de ativao so gerados pela chamada funo gdc(24,10), e subseqentes cha-
madas recursivas gdc? Inclua em cada registro de ativao da pilha o valor de retorno, assim
como os valores dos argumentos usados na respectiva chamada.
9.7 Analise o programa Ada na Figura 9.9.
(a) Mostre o contedo do novo frame de pilha quando a chamada Quicksort(a) iniciada, para
a matriz a {4,2,5,1}.
(b) Mostre o contedo de cada novo frame de pilha adicionado quando cada uma das prximas
quatro chamadas for iniciada (por exemplo, para Sort, para Swap e duas vezes para Sort
novamente).
9.8 Como o procedimento de Ada da Figura 9.9 pode ser reescrito em C, que no permite aninhamento
de funes? Que mecanismo de passagem de parmetros necessrio na verso em C para o par-
metro List da funo Quicksort?
Implementao
de Funes 10
Na teoria, no h diferena entre teoria e prtica, mas no na prtica.
Annimo

VISO GERAL DO CAPTULO

10.1 DECLARAO E CHAMADA


DE FUNES EM CLITE 244
10.2 COMPLETANDO O SISTEMA
DE TIPOS DE CLITE 247
10.3 SEMNTICA DE CHAMADA
E RETORNO DE FUNES 249
10.4 TRATAMENTO FORMAL
DE TIPOS E SEMNTICAS 252
10.5 RESUMO 260
EXERCCIOS 260

As funes so elementos-chave da abstrao procedimental em programao.


Assim, projetistas e implementadores de linguagens devem trat-las como uma preocu-
pao central. Os projetistas de linguagem devem levar em considerao o projeto de
funes, de modo que os programadores possam us-las de forma efetiva, e tambm a
implementao de funes, de modo que quem escreva compiladores possa implemen-
t-las efetivamente e eficientemente.

243
244 Captulo 10 Implementao de Funes

Para ilustrar o projeto e a implementao de funes em profundidade, este captulo


desenvolve uma definio e uma implementao completas da sintaxe, sistema de tipos
e semntica de uma extenso de Clite que permite que os programas tenham variveis
globais, declaraes e chamadas de funes.
Como um estudo opcional, este captulo tambm fornece uma definio formal para
o sistema de tipos e a semntica de funes em Clite. Esse tratamento fornece aos leitores
uma perspectiva do rigor matemtico necessrio para uma especificao completa de uma
linguagem de programao moderna.

1 0 .1 D ECL AR AO E CH AM ADA
D E FU N ES EM CLIT E
Nesta seo, enfocamos a sintaxe concreta e abstrata, sistema de tipos e semntica
que incorporam o princpio da declarao, chamada, escopo e visibilidade de funes
discutidos nos captulos anteriores. A sintaxe de Clite fornece um ponto de partida para
esta discusso.
Lembre-se do exemplo do programa em Clite da Figura 10.1, que foi primeiramente
introduzido no Captulo 4. Nesse programa, main inicialmente chamado, e as variveis
globais h e i ficam acessveis a ele, junto s variveis locais a e b.
Aps os trs primeiros comandos de atribuio em main terem sido executados, as
variveis h, a e b possuem os valores 5, 3 e 2, respectivamente. Depois, main chama A e
A chama B, cada chamada associando a si prpria um registro ativador que contm um
conjunto diferente de locais e parmetros junto s globais h e i. Parte do nosso propsito
neste captulo explicar como essas caractersticas semnticas podem ser implementadas
no estabelecimento de uma pilha de tempo de execuo.

10.1.1 Sintaxe Concreta


A sintaxe concreta dessa extenso de Clite permite que um Programa declare funes
e variveis globais alm de uma funo main. A metade superior da Figura 10.2 mostra
essas regras de gramtica que, quando adicionadas gramtica da Figura 2.7, definem a

int h, i;

void B(int w) {
int j, k;
i = 2*w;
w = w+1;
}
void A(int x, int y) {
bool i, j;
B(h);
}
int main() {
int a, b;
h = 5; a = 3; b = 2;
A(a, b);
}

| Figura 10.1 Exemplo do Programa em Clite


10.1 Declarao e Chamada de Funes em Clite 245

Programa { Identificador de Tipo FunoOuGlobal } FunoPrincipal


Tipo int | boleano | float | char | void
FunoOuGlobal ( Parmetros ) { Declaraes Comandos } | Global
Parmetros [ Parmetro { , Parmetro } ]
Parmetro Identificador de Tipo
Global { , Identificador };
FunoPrincipal int main ( ) { Declaraes Comandos }

Comando ; | Bloco | Atribuio | ComandoSe


ComandoEnquanto | ComandoChamada | ComandoRetorno
ComandoChamada Chamada;
ComandoRetorno return Expresso;
Fator Identificador | Literal | ( Expresso ) | Chamada
Chamada Identificador ( Argumentos )
Argumentos [ Expresso { , Expresso }]
| Figura 10.2 Regras de Gramtica para a Extenso de Clite

sintaxe das funes e globais. Os sublinhados indicam adies s regras originais de Clite
dadas no Captulo 2.
Nessa definio, a FunoPrincipal idntica noo original de qualquer nmero
de declaraes de funo e/ou varivel global para formar um Programa completo. O
novo Tipo void designa uma funo que no retorna resultado.1
O programa da Figura 10.1 segue essa nova sintaxe. Ele tem duas variveis globais h
e i e duas funes A e B, alm da sua FunoPrincipal (main). As funes A e B tm am-
bas Tipo void, indicando que no retornam um resultado e so chamadas por comandos
de Chamada independentes. A funo A possui dois parmetros int e B possui um.
A sintaxe de Clite permite que uma chamada de funo aparea como um Co-
mandoChamada separado ou uma Chamada alternativa dentro de um Fator. Neste
ltimo caso, o corpo da funo chamada, que um Bloco, deve conter um Comando-
Retorno. A parte inferior da Figura 10.2 mostra as regras da gramtica que realizam
esses objetivos.
Por exemplo, o programa da Figura 10.1 possui dois ComandosChamada, um de
main para A e um de A para B. O primeiro possui dois Argumentos a e b e o segundo
possui um nico Argumento h. Esse programa no possui Chamadas como instncias de
Fator, j que tanto A quanto B so funes void. Da mesma forma que em C++, um Co-
mandoRetorno s pode aparecer em funes que no tenham Tipo void.

1. Pode ser intrigante ver que a funo main no void, j que ela parece no retornar resultados. Isso con-
sistente com C, no qual a funo main retorna um inteiro para informar ao sistema operacional se o programa
foi executado de forma correta. Em C, o valor padro 0 (ausncia de um comando de retorno) significa que ele
executou corretamente, enquanto um valor diferente de zero normalmente um cdigo de erro definido pelo
programador. Para simplificar, a funo main em Clite no ter um comando return, embora seu tipo de re-
torno seja diferente de void.
246 Captulo 10 Implementao de Funes

J que uma funo chamada pode ou no ser do tipo void, importante assegurar
que um comando return aparea em cada funo no-void. Esse requisito imposto
pelo sistema de tipos de Clite, que apresentado posteriormente.

10.1.2 Sintaxe Abstrata


O prximo passo neste estudo examinar como a sintaxe abstrata de Clite esten-
dida para abordar essas novas idias. A viso adicional de sintaxe abstrata fornece uma
base para estender a idia de validao de tipos a programas com globais, declaraes e
chamadas de funes. Tambm fornece a base para a definio de um interpretador que
transforma a pilha de tempo de execuo quando cada chamada de funo iniciada
ou terminada.
Refinamentos na sintaxe abstrata de Clite apresentados no Captulo 2, que supor-
taro globais, declaraes e chamadas de funes, esto resumidos na Figura 10.3. As
partes sublinhadas indicam adies sintaxe abstrata original. Essas adies espelham
aquelas que ocorrem dentro da sintaxe concreta apresentada anteriormente.
Observe as duas formas diferentes nas quais uma Chamada aparece em um pro-
grama como um comando separado ou dentro de uma expresso. No primeiro caso, a
Chamada usada para chamar uma funo cujo tipo t void. No segundo caso, a funo
chamada deve retornar um resultado. Assim, um comando Return deve aparecer no corpo
de uma funo que no seja void, mas no no corpo de uma funo void. Essa distino
importante ser imposta nas regras de verificao de tipos e usada na definio do inter-
pretador para Clite.
A Figura 10.4 fornece um esboo da sintaxe abstrata do programa mostrado na Fi-
gura 10.1. Aqui, as setas pontilhadas indicam abreviaes de partes da rvore de sintaxe
abstrata. Apenas os elementos-chave de funes, locais, parmetros e chamadas so mos-
trados em detalhes aqui.
Para trabalhar com essas definies, implementamos um analisador descendente re-
cursivo para essa extenso de Clite. Esse analisador pode ser baixado do site do livro e
executado com o programa da Figura 10.1 como entrada.
A incluso de chamadas e retornos de funes em uma linguagem de programao
carrega um conjunto distinto de requisitos de verificao de tipo e semntica. Nas sees
a seguir, desenvolvemos regras de verificao de tipos e semntica para programas em
Clite que tenham escopo esttico.

Programa  Declaraes globais; Funes funes


Funes  Funo*
Funo  Tipo t; String id; Declaraes params, locais; Bloco corpo
Tipo  int | boleano | float | char | void
Comando  Salto | Bloco | Atribuio | Condio | Lao | Chamada | Retorno
Chamada  String nome; Expresses args
Expresses  Expresso*
Retorno  Varivel alvo; Expresso resultado
Expresso  Varivel | Valor | Binrio | Unrio | Chamada
| Figura 10.3 Sintaxe Abstrata de Funes, Globais e Chamadas em Clite
10.2 Completando o Sistema de Tipos de Clite 247

Programa
globais corpo

Declaraes Funes

Funo
h i
int main
Funo
void A ...
Funo Chamada
void B params locais corpo a b A(a,b);

x y i j
Block
w j k

i 2 * w; w w 1;

| Figura 10.4 Esboo de Sintaxe Abstrata para um Programa em Clite

Esta discusso deixa claro como a pilha de tempo de execuo distribuda para suportar
escopo, funes, chamadas e ligao de parmetros-argumentos.

10 .2 COMPLETANDO O SISTEMA DE TIPOS DE CLITE


O sistema de tipos de Clite possui regras que evitam que erros de tipo ocorram
durante uma chamada a uma funo. Essas regras complementam as regras originais de
verificao de tipos de Clite (veja o Captulo 6) para assegurar que qualquer programa
Clite sintaticamente correto seja seguro quanto a tipos com essa nova considerao.
Regra de Tipo 10.1 Cada funo e id global devem ser nicos.
Analise o programa da Figura 10.1. Essa regra evita que um programa tenha duas
funes diferentes chamadas A, por exemplo. A sintaxe concreta de Clite no pode por si
s assegurar essa unicidade mais do que pode assegurar que todas as variveis em uma
declarao tenham nomes nicos.
Regra de Tipo 10.2 Os params e locais de cada funo devem ter ids nicos
entre si.
Por exemplo, essa regra assegura que ids da lista de parmetros e variveis locais na
funo A  x, y, i e j  no estejam duplicados.
Regra de Tipo 10.3 Cada Comando no corpo de cada funo deve ser vlido
quanto s variveis locais, aos parmetros e s globais visveis da funo.
248 Captulo 10 Implementao de Funes

int fibonacci (int n) {


int fib0, fib1, temp, k;
fib0 = 0; fib1 = 1; k = n;
while (k  0) {
temp = fib0;
fib0 = fib1;
fib1 = fib0 + temp;
k = k - 1;
}
return fib0;
}
int main () {
int answer;<