Sie sind auf Seite 1von 121

http://www.buildyourownlisp.

com/contents

Build Your Own Lisp


Portugus: https://construa-seu-proprio-lisp.herokuapp.com/contents

Introduo Captulo 1
Sobre
Neste livro voc aprender a linguagem de programao C e ao mesmo tempo tambm como
construir sua prpria linguagem de programao, um Lisp mnimo, em menos de 1000 linhas de
cdigo! Ns usaremos uma biblioteca para fazer um pouco do trabalho inicial, ento estou
trapaceando um pouco na questo do nmero de linhas, mas o resto do cdigo ser completamente
original, e voc ter realmente criado um Lisp poderosinho ao final.
Este livro inspirado em outros tutoriais que seguem os passos de construir uma linguagem de
programao do zero. Escrevi este livro para mostrar que este tipo de projeto divertido e criativo
uma excelente maneira de aprender uma linguagem, e no limitado a linguagens abstratas de alto
nvel, ou programadores experientes.
Muitas pessoas querem muito aprender C, mas no tm por onde comear. Agora no h desculpa.
Se voc seguir este livro eu prometo que, na pior das hipteses, voc ter uma nova linguagem de
programao para brincar, e talvez voc se torne um programador C experiente tambm!

Para quem este livro


Este livro para qualquer pessoa que queira aprender C, ou que alguma vez imaginou como seria
construir sua prpria linguagem de programao. Este livro no apropriado como um primeiro
livro sobre programao, mas qualquer um com mnima experincia de programao, em qualquer
linguagem, deve encontrar algo novo e interessante nele.
Tentei fazer este livro o mais amigvel possvel para iniciantes. Adoro receber os iniciantes,
porque eles tm tanto a descobrir! Mas iniciantes podem tambm achar este livro desafiador.
Ns vamos cobrir muitos conceitos novos, e essencialmente aprender duas linguagens ao
mesmo tempo.
Se voc procurar ajuda de algum, s vezes pode encontrar pessoas que no so pacientes com
voc. Pode acabar descobrindo que, em vez de ajudar, elas gastam tempo mostrando o quanto elas
sabem sobre o assunto. Programadores experientes podem ficar dizendo que voc est errado. A
mensagem que eles podem passar que voc devia parar agora, em vez de infligir seu cdigo ruim
ao mundo.

http://www.buildyourownlisp.com/contents

Depois de algumas conversas desse tipo, voc talvez pense que no um programador, ou que no
gosta de programar, ou que simplesmente no compreende. Pode pensar que em certo momento
voc curtiu da ideia de construir a prpria linguagem, mas agora se deu conta que isso abstrato
demais e que voc no se importa mais, que voc agora est ocupado com outras coisas, e qualquer
ideia de que foi divertido, legal ou interessante agora se tornou um obstculo.
Sobre isso, eu posso simplesmente pedir desculpa. Programadores podem ser hostis, "maches",
arrogantes, inseguros e agressivos. No existe desculpa para este comportamento. Saiba que estou
do seu lado. Ningum manja no comeo. Todo mundo se debate e duvida das prprias habilidades.
Por favor no desista ou deixe a alegria ser sugada da sua experincia criativa. Fique orgulhoso do
que voc crie, no importa o que seja. Pessoas como eu no querem que voc pare de programar.
Ns queremos ouvir sua voz e o que voc tem a dizer.

Por que aprender C


C uma das linguagens mais populares e influentes no mundo. a linguagem padro pra
desenvolvimento em Linux, e tem sido usada extensivamente na criao do OS X e em certo mbito
no Microsoft Windows. usada em microcomputadores tambm. Sua geladeira e carro
provavelmente tambm rodam ela. Em desenvolvimento de software moderno, o uso de C pode ser
escapvel, mas seu legado no . Para qualquer um querendo fazer carreira em desenvolvimento de
software, ser inteligente aprender C.
Mas C no se trata de desenvolvimento de software e carreiras. C se trata de liberdade. Ficou
famosa por trs de tecnologias de colaborao e liberdade - Unix, Linux, e o movimento de
software livre. Ela personifica a ideia de liberdade pessoal na computao. Ela te inclina a
tomar o controle da tecnologia afetando sua vida.
Nestes dias, em que a tecnologia mais poderosa que nunca, isso no poderia ser mais importante.
A ideologia de liberdade refletida na natureza de C propriamente dita. H pouca coisa que C
esconde de voc, incluindo suas verrugas e falhas. H pouca coisa que C impede voc de fazer,
incluindo quebrar o seu programa de maneiras horrveis. Quando voc programa em C, voc no
anda por um caminho, mas por um plano de deciso, e C desafia voc a decidir o que fazer.
C tambm a linguagem da diverso e do aprendizado. Antes da mdia mainstream dominar, ns
tnhamos uma palavra para isso. Hacking. A filosofia que glorifica o que divertido e inteligente.
Nada a ver com acesso no autorizado ao computador de outras pessoas. Hacking a filosofia de
explorao, expresso pessoal, forar os limites, e quebrar as regras. Ela se levanta contra hierarquia
e burocracia. Ela celebra o indivduo. Hacking isca voc para diverso, aprendizado e glria.
Hacking a promessa que com um computador e acesso internet, voc tem a capacidade de mudar
o mundo.
Querer dominar C se importar com o que poderoso, inteligente e livre. tornar-se um
programador com todos os vastos poderes da tecnologia na ponta dos seus dedos e a

http://www.buildyourownlisp.com/contents

responsabilidade de fazer algo que beneficie o mundo.

Como aprender C
No tem como esconder que C uma linguagem difcil. Ela tem muitos conceitos no familiares, e
no faz nenhuma tentativa de ajudar um usurio novio. Neste livro eu no vou cobrir em detalhe
coisas como a sintaxe da linguagem, ou como escrever loops e estruturas condicionais.
Por outro lado, eu vou mostrar como construir um programa em C no mundo real. Esta abordagem
sempre mais difcil para o leitor, mas espero que v ensin-lo muitas coisas implcitas que uma
abordagem tradicional no pode. No posso garantir que este livro far de voc um usurio
confiante de C. O que posso prometer, que essas 1000 linhas de cdigo vo estar carregadas de
contedo - e voc vai aprender alguma coisa til.
Este livro consiste de 16 captulos curtos. Como voc vai complet-los, fica a seu critrio.
possvel passar pelo livro inteiro num fim de semana, ou ento ir devagar e fazer um captulo ou
dois a cada noite por uma semana. No deve tomar muito tempo terminar, e com sorte vai deix-lo
com um gostinho para desenvolver sua linguagem um pouco mais.

Por que construir um Lisp


A linguagem que vamos construir neste livro um Lisp, que uma famlia de linguagens de
programao caracterizadas pelo fato que todas as suas computaes so representadas por listas.
Isso pode soar mais assustador do que realmente . Lisps so na verdade linguagens muito fceis,
distintivas e poderosas.
Construir um Lisp um excelente projeto por muitos motivos. Ele pe voc no lugar de
projetistas de linguagens, e lhe d uma apreciao para o processo inteiro de programar, desde
a linguagem at a mquina em si. Ele te ensina sobre programao funcional, e maneiras novas
de ver computao. O produto final com que voc ser recompensado fornece um padro para
futuros pensamentos e desenvolvimentos, dando-lhe um cho inicial para tentar coisas novas.
simplesmente impossvel compreender a criatividade e a inteligncia que existe em
programao e cincia da computao at que voc explore as prprias linguagens.
O tipo de Lisp que vamos construir um que inventei para os objetivos deste livro. Eu o projetei
tendo em vista minimalismo, simplicidade e clareza, e fui me afeioando a ele ao longo do
caminho. Espero que voc venha a gostar dele tambm. Conceitualmente, sintaticamente, e na
implementao, este Lisp tem uma srie de diferenas para outras grandes marcas de Lisp. Tanto
que tenho certeza que acabarei recebendo e-mails de programadores Lisp me dizendo que ele no
um Lisp porque no tem/faz/se-parece-com isto ou aquilo.
No fiz este Lisp diferente para confundir iniciantes. Fi-lo diferente porque diferente legal.

http://www.buildyourownlisp.com/contents

Se voc est buscando aprender sobre a semntica e comportamento dos Lisps convencionais, e
como program-los, este livro pode no ser pra voc. O que este livro oferece no lugar conceitos
novos e nicos, expresso prpria, criatividade e diverso. Qualquer que seja sua motivao, atente
a este aviso agora. Nem tudo que eu digo ser objetivamente correto ou verdade! Voc ter que
decidi-lo por si mesmo.

Seu prprio Lisp


A melhor maneira de seguir este livro , como o ttulo diz, escrever seu prprio Lisp. Se voc se
sente suficientemente confiante, quero que adicione seus prprios recursos, modificaes e
mudanas. Seu Lisp deve se adequar a voc e sua prpria filosofia. Ao longo do livro oferecerei
descries e discernimento, mas juntamente fornecerei bastante cdigo. Isto vai tornar fcil de
acompanhar junto copiando e colando cada seo do seu programa sem realmente entender. Por
favor no faa isso!
Digite cada pedao de exemplo de cdigo voc mesmo. Isto se chama O Jeito Difcil. No porque
seja difcil tecnicamente, mas porque requer disciplina. Fazendo as coisas do Jeito Difcil voc vai
entender o raciocnio por trs do que est digitando. Idealmente as coisas iro clicando medida
que voc segue adiante caractere por caractere. Ao apenas ler voc pode ter uma intuio de por que
parece certo, ou o que talvez esteja acontecendo, mas isto no vai sempre se traduzir em
entendimento real a no ser que voc mesmo escreva!
Em um mundo perfeito, voc usaria meu cdigo como referncia - um folheto de instruo e guia
para construir a prpria linguagem de programao que sempre sonhou. Na realidade isso no
prtico ou vivel, mas a filosofia bsica permanece. Se quiser mudar alguma coisa, v em frente e
mude.

http://www.buildyourownlisp.com/contents

Instalao Captulo 2
Configurao
Antes de comear a programar em C precisaremos instalar algumas coisas, e configurar nosso
ambiente para que tenhamos tudo que precisamos. Como C uma linguagem to universal,
com sorte isso ser razoavelmente simples. Precisamos instalar duas coisas principais: um
editor de texto e um compilador.

Editor de texto
Um editor de texto um programa que permite voc editar arquivos de texto de maneira adequada
programao.
No Linux o editor de texto que recomendo o gedit. Qualquer outro editor de texto bsico que vem
instalado com sua distribuio tambm vai funcionar bem. Se voc um usurio de Vim ou Emacs,
eles vo funcionar bem tambm. Por favor no use uma IDE. No necessrio para um projeto to
pequeno e no vai ajud-lo a entender o que se passa.
No Mac um editor de texto simples que pode ser usado o TextWrangler. Se voc tem uma
preferncia diferente, tudo bem, mas por favor no use XCode para editar texto. Este um projeto
pequeno e usar uma IDE no vai ajud-lo a entender o que se passa.
No Windows, meu editor de texto preferido o Notepad++. Se voc tem outra preferncia, tudo
bem. Por favor no use o Visual Studio pois ele no tem suporte adequado para programao em C.
Se voc tentar us-lo vai enfrentar muitos problemas.

Compilador
O compilador um programa que transforma o cdigo fonte C em um programa que seu
computador pode rodar. O processo de instalao para ele diferente dependendo de qual sistema
operacional voc est usando.
Compilar e rodar programas em C tambm requer algum conhecimento bsico da linha de
comando. Isto eu no vou cobrir, ento vou assumir que voc tem pelo menos alguma familiaridade
em usar a linha de comando. Se isto preocupa voc um pouco, ento procure por tutoriais online em
como us-la, relevantes ao seu sistema operacional.
No Linux voc pode instalar um compilador baixando alguns pacotes. Se voc estiver rodando
Ubuntu ou Debian, pode instalar tudo que precisa com o seguinte comando: sudo apt-get

http://www.buildyourownlisp.com/contents

install build-essential. Caso esteja rodando Fedora ou alguma variante Linux parecida,
pode usar o comando: su -c "yum groupinstall development-tools".
No Mac, voc pode instalar um compilador baixando e instalando a ltima verso do XCode da
Apple. Se voc no tem certeza como fazer isso, pode buscar online por "instalar xcode" e siga o
conselho mostrado. A seguir, voc vai precisar instalar as Command Line Tools. No Mac OS X 10.9
isso pode ser feito rodando: xcode-select --install na linha de comando. Em verso
anteriores ao 10.9 isso pode ser feito indo em XCode Preferences, Downloads, e selecionando
Command Line Tools para instalao.
No Windows voc pode instalar um compilador baixando e instalando MinGW. Caso use o
instalador, em algum momento ele vai apresentar uma lista de possveis pacotes. Certifique-se de
marcar pelo menos mingw32-base e msys-base. Uma vez instalado, voc precisa adicionar o
compilador e outros programas na varivel de sistema PATH. Para fazer isso, siga as instrues aqui
(em ingls) adicionando o valor ;C:\MinGW\bin varivel chamada PATH. Voc pode criar esta
varivel caso ela no exista. Voc talvez tenha que reiniciar cmd.exe para as mudanas fazerem
efeito. Isto vai permitir que voc rode um compilador da linha de comando cmd.exe. Vai tambm
instalar outros programas que fazem cmd.exe se comportar como uma linha de comando Unix.

Testando o Compilador
Para testar se o seu compilador C est instalado corretamente, digite o seguinte na linha de
comando.
cc --version

Se voc receber uma informao sobre a verso do compilador, ento ele deve estar instalado
corretamente. Voc est pronto! Se voc receber qualquer tipo de mensagem de erro sobre um
comando no reconhecido ou no encontrado, ento ainda no est pronto. Voc talvez tenha que
reiniciar a linha de comando ou seu computador para as mudanas fazerem efeito.

Hello World
Agora que seu ambiente est configurado, comece por abrir o seu editor de texto e digitando o
seguinte programa. Crie um diretrio onde voc vai colocar seu trabalho para este livro e salvar o
arquivo como hello_world.c. Este o seu primeiro programa C!
#include <stdio.h>
int main(int argc, char** argv) {
puts("Hello, world!");
return 0;
}

Inicialmente isso pode no fazer sentido nenhum. Tentarei explicar passo a passo.

http://www.buildyourownlisp.com/contents

Na primeira linha ns inclumos (include) o que chamado de um cabealho (header). Este


comando nos permite usar as funo da stdio.h, a biblioteca de entrada e sada padro (standard
input and output) que vem includa em C. Uma das funes dessa biblioteca a funo puts que
voc v a seguir no programa.
A seguir ns declaramos uma funo chamada main. Esta funo declarada como tendo por sada
um int, e recebe como entrada um int chamado argc e um char** chamado argv. Todos os
programas C precisam conter essa funo. Todos os programas comeam a rodar a partir dessa
funo.
Dentro da main, a funo puts chamada com o argumento "Hello, world!". Isso joga a
mensagem Hello, world! na sada da linha de comando. A funo puts abreviao para put
string. O segundo comando dentro da funo return 0;. Isto diz a funo main para terminar
e devolver 0. Quando um programa C devolve 0 isso indica que no houveram erros rodando o
programa.

Compilao
Antes de rodar este programa precisamos compil-lo. Isto ir produzir o executvel que poderemos
efetivamente rodar no nosso computador. Abra a linha de comando e navegue at o diretrio em que
o arquivo hello_world.c est salvo. Voc pode compilar o seu programa usando o seguinte
comando.
cc -std=c99 -Wall hello_world.c -o hello_world

Isso vai compilar o cdigo em hello_world.c, reportando quaisquer alertas (warnings), e


produz o programa em um novo arquivo chamado hello_world. Ns usamos a opo
-std=c99 para dizer ao compilador qual verso ou padro (standard) de C ns estamos usando
para programar. Isso permite ao compilador garantir que nosso cdigo esteja padronizado, de
maneira que pessoas com sistemas operacionais diferentes sero capaz de usar nosso cdigo.
Caso funcionou, voc ver o arquivo produzido no diretrio atual. Ele pode ser rodado digitando
./hello_world (ou apenas hello_world no Windows). Se tudo estiver correto, voc deve
ser uma mensagem amigvel Hello, world! aparecer.
Parabns! Voc acabou de compilar e rodar seu primeiro programa C.

Erros
Se houver alguns problemas com seu programa C o processo de compilao pode falhar. Estas
questes podem variar de simples erros de sintaxe at outros problemas complicados que so
difceis de entender.

http://www.buildyourownlisp.com/contents

s vezes a mensagem de erro do compilador far sentido, mas se voc est tendo dificuldade de
entend-la, experimente buscar ela online. Voc dever tentar achar uma explicao concisa para o
que ela significa, e descobrir como corrigir. Lembre disso: h muitas pessoas que tiveram o mesmo
problema antes de voc.

s vezes havero muitos erros de compilao decorrentes do mesmo cdigo fonte. Sempre
passe pelos erros de compilao desde o primeiro at o ltimo. Isso pode ser uma arte bem
alm do escopo deste livro.
s vezes o compilador compilar um programa, mas quando voc roda, ele falha. Depurar
programas C nesta situao difcil. Tambm pode ser uma arte bem alm do escopo deste livro.
Caso seja um iniciante, a primeira parada para depurar um programa C falhando seria imprimir (isto
, jogar na sada) bastante informao medida que o programa roda. Usando este mtodo voc
deve tentar isolar exatamente qual parte do cdigo est incorreta e o que, se for o caso, est dando
errado. Essa uma tcnica de depurao que ativa. Isso uma coisa importante. Desde que voc
esteja fazendo alguma coisa, e no apenas encarando o cdigo, o processo menos doloroso e a
tentao de desistir reduzida.
Para pessoas se sentindo mais confiantes um programa chamado gdb pode ser usado para debugar
seu programa. Isso pode ser difcil e complicado de usar, mas tambm bem poderoso, pode lhe dar
informao extremamente valiosa, o que aconteceu errado e onde. Informaes em como usar o
gdb podem ser encontradas online (em ingls).
No Mac, as verses mais recentes do OS X no vm com gdb. No lugar dele, voc pode usar o
lldb que faz praticamente a mesma tarefa.
No Linux ou Mac, o valgrind pode ser usado para ajudar a depurar vazamento de memria
(memory leaks) e outros erros desagradveis. Valgrind uma ferramenta que pode salvar horas, ou
mesmo dias, de depurao. No leva muito tempo para ficar proficiente nele, ento investig-lo
altamente recomendado. Informao em como us-lo pode ser encontrada online (em ingls).

Documentao
Ao longo deste livro voc pode topar com uma funo em algum cdigo de exemplo que voc no
reconhea, e ficar imaginando o que ser que ela faz. Neste caso voc voc pode olhar na
documentao online (em ingls) da biblioteca padro. A documentao vai explicar todas as
funes includas na biblioteca padro, o que elas fazem, e como us-las.

Referncia

http://www.buildyourownlisp.com/contents

Para que serve esta seo?


Nesta seo eu vincularei o cdigo que eu escrevi para cada captulo do livro. Ao terminar um
captulo seu cdigo provavelmente se parecer com o meu. Este cdigo pode ser usado como
referncia caso a explicao no tenha sido clara.
Caso encontre algum bug (erro), por favor no copie e cole meu cdigo no seu projeto. Tente
rastrear o problema voc mesmo e use o meu cdigo como uma referncia para contrastar o que
pode estar errado, ou onde o erro pode estar.
hello_world.c

Metas bnus
Para que serve esta seo?
Nesta seo listarei algumas coisas para experimentar por diverso, e aprendizado.
legal que voc tente fazer alguns desses desafios. Alguns sero difceis e alguns sero muito mais
fceis. Por isto, no se preocupe se voc no consiga descobrir todos. Alguns podem nem mesmo
ser possveis!
Muitos vo requerer alguma pesquisa na internet. Esta uma parte integral de aprender uma nova
linguagem ento no deve ser evitada. A habilidade de ensinar voc mesmo uma das competncias
mais valiosas em programao

Mude a mensagem Hello World! dada pelo seu programa para algo diferente.
O que acontece quando nenhuma funo main fornecida?
Use a documentao online para buscar a funo puts.
Pesquise como usar o gdb e rod-lo com seu programa.

O Bsico Captulo 3
Viso Geral
Neste captulo preparei uma viso geral dos recursos bsicos de C. Existem bem poucos
recursos em C, e a sintaxe relativamente simples. Mas isso no significa que fcil. Toda a
profundidade se esconde debaixo da superfcie. Por causa disso vamos cobrir rapidamente os
recursos e a sintaxe agora, e depois os veremos em maior profundidade medida que
continuamos.
O objetivo deste captulo colocar todo mundo no mesmo nvel. Pessoas totalmente novatas em C
devero portanto tomar algum tempo nele, enquanto outros j com alguma experincia podem achar

http://www.buildyourownlisp.com/contents

10

fcil dar uma olhada rpida e voltar depois caso necessrio.

Programas
Um programa em C consiste em somente definies de funes e definies de estruturas.
Portanto um arquivo de cdigo-fonte simplesmente uma lista de funes e tipos (types). Estas
funes podem chamar umas s outras ou a si prprias, e podem usar quaisquer tipos de dados que
foram declarados ou j venham com a linguagem.
possvel chamar funes em outras bibliotecas, ou usar seus tipos de dados. assim que camadas
de complexidade so acumuladas em programao C.
Como vimos no captulo anterior, a execuo de um programa C sempre comea na funo
chamada main. Daqui ela chama mais e mais funes, para executar todas as aes que ela requer.

Variveis
Funes em C consistem em manipulao de variveis. Estas so itens de dados aos quais damos
um nome.
Cada varivel em C tem um tipo explcito. Esses tipos so declarados por ns mesmos ou j vm
com a linguagem. Podemos declarar uma nova varivel escrevendo o nome do seu tipo, seguida do
seu nome, e opcionalmente setando-a para algum valor usando =. Esta declarao um comando
(statement), e terminamos todos os comandos em C com um ponto e vrgula ;.
Para criar um novo int chamado contagem podemos escrever o seguinte...
int contagem;

Ou declar-lo e setar o valor...


int contagem = 10;

Aqui esto algumas descries e exemplos de alguns dos tipos de dados que j vm com a
linguagem.
void
char
int

Tipo vazio
Caractere nico/Byte
Inteiro
Inteiro que pode guardar valores
long
maiores
float Nmero decimal
double Nmero decimal com mais
preciso

char last_initial = 'H';


int age = 23;
long age_of_universe = 13798000000;
float liters_per_pint = 0.568f;
double speed_of_swallow = 0.01072896;

http://www.buildyourownlisp.com/contents

11

Declaraes de funes
Uma funo uma computao que manipula variveis, e opcionalmente muda o estado de um
programa. Ela recebe como entrada algumas variveis e devolve uma varivel nica como sada.
Para declarar uma funo, escrevemos o tipo da varivel que ela devolve, o nome da funo, e a
seguir entre parnteses uma lista das variveis ela recebe como entrada, separada por vrgulas. O
contedo da funo colocado dentro de chaves {}, e lista todos os comandos que a funo
executa, terminados por pontos e vrgulas ;. O comando return usado para deixar a funo
finalizar e devolver o valor de uma varivel.
Por exemplo, uma funo que recebe duas variveis int chamadas x e y e soma-as poderia parecer
com isto:
int calcula_soma(int x, int y) {
int resultado = x + y;
return resultado;
}

Ns chamamos funes escrevendo seu nome e colocando os argumentos para a funo entre
parnteses, separados por vrgulas. Por exemplo, para chamar a funo acima e armazenar o
resultado em uma varivel somados poderamos escrever o seguinte:
int somados = calcula_soma(10, 18);

Declaraes de estruturas
Estruturas so usadas para declarar novos tipos. Estruturas so um conjunto de variveis
aglomeradas em um nico pacote.
Ns podemos usar estruturas para representar tipos de dados mais complexos. Por exemplo, para
representar um ponto no espao 2D ns poderamos criar uma estrutura chamada point que
empacota dois valores float (decimais) chamados x e y. Para declarar estruturas ns podemos
usar a palavra-chave struct em conjunto com a palavra-chave typedef. Nossa declarao se
pareceria com isto:
typedef struct {
float x;
float y;
} point;

Podemos colocar esta definio acima de quaisquer funes que desejam us-la. Este tipo no
diferente dos tipos de dados embutidos na linguagem, e podemos us-lo em todas as mesmas
maneiras. Para acessar um campo individual, usamos um ponto ., seguido do nome do campo,
como x.

http://www.buildyourownlisp.com/contents

12

point p;
p.x = 0.1;
p.y = 10.0;
float length = sqrt(p.x * p.x + p.y * p.y)

Apontadores (pointers ou ponteiros)


Um apontador uma variao de um tipo normal onde o nome do tipo sufixado com um
asterisco. Por exemplo, poderamos declarar um apontador para um inteiro escrevendo int*.
J vimos um tipo apontador antes: char** argv. Este um apontador para apontadores de
caracteres, e usado como entrada para a funo main.
Apontadores so usadas para uma grande quantidade de coisas como strings (tipo de dados de texto)
ou listas. Eles so uma parte difcil de C e sero explicados em maior detalhe nos captulos
posteriores. No faremos uso deles por um tempo, ento por enquanto simplesmente bom saber
que eles existem, e como identific-los. No deixe eles assustarem voc!

Strings
Em C, strings so representadas pelo apontador char*. Por baixo dos panos, eles so armazenados
como uma lista de caracteres onde o caractere final um caractere especial chamado o terminador
null. Strings so uma parte complicada e importante de C, que aprenderemos a usar efetivamente
nos prximos captulos.
Strings podem ser declaradas literalmente colocando seu texto entre aspas. Usamos isso no captulo
anterior com nossa string "Hello, World!". Por ora, lembre-se que se voc ver char*, pode
l-lo como string.

Condicionais
Comandos condicionais permitem ao programa executar algum cdigo apenas se determinadas
condies so satisfeitas.
Para executar cdigo sob alguma condio ns usamos o comando if. Este escrito como if
seguido de alguma condio entre parnteses, seguida do cdigo para executar entre chaves. Um
comando if pode ser seguido opcionalmente por um comando else, seguido de outras comandos
entre chaves. O cdigo dentro dessas chaves ser executado no caso em que a condio falsa.
Podemos testar mltiplas condies usando os operadores lgicos || para OU, e && para E.
Dentro dos parnteses de um comando condicional, qualquer valor que no for 0 ser avaliado

http://www.buildyourownlisp.com/contents

13

como verdadeiro. Isso importante lembrar pois muitas condies usam isto para checar coisas
implicitamente.
Se desejssemos checar se um int chamado x fosse maior que 10 e menor que 100,
escreveramos o seguinte:
if (x > 10 && x < 100) {
puts("x eh maior que 10 e menor que 100!");
} else {
puts("x eh menor que 11 ou maior que 99!");
}

Laos
Laos permitem que algum cdigo seja repetido at que alguma condio se torne falsa, ou algum
contador termine.
H dois laos principais em C. O primeiro o lao while (enquanto). Este lao repete a execuo
de um bloco de cdigo at que alguma condio se torne falsa. Ele escrito com while seguido de
uma condio entre parnteses, seguida do cdigo a ser repetido entre chaves. Por exemplo, um lao
que conta para baixo de 10 a 1 poderia ser escrito da maneira que segue.
int i = 10;
while (i > 0) {
puts("Loop Iteration");
i = i - 1;
}

O segundo tipo de lao o for. No lugar de uma condio, este lao requer trs expresses
separadas por pontos e vrgulas ;. Estas so: um inicializador, uma condio e um incrementador.
O inicializador executado antes do lao comear. A condio checada a antes de cada iterao
do lao. Caso for falsa, o lao interrompido. O incrementador executado ao fim de cada iterao
do lao. Estes laos so frequentemente usados para contar pois so mais compactos que o lao
while.
Por exemplo, para escrever um lao que conta para cima de 0 a 9, poderamos escrever o cdigo a
seguir -- neste caso o operador ++ incrementa (isto , aumenta de 1 em 1) a varivel i.
for (int i = 0; i < 10; i++) {
puts("Loop Iteration");
}

http://www.buildyourownlisp.com/contents

14

Metas bnus
Use um lao for para imprimir Hello World! 5 vezes.
Use um lao while para imprimir Hello World! 5 times.
Declare uma funo que imprima Hello World! n vezes. Chame-a desde a funo
main.
Quais outros tipos embutidos existem que no foram listados?
Quais outros operadores condicionais existem que alm de maior que >, e menor que <?
Quais outros operadores matemticos existem alm de soma +, e subtrao -?
O que o operador +=, e como ele funciona?
O que o lao do, e como ele funciona?
O que o comando switch e como ele funciona?
O que a palavra-chave break e o que ela faz?
O que a palavra-chave continue e o que ela faz?
O que a palavra-chave typedef faz exatamente?

Um Prompt Interativo Captulo 4


Leia, Avalie, Imprima
medida que construmos nossa linguagem de programao precisaremos de alguma maneira
de interagir com ela. C usa um compilador, onde voc muda o seu programa, recompile e rodao. Seria bom se pudssemos fazer algo melhor, para interagir com a linguagem de maneira
dinmica, de maneira que possamos testar seu comportamento em diversas condies bem
rapidamente. Para isso, construiremos algo chamado prompt interativo.
Isso um programa que solicita (em ingls, prompts) alguma entrada ao usurio, e quando esta for
fornecida, ele responde de volta com alguma mensagem. Usar isso ser a maneira mais fcil de
testar nossa linguagem de programao e ver como ela age. Este sistema tambm chamado de
REPL, que significa read-evaluate-print loop (lao leia-avalie-imprima). uma maneira comum de
interagir com uma linguagem de programao, que voc talvez tenha usado antes em linguagens
como Python ou Ruby.
Antes de construir um REPL completo, comearemos com alguma coisa mais simples. Vamos fazer
um sistema que solicita entrada, e ecoa qualquer entrada de volta. Se fizermos isto, podemos depois
estend-lo para analisar a entrada do usurio e avali-la, como se fosse um verdadeiro programa
Lisp.

http://www.buildyourownlisp.com/contents

15

Um prompt interativo
Para a construo bsica, queremos escrever um lao que repetidamente escreve uma mensagem, e
ento espera por alguma entrada. Para obter entrada do usurio, podemos usar uma funo chamada
fgets, que l qualquer entrada at uma quebra de linha. Precisamos de algum lugar para
armazenar essa entrada do usurio. Para isso vamos declarar um buffer de entrada com tamanho
constante.
Uma vez que tenhamos a entrada do usurio armazenada, podemos imprimi-la de volta usando uma
funo chamada printf.
#include <stdio.h>
/* Declara um buffer para entrada com tamanho 2048 */
static char input[2048];
int main(int argc, char** argv) {
/* Imprime informacao sobre versao e como sair */
puts("Lispy Version 0.0.0.0.1");
puts("Press Ctrl+c to Exit\n");
/* Num laco que nunca acaba */
while (1) {
/* Imprima nosso prompt */
fputs("lispy> ", stdout);
/* Leia uma linha de entrada do usuario com tamanho maximo 2048 */
fgets(input, 2048, stdin);
/* Ecoa a entrada de volta para o usuario user */
printf("No you're a %s", input);
}
}

return 0;

O que aquele texto em verde fraco?


O cdigo acima contem comentrios. Estes so sees do cdigo entre smbolos /* */, que so
ignorados pelo compilador, mas so usados para informar a pessoa lendo o que est acontecendo.
Preste ateno neles!
Vamos repassar esse programa com um pouco mais de profundidade.
A linha static char input[2048]; declara um array global de 2048 caracteres. Isto um
bloco reservado de dados que podemos acessar de qualquer lugar do nosso programa. Nele vamos
armazenar a entrada do usurio que digitada na linha de comando. A palavra chave static torna
essa varivel local a este arquivo, e a seo [2048] o que declara o seu tamanho.
Ns escrevemos um lao infinito usando while (1). Em um bloco condicional, 1 sempre avalia

http://www.buildyourownlisp.com/contents

16

como verdadeiro. Por isso, comandos dentro desse lao vo executar para sempre.
Para imprimir nosso prompt, usamos a funo fputs. Esta uma pequena variao de puts que
no acrescenta um caractere de quebra de linha. Usamos a funo fgets para obter entrada do
usurio da linha de comando. Ambas funes requerem um arquivo para onde escrever, ou de onde
ler. Para isso, fornecemos as variveis especiais stdin e stdout. Estas so declaradas em
<stdio.h> e so variveis de arquivo especiais representando a entrada e a sada da linha de
comando. Quando passamos essa varivel, a funo fgets espera o usurio digitar uma linha de
texto, e quando ele o faz ela armazena-a no buffer input, incluindo o caractere quebra de linha.
Para que a fgets no leia dados demais, tambm fornecemos o tamanho do buffer 2048.
Para ecoar a mensagem de volta ao usurio, usamos a funo printf. Esta uma funo que
prov uma maneira de imprimir mensagens consistindo de vrios elementos. Ela casa argumentos
com padres dentro da string fornecida. Por exemplo, em nosso caso, podemos ver o padro %s na
string dada. Isto significa que ele ser substitudo por qualquer que seja o prximo argumento
passado a seguir, interpretado como uma string.
Para mais informao sobre esses diferentes padres, veja a documentao sobre printf.
Como que vou saber sobre funes como fgets e printf?
No imediatamente bvio como saber sobre essas funes padro, e quando us-las. Ao confrontar
com um problema, necessrio alguma experincia para saber quando este j foi resolvido para
voc por funes de bibliotecas.
Por sorte C tem uma biblioteca padro bem pequena e quase todas elas podem ser aprendidas na
prtica. Se quiser fazer algo que seja bem bsico, ou fundamental, vale a pena olhar a
documentao de referncia para a biblioteca padro e checar se existem quaisquer funes
includas que fazem o que voc quer.

Compilao
Voc pode compilar isso com o mesmo comando que foi usado no segundo captulo.
cc -std=c99 -Wall prompt.c -o prompt

Depois de compil-lo, voc deve tentar rod-lo. Voc pode usar Ctrl+c para sair do programa
quando terminar. Se tudo deu certo, seu programa deve rodar mais ou menos como isso:
Lispy Version 0.0.0.0.1
Press Ctrl+c to Exit
lispy> hello
No You're a hello
lispy> my name is Dan
No You're a my name is Dan
lispy> Stop being so rude!

http://www.buildyourownlisp.com/contents

17

No You're a Stop being so rude!


lispy>

Editando a entrada
Se voc est trabalhando no Linux ou Mac, notar um comportamento estranho caso tente usar as
setas do teclado para tentar editar a entrada.
Lispy Version 0.0.0.0.3
Press Ctrl+c to Exit
lispy> hel^[[D^[[C

Usar as setas est criando estes caracteres estranhos ^[[D ou ^[[C, em vez de mover o cursor pela
entrada. O que realmente queremos ser capaz de mover em torno da linha, deletando e editando a
entrada caso tenhamos cometido algum erro.
No Windows este comportamento o padro. No Linux e no Mac isso providenciado por uma
biblioteca chamada editline. No Linux e no Mac, precisamos substituir nossas chamadas para
fputs e fgets com chamadas para as funes que esta biblioteca fornece.
Caso esteja desenvolvendo no Windows e queira simplesmente continuar, fique vontade para
pular para o fim deste captulo j que as prximas sees podem no ser relevantes.

Usando a Editline
A biblioteca editline fornece duas funes que vamos usar chamadas readline (leia linha) e
add_history (adicione histrico). A primeira funo, readline usada para ler entrada de
algum prompt, permitindo editar a entrada. A segunda funo add_history nos permite gravar o
histrico das entradas de maneira que elas possam ser obtidas com as setas para cima e para baixo.
Ns substitumos fputs e fgets com chamadas para essas funes para obter o seguinte:
#include <stdio.h>
#include <stdlib.h>
#include <editline/readline.h>
#include <editline/history.h>
int main(int argc, char** argv) {
/* Imprime informacao sobre versao e como sair */
puts("Lispy Version 0.0.0.0.1");
puts("Press Ctrl+c to Exit\n");
/* Num laco que nunca acaba */
while (1) {
/* Imprima nosso prompt e obtem entrada */
char* input = readline("lispy> ");

http://www.buildyourownlisp.com/contents

18

/* Adiciona a entrada ao historico */


add_history(input);
/* Ecoa a entrada de volta ao usuario */
printf("No you're a %s\n", input);
/* Libera a entrada obtida */
free(input);
}
}

return 0;

Ns inclumos alguns novos cabealhos (headers). H o #include <stdlib.h>, que nos d


acesso funo free usada mais tarde no cdigo. Tambm adicionamos #include
<editline/readline.h> e #include <editline/history.h> que nos d acesso s
funes editline, readline e add_history.
Em lugar de solicitar a entrada, e peg-la com fgets, fazemos ambos em uma vez usando apenas
readline. O resultado disso ns passamos para add_history para grav-lo. Finalmente o
imprimimos como antes, usando printf.
Diferentemente da fgets, a funo readline retira o caractere quebra-de-linha direita da
entrada, ento precisamos adicion-lo na chamada funo printf. Tambm precisamos deletar a
entrada nos dada pela funo readline usando free. Isto porque diferentemente da fgets,
que escreve para um buffer existente, a funo readline aloca nova memria quando chamada.
Quando devemos liberar memria algo que vamos cobrir em profundidade em captulos
posteriores.

Compilando com a Editline


Se tentar compilar isto imediatamente com o comando anterior voc obter um erro. Isto porque
primeiro precisamos instalar a biblioteca editline no seu computador.
fatal error: editline/readline.h: No such file or directory #include
<editline/readline.h>

No Mac a biblioteca editline vem com Command Line Tools (ferramentas da linha de
comando). Instrues para instalao podem ser encontradas no Captulo 2. Voc pode ainda
receber um erro sobre o cabealho de histrico (history header) no ser encontrado. Neste caso,
remova a linha #include <editline/history.h>, pois esta pode no ser necessria.
No Linux voc pode instalar a editline com sudo apt-get install libedit-dev. No
Fedora, pode usar o comando su -c "yum install libedit-dev*".
Tendo instalado a editline, voc pode tentar compilar novamente. Dessa vez obter um erro
diferente.

http://www.buildyourownlisp.com/contents

19

undefined reference to `readline'


undefined reference to `add_history'

Isto significa que voc no vinculou (em ingls, linked) seu programa com a editline. Este
processo de vinculao (ligao, ou linking) permite ao compilador diretamente embutir chamadas
para a editline ao seu programa. Voc pode faz-lo ligar adicionando a opo -ledit ao seu
comando de compilar, antes da opo de sada -o).
cc -std=c99 -Wall prompt.c -ledit -o prompt

Rode isso e verifique se agora consegue editar a entrada medida que voc digita.
Ainda no est funcionando!
Alguns sistemas podem ter pequenas variaes em como instalar, incluir e vincular com
editline. Por exemplo, no Arch Linux o cabealho history da editline histedit.h. Caso
esteja tendo problemas, pesquise online e veja se consegue encontrar instrues especficas para sua
distribuio em como instalar e usar a biblioteca editline. Se isso no der certo, busque por
instrues para a biblioteca readline, que um substituto para a editline. No Mac ela pode ser
instalada usando HomeBrew ou MacPorts.

O pr-processador C
Para um projeto to pequeno, pode ser tranquilo ter que programar diferentemente dependendo de
qual sistema operacional estamos usando, mas se queremos enviar o cdigo fonte a um amigo em
um sistema operacional diferente para me ajudar com a programao isso vai causar problemas.
Num mundo ideal eu gostaria que meu cdigo fonte fosse capaz de compilar no importa onde ou
em qual computador ele est sendo compilado. Este um problema geral em C, e chamado
portabilidade. Nem sempre h uma soluo fcil ou correta.

Mas C fornece um mecanismo para ajudar, chamado o pr-processador.


O pr-processador um programa que roda antes do compilador. Ele tem uma gama de propsitos,
e ns j o estamos usando sem saber. Qualquer linha que comece com um caractere
octothorpe/jogo-da-velha/sustenido # um comando do pr-processador. Ns o estamos usando
para incluir (include) arquivos de cabealho, nos dando acesso s funes da biblioteca padro e
outras.
Um outro uso do pr-processador detectar qual sistema operacional o cdigo est sendo
compilado, e usar isto para emitir cdigo diferente.
Isto exatamente como ns vamos faz-lo. Se estivermos rodando Windows, vamos deixar o prprocessador emitir cdigo com algumas funes que preparei imitando readline e
add_history, seno vamos incluir os cabealhos da editline e us-los.

http://www.buildyourownlisp.com/contents

20

Para declarar qual cdigo o compilador deve emitir, podemos envolv-lo em comandos do prprocessador #ifdef, #else, e #endif. Estes so como uma funo if que acontece antes do
cdigo ser compilado. Todo o contedo do arquivo desde o primeiro #ifdef ao prximo #else
usado se a condio for verdadeira, caso contrrio todo o contedo desde o #else at o ltimo
#endif ser usado no lugar. Colocando isso em torno das nossas funes imitadoras, e de nossos
cabealhos da editline, o cdigo que ser emitido dever compilar no Windows, Linux ou Mac.
#include <stdio.h>
#include <stdlib.h>
/* Caso estivermos compilando no windows, compile estas funcoes */
#ifdef _WIN32
#include <string.h>
static char buffer[2048];
/* Funcao imitadora readline */
char* readline(char* prompt) {
fputs(prompt, stdout);
fgets(buffer, 2048, stdin);
char* cpy = malloc(strlen(buffer)+1);
strcpy(cpy, buffer);
cpy[strlen(cpy)-1] = '\0';
return cpy;
}
/* Funcao imitadora add_history */
void add_history(char* unused) {}
/* Senao, inclua os cabecalhos da editline
#else
#include <editline/readline.h>
#include <editline/history.h>
#endif

*/

int main(int argc, char** argv) {


puts("Lispy Version 0.0.0.0.1");
puts("Press Ctrl+c to Exit\n");
while (1) {
/* Agora em qualquer caso readline vai estar corretamente definida */
char* input = readline("lispy> ");
add_history(input);
printf("No you're a %s\n", input);
free(input);
}
return 0;
}

http://www.buildyourownlisp.com/contents

21

Referncia
prompt_unix.c
prompt_windows.c
prompt.c

Metas bnus
Mude o prompt de lispy> para algo de sua escolha.
Mude o que ecoado de volta ao usurio.
Adicione uma mensagem extra na informao de verso e como sair.
O que \n significa naquelas strings?
Que outros padres podem ser usados com printf?
O que acontece quando voc passa para printf uma varivel que no casa com o
padro?
O que faz o comando do pr-processador #ifndef?
O que faz o comando do pr-processador#define?
Se _WIN32 est definido no Windows, o que est definido no Linux ou Mac?

Linguagens Captulo 5
O que uma linguagem de programao?
Uma linguagem de programao bem parecida a uma linguagem real. Existe uma estrutura por
trs dela, e algumas regras que ditam o que e o que no vlido dizer. Quando lemos e
escrevemos linguagem natural, estamos subconscientemente aprendendo estas regras, e o mesmo
verdade para linguagens de programao. Ns podemos utilizar essas regras para entender os
outros, e gerar nosso prprio discurso, ou cdigo.
Nos anos 1950, o linguista Noam Chomsky formalizou algumas observaes importantes sobre
linguagens. Muitas delas formam a base do nosso entendimento de linguagem hoje. Uma delas foi a
observao que linguagens naturais tendem a ser construdas a partir de subestruturas recursivas e
repetidas.
Como exemplo disto, vamos examinar a frase:
Os gatos caminhavam no carpete.

http://www.buildyourownlisp.com/contents

22

Usando as regras do Portugus, o substantivo gatos pode ser substitudo por dois substantivos
separados por e.
Os gatos e cachorros caminhavam no carpete.
Cada um dos dois substantivos podem tambm ser substitudos novamente. Poderamos usar a
mesma regra anterior, e substituir gatos por dois novos substantivos juntados com e. Ou
poderamos usar uma outra regra e substituir cada substantivo por um substantivo mais um adjetivo,
para adicionar descrio eles.
Os gatos e ratos e cachorros caminhavam no carpete.
Os gatos brancos e cachorros pretos caminhavam no carpete.
Estes so apenas dois exemplos, mas o Portugus tem muitas outras regras diferentes sobre como os
tipos de palavras podem ser mudados, manipulados e substitudos.
Ns notamos este exato comportamento em linguagens de programao tambm. Em C, o corpo de
um comando if contm uma lista de novos comandos. Cada um destes novos comandos podem ser
eles mesmos um outro comando if. Estas estruturas e substituies repetidas se refletem em todas
as partes da linguagem. Elas so s vezes chamadas de regras de reescrita (em ingls, re-write
rules), porque elas dizem como uma coisa pode ser reescrita como alguma outra coisa.
if (x > 5) { return x; }
if (x > 5) { if (x > 10) { return x; } }
A consequncia desta observao por Chomsky importante. Ela significa que embora exista um
nmero infinito de coisas diferentes que podem ser ditas, ou escritas em uma linguagem particular,
ainda possvel processar e entender todas elas com um nmero finito de regras de reescrita. Um
conjunto de regras de reescrita chamado de gramtica.
Ns podemos descrever regras de reescrita de vrias maneiras. Uma delas textual. Ns podemos
dizer algo como, "uma sentena deve ser uma frase verbal", ou "uma frase verbal pode ser ou um
verbo, ou um advrbio e um verbo". Este mtodo bom para humanos mas muito impreciso para
os computadores entender. Ao programar, precisamos escrever uma descrio mais formal duma
gramtica.
Para escrever uma linguagem de programao como Lisp ns vamos precisar entender gramticas.
Para ler a entrada do usurio ns precisamos escrever uma gramtica que a descreva. Ento ns
podemos us-la juntamente com a entrada do usurio, para decidir se a entrada vlida. Ns
tambm podemos us-la para construir uma representao interna estruturada, o que far bem mais
fcil o trabalho de entend-la, e ento avali-la, executando a computao codificada nela.
a que entra uma biblioteca chamada mpc.

http://www.buildyourownlisp.com/contents

23

Parser Combinators
mpc uma biblioteca combinadora de analisadores sintticos (em ingls, Parser Combinator) que
escrevi. Isto significa que ela uma biblioteca que permite a voc construir programas que
entendem e processam linguagens especficas. Eles so conhecidos como parsers, ou analisadores
sintticos. H muitas maneiras diferentes de construir parsers, mas o legal de usar uma biblioteca
combinadora de parsers que ela permite voc construir parsers facilmente, simplesmente
especificando a gramtica ... ou quase isso.
Muitas bibliotecas combinadoras de parsers funcionam deixando voc escrever cdigo normal que
se parece um pouco com uma gramtica, no deixando voc especificar uma gramtica diretamente.
Em muitas situaes isto no um problema, mas s vezes pode ficar desajeitado e complicado.
Para nossa sorte, mpc nos permite escrever cdigo normal que se parece com uma gramtica, OU
podemos usar uma notao especial para escrever a gramtica diretamente!

Codificando gramticas
Ento, com o que se parece um cdigo que se parece com uma gramtica? Vamos dar uma olhada
no mpc, tentando escrever cdigo para uma gramtica que reconhece a linguagem do cachorro
Shiba Inu. Mais conhecido coloquialmente como Doge, nos memes da Internet. Esta linguagem ns
vamos definir como segue:
Nota do tradutor: mantendo os nomes em ingls, para simetria com o cdigo e proximidade do meme

Um Adjective ou "wow", "many", "so" ou "such".


Um Noun (substantivo, em ingls) ou "lisp", "language", "c", "book" ou "build".
Um Phrase um Adjective seguido de um Noun.
Um Doge zero ou mais Phrases.
Podemos comear tentando definir Adjective e Noun. Para fazer isso, a gente cria dois parsers,
representados pelo tipo mpc_parser_t*, e os armazenamos nas variveis Adjective e Noun.
Ns usamos a funo mpc_or para criar um parser onde uma de muitas opes podem ser usadas,
e a funo mpc_sym para envolver nossas strings iniciais.
Se voc semicerrar os olhos, pode tentar ler o cdigo como se fosse as regras que especificamos
acima.
/* Constroi um parser 'Adjective' para reconhecer descricoes */
mpc_parser_t* Adjective = mpc_or(4,
mpc_sym("wow"), mpc_sym("many"),
mpc_sym("so"), mpc_sym("such")
);

http://www.buildyourownlisp.com/contents

24

/* Constroi um 'Noun' para reconhecer coisas */


mpc_parser_t* Noun = mpc_or(5,
mpc_sym("lisp"), mpc_sym("language"),
mpc_sym("book"),mpc_sym("build"),
mpc_sym("c")
);

Como posso acessar essas funes mpc?


Por enquanto no se preocupe sobre compilar ou rodar qualquer dos exemplos de cdigo neste
captulo. Apenas foque em entender a teoria por trs das gramticas. No prximo captulo vamos
nos acertar com a mpc e us-la para uma linguagem prxima do nosso Lisp.
Para definir Phrase ns podemos referenciar nossos parsers existentes. Ns precisaremos usar a
funo mpc_and, que especifica que alguma coisa requerida e a seguir uma outra. Como entrada
ns passamos Adjective e Noun, nossos parsers definidos anteriormente. Esta funo tambm
recebe os argumentos mpcf_strfold e free, que dizem como juntar ou deletar os resultados
desses parsers. Ignore esses argumentos por enquanto.
mpc_parser_t* Phrase = mpc_and(2, mpcf_strfold,
Adjective, Noun, free);

Para definir Doge ns precisamos especificar que zero ou mais de algum parser necessrio. Para
isso, precisamos usar a funo mpc_many. Como antes, esta funo requer a varivel especial
mpcf_strfold para dizer como os resultados sero juntados, o que podemos ignorar.
mpc_parser_t* Doge = mpc_many(mpcf_strfold, Phrase);

Depois de criar um parser que procura por zero ou mais ocorrncias de um outro parser, uma coisa
interessante aconteceu. Nosso Doge aceita entrada de qualquer tamanho. Isto significa que sua
linguagem infinita. Aqui esto apenas alguns exemplos de possveis strings Doge poderia aceitar.
Da mesma forma que descobrimos na primeira seo deste captulo, ns usamos um nmero finito
de regras de reescrita para criar uma linguagem infinita.
"wow book such language many lisp"
"so c such build such language"
"many build wow c"
""
"wow lisp wow c many language"
"so c"

Se usarmos mais funes mpc, podemos devagarinho construir parsers que analisam ("parseiam")
linguagens mais e mais complicadas. O cdigo que usamos l-se mais ou menos como uma
gramtica, mas se torna mais bagunado com complexidade adicional. Devido a isso, usar essa
abordagem nem sempre uma tarefa fcil. Um conjunto de funes auxiliares que usam
construes simples para tornar fceis algumas tarefas frequentes esto todas documentadas no
repositrio Git da mpc. Essa uma boa abordagem para linguagens complicadas pois ela permite
um controle fino, mas no sero necessrios para nosso caso.

http://www.buildyourownlisp.com/contents

25

Gramticas naturais
mpc nos permite escrever gramticas numa maneira mais natural tambm. Em vez de usar funes
C que se parecem menos com uma gramtica, ns podemos especificar a coisa toda em uma grande
string. Ao usar este mtodo, no precisamos nos preocupar de como juntar ou descartar as entradas,
com funes como mpcf_strfold ou free. Tudo feito automaticamente para ns.
Eis aqui como poderamos recriar os exemplos anteriores usando este mtodo.
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*

Adjective
Noun
Phrase
Doge

=
=
=
=

mpc_new("adjective");
mpc_new("noun");
mpc_new("phrase");
mpc_new("doge");

mpca_lang(MPCA_LANG_DEFAULT,
"
adjective : \"wow\" | \"many\"
| \"so\" | \"such\";
noun
: \"lisp\" | \"language\"
| \"book\" | \"build\" | \"c\";
phrase
: <adjective> <noun>;
doge
: <phrase>*;
",
Adjective, Noun, Phrase, Doge);

\
\
\
\
\
\
\

/* Faz algum parsing aqui... */


mpc_cleanup(4, Adjective, Noun, Phrase, Doge);

Sem ter um entendimento preciso da sintaxe para essa longa string, deve ser bvio como a
gramtica fica muito mais clara nesse formato. Se aprendermos tudo o que os smbolos especiais
significam, mal precisamos semicerrar os olhos.
Uma outra coisa a notar que o processo agora est em duas etapas. Primeiro ns criamos e
nomeamos vrias regras usando mpc_new e a seguir as definimos usando mpca_lang.
O primeiro argumento para mpca_lang so as opes da linha de comando. Para isso,
simplesmente usamos os defaults (valores padres preestabelecidos). O segundo uma longa string
multi-line (isto , em mltiplas linhas) em C. Esta a especificao da gramtica. Ela consiste de
um certo nmero de regras de reescrita. Cada regra tem o nome da regra esquerda, um dois
pontos :, e direita sua definio terminada com um ponto e vrgula ;.
Os smbolos especiais usados para definir as regras ao lado direito funcionam da seguinte forma:
"ab"
'a'
'a' 'b'
'a' | 'b'

A string ab requerida.
O caractere a requerido.
Primeiro 'a' requerido, a seguir 'b'
requerido.
Ou 'a' requerido, ou 'b' requerido.

http://www.buildyourownlisp.com/contents

'a'*
'a'+
<abba>
Parece familiar...

26
Zero ou mais 'a' so requeridos.
Um ou mais 'a' so requeridos.
A regra chamada abba requerida.

Voc notou que a descrio da string de entrada para mpca_lang soa como se estivesse
especificando uma gramtica? porque ela est mesmo. mpc usa a si mesma para analisar
("parsear") a entrada que voc fornece para mpca_lang. Ela faz isso especificando uma
gramtica em cdigo usando o mtodo anterior. Quo elegante isso, hein?
Usando a tabela descrita acima, verifique que o que eu escrevi acima igual ao que est
especificado no cdigo.
Este mtodo de especificar uma gramtica o que vamos usar nos prximos captulos. Ele pode
parecer um pouco consternante no comeo. Gramticas podem ser difceis de entender. Mas
medida que continuamos voc se tornar mais e mais familiarizado com como cri-las e edit-las.
Este captulo sobre teoria, ento se voc vai tentar alguma das tarefas bnus, no se preocupe
muito sobre estarem corretas. Pensar com a mentalidade certa mais importante. Fique vontade
para inventar smbolos e notao para certos conceitos para faz-los mais simples de escrever.
Alguma tarefa bnus pode precisar de estruturas de gramtica cclicas ou recursivas, ento no se
preocupe caso tenha que us-las!

Referncia
doge_code.c
doge_grammar.c

Metas bnus
Escreva mais alguns exemplos de strings que a linguagem Doge contm.
Por que h barras invertidas \ em frente das aspas " na gramtica?
Por que h barras invertidas \ ao fim da linha na gramtica?
Descreva textualmente uma gramtica para nmeros decimas como 0.01 ou 52.221.
Descreva textualmente uma gramtica para endereos de URLs como
https://construa-seu-proprio-lisp.herokuapp.com/.
Descreva textualmente uma gramtica para sentenas em Portugus simples como o
gato sentou no tapete.
Descreva mais formalmente as gramticas acima. Use |, *, ou qualquer smbolo que voc
mesmo invente.

http://www.buildyourownlisp.com/contents

27

Se voc est familiarizado com JSON, descreva textualmente uma gramtica para ele.

Anlise Sinttica Captulo 6


Notao Polonesa
Para testar a mpc vamos implementar uma gramtica simples que lembra um subconjunto do nosso
Lisp. Ela chamada de Notao Polonesa e uma notao para aritmtica onde o operador vem
antes dos operandos.
Por exemplo...
1 + 2 + 6
+ 1 2 6
6 + (2 * 9)
+ 6 (* 2 9)
(10 * 2) / (4 + 2) / (* 10 2) (+ 4 2)
Precisamos arranjar uma gramtica que descreva essa notao. Podemos comear descrevendo-a
textualmente e em seguida formalizar nossos pensamentos.
Para comear, observamos que na notao polonesa o operador sempre vem antes numa expresso,
seguido por nmeros ou outras expresses entre parnteses. Isso significa que podemos dizer "um
programa um operador seguido de uma ou mais expresses," onde "uma expresso ou um
nmero, ou, entre parnteses, um operador seguido de uma ou mais expresses.
Mais formalmente...
Program
Expression
Operator
Number

o comeo da entrada, um Operator, uma ou mais


Expression, e o fim da entrada.
ou um Number ou '(', um Operator, uma ou
mais Expression, e um ')'.
'+', '-', '*', ou '/'.
um - opcional, e um ou mais caracteres entre 0 e 9

Expresses Regulares
Devemos ser capaz de codificar a maior parte das regras acima usando coisas que j sabemos, mas
Number e Program podem representar algum problema. Essas regras contm algumas construes
que ainda no aprendemos como expressar. No sabemos como expressar o comeo ou o fim da
entrada, caracteres opcionais, ou uma faixa de caracteres.
Elas podem ser expressas, mas requerem uma coisa chamada Expresso Regular. Expresses
regulares so uma maneira de escrever gramticas para pequenas pores de texto como palavras ou

http://www.buildyourownlisp.com/contents

28

nmeros. Gramticas escritas usando expresses regularas no podem consistir de mltiplas regras,
mas do controle preciso e conciso sobre o que est sendo casado e o que no. Eis aqui algumas
regras bsicas para escrever expresses regulares.
.
a
[abcdef]
[a-f]
a?
a*
a+
^
$

esperado algum caractere qualquer.


esperado o caractere a.
esperado algum caractere do conjunto abcdef.
esperado algum caractere na faixa de a a f.
O caractere a opcional.
Zero ou mais do caractere a so esperados.
Um ou mais do caractere a so esperados.
O comeo da entrada esperado.
O fim da entrada esperado.

Estas so todas as regras de expresses regulares que vamos precisar por enquanto. H livros
inteiros sobre o assunto de aprender expresses regulares. Para os curiosos, mais informaes
podem ser achadas online ou dessas fontes. Vamos us-las em prximos captulos, ento algum
conhecimento bsico ser necessrio, mas voc no precisa domin-las por enquanto.
Em uma gramtica mpc ns escrevemos expresses regulares colocando-as entre barras /. Usando
o guia acima a nossa regra Number pode ser expressa como uma expresso regular usando a string
/-?[0-9]+/.

Instalando mpc
Antes de trabalharmos na escrita dessa gramtica precisamos primeiro incluir os cabealhos da
mpc, e ento vincular a biblioteca mpc, da mesma forma que fizemos para editline no Linux e
Mac. Comeando com seu cdigo do captulo 4, voc pode renomear o arquivo para parsing.c e
baixar mpc.h e mpc.c do repositrio mpc. Ponha estes no mesmo diretrio que seu cdigo fonte.
Para incluir mpc coloque #include "mpc.h" no topo do arquivo. Para vincular mpc coloque
mpc.c diretamente no comando de compilao. No Linux voc tambm precisa ligar s bibliotecas
de matemtica adicionando a opo -lm.
No Linux e Mac
cc -std=c99 -Wall parsing.c mpc.c -ledit -lm -o parsing

No Windows
cc -std=c99 -Wall parsing.c mpc.c -o parsing

Espera a, voc no quer dizer #include <mpc.h>?

http://www.buildyourownlisp.com/contents

29

Existem duas maneiras de incluir arquivos em C. Uma delas usando colchetes angulares <> como
j vimos at agora, e a outra usando aspas duplas "".
A nica diferena entre as duas que usando colchetes angulares procura os cabealhos nos
diretrios do sistema primeiro, j as aspas duplas procuram no diretrio atual primeiro. Por causa
disso cabealhos do sistema como <stdio.h> so tipicamente usados com colchetes angulares, e
cabealhos locais como "mpc.h" tipicamente so usados com aspas duplas.

Gramtica para notao polonesa


Formalizando as regras acima ainda mais, e usando algumas expresses regulares, podemos
escrever uma gramtica final para a linguagem de notao polonesa como segue. Leia o cdigo
abaixo e verifique que ele fecha com o que tnhamos escrito textualmente, e nossas ideias de
notao polonesa.
/* Cria Alguns Parsers
mpc_parser_t* Number
mpc_parser_t* Operator
mpc_parser_t* Expr
mpc_parser_t* Lispy

*/
= mpc_new("number");
= mpc_new("operator");
= mpc_new("expr");
= mpc_new("lispy");

/* Define eles com a seguinte linguagem */


mpca_lang(MPCA_LANG_DEFAULT,
"
number
: /-?[0-9]+/ ;
operator : '+' | '-' | '*' | '/' ;
expr
: <number> | '(' <operator> <expr>+ ')' ;
lispy
: /^/ <operator> <expr>+ /$/ ;
",
Number, Operator, Expr, Lispy);

\
\
\
\
\

Precisamos adicionar isso ao prompt interativo que comeamos no captulo 4. Coloque este cdigo
logo no comeo da funo main, antes de parte que mostra a informao de verso e como sair. Ao
fim do nosso programa, tambm precisamos deletar os parsers quando no precisamos mais deles.
Logo antes da main retornar ns devemos adicionar o cdigo de limpeza a seguir.
/* Desfaz definicao e deleta nossos Parsers */
mpc_cleanup(4, Number, Operator, Expr, Lispy);

Estou tendo o erro undefined reference to `mpc_lang'


O correto mpca_lang, com um a no final!

http://www.buildyourownlisp.com/contents

30

Analisando a entrada
Nosso cdigo cria um parser mpc para nossa linguagem Notao Polonesa, mas ainda precisamos
us-lo de verdade na entrada suprida pelo usurio cada vez que solicitamos. Precisamos editar
nosso lao while para que em vez de simplesmente ecoar a entrada, ele realmente tente analisar a
entrada usando nosso parser. Podemos fazer isso substituindo a chamada printf com o seguinte
cdigo mpc, que faz uso do nosso parser de programa Lispy.
/* Tenta Parsear/Analisar a Entrada */
mpc_result_t r;
if (mpc_parse("<stdin>", input, Lispy, &r)) {
/* Caso Successo, Imprime a AST */
mpc_ast_print(r.output);
mpc_ast_delete(r.output);
} else {
/* Senao, Imprime o Erro */
mpc_err_print(r.error);
mpc_err_delete(r.error);
}

Esse cdigo chama a funo mpc_parse com nosso parser Lispy, e a string de entrada input.
Ele copia o resultado da anlise para r e retorna 1 caso teve sucesso ou 0 caso falhou. Ns usamos
o operador de endereo & em r quando passamo-lo funo. Este operador ser explicado em mais
detalhes em captulos posteriores.
No caso de sucesso, uma estrutura interna copiada para r, no campo output. Ns podemos
imprimir esta estrutura usando mpc_ast_print e delet-la usando mpc_ast_delete.
Caso no, ter havido algum erro, que copiado para r no campo error. Podemos imprimir o erro
usando mpc_err_print e delet-lo usando mpc_err_delete.
Compile essas atualizaes, e bote o programa para rodar. Tente algumas entradas diferentes e veja
como o sistema reage. Comportamento correto deve se parecer com o seguinte.
Lispy Version 0.0.0.0.2
Press Ctrl+c to Exit
lispy> + 5 (* 2 2)
>
regex
operator|char:1:1 '+'
expr|number|regex:1:3 '5'
expr|>
char:1:5 '('
operator|char:1:6 '*'
expr|number|regex:1:8 '2'
expr|number|regex:1:10 '2'
char:1:11 ')'
regex
lispy> hello
<stdin>:1:1: error: expected whitespace, '+', '-', '*' or '/' at 'h'

http://www.buildyourownlisp.com/contents

31

lispy> / 1dog
<stdin>:1:4: error: expected one of '0123456789', whitespace, '-', one or more
of one of '0123456789', '(' or end of input at 'd'
lispy>

Estou tendo o erro <stdin>:1:1: error: Parser Undefined!.


Este erro devido sintaxe para sua gramtica fornecida mpca_lang estar incorreta. Veja se
consegue descobrir qual parte da gramtica est incorreta. Voc pode usar o cdigo de referncia
para este captulo para ajud-lo a descobrir isso, e verificar como a gramtica deve ser.

Referncia
parsing.c

Metas bnus
Escreva uma expresso regular que case strings com tudo a ou b tais como aababa ou
bbaa.
Escreva uma expresso regular que case strings de a e b consecutivos, tais como ababab
ou aba.
Escreva uma expresso regular que case com pit, pot e respite mas no case com
not peat, spit, ou part.
Mude a gramtica para adicionar um novo operador como o %.
Mude a gramtica para reconhecer operadores escritos em formato textual add, sub, mul,
div.
Mude a gramtica para reconhecer nmeros decimais como 0.01, 5.21, ou 10.2.
Mude a gramtica para fazer os operadores escritos convencionalmente, entre duas
expresses.
Use a gramtica do captulo anterior para analisar Doge. Voc precisa adicionar comeo e
fim da entrada.

http://www.buildyourownlisp.com/contents

32

Avaliao Captulo 7
rvores
Agora podemos ler a entrada, temos ela estruturada internamente, mas ainda no conseguimos
avali-la (isto , interpret-la). Neste captulo adicionamos o cdigo que avalia esta estrutura e
executa de verdade as computaes codificadas nela.
Essa estrutura interna que vimos impressa pelo programa no captulo anterior chamada rvore
sinttica abstrata (Abstract Syntax Tree), e representa a estrutura do programa baseada na entrada
digitada pelo usurio. Nas folhas desta rvore esto nmeros e operadores - os dados a serem
realmente processados. Nos galhos esto as regras usadas para produzir esta parte da rvore - a
informao em como atravess-la (e.g. em que ordem visitar os galhos e folhas?) e avali-la.

Antes de resolver exatamente como vamos fazer essa travessia, vamos ver exatamente como
esta estrutura definida internamente. Se olharmos dentro do mpc.h podemos dar uma olhada
na definio de mpc_ast_t, que a estrutura de dados que obtemos da anlise sinttica.
typedef struct mpc_ast_t {
char* tag;
char* contents;
mpc_state_t state;
int children_num;
struct mpc_ast_t** children;
} mpc_ast_t;

Esta estrutura tem alguns campos que podemos acessar. Vamos dar uma olhada neles, um por um.
O primeiro campo tag (etiqueta). Quando imprimimos a rvore, a tag era a informao que
precedia o contedo do n. Era uma string contendo uma lista de todas as regras usadas para
analisar quele item. Por exemplo, expr|number|regex.
Esse campo tag vai ser importante pois permite que vejamos quais regras de parsing foram usadas
para criar o n.
O segundo campo contents. Este conter o contedo de verdade do n, como '*', '(' ou
'5'. Voc vai notar que para galhos ele vazio, mas para folhas podemos us-los para encontrar o
operador ou nmero a usar.
O prximo campo state. Este contm informao sobre o estado em que o parser estava quando
encontrou esse n, como o nmero da linha e coluna. No faremos uso dele neste programa.
Finalmente, vemos dois campos que iro nos ajudar a atravessar a rvore. Eles so
children_num e children. O primeiro campo nos diz quantos filhos um n tem, e o segundo
um conjunto desses filhos.

http://www.buildyourownlisp.com/contents

33

O tipo do campo children mpc_ast_t**, que um apontador duple. Isso no to


assustador quanto parece, e ser explicado em maior detalhe em captulos posteriores. Por enquanto,
voc pode pensar nele como uma lista dos ns desta rvore.
Ns podemos acessar um n filho acessando este campo usando a notao de array. Fazemos isso
escrevendo o nome do campo children e adicionando um sufixo com colchetes contendo o
ndice do filho a ser acessado. Por exemplo, para acessar o primeiro filho do n, podemos usar
children[0]. Note como C conta os ndices do array a partir de 0.
Como o tipo mpc_ast_t* um apontador para uma estrutura, h uma sintaxe um pouco diferente
para acessar os seus campos. Precisamos usar uma seta -> em vez de um ponto .. H um motivo
fundamental para essa troca de operadores, ento por enquanto simplesmente lembre-se que o
acesso de campo de tipos apontadores usa uma flecha.
/* Carrega AST do output */
mpc_ast_t* a = r.output;
printf("Tag: %s\n", a->tag);
printf("Contents: %s\n", a->contents);
printf("Number of children: %i\n", a->children_num);
/* Pega primeiro filho */
mpc_ast_t* c0 = a->children[0];
printf("First Child Tag: %s\n", c0->tag);
printf("First Child Contents: %s\n", c0->contents);
printf("First Child Number of children: %i\n",
c0->children_num);

Recurso
H uma coisa estranha sobre esta estrutura de rvore. Ela se referencia si mesma. Cada um dos
seus filhos so eles prprios rvores novamente, e os filhos destes filhos tambm so rvores
novamente. Da mesma forma que nossas linguagens e regras de reescrita, dados nesta estrutura
contm subestruturas repetidas que lembram seus parentes.

Esse padro de subestruturas repetidas pode continuar infinitamente. Claramente se queremos


uma funo que funcione com todas as rvores possveis ns no podemos apenas olhar alguns
ns abaixo, ns temos que defini-la para funcionar com qualquer profundidade.
Felizmente podemos fazer isto, explorando a natureza de como essas subestruturas repetem e
usando uma tcnica chamada recurso.
De maneira simples, uma funo recursiva uma que chama a si mesma como parte do seu clculo.
Soa estranho para uma funo ser definida em termos dela mesma. Mas considere que funes
podem ter diferentes sadas quando fornecidas entradas diferentes. Se provemos entradas mudadas
ou substitudas para uma chamada recursiva da mesma funo, e provemos uma maneira para esta

http://www.buildyourownlisp.com/contents

34

funo no chamar a si mesma em certas condies, podemos ter mais confiana de que essa
funo recursiva est fazendo algo til.
Como um exemplo, vamos escrever uma funo recursiva que ir contar o nmero de ns em nossa
estrutura rvore.
Para comear, vamos definir como a funo deve agir no caso mais simples - se a rvore no tem
nenhum filho. Neste caso, sabemos que o resultado simplesmente um. Agora podemos definir o
caso mais complexo - se a rvore tem um ou mais filhos. Neste caso, o resultado ser um (para o
prprio n), mais a soma do nmero de ns de todos os seus filhos.
Mas como descobrimos o nmero de ns em todos os seus filhos? Bem, podemos usar a funo que
estamos no processo de definir! Yeah, Recurso!
Em poderamos escrever algo assim:
int number_of_nodes(mpc_ast_t* t) {
if (t->children_num == 0) { return 1; }
if (t->children_num >= 1) {
int total = 1;
for (int i = 0; i < t->children_num; i++) {
total = total + number_of_nodes(t->children[i]);
}
return total;
}
return 0;
}

Funes recursivas so esquisitas porque requerem um certo salto de f. Primeiro, temos que
assumir que temos uma funo que faz uma coisa corretamente, e ento temos que prosseguir e usar
essa funo, para escrever a funo inicial que assumimos que tnhamos!
Como a maioria das coisas, funes recursivas quase sempre acabam seguindo um padro parecido.
Primeiro um caso base definido. Este o caso que termina a recurso, como t>children_num == 0 no exemplo anterior. Em seguida, definido o caso recursivo, que
quebra a computao em partes menores e chama a si mesmo recursivamente para computar as
outras partes, e por fim combin-las.
Funes recursivas precisa de uma certa concentrao, ento faa uma pausa agora e entenda-as
antes de continuar para outros captulos porque faremos bastante uso delas no resto do livro. Se
voc est um pouco inseguro, pode tentar alguma das metas bnus para este captulo.

Avaliao
Para avaliar a rvore sinttica vamos escrever uma funo recursiva. Mas antes de comear, vamos
tentar ver quais observaes podemos fazer sobre a estrutura da rvore que recebemos como
entrada. Tente imprimir algumas expresses usando seu programa do captulo anterior. O que voc
percebe?

http://www.buildyourownlisp.com/contents

35

lispy> * 10 (+ 1 51)
>
regex
operator|char:1:1 '*'
expr|number|regex:1:3 '10'
expr|>
char:1:6 '('
operator|char:1:7 '+'
expr|number|regex:1:9 '1'
expr|number|regex:1:11 '51'
char:1:13 ')'
regex

Uma observao que se um n tem uma tag/etiqueta number ele sempre um nmero, no tem
filhos, e podemos simplesmente convert-lo para um inteiro. Isso funcionar como um caso base na
nossa recurso.
Se um n est etiquetado com expr, e no number, precisamos olhar para o segundo filho ( o
primeiro filho sempre '(') e ver qual o operador. Ento precisamos aplicar este operador para a
avaliao dos filhos restantes, excluindo o ltimo filho que sempre ')'. Esse nosso caso
recursivo. Isto tambm precisa ser feito para o n raiz.
Quando avaliamos nossa rvore, assim como quando contamos os ns, precisaremos acumular o
resultado. Para representar este resultado usaremos o tipo C long, que significa um longo inteiro
long integer.
Para detectar a etiqueta de um n, ou para obter o nmero de um n, vamos precisar usar os campos
tag e contents. Estes so campos string, ento teremos que aprender algumas funes de string
antes.
atoi
strcmp

strstr

Converte um char* para um long.


Recebe duas strings (char*) como entrada e
caso forem iguais, devolve 0.
Recebe duas strings (char*) e devolve um
apontador para a localizao da segunda dentro da
primeira, ou 0 caso o segundo no uma substring do primeiro.

Podemos usar strcmp para checar qual operador usar, e strstr para checar se uma tag contm
alguma sub-string. Juntas, nossa funo de avaliao recursiva fica assim:
long eval(mpc_ast_t* t) {
/* Caso etiquetado como numero, retorna diretamente. */
if (strstr(t->tag, "number")) {
return atoi(t->contents);
}
/* O operador eh sempre o segundo filho. */
char* op = t->children[1]->contents;

http://www.buildyourownlisp.com/contents

36

/* Armazenamos o terceiro filho em `x` */


long x = eval(t->children[2]);
/* Itera sobre os filhos restantes, e combina resultado. */
int i = 3;
while (strstr(t->children[i]->tag, "expr")) {
x = eval_op(x, op, eval(t->children[i]));
i++;
}
}

return x;

Podemos definir a funo eval_op como segue. Ela recebe um nmero, uma string operador, e
um outro nmero. Ela testa qual operador passado, e executa a operao correspondente para as
entradas.
/* Usa string operador para ver qual operacao executar */
long eval_op(long x, char* op, long y) {
if (strcmp(op, "+") == 0) { return x + y; }
if (strcmp(op, "-") == 0) { return x - y; }
if (strcmp(op, "*") == 0) { return x * y; }
if (strcmp(op, "/") == 0) { return x / y; }
return 0;
}

Impresso
Em lugar de imprimir a rvore, agora queremos imprimir o resultado da avaliao. Por isso
precisamos passar a rvore para nossa funo eval, e imprimir o resultado que obtemos usando
printf e o especificador %li que usado para o tipo long.
Tambm precisamos lembrar de deletar a rvore de sada quando terminamos de avali-la.
long result = eval(r.output);
printf("%li\n", result);
mpc_ast_delete(r.output);

Se tudo isso deu certo, devemos poder fazer alguma matemtica bsica na nossa nova linguagem de
programao!
Lispy Version 0.0.0.0.3
Press Ctrl+c to Exit
lispy> + 5 6
11
lispy> - (* 10 10) (+ 1 1 1)
97

http://www.buildyourownlisp.com/contents

37

Referncia
evaluation.c

Metas bnus
Escreva uma funo recursiva para calcular o nmero de folhas de uma rvore.
Escreva uma funo recursiva para calcular o nmero de galhos de uma rvore.
Escreva uma funo recursiva para calcular o maior nmero de filhos saindo dum galho
duma rvore.
Como usar strstr para ver se um n foi etiquetado como expr?
Como usar strcmp para ver se um n possua o contedo '(' ou ')'?
Adicione o operador %, que devolve o resto da diviso. Por exemplo % 10 6 4.
Adicione o operador ^, que eleva um nmero ao outro. Por exemplo ^ 4 2 16.
Adicione a funo min, que devolve o menor nmero. Por exemplo min 1 5 3 1.
Adicione a funo max, que devolve o maior nmero. Por exemplo max 1 5 3 5.
Mude o operador menos - para que quando receba um argumento ele o transforme em
negativo.

Tratamento de Erros Captulo 8


Crashes
Alguns de vocs talvez notaram um problema com o programa do captulo anterior. Tente digitar o
seguinte no prompt e veja o que acontece.
Lispy Version 0.0.0.0.3
Press Ctrl+c to Exit
lispy> / 10 0

Eita! O programa falhou tentando dividir por zero. No tem problema que um programa falhe
durante o desenvolvimento, mas nosso programa final idealmente nunca falharia, e deve sempre
explicar ao usurio o que deu errado.
No momento, nosso programa pode produzir erros de sintaxe mas ainda no tem
funcionalidade para reportar erros na avaliao das expresses. Precisamos embutir algum tipo
de funcionalidade de tratamento de erros para fazer isso. Pode ser um pouco desajeitado em C,

http://www.buildyourownlisp.com/contents

38

mas se comearmos no caminho certo, vai valer a pena mais tarde quando nosso sistema fica
mais complicado.
Programas C falhando so coisas da vida. Se alguma coisa d errado, o sistema operacional os
expulsa. Programas podem falhar por causa de muitas razes diferentes, de muitas maneiras
diferentes. Voc ver pelo menos um Heisenbug (isto , um bug que parece desaparecer quando
voc tenta investig-lo).
Mas no h mgica em como programas C funcionam. Se voc encontrar um bug realmente
perturbador, no desista ou sente e encare a tela at seus olhos sangrarem. Aproveita esta chance
para aprender propriamente como usar gdb e valgrind. Sero mais armas no seu arsenal, e
depois do investimento inicial, pouparo bastante tempo e dor.

Valor Lisp
H muitas maneiras de lidar com erros em C, mas neste contexto meu jeito preferido tornar erros
um possvel resultado de avaliar uma expresso. Assim podemos dizer que, em Lispy, uma
expresso resultar ou em um nmero, ou em um erro. Por exemplo, + 1 2 resultar em um
nmero, mas / 10 0 resultar em um erro.
Para isso, precisamos uma estrutura de dados que possa agir como uma coisa ou qualquer outra.
Para simplificar, vamos usar uma struct com campos especficos para cada coisa que pode ser
representadas, e um tipo especial type para nos dizer exatamente quais campos so significativos
para acessar.
Vamos chamar isto de um lval, que significa Lisp Value (valor Lisp).
/* Declara uma struct lval */
typedef struct {
int type;
long num;
int err;
} lval;

Enumeraes
Voc vai notar que o tipo dos campos type, e err int. Isto significa que so representados por
um nico nmero inteiro.
O motivo que escolhemos int porque vamos atribuir significado a cada valor inteiro, para
codificar o que necessitamos. Por exemplo, podemos fazer uma regra "Se type for 0, ento a
estrutura um Number.", ou "Se type for 1 ento a estrutura um Error.". Esta uma forma
simples e efetiva de fazer as coisas.

http://www.buildyourownlisp.com/contents

39

Mas se enchermos com 0 e 1 perdidos no nosso cdigo, vai se tornar cada vez mais obscuro o que
est acontecendo. Em vez disso, podemos usar constantes nomeadas que tenham esses valores
inteiros atribudos. Isto d ao leitor uma indicao do por que algum poderia comparar um nmero
com 0 ou 1 e o que quer dizer neste contexto.
Em C, isto se faz usando um enum.
/* Cria Enumeracao dos Possiveis Tipos lval */
enum { LVAL_NUM, LVAL_ERR };

Um enum uma declarao de variveis s quais por baixo dos panos so atribudos valores
inteiros constantes automaticamente. O cdigo acima descreve como declararamos valores
enumerados para o campo type.
Tambm queremos declarar uma enumerao para o campo error. Temos trs casos de erro no
nosso programa especfico. H diviso por zero, um operador desconhecido, ou um nmero sendo
passado que seja grande demais para ser representado internamente usando um long. Estes casos
podem ser enumerados como segue.
/* Create Enumeration of Possible Error Types */
enum { LERR_DIV_ZERO, LERR_BAD_OP, LERR_BAD_NUM };

Funes de valor Lisp


Nosso tipo lval est quase pronto. Diferentemente do tipo long anterior, no temos atualmente
uma maneira de criar novas instncias dele. Para fazer isso, podemos declarar duas funes que
constri um lval a partir de um tipo error ou um tipo number.
/* Cria um novo lval do tipo numero */
lval lval_num(long x) {
lval v;
v.type = LVAL_NUM;
v.num = x;
return v;
}
/* Cria um novo lval do tipo erro */
lval lval_err(int x) {
lval v;
v.type = LVAL_ERR;
v.err = x;
return v;
}

Estas funes primeiro criam um lval chamado v, e atribuem os campos antes de retorn-lo.
Como nossa funo lval pode agora ser uma de duas coisas, no podemos mais simplesmente
usar printf para imprimi-la. Vamos querer se comportar de maneira diferente dependendo do tipo
do lval que for dado. H uma maneira concisa de fazer isto em C usando o comando switch.

http://www.buildyourownlisp.com/contents

40

Ele toma algum valor como entrada, e compara com alguns valores conhecidos, conhecidos como
casos. Quando os valores so iguais, ele executa o cdigo que segue at o prximo comando
break.
Usando isso podemos construir uma funo que imprime um lval de qualquer tipo, assim:
/* Imprime um "lval" */
void lval_print(lval v) {
switch (v.type) {
/* Caso o tipo for um numero, imprima-o */
/* A seguir, sai fora (break) do switch. */
case LVAL_NUM: printf("%li", v.num); break;

/* Caso o tipo for um erro */


case LVAL_ERR:
/* Checa qual o tipo do erro e imprime-o */
if (v.err == LERR_DIV_ZERO) {
printf("Error: Division By Zero!");
}
if (v.err == LERR_BAD_OP)
{
printf("Error: Invalid Operator!");
}
if (v.err == LERR_BAD_NUM) {
printf("Error: Invalid Number!");
}
break;

}
/* Imprime um "lval" seguido duma quebra de linha */
void lval_println(lval v) { lval_print(v); putchar('\n'); }

Avaliando erros
Agora que sabemos como trabalhar com o tipo lval, precisamos mudar nossas funes de
avaliao para us-lo em vez de long.
Precisamos mudar as assinaturas de tipo, precisamos mudar as funes de forma que funcionem
corretamente ao encontrar na entrada tanto um error quanto um number.
Em nossa funo eval_op, se encontramos um erro devemos retorn-lo imediatamente, e somente
fazer alguma computao se ambos os argumentos so nmeros. Devemos modificar nosso cdigo
para retornar um erro em vez de tentar dividir por zero. Isso vai corrigir a falha descrita no comeo
deste captulo.
lval eval_op(lval x, char* op, lval y) {
/* Se algum valor for erro, retorna-o */
if (x.type == LVAL_ERR) { return x; }
if (y.type == LVAL_ERR) { return y; }
/* Senao, faz a matematica nos valores numericos */
if (strcmp(op, "+") == 0) { return lval_num(x.num + y.num); }

http://www.buildyourownlisp.com/contents

41

if (strcmp(op, "-") == 0) { return lval_num(x.num - y.num); }


if (strcmp(op, "*") == 0) { return lval_num(x.num * y.num); }
if (strcmp(op, "/") == 0) {
/* Se o segundo operando for zero, retorna erro */
return y.num == 0
? lval_err(LERR_DIV_ZERO)
: lval_num(x.num / y.num);
}
}

return lval_err(LERR_BAD_OP);

O que aquele ? est fazendo ali?


Voc vai notar que para a diviso checar se o segundo argumento zero usamos um ponto de
interrogao ?, seguido de dois pontos :. Este o chamado operador ternrio, e permite escrever
expresses condicionais em uma linha.
Ele funciona mais ou menos assim: <condio> ? <ento> : <seno>. Em outras
palavras, se a condio verdadeira, ele retorna o que segue o ?, seno ele retorna o que segue o :.
Algumas pessoas no gostam desde operador porque acreditam que ele torna o cdigo obscuro. Se
voc no est familiarizado com o operador ternrio, pode inicialmente ach-lo estranho de usar;
mas uma vez que o conhece bem raramente h problemas.
Precisamos dar um tratamento similar para nossa funo eval. Neste caso, como definimos
eval_op para tratar erros de maneira robusta, precisamos apenas adicionar as condies de erro
para nossa funo de converso de nmeros.
Neste caso, usamos a funo strtol para converter de string para long. Isto nos permite checar
uma varivel especial errno para se certificar que a converso funcionou corretamente. Essa
uma maneira mais robusta de converter nmeros que nosso mtodo anterior usando atoi.
lval eval(mpc_ast_t* t) {
if (strstr(t->tag, "number")) {
/* Checa se existe algum erro na conversao */
errno = 0;
long x = strtol(t->contents, NULL, 10);
return errno != ERANGE ? lval_num(x) : lval_err(LERR_BAD_NUM);
}
char* op = t->children[1]->contents;
lval x = eval(t->children[2]);
int i = 3;
while (strstr(t->children[i]->tag, "expr")) {
x = eval_op(x, op, eval(t->children[i]));
i++;
}
}

return x;

http://www.buildyourownlisp.com/contents

42

O ltimo pequeno passo mudar como imprimimos o resultado encontrado pela nossa avaliao
para usar nossa nova funo de impresso que pode imprimir qualquer tipo de lval.
lval result = eval(r.output);
lval_println(result);
mpc_ast_delete(r.output);

E pronto! Tente rodar este novo programa e certifique-se que no h falhas ao dividir por zero.
lispy> / 10 0
Error: Division By Zero!
lispy> / 10 2
5

Encanamento
Alguns de vocs que chegaram nesse ponto do livro podem se sentir desconfortveis com a
maneira que ele est progredindo. Talvez voc creia que conseguiu seguir as instrues bem o
suficiente, mas no tem um entendimento claro de todos os mecanismos acontecendo por trs
dos panos.
Caso seja o seu caso, quero assegurar-lhe que voc est indo bem. Caso no entenda todos os
detalhes porque posso no ter explicado tudo em profundidade suficiente. E no tem problema.
Ser capaz de progredir e fazer o cdigo funcionar sob essas condies uma grande habilidade em
programao, e se voc chegou at aqui isso mostra que voc a tem.
Em programao, chamamos isso de fazer o encanamento (do ingls, plumbing). A grosso modo,
isso seguir instrues tentando amarrar um bando de bibliotecas ou componentes, sem
compreender inteiramente como eles funcionam internamente.
Isto requer f e intuio. F necessrio para para acreditar que se as estrelas se alinharem, e todas
as encantaes executarem corretamente para essa mquina mgica, a coisa certa vai realmente
acontecer. E intuio necessria para descobrir o que deu errado, e como corrigir coisas quando
elas no vo de acordo com o planejado.
Infelizmente essas coisas no podem ser ensinadas diretamente, ento se voc chegou at aqui ento
voc superou uma ladeira difcil, e nos captulos seguintes prometo que vamos terminar com o
encanamento, e realmente comear a programar coisas que sejam novas e saudveis.

http://www.buildyourownlisp.com/contents

43

Referncia
error_handling.c

Metas bnus

Rode o cdigo do captulo anterior com o gdb e faa-o falhar. Veja o que acontece.
Como voc d um nome a um enum?
O que so tipos de dados union e como eles funcionam?
Quais as vantagens de usar um union em vez de um struct?
possvel usar um union na definio de lval?
Amplie anlise sinttica e avaliao para suportar o operador resto da diviso %.
Amplie anlise sinttica e avaliao para suportar tipos decimais usando um campo
double.

S-Expressions (expresses simblicas) Captulo 9


Listas e Lisps
Lisps so famosos por ter pouca distino entre dados e cdigo. Elas usam as mesmas
estruturas para representar ambos. Isso as permite fazerem muitas coisas poderosas que outras
linguagens no podem fazer. Se queremos este poder para nossa linguagem de programao
teremos que separar o processo de ler entrada, e avaliar a entrada que armazenamos.
O resultado final deste captulo diferir apenas um pouco em termos de comportamento do captulo
anterior. Isto porque vamos gastar algum tempo mudando como as coisas funcionam
internamente. Isto chamado refatorar e far nossa vida muito mais fcil mais adiante. Como no
preparo uma refeio, s porque estamos colocando comida em pratos no significa que estamos
perdendo tempo. s vezes a antecipao ainda melhor que comer!
Para armazenar o programa teremos que criar uma estrutura interna de lista que construda
recursivamente com nmeros, smbolos e outras listas. Em Lisp, esta estrutura comumente
chamada de S-Expression significando Symbolic Expression (expresso simblica). Ns
ampliaremos nossa estrutura lval para ser capaz de represent-la. O comportamento de avaliao
das S-Expressions o comportamento tpico de Lisps, que j estamos acostumados at aqui. Para
avaliar uma S-Expression ns olhamos ao primeiro item na lista, e assumimos que o operador. A
seguir olhamos para todos os outros itens na lista, e tomamo-los como operandos para obter o
resultado.

http://www.buildyourownlisp.com/contents

44

Introduzindo S-Expressions vamos finalmente entrar no mundo de Lisp.

Apontadores
Em C, nenhum conceito de listas pode ser explorado sem lidar devidamente com apontadores.
Apontadores so um aspecto de C famosamente mal-compreendido. Eles so difceis de ensinar
porque enquanto so conceitualmente muito simples, eles vm com toda uma nova terminologia, e
muitas vezes no tem um caso de uso claro. Isto faz eles parecerem muito mais monstruosos do que
realmente so. Felizmente para ns, temos alguns casos de uso ideais, e ambos so extremamente
tpicos em C, e provavelmente sero como voc vai acabar usando apontadores 90% das vezes.
O motivo porque precisamos apontadores em C tem a ver com como a chamada de funes
funciona. Quando voc chama uma funo em C, os argumentos so passados por valor. Isto
significa que uma cpia deles passada para a funo que voc chama. Isto verdade para int,
long, char, e tipos struct definidos pelo usurios como o lval. A maioria das vezes isto
timo, mas ocasionalmente isto causa alguns problemas.
Um problema comum ocorre quando temos um grande struct contendo muitos outros sub structs
que desejamos passar por a. Cada vez que chamamos uma funo precisamos criar uma outra cpia
dele. De repente a quantidade de dados que precisa ser copiada pra todo lado s pra chamar uma
funo pode se tornar enorme.
Um segundo problema este: Quando definimos um struct, ele sempre de um tamanho fixo.
Ele tem um tamanho limitado de campos, e cada um destes campos precisa ser um struct que ele
prprio limitado em tamanho. Se quero chamar uma funo apenas com uma lista de coisas, onde o
nmero de coisas varia de chamada para chamada, claramente no posso usar um struct para
fazer isso.
Para contornar estes problemas os desenvolvedores de C (ou, voc sabe... algum) apareceu com
uma ideia inteligente. Eles imaginaram a memria do computador como uma nica lista de bytes.
Nesta lista, cada byte pode ser dado o seu prprio ndice global, ou posio. Mais ou menos como o
nmero de uma casa. O primeiro byte numerado 0, o segundo 1, etc.
Nesse caso, todos os dados no computador, incluindo os structs e as variveis usadas no programa
rodando atualmente, comeam em algum ndice nesta lista enorme. Se, em vez de copiar o dado
para a funo, ns copiemos um nmero representando o ndice onde este dados comea, a funo
sendo chamada pode buscar qualquer quantidade de dados que ela quiser.
Usando endereos em vez de dados de verdade, permitimos uma funo acessar e modificar alguma
localizao em memria sem ter que copiar qualquer dado. Funes podem tambm usar
apontadores para fazer outras coisas, como jogar dados para algum endereo fornecido como
entrada.
Como o tamanho total da memria do computador fixo, o nmero de bytes necessrio para

http://www.buildyourownlisp.com/contents

45

representar um endereo sempre o mesmo. Mas se mantermos controle sobre ele, o nmero de
bytes que o endereo aponta pode crescer ou encolher. Isto significa que podemos criar uma
estrutura de dados com tamanho variado e ainda pass-la para uma funo, que pode inspecion-la
e modific-la.
Ento um apontador apenas um nmero. Um nmero representando o ndice inicial de algum dado
na memria. O tipo do apontador indica a ns e ao compilador, qual tipo de dado pode ser acessado
nesta localizao.
Podemos declarar tipos de apontadores sufixando os existentes com o caractere *. J vimos alguns
exemplos disso com mpc_parser_t*, mpc_ast_t*, e char*.
Para criar um apontador para algum dado, precisamos pegar o seu ndice, ou endereo. Para obter o
endereo de algum dado usamos o operador endereo de: &. Novamente voc j viu isso antes
quando passamos um apontador para mpc_parse para que ela jogasse a sada no nosso
mpc_result_t.
Finalmente, para obter o dado de um endereo (operao chamada de de-referenciar), usamos o
operador * no lado esquerdo da varivel. Para obter o dado no campo de um apontador para uma
estrutura, usamos a flecha ->. Isto voc viu no captulo 7.

O Stack & O Heap


Eu disse que a memria pode ser visualizada como uma longa lista de bytes. Na verdade melhor
imagin-la como dividida em duas sees. Estas sees so chamadas o stack (ou, a pilha) e o heap
(traduo a grosso modo, o monte).
Alguns de vocs pode ter ouvido histrias sobre essas localizaes misteriosas, tais como "a pilha
sempre cresce para baixo mas o heap cresce para cima", ou "podem haver muitas pilhas/stacks,
mas apenas um heap". Este tipo de coisa no importa muito. Lidar com a pilha e o heap em C pode
ser complexo, mas no precisa ser um mistrio. Em essncia, eles so apenas duas sees da
memria usadas para duas tarefas diferentes.

O Stack, ou a pilha
O Stack a memria onde seu programa vive. onde todas suas variveis temporrias e
estruturas de dados existem enquanto voc as manipula e as altera. Cada vez que voc chama
uma funo, uma nova rea do stack separada para ela usar. Dentro desta rea so colocadas
variveis locais, cpias de quaisquer argumentos passados funo, assim como alguns dados
contabilizando coisas como quem chamou a funo, e o que fazer quando terminar. Quando a
funo terminar, a rea que usou desalocada, pronta para ser usada novamente por outra
coisa.
Gosto de pensar na pilha como o local de uma construo. Cada vez que precisamos fazer algo

http://www.buildyourownlisp.com/contents

46

novo, reservamos uma pequena seo do espao o suficiente para ferramentas e materiais, e
trabalhamos nela. Podemos ir a outras partes do lugar, ou at mesmo sair do lugar, se precisarmos
de certas coisas, mas todo nosso trabalho feito nessa seo reservada. Tendo terminado a tarefa,
levamos o que construmos para um novo lugar e limpamos aquela seo do espao que usamos
para faz-lo.

O Heap
O Heap (ou, monte) uma seo de memria reservada para armazenar objetos com vida til
mais longa. A memria nesta rea precisa ser manualmente alocada e desalocada. Para alocar
nova memria, utiliza-se a funo malloc. Esta funo recebe como entrada o nmero de
bytes desejados, e retorna um apontador para o novo bloco de memria com aquela quantidade
de bytes reservada.
Ao terminar de usar a memria nesse espao, ela deve ser liberada novamente. Para fazer isso, o
apontador recebido da malloc deve ser passado para a funo free.
Usar o Heap mais complicadinho que o Stack, porque precisa que o programador lembre de
chamar free e de cham-la corretamente. Caso contrrio, o programa pode continuamente alocar
mais e mais memria. Isto se chama um vazamento de memria, ou memory leak. Uma regra fcil
para evitar isso se certificar que para cada malloc haja um (e apenas um) free. Se isto puder
ser garantido, o programa deve estar tratando o Heap corretamente.
Eu imagino o Heap como um grande servio de self-storage ou auto armazenamento. Chamamos a
recepo com malloc e pedimos um certo nmero de caixas. Com estas caixas podemos fazer o
que quiser, e sabemos que elas vo persistir no importa quo bagunado o local da construo
fique. Podemos trazer ou levar coisas entre o self-storage e o local da construo. til armazenar
materiais e objetos grandes que s precisamos usar de vez em quando. O nico problema que
temos que lembrar de chamar a recepo do self-storage novamente com free quando acabarmos.
Seno teremos requisitado todas as caixas, no teremos mais espao e pagaremos uma conta alta.

Parsing Expressions
Como estamos agora pensando em S-Expressions (expresses simblicas), e no mais em notao
polonesa, precisamos atualizar nosso parser (analisador sinttico). A sintaxe para S-Expressions
simples. apenas um certo nmero de outras expresses entre parnteses, onde uma expresso pode
ser um nmero, operador, ou outra S-Expression. Podemos modificar nossas regras de parsing
existentes para refletir isso. Tambm vamos renomear nossa regra operator para symbol.
Faremos isso antecipando a adio de mais operadores, variveis e funo mais tarde.
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*

Number
Symbol
Sexpr
Expr

=
=
=
=

mpc_new("number");
mpc_new("symbol");
mpc_new("sexpr");
mpc_new("expr");

http://www.buildyourownlisp.com/contents
mpc_parser_t* Lispy

47

= mpc_new("lispy");

mpca_lang(MPCA_LANG_DEFAULT,
"
\
number : /-?[0-9]+/ ;
\
symbol : '+' | '-' | '*' | '/' ;
\
sexpr : '(' <expr>* ')' ;
\
expr
: <number> | <symbol> | <sexpr> ; \
lispy : /^/ <expr>* /$/ ;
\
",
Number, Symbol, Sexpr, Expr, Lispy);

Devemos tambm lembrar de limpar/liberar essas regras ao sair.


mpc_cleanup(5, Number, Symbol, Sexpr, Expr, Lispy);

Estrutura da Expresso
Precisamos duma maneira de armazenar S-Expressions como lval. Isto significa que vamos
tambm precisar armazenar Symbols e Numbers. Vamos adicionar dois novos tipos ao enum. O
primeiro LVAL_SYM, que vamos usar para representar operadores como +. O segundo novo tipo
LVAL_SEXPR, que vamos usar para representar S-Expressions.
enum { LVAL_ERR, LVAL_NUM, LVAL_SYM, LVAL_SEXPR };

S-Expressions so listas de tamanho varivel de outros valores. Como aprendemos no comeo deste
captulo, no podemos criar structs de tamanho varivel, ento vamos precisar usar um apontador.
Vamos criar um campo apontador cell que aponta para um local onde armazenamos uma lista de
lval*. Mais especificamente, apontadores para os outros lval individuais. Nosso campo deve
portanto ser um tipo apontador duplo lval**. Um apontador para apontadores de lval.
Tambm vamos precisar controlar quantos lval* esto nessa lista, ento adicionamos um campo
extra count para gravar isso.
Para representar smbolos vamos usar uma string. Tambm vamos mudar a representao dos erros
para uma string. Isto significa que podemos armazenar uma mensagem de erro nica em vez de um
cdigo de erro. Isto far a reportagem de erros melhor e mais flexvel, e podemos nos livrar do
enum de erros original. Nosso lval atualizado fica assim:
typedef struct lval {
int type;
long num;
/* Tipos Error e Symbol tem alguns dados string */
char* err;
char* sym;
/* Contagem e Apontador para uma lista de "lval*" */
int count;
struct lval** cell;
} lval;

http://www.buildyourownlisp.com/contents

48

Existem apontadores para apontadores para apontadores?


Existe uma velha piada de programao que diz que voc avalia programadores C pelo nmero de
estrelas (asteriscos) nos apontadores deles.
Programadores iniciantes podem apenas usar char* e de vez em quando algum int*, ento so
chamados programadores uma estrela. A maioria dos programadores intermedirios contm algum
apontador duplo como lval**. Estes programadores so portando chamados programadores duas
estrelas. Encontrar um apontador triplo uma coisa especial. Voc deve estar vendo o trabalho de
algum imponente e terrvel, escrevendo cdigo que no foi feito para ser lido por olhos mortais.
Por isso, ser chamado um programador trs estrelas raramente um elogio.
Que eu saiba, um apontador qudruplo nunca foi visto no mundo afora.
O que a palavra-chave struct faz ali?
Nossa definio nova do lval precisa conter uma referncia a si prpria. Isto significa que temos
que modificar um pouco como ela definida. Antes de abrir chaves podemos colocar o nome do
struct e a seguir referenciar isso dentro da definio usando struct lval. Embora um struct
pode se referenciar ao seu prprio tipo, ele precisa conter apenas apontadores para seu prprio tipo,
no o seu prprio tipo diretamente. Caso contrrio, o tamanho do struct se referenciaria a si mesmo,
e cresceria infinitamente em tamanho quando voc tentasse calcul-lo!

Construtores & Destruidores


Podemos mudar nossas funes de construo de lval para retornar apontadores para um lval,
em vez de um lval diretamente. Isto tornar mais fcil o controle das variveis lval. Para isso
precisamos usar malloc com a funo sizeof para alocar espao o suficiente para o struct
lval, e a seguir preencher os campos com as informaes relevantes usando o operador seta ->.
Quando construmos um lval seus campos podem conter apontadores para outras coisas que
foram alocadas no heap. Isto significa que precisamos ser cuidadosos. Sempre que terminamos de
usar um lval tambm precisamos apontar as coisas que ele est apontando no heap. Teremos que
fazer uma regra para ns mesmos. Sempre que liberarmos a memria alocada para um lval,
tambm temos que liberar todas as coisas que ele estiver apontando.
/* Constroi um apontador para um novo lval Number */
lval* lval_num(long x) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_NUM;
v->num = x;
return v;
}
/* Constroi um apontador para um novo lval Error */
lval* lval_err(char* m) {
lval* v = malloc(sizeof(lval));

http://www.buildyourownlisp.com/contents

49

v->type = LVAL_ERR;
v->err = malloc(strlen(m) + 1);
strcpy(v->err, m);
return v;

/* Constroi um novo apontador para um novo lval Symbol */


lval* lval_sym(char* s) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_SYM;
v->sym = malloc(strlen(s) + 1);
strcpy(v->sym, s);
return v;
}
/* Um apontador para um novo lval Sexpr vazio */
lval* lval_sexpr(void) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_SEXPR;
v->count = 0;
v->cell = NULL;
return v;
}

O que NULL?
NULL uma constante especial que aponta para o local da memria 0. Em muitos lugares ele
usado como conveno para significar um dado vazio ou um no-valor. Acima ns o usamos para
especificar que temos um tipo apontador, mas que no est apontando para nenhum dado. Voc ver
o NULL aparecer bastante mais adiante, ento lembre-se de prestar ateno nele.
Por que voc est usando strlen(s) + 1?
Em C, strings so null terminated, isto , terminadas com null. Isto significa que o ltimo caractere
delas sempre o caractere zero \0. Esta uma conveno em C para sinalizar o fim de uma string.
importante que todas as strings sejam armazenadas dessa maneira, seno o programa falha de
maneiras desagradveis.
A funo strlen retorna somente o nmero de bytes numa string excluindo o terminador null. Por
isso precisamos adicionar um, para nos assegurar que existe espao suficiente alocado para tudo!
Precisamos agora de uma funo especial para deletar lval*. Esta deve chamar free no prprio
apontador para liberar a memria adquirida do malloc, mas mais importante, deve inspecionar o
tipo do lval, e liberar qualquer memria apontada pelos seus campos. Se combinarmos cada
chamada com uma das funes de construo com lval_del podemos nos certificar de no ter
vazamentos de memria.
void lval_del(lval* v) {
switch (v->type) {
/* Nao faz nada especial para o tipo numero */
case LVAL_NUM: break;

http://www.buildyourownlisp.com/contents

50

/* Para erro ou simbolo, libera os dados string */


case LVAL_ERR: free(v->err); break;
case LVAL_SYM: free(v->sym); break;

/* Caso Sexpr, entao deleta todos os elementos dentro */


case LVAL_SEXPR:
for (int i = 0; i < v->count; i++) {
lval_del(v->cell[i]);
}
/* Tambem libera a memoria alocada para conter os apontadores */
free(v->cell);
break;

/* Libera a memoria alocada para o proprio struct "lval" */


free(v);

Lendo Expresses
Primeiro vamos ler o programa e construir um lval* que representa todo ele. A seguir, vamos
avaliar este lval* para obter o resultado do nosso programa. Este primeiro estgio deve converter
a rvore de sintaxe abstrata em uma S-Expression, e o segundo estgio deve avaliar esta SExpression usando nossas regras Lisp normais.
Para completar o primeiro estgio podemos olhar recursivamente para cada n da rvore, e construir
tipos lval* diferentes dependendo dos campos tag e contents do n.
Caso o n est etiquetado como number ou symbol, ento usamos nossos construtores para
retornar um lval* diretamente para estes tipos. Caso este n seja o root, ou uma sexpr, ento
criamos um lval S-Expression vazio e pacientemente adicionamos cada sub-expresso vlida na
rvore.
Para adicionar um elemento uma S-Expression podemos criar uma funo lval_add. Esta
funo adiciona um contagem count) da expresso, e a seguir usa realloc para realocar a
quantidade de espao necessria para v->cell. Este novo espao pode ser usado para armazenar o
lval* extra necessrio. Usando este novo espao, ela seta o valor final da lista com v>cell[v->count-1] para o valor lval* x recebido.
Lisps no usam Cons cells?
Outros Lisps tm uma definio um pouco diferente do que uma S-Expression. Na maioria dos
outros Lisps, S-Expressions so definidas como ou um atom como o smbolo de um nmero, ou
dois outras S-Expressions unidas, ou "cons-adas" junto.
Isto naturalmente leva a uma implementao usando listas ligadas, uma estrutura de dados diferente
da que estamos usando. Escolhi representar S-Expressions como um array de tamanho varivel
neste livro com objetivo de simplicidade, mas importante saber que a definio oficial e sua

http://www.buildyourownlisp.com/contents

51

implementao tpico so sutilmente diferentes.


lval* lval_read_num(mpc_ast_t* t) {
errno = 0;
long x = strtol(t->contents, NULL, 10);
return errno != ERANGE ?
lval_num(x) : lval_err("invalid number");
}
lval* lval_read(mpc_ast_t* t) {
/* Caso Symbol ou Number, devolve a conversao para tal tipo */
if (strstr(t->tag, "number")) { return lval_read_num(t); }
if (strstr(t->tag, "symbol")) { return lval_sym(t->contents); }
/* Caso root (>) ou sexpr entao cria lista vazia */
lval* x = NULL;
if (strcmp(t->tag, ">") == 0) { x = lval_sexpr(); }
if (strstr(t->tag, "sexpr")) { x = lval_sexpr(); }
/* Preenche a lista com qualquer expressao valida
for (int i = 0; i < t->children_num; i++) {
if (strcmp(t->children[i]->contents, "(") == 0)
if (strcmp(t->children[i]->contents, ")") == 0)
if (strcmp(t->children[i]->contents, "}") == 0)
if (strcmp(t->children[i]->contents, "{") == 0)
if (strcmp(t->children[i]->tag, "regex") == 0)
x = lval_add(x, lval_read(t->children[i]));
}
}

contida dentro */
{
{
{
{
{

continue;
continue;
continue;
continue;
continue;

}
}
}
}
}

return x;

lval* lval_add(lval* v, lval* x) {


v->count++;
v->cell = realloc(v->cell, sizeof(lval*) * v->count);
v->cell[v->count-1] = x;
return v;
}

Imprimindo Expresses
Estamos agora bem perto de experimentar todas nossas nova mudanas. Precisamos modificar
nossa funo para imprimir tipos S-Expressions. Usando isso podemos verificar se a fase de leitura
est funcionando corretamente, imprimindo as S-Expressions que lermos e verificando que elas
casam com a entrada.
Para imprimir S-Expressions podemos criar uma outra funo que varre todas as sub-expresses de
uma expresso e imprime-as individualmente separadas por espaos, da mesma maneira que so
fornecidas.
void lval_expr_print(lval* v, char open, char close) {
putchar(open);
for (int i = 0; i < v->count; i++) {

http://www.buildyourownlisp.com/contents

52

/* Imprime o valor contido dentro */


lval_print(v->cell[i]);
/* Nao imprime espaco caso ultimo elemento */
if (i != (v->count-1)) {
putchar(' ');
}

}
putchar(close);

void lval_print(lval* v) {
switch (v->type) {
case LVAL_NUM:
printf("%li", v->num); break;
case LVAL_ERR:
printf("Error: %s", v->err); break;
case LVAL_SYM:
printf("%s", v->sym); break;
case LVAL_SEXPR: lval_expr_print(v, '(', ')'); break;
}
}
void lval_println(lval* v) { lval_print(v); putchar('\n'); }

No posso declarar essas funes porque elas chamam uma outra.


A funo lval_expr_print chama a funo lval_print e vice-versa. No h outra maneira
que possamos orden-las no cdigo fonte para resolver esta dependncia. Em vez disso, precisamos
declarar adiantado uma delas, que declarar uma funo sem d-la um corpo. Isso faz que as
outras funes possam cham-la, e permite voc defini-la devidamente mais tarde. Para escrever
uma declarao adiantada, escreva a definio da funo mas em vez do corpo, coloque um ponto e
vrgula ;. Neste exemplo, devemos colocar void lval_print(lval* v); em algum lugar
no cdigo fonte antes de lval_expr_print.
Voc certamente vai topar com isso mais tarde, e nem sempre vou alert-lo antes, ento tente
lembrar de como consertar isso!
Em nosso lao principal, podemos remover a avaliao por enquanto, e em seu lugar, tentar ler o
resultado e imprimir o que temos.
lval* x = lval_read(r.output);
lval_println(x);
lval_del(x);

Caso funcionar, voc ver algo como o que segue, quando digitar alguma entrada ao seu programa.
lispy> + 2 2
(+ 2 2)
lispy> + 2 (* 7 6) (* 2 5)
(+ 2 (* 7 6) (* 2 5))
lispy> *
55
101 (+ 0 0 0)
(* 55 101 (+ 0 0 0))
lispy>

http://www.buildyourownlisp.com/contents

53

Avaliando Expresses
O comportamento da nossa funo de avaliao essencialmente o mesmo de antes. Precisamos
adapt-la para lidar com lval* e nossa definio mais descontrada do que constitui uma
expresso. Podemos pensar de nossa funo de avaliao como um tipo de transformador. Ela
recebe um lval* e transforma-o de alguma forma para um novo lval*. Em alguns casos, ela
pode simplesmente retornar a mesma coisa. Em outros casos, ela pode modificar o lval* da
entrada e retorn-lo. Em muitos casos ela vai deletar a entrada, e devolver algo completamente
novo. Quando formos devolver alguma coisa nova, precisamos sempre lembrar de deletar o lval*
que recebemos como entrada.
Para S-Expressions, primeiro avaliamos todos os filhos da S-Expression. Se qualquer um desses
filhos for um erro, retornamos o primeiro erro que encontrarmos usando uma funo que vamos
definir mais tarde chamada lval_take.
Caso a S-Expression no tenha filhos, retornamo-la diretamente. Isto corresponde expresso
vazia, indicada por (). Tambm checamos por expresses nicas. Estas so expresses com apenas
um filho como (5). Neste caso retornamos a expresso nica contida dentro dos parnteses.
Se nenhum destes for o caso, sabemos que temos uma expresso vlida com mais de um filho.
Neste caso, separamos o primeiro elemento da expresso usando uma funo que definiremos mais
tarde chamada lval_pop. A seguir, checamos se ela for um symbol e no qualquer outra coisa. Se
for um smbolo, checamos qual smbolo , e passamo-lo com seus argumentos para uma funo
builtin_op que faz nossos clculos. Caso o primeiro elemento no for um smbolo, deletamo-lo
junto com os valores passados funo de avaliao, retornando um erro.
Para avaliar todos os outros tipos, apenas os retornamos diretamente.
lval* lval_eval_sexpr(lval* v) {
/* Avalia os Filhos */
for (int i = 0; i < v->count; i++) {
v->cell[i] = lval_eval(v->cell[i]);
}
/* Checa se tem Erros */
for (int i = 0; i < v->count; i++) {
if (v->cell[i]->type == LVAL_ERR) { return lval_take(v, i); }
}
/* Expressao Vazia */
if (v->count == 0) { return v; }
/* Expressao Unica */
if (v->count == 1) { return lval_take(v, 0); }
/* Certifica que primeiro elemento eh um Symbol */
lval* f = lval_pop(v, 0);
if (f->type != LVAL_SYM) {

http://www.buildyourownlisp.com/contents

54

lval_del(f); lval_del(v);
return lval_err("S-expression Does not start with symbol!");

/* Chama builtin com operador */


lval* result = builtin_op(v, f->sym);
lval_del(f);
return result;

lval* lval_eval(lval* v) {
/* Avalia S-Expressions */
if (v->type == LVAL_SEXPR) { return lval_eval_sexpr(v); }
/* Todos outros tipos lval permanecem a mesma coisa */
return v;
}

Usamos duas funes que no esto definidas no cdigo acima. Elas so lval_pop e
lval_take. Estas so funes de propsito geral para manipular tipos lval S-Expression, que
faremos uso aqui e no futuro.
A funo lval_pop extrai um nico elemento de uma S-Expression no ndice i e desloca o resto
da lista para trs, para que ela no mais contenha aquele lval*. A seguir ela retorna o valor
extrado. Note que ela no deleta a lista da entrada. como pegar um elemento duma lista e
remov-lo, deixando o que sobrar. Isso significa que tanto o elemento removido quanto lista velha
precisam ser deletados em algum momento com lval_del.
A funo lval_take similar lval_pop, mas ela delete a lista da qual extraiu o elemento.
Isso como pegar um elemento duma lista e deletar o resto. uma pequena variao de
lval_pop, mas torna nosso cdigo mais fcil de ler em alguns lugares. Diferentemente de
lval_pop, apenas a expresso que voc pega da lista precisa ser deletada com lval_del.
lval* lval_pop(lval* v, int i) {
/* Encontra o item na posicao "i" */
lval* x = v->cell[i];
/* Desloca memoria depois do item na posicao "i" para acima do topo */
memmove(&v->cell[i], &v->cell[i+1],
sizeof(lval*) * (v->count-i-1));
/* Diminui a contagem de itens na lista */
v->count--;
/* Realoca a memoria usada */
v->cell = realloc(v->cell, sizeof(lval*) * v->count);
return x;
}
lval* lval_take(lval* v, int i) {
lval* x = lval_pop(v, i);
lval_del(v);
return x;
}

http://www.buildyourownlisp.com/contents

55

Tambm precisamos definir a funo de avaliao builtin_op. Esta como a funo eval_op
usada em nosso captulo anterior, mas modificada para receber um nico lval* representando
uma lista de todos os argumentos para usar na operao. Ela precisa fazer algumas checagens de
erros mais rigorosas. Se quaisquer das entradas for um lval* no-nmero, precisamos retornar um
erro.
Primeiro, ela checa se todos os argumentos da entrada so nmeros. A seguir, ela extrai o primeiro
argumento para comear. Se no h mais sub-expresses e o operador subtrao, ela executa
negao unria neste primeiro nmero. Isto faz expresses como (- 5) avaliarem corretamente.
Se h mais argumentos, ela segue extraindo o prximo da lista e executando aritmtica dependendo
de qual operador devemos usar. Se um zero encontrado numa diviso, ela deleta os x e y
temporrios, e a lista de argumentos a e devolve um erro.
Se no houve nenhum erro, os argumentos da entrada so deletados e uma nova expresso
devolvida.
lval* builtin_op(lval* a, char* op) {
/* Certifica-se que todos argumentos sejam numeros */
for (int i = 0; i < a->count; i++) {
if (a->cell[i]->type != LVAL_NUM) {
lval_del(a);
return lval_err("Cannot operate on non-number!");
}
}
/* Extrai o primeiro elemento */
lval* x = lval_pop(a, 0);
/* Caso nenhum argumento e sub, executa negacao unaria */
if ((strcmp(op, "-") == 0) && a->count == 0) {
x->num = -x->num;
}
/* Enquanto ainda houver elementos sobrando */
while (a->count > 0) {
/* Extrai o proximo elemento */
lval* y = lval_pop(a, 0);
if
if
if
if

(strcmp(op, "+") == 0) { x->num += y->num; }


(strcmp(op, "-") == 0) { x->num -= y->num; }
(strcmp(op, "*") == 0) { x->num *= y->num; }
(strcmp(op, "/") == 0) {
if (y->num == 0) {
lval_del(x); lval_del(y);
x = lval_err("Division By Zero!"); break;
}
x->num /= y->num;

lval_del(y);
}

http://www.buildyourownlisp.com/contents
lval_del(a); return x;
}

Isso termina nossas funes de avaliao. Precisamos apenas mudar a main novamente para que
passe a entrada por esta avaliao antes de imprimi-la.
lval* x = lval_eval(lval_read(r.output));
lval_println(x);
lval_del(x);

Agora voc deve ser capaz de avaliar expresses corretamente da mesma maneira que no captulo
anterior. Tome um pouco de ar, e brinque um pouco com a nova avaliao. Certifique-se que tudo
est funcionando corretamente, e o comportamento est como o esperado. No prximo captulo
vamos fazer bastante uso destas mudanas para implementar alguns recursos novos legais.
lispy>
39
lispy>
-100
lispy>
()
lispy>
/
lispy>
Error:
lispy>

+ 1 (* 7 5) 3
(- 100)

/
(/ ())
Cannot operate on non-number!

Referncia
s_expressions.c

Metas Bnus

D um exemplo de uma varivel em nosso programa que viva no Stack.


D um exemplo de uma varivel em nosso programa que viva no <Heap.
O que a funo strcpy faz?
O que a funo realloc faz?
O que a funo memmove faz?
Qual a diferena entre memmove e memcpy?
Amplie anlise sinttica e avaliao para suportar o operador resto da diviso %.
Amplie anlise sinttica e avaliao para suportar tipos decimais usando um campo
double.

56

http://www.buildyourownlisp.com/contents

57

Q-Expressions (expresses citadas) Captulo 10


Adicionando Recursos
Voc vai notar que os captulos seguintes seguiro um padro em comum. Este padro a
abordagem tpica usada para adicionar recursos novos a uma linguagem. Consiste em uma
sequncia de passos que levam um novo recurso do comeo ao fim. Estes passos esto listados
abaixo, e so exatamente o que vamos fazer neste captulo para introduzir um novo recurso
chamado uma Q-Expression.
Sintaxe
Representao
Anlise sinttica (parsing)
Semntica

Adiciona nova regra gramtica para o recurso.


Adiciona nova variao de tipo de dado para
representar o recurso.
Adiciona novas funes para ler o recurso da
rvore de sintaxe abstrata (AST).
Adiciona novas funes para avaliar e manipular
o recurso.

Quoted Expressions
Neste captulo vamos implementar um novo tipo de valor Lisp chamado uma Q-Expression.
Isto significa quoted expression (expresso citada), e um tipo de expresso Lisp que no
avaliada pela mecnica padro de Lisp. Quando encontradas pela funo de avaliao, Qexpressions so deixadas exatamente como esto. Isto as torna ideal para determinados propsitos.
Ns podemos us-las para armazenar e manipular outros valores Lisp como nmeros, smbolos, ou
outras S-Expressions mesmo.
Depois que adicionarmos Q-Expressions vamos implementar um conjunto conciso de operadores
para manipul-las. Assim como os operadores aritmticos, estes se mostraro fundamentais em
como pensamos a respeito e como brincamos com expresses.
A sintaxe para Q-Expressions muito similar das S-Expressions. A nica diferena que em vez
de parnteses (), Q-Expressions so envoltas em chaves {}. Podemos adicionar isto nossa
gramtica conforme segue.
Nunca ouvi falar de Q-Expressions.
Q-Expressions no existem em outros Lisps. Outros Lisps usam Macros para parar a avaliao.

http://www.buildyourownlisp.com/contents

58

Estas se parecem como funes normais, mas no avaliam seus argumentos. Existe uma macro
especial chamada quote ' que pode ser usada para parar a avaliao de quase qualquer coisa. Esta
a inspirao para Q-Expressions, que s existem em nosso Lisp, e sero usadas no lugar de Macros
para fazer todas as mesmas coisas e mais.
A maneira que usei S-Expression e Q-Expression neste livro um leve abuso da terminologia, mas
espero que este delito torne mais claro o comportamento do nosso Lisp.
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*

Number
Symbol
Sexpr
Qexpr
Expr
Lispy

=
=
=
=
=
=

mpc_new("number");
mpc_new("symbol");
mpc_new("sexpr");
mpc_new("qexpr");
mpc_new("expr");
mpc_new("lispy");

mpca_lang(MPCA_LANG_DEFAULT,
"
\
number : /-?[0-9]+/ ;
\
symbol : '+' | '-' | '*' | '/' ;
\
sexpr : '(' <expr>* ')' ;
\
qexpr : '{' <expr>* '}' ;
\
expr
: <number> | <symbol> | <sexpr> | <qexpr> ; \
lispy : /^/ <expr>* /$/ ;
\
",
Number, Symbol, Sexpr, Qexpr, Expr, Lispy);

Precisamos lembrar de atualizar nossa funo de limpeza para lidar com a nova regra que
adicionamos.
mpc_cleanup(6, Number, Symbol, Sexpr, Qexpr, Expr, Lispy);

Lendo Q-Expressions
Como Q-Expressions so to similares s S-Expressions, muito do seu comportamento interno ser
o mesmo. Vamos reutilizar nossos campos de dados de S-Expression para representar QExpressions, mas ainda vamos precisar adicionar um tipo separado para a enumerao.
enum { LVAL_ERR, LVAL_NUM, LVAL_SYM, LVAL_SEXPR, LVAL_QEXPR };

Podemos adicionar um construtor para esta variao.


/* Um apontador para um novo lval Qexpr vazio */
lval* lval_qexpr(void) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_QEXPR;
v->count = 0;
v->cell = NULL;
return v;
}

Para imprimir e deletar Q-Expressions, fazemos essencialmente a mesma coisa que com SExpressions. Podemos adicionar as linhas relevantes para nossas funes de imprimir e deletar

http://www.buildyourownlisp.com/contents

59

conforme segue.
void lval_print(lval* v) {
switch (v->type) {
case LVAL_NUM:
printf("%li", v->num); break;
case LVAL_ERR:
printf("Error: %s", v->err); break;
case LVAL_SYM:
printf("%s", v->sym); break;
case LVAL_SEXPR: lval_expr_print(v, '(', ')'); break;
case LVAL_QEXPR: lval_expr_print(v, '{', '}'); break;
}
}
void lval_del(lval* v) {
switch
case
case
case

(v->type)
LVAL_NUM:
LVAL_ERR:
LVAL_SYM:

{
break;
free(v->err); break;
free(v->sym); break;

/* Caso Qexpr ou Sexpr, entao deleta todos elementos dentro */


case LVAL_QEXPR:
case LVAL_SEXPR:
for (int i = 0; i < v->count; i++) {
lval_del(v->cell[i]);
}
/* Tambem libera memoria alocada para conter os apontadores */
free(v->cell);
break;
}
}

free(v);

Usando estas mudanas simples podemos atualizar nossa funo de leitura lval_read para
conseguir ler Q-Expressions. Por que reusamos todos os campos de dados S-Expression para nosso
tipo Q-Expression, podemos reusar todas as funes para S-Expressions como lval_add. Por
isso, para ler Q-Expressions precisamos apenas adicionar um caso especial para construir uma QExpression vazia para lval_read logo abaixo onde detectamos e criamos S-Expressions vazias
da rvore de sintaxe abstrata.
if (strstr(t->tag, "qexpr"))

{ x = lval_qexpr(); }

Como no h maneira especial de avaliar Q-Expressions, no precisamos editar nenhuma das


funes de avaliaes. Nossas Q-Expressions devem estar prontas para experimentar. Compile e
rode o programa. Tente us-las como um novo tipo de dados e verifique que no estejam sendo
avaliadas.
lispy> {1 2 3 4}
{1 2 3 4}
lispy> {1 2 (+ 5 6) 4}
{1 2 (+ 5 6) 4}
lispy> {{2 3 4} {1}}
{{2 3 4} {1}}
lispy>

http://www.buildyourownlisp.com/contents

60

Funes Builtins
Podemos ler Q-Expressions, mas elas ainda so inteis. Precisamos de alguma maneira de
manipul-las.
Para isso podemos definir alguns operadores built-in (isto , que j venham junto com a linguagem)
para trabalhar com nosso tipo lista. Escolher um conjunto conciso de operadores importante. Se
implementarmos algumas operaes fundamentais, podemos us-las para definir novas operaes
sem precisar adicionar cdigo C extra. Existem algumas maneiras de escolher estes operadores
fundamentais, mas escolhi um conjunto que nos permitir fazer tudo que precisamos. Eles so
definidos a seguir.
list
head
(cabea)

tail
(cauda)

join
(junta)

eval

Recebe um ou mais argumentos e devolve uma nova Q-Expression contendo os


argumentos
Recebe uma Q-Expression e devolve uma Q-Expression com apenas o primeiro elemento
Recebe uma Q-Expression e devolve uma Q-Expression com o primeiro elemento
removido
Recebe uma ou mais Q-Expressions e devolve uma Q-Expression delas unidas
Recebe uma Q-Expression e a avalia como se fosse uma S-Expression

Da mesma forma como para nossos operadores matemticos, devemos adicionar estas funes
como smbolos vlidos possveis. Depois, poderemos definir seu comportamento de maneira similar
a builtin_op.
mpca_lang(MPCA_LANG_DEFAULT,
"
\
number : /-?[0-9]+/ ;
\
symbol : \"list\" | \"head\" | \"tail\"
\
| \"join\" | \"eval\" | '+' | '-' | '*' | '/' ; \
sexpr : '(' <expr>* ')' ;
\
qexpr : '{' <expr>* '}' ;
\
expr
: <number> | <symbol> | <sexpr> | <qexpr> ;
\
lispy : /^/ <expr>* /$/ ;
\
",
Number, Symbol, Sexpr, Qexpr, Expr, Lispy)

Primeira tentativa
Nossas funes builtins devem ter a mesma interface que builtin_op. Isto significa que os
argumentos devem ser empacotados em uma S-Expression que a funo deve usar e a seguir deletar.
Elas devem devolver um novo lval* como resultado da avaliao.

http://www.buildyourownlisp.com/contents

61

A funcionalidade de pegar a cabea ou a cauda de uma Q-Expression propriamente dita no deve


ser muito difcil para ns. Podemos usar as funes que j definimos para S-Expressions como
lval_take e lval_pop. Mas como builtin_op, precisamos checar que as entradas que
recebermos sejam vlidas.
Vamos dar uma olhada em head e tail primeiro. Existem algumas condies para as quais estas
funes no podem funcionar. Antes de tudo, precisamos nos assegurar que elas so somente
passadas como argumento nico, e que este argumento uma Q-Expression. Ento precisamos nos
certificar que esta Q-Expression no est vazia e realmente tem alguns elementos.
A funo head pode repetidamente extrair e deletar o item no ndice 1 at que no h sobre mais
nada na lista.
A funo tail ainda mais simples. Ela pode extrair e deletar o item no ndice 0, deixando a
cauda sobrando. Uma tentativa inicial para implementar estas funes pode se parecer com isso:
lval* builtin_head(lval* a) {
/* Checa condicoes de erro */
if (a->count != 1) {
lval_del(a);
return lval_err("Function 'head' passed too many arguments!");
}
if (a->cell[0]->type != LVAL_QEXPR) {
lval_del(a);
return lval_err("Function 'head' passed incorrect types!");
}
if (a->cell[0]->count == 0) {
lval_del(a);
return lval_err("Function 'head' passed {}!");
}
/* Do contrario, obtem o primeiro argumento */
lval* v = lval_take(a, 0);
/* Deleta todos elementos que nao sao cabeca e devolve */
while (v->count > 1) { lval_del(lval_pop(v, 1)); }
return v;
}
lval* builtin_tail(lval* a) {
/* Checa condicoes de erro */
if (a->count != 1) {
lval_del(a);
return lval_err("Function 'tail' passed too many arguments!");
}
if (a->cell[0]->type != LVAL_QEXPR) {
lval_del(a);
return lval_err("Function 'tail' passed incorrect types!");
}
if (a->cell[0]->count == 0) {
lval_del(a);

http://www.buildyourownlisp.com/contents

62

return lval_err("Function 'tail' passed {}!");


}
/* Pega primeiro argumento */
lval* v = lval_take(a, 0);
/* Deleta primeiro elemento e devolve */
lval_del(lval_pop(v, 0));
return v;
}

Macros
Estas funes head e tail fazem a coisa certa, mas o cdigo bem obscuro, e longo. H
tanta checagem de erro que a funcionalidade em si difcil de ver. Um mtodo que podemos
usar para limp-la usar uma Macro.
Macros so comandos para o pr-processador para criarem coisas-que-parecem-funes que so
avaliadas antes do programa ser compilado. Pode ser usados para muitas coisas diferentes, uma
delas o que precisamos fazer aqui, cdigo de limpeza.
Macros funcionam recebendo alguns argumentos (que podem ser quase que qualquer coisa), e
copiam e colam estes para um determinado padro. Mudando o padro ou os argumentos, podemos
alterar o cdigo gerado pela macro. Para definir macros, usamos a diretiva #define do prprocessador. Depois sito, escrevemos o nome da macro, seguido dos nomes dos argumentos entre
parnteses. Depois disto, o padro especificado, para declarar qual cdigo deve ser gerado para os
dados argumentos.
Podemos projetar uma macro para ajudar com nossas condies de erro chamada LASSERT.
Macros so tipicamente nomes dados em maisculas para ajudar a distingui-las de funes C
normais. Esta macro recebe trs argumentos args, cond, e err. Ela ento gera cdigo como
mostrado no lado direito, mas como estas variveis coladas nos lugares onde esto nomeadas. Este
padro bem adequado para todas nossas condies de erro.
#define LASSERT(args, cond, err) \
if (!(cond)) { lval_del(args); return lval_err(err); }

Podemos usar isto para mudar como nossas funes acima so escritas, sem realmente mudar o
cdigo gerado pelo compilador. Isto torna mais fcil de ler para o programador, e economiza um
pouco de digitao. O restante das condies de erro para nossas funes devem se tornar mais
fceis de escrever tambm!

Head & Tail


Usando isto nossas funes head e tail so definidas como segue. Note quo mais clara sua
funcionalidade real.

http://www.buildyourownlisp.com/contents

63

lval* builtin_head(lval* a) {
LASSERT(a, a->count == 1,
"Function 'head' passed too many arguments!");
LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
"Function 'head' passed incorrect type!");
LASSERT(a, a->cell[0]->count != 0,
"Function 'head' passed {}!");
lval* v = lval_take(a, 0);
while (v->count > 1) { lval_del(lval_pop(v, 1)); }
return v;
}
lval* builtin_tail(lval* a) {
LASSERT(a, a->count == 1,
"Function 'tail' passed too many arguments!");
LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
"Function 'tail' passed incorrect type!");
LASSERT(a, a->cell[0]->count != 0,
"Function 'tail' passed {}!");

lval* v = lval_take(a, 0);


lval_del(lval_pop(v, 0));
return v;

List & Eval


A funo list simples. Ela apenas converte a S-Expression da entrada em uma Q-Expression e a
devolve.
A funo eval similar, ao contrrio. Ela recebe como entrada uma nica Q-Expression, que
converte para uma S-Expression, e a avalia usando lval_eval.
lval* builtin_list(lval* a) {
a->type = LVAL_QEXPR;
return a;
}
lval* builtin_eval(lval* a) {
LASSERT(a, a->count == 1,
"Function 'eval' passed too many arguments!");
LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
"Function 'eval' passed incorrect type!");

lval* x = lval_take(a, 0);


x->type = LVAL_SEXPR;
return lval_eval(x);

Join
A funo join nossa ltima funo a definir.
Diferente das outras, ela pode receber mltiplos argumentos, ento sua estrutura se parece um
pouco como a da builtin_op. Primeiro verificamos que todos os argumentos so Q-Expressions

http://www.buildyourownlisp.com/contents

64

e ento s unimos uma por uma. Para fazer isso, usamos a funo lval_join. Isto funciona
repetidamente extraindo cada item do segundo e adicionando-o ao primeiro at que o segundo esteja
vazio. Ento deletamos o segundo e devolvemos o primeiro.
lval* builtin_join(lval* a) {
for (int i = 0; i < a->count; i++) {
LASSERT(a, a->cell[i]->type == LVAL_QEXPR,
"Function 'join' passed incorrect type.");
}
lval* x = lval_pop(a, 0);
while (a->count) {
x = lval_join(x, lval_pop(a, 0));
}

lval_del(a);
return x;

lval* lval_join(lval* x, lval* y) {


/* Para cada celula em 'y', adiciona-a a 'x' */
while (y->count) {
x = lval_add(x, lval_pop(y, 0));
}

/* Deleta o 'y' vazio e devolve 'x' */


lval_del(y);
return x;

Busca de Builtins
Temos agora todas nossas funes builtin definidas. Precisamos fazer uma funo que possa chamar
a builtin correta dependendo do smbolo que ela encontrar na avaliao. Podemos fazer isso usando
strcmp e strstr.
lval* builtin(lval* a, char* func) {
if (strcmp("list", func) == 0) { return builtin_list(a); }
if (strcmp("head", func) == 0) { return builtin_head(a); }
if (strcmp("tail", func) == 0) { return builtin_tail(a); }
if (strcmp("join", func) == 0) { return builtin_join(a); }
if (strcmp("eval", func) == 0) { return builtin_eval(a); }
if (strstr("+-/*", func)) { return builtin_op(a, func); }
lval_del(a);
return lval_err("Unknown Function!");
}

A seguir, podemos mudar nossa linha de avaliao em lval_eval_sexpr para chamar


builtin em vez de builtin_op.

http://www.buildyourownlisp.com/contents

65

/* Chama builtin com operador */


lval* result = builtin(v, f->sym);
lval_del(f);
return result;

Finalmente Q-Expressions devem estar completamente suportadas em nossa linguagem. Compile e


rode a ltima verso e veja o que voc pode fazer com os novos operadores de lista. Experimente
colocar cdigo e smbolo dentro das nossas listas e avali-las em diferentes maneiras. A capacidade
de colocar S-Expressions dentro de uma lista usando Q-Expressions muito legal. Significa que
podemos tratar cdigo como dados mesmo. Isto emblemtico dos Lisps, e algo que no pode ser
feito realmente em linguagens como C!
lispy> list 1 2 3 4
{1 2 3 4}
lispy> {head (list 1 2 3 4)}
{head (list 1 2 3 4)}
lispy> eval {head (list 1 2 3 4)}
{1}
lispy> tail {tail tail tail}
{tail tail}
lispy> eval (tail {tail tail {5 6 7}})
{6 7}
lispy> eval (head {(+ 1 2) (+ 10 20)})
3

Referncia
q_expressions.c

Metas bnus
Quais so os quatro passos tpicos para adicionar novos recursos uma linguagem?
Crie uma Macro especificamente para testar nmero incorreto de argumentos.
Crie uma Macro especificamente para testar ser chamado com uma lista vazia.
Adicione uma funo builtin cons que receba um valor e uma Q-Expression e o adicioneo ao comeo.
Adicione uma funo builtin len que devolva o nmero de elementos em uma QExpression.
Adicione uma funo builtin init que devolva tudo de uma Q-Expression exceto o ltimo
elemento.

http://www.buildyourownlisp.com/contents

66

Variveis Captulo 11
Imutabilidade
Nos captulos anteriores fizemos progresso considervel na infraestrutura da nossa linguagem.
J podemos fazer algumas coisas legais que outras linguagens no fazem, como colocar cdigo
dentro de listas. Agora a hora de comear a adicionar os recursos que tornaro nossa linguagem
prtica. O primeiro deles vai ser variveis.
Elas so chamadas variveis, mas um nome enganoso, porque nossas variveis no iro variar.
Nossas variveis so imutveis, isto , elas no podem mudar. Tudo em nossa linguagem at agora
agiu como se fosse imutvel. Quando avaliamos uma expresso, imaginamos que a coisa anterior
foi deletada e uma coisa nova foi devolvida. Na implementao, frequentemente mais fcil
reutilizarmos os dados da coisa anterior para construir a prxima coisa, mas conceitualmente uma
boa maneira de pensar a respeito de como nossa linguagem funciona.
Ento, na verdade nossas variveis so meramente uma maneira de nomear valores. Elas nos
permitem atribuir um nome para um valor, e ento nos deixa obter uma cpia daquele valor mais
tarde quando precisarmos.
Para permitir nomear valores, precisamos criar uma estrutura que armazene o nome e valor de tudo
que for nomeado em nosso programa. Chamamos isto de ambiente. Quando comeamos um novo
prompt interativo, queremos criar um novo ambiente para funcionar com ele, em que cada novo
trecho da entrada avaliado. A seguir podemos armazenar e relembrar variveis enquanto
programamos.
O que acontece se reatribuirmos um nome a algo novo? Isto no o mesmo que
mutabilidade?
No nosso Lisp, quando reatribumos um nome vamos deletar a associao anterior e criar uma nova.
Isto d a iluso que a coisa atribuda quele nome mudou e mutvel, mas de fato deletamos a coisa
velha e atribumos uma coisa nova. Isto diferente de C onde podemos realmente mudar o dado
apontado por um apontador ou armazenado em uma struct, sem delet-lo e criar um novo.

Sintaxe de Smbolo
Agora que vamos permitir nosso usurio definir variveis, precisamos que a gramtica para
smbolos seja mais flexvel. Em vez de apenas nossas funes builtin, ela deve casar com qualquer
smbolo possvel vlido. Diferentemente de C, onde o nome que pode ser dado a uma varivel
relativamente restringido, vamos permitir vrios tipos de caracteres no nome de uma varivel.

http://www.buildyourownlisp.com/contents

67

Podemos criar uma expresso regular que expressa a faixa de caracteres disponvel como segue.
/[a-zA-Z0-9_+\\-*\\/\\\\=<>!&]+/

primeira vista, parece que simplesmente batemos com nossas mos no teclado. Na verdade,
uma expresso regular usando um especificador de intervalo []. Dentro do especificador de
intervalo caracteres especiais perdem seu sentido especial, mas alguns destes caracteres ainda
precisam ser escapados com barras invertidas /. Como isto uma parte duma string C, precisamos
colocar duas barras invertidas para representar um nico caractere barra invertida na entrada.
Esta regra permite smbolos terem qualquer um entre os caracteres normais de identificadores C azA-Z0-9_, os caracteres de operao aritmtica +\\-*\\/, o caractere barra invertida \\\\, os
caracteres do operador de comparao =<>! ou um ampersand &. Isto nos dar toda a flexibilidade
que precisamos para definir smbolos novos e existentes.
mpca_lang(MPCA_LANG_DEFAULT,
"
number : /-?[0-9]+/ ;
symbol : /[a-zA-Z0-9_+\\-*\\/\\\\=<>!&]+/ ;
sexpr : '(' <expr>* ')' ;
qexpr : '{' <expr>* '}' ;
expr
: <number> | <symbol> | <sexpr> | <qexpr> ;
lispy : /^/ <expr>* /$/ ;
",
Number, Symbol, Sexpr, Qexpr, Expr, Lispy);

\
\
\
\
\
\
\

Apontador para funo


Tendo introduzido variveis, smbolos no mais representaro funes em nossa linguagem, mas
um nome para buscarmos em nosso ambiente e obter um valor de volta.
Portanto, precisamos um novo valor para representar funes em nossa linguagem, que possamos
devolver quando um dos nossos smbolos builtins seja encontrado. Para criar este novo tipo de
lval vamos usar algo chamado um apontador para funo (ou, ponteiro para funo).
Apontadores para funes so um grande recurso de C que permite armazenar e repassar
apontadores para funes. No faz sentido alterar o dado apontado por estes apontadores. Em vez
disso, usamo-los para chamar a funo para a qual apontam, como se fossem uma funo normal.
Como apontadores normais, apontadores para funes tm algum tipo associado eles. Este tipo
especifica o tipo da funo sendo apontada, no o tipo de dado que ele aponta. Isto permite o
compilador identificar se foi chamado corretamente.
No captulo anterior, nossas funes builtins receberam um lval* como entrada e devolveram um
lval* como sada. Neste captulo nossas funes builtins recebero um apontador extra para o
ambiente lenv* como entrada. Podemos declarar a nova funo um novo tipo apontador de funo
chamado lbuiltin, para este tipo de funo, da seguinte forma:

http://www.buildyourownlisp.com/contents

68

typedef lval*(*lbuiltin)(lenv*, lval*);

Por que esta sintaxe to estranha?


Algumas vezes a sintaxe de C pode parecer particularmente estranha. Pode ajudar se entendermos
exatamente por que a sintaxe dessa forma. Vamos desconstruir a sintaxe no exemplo acima parte
por parte.
Primeiro o typedef. Ele pode ser colocado antes de qualquer declarao de varivel normal. Ele
resulta no nome da varivel, sendo declarado como um novo tipo, correspondendo ao que seria o
tipo inferido para aquela varivel. Por isso, na declarao acima, o que parece um nome de funo
se torna o nome do novo tipo.
A seguir, todos aqueles *. Tipos apontadores em C foram concebidos para serem escritos com o
asterisco * ao lado esquerdo do nome da varivel, no ao lado direito do tipo int *x;. Isto
porque a sintaxe de tipos de C funciona com um esquema de inferncia. Em vez de ler "Crie um
novo apontador int apontador x", feita para ler "Crie uma nova varivel x onde dereferenciar x
resulta em um int". Portanto, x inferido como sendo um apontador para int.
Esta ideia expandida em apontadores para funo. Podemos ler a declarao acima como: "Para
obter um lval*, dereferenciamos lbuiltin e a chamamos com um lenv* e um lval*."
Portanto, lbuiltin precisa ser um apontador para funo que recebe um lenv* e um lval* e
devolve um lval*.

Tipos Cclicos
O tipo lbuiltin referencia o tipo lval e o tipo lenv. Isto significa que estes devem ser
declarados antes no cdigo fonte.
Mas queremos fazer um campo lbuiltin em nossa struct lval para que possamos criar valores
funes. Ento, nossa declarao lbuiltin deve ir antes da nossa declarao lval. Isto leva ao
que chamamos uma dependncia cclica de tipos, onde dois tipos dependem um do outro.
Nos deparamos com este problema antes com funes que dependem uma da outra. A soluo foi
criar uma declarao adiantada que declarava uma funo mas deixava o corpo dela vazio.
Em C podemos fazer exatamente a mesma coisa com tipos. Primeiro, declaramos os dois tipos
struct sem um corpo. Segundo, definimos com typedef os nomes lval e lenv. Em seguida
podemos definir nosso tipo apontador para funo lbuiltin, e finalmente podemos definir o
corpo da nossa estrutura lval. Agora todos nossos problemas com tipos esto resolvidos e o
compilador no vai reclamar mais.
/* Declaracoes Adiantadas */
struct lval;

http://www.buildyourownlisp.com/contents

69

struct lenv;
typedef struct lval lval;
typedef struct lenv lenv;
/* Valor Lisp */
enum { LVAL_ERR, LVAL_NUM,
LVAL_SYM,
LVAL_FUN, LVAL_SEXPR, LVAL_QEXPR };
typedef lval*(*lbuiltin)(lenv*, lval*);
struct lval {
int type;
long num;
char* err;
char* sym;
lbuiltin fun;
int count;
lval** cell;
};

Tipo Funo
Como adicionamos um novo tipo possvel lval com a enumerao LVAL_FUN. Devemos
atualizar todas nossas funes relevantes que funcionam em lvals para lidar corretamente com
esta atualizao. Na maioria dos casos isto significa apenas inserir novos casos em comandos
switch.
Podemos comear fazendo uma nova funo construtora para este tipo.
lval* lval_fun(lbuiltin func) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_FUN;
v->fun = func;
return v;
}

Na deleo no precisamos fazer nada especial para apontadores de funo.


case LVAL_FUN: break;

Na impresso podemos simplesmente imprimir uma string nominal.


case LVAL_FUN:

printf("<function>"); break;

Tambm vamos adicionar uma nova funo para copiar um lval. Isto vai ser til quando
colocarmos e tirarmos coisas dentro do ambiente. Para nmeros e funes podemos simplesmente
copiar diretamente os campos relevantes. Para strings, precisamos copiar usando malloc e
strcpy. Para copiar listas, precisamos alocar a quantidade correta de espao e a seguir copiar cada
elemento individualmente.

http://www.buildyourownlisp.com/contents

70

lval* lval_copy(lval* v) {
lval* x = malloc(sizeof(lval));
x->type = v->type;
switch (v->type) {
/* Copia funcoes e numeros diretamente */
case LVAL_FUN: x->fun = v->fun; break;
case LVAL_NUM: x->num = v->num; break;
/* Copia strings usando malloc e strcpy */
case LVAL_ERR:
x->err = malloc(strlen(v->err) + 1);
strcpy(x->err, v->err); break;
case LVAL_SYM:
x->sym = malloc(strlen(v->sym) + 1);
strcpy(x->sym, v->sym); break;
/* Copia listas copiando cada sub-expressao */
case LVAL_SEXPR:
case LVAL_QEXPR:
x->count = v->count;
x->cell = malloc(sizeof(lval*) * x->count);
for (int i = 0; i < x->count; i++) {
x->cell[i] = lval_copy(v->cell[i]);
}
break;
}
}

return x;

Ambiente
Nossa estrutura de ambiente precisa codificar uma lista de relacionamentos entre nomes e valores.
Existem muitas maneiras de construir uma estrutura que pode fazer este tipo de coisa. Vamos usar o
mtodo mais simples possvel que funciona bem. Isto , vamos usar duas listas do mesmo tamanho.
Uma uma lista de lval*, e a outra uma lista de char*. Cada entrada em uma lista tem um
valor correspondente na outra lista na mesma posio.
J declaramos adiantado nossa estrutura lenv, ento agora podemos defini-la como segue.
struct lenv {
int count;
char** syms;
lval** vals;
};

Precisamos de algumas funes para criar e deletar esta estrutura. Elas so bem simples. A criao
inicializa os campos do struct, enquanto a deleo itera pelos itens em ambas listas e deleta ou
libera eles.

http://www.buildyourownlisp.com/contents

71

lenv* lenv_new(void) {
lenv* e = malloc(sizeof(lenv));
e->count = 0;
e->syms = NULL;
e->vals = NULL;
return e;
}
void lenv_del(lenv* e) {
for (int i = 0; i < e->count; i++) {
free(e->syms[i]);
lval_del(e->vals[i]);
}
free(e->syms);
free(e->vals);
free(e);
}

A seguir podemos criar duas funes que ou obtm valores do ambiente ou colocam valores nele.
Para obter um valor do ambiente, percorremos todos os itens no ambiente e checamos se o smbolo
dado casa qualquer uma das strings armazenadas. Se achamos alguma que corresponde, podemos
devolver uma cpia do valor armazenado. Se nenhum valor encontrado, devemos retornar um
erro.
A funo para colocar novas variveis no ambiente um pouco mais complexa. Primeiro queremos
checar se uma varivel com o mesmo nome j existe. Se este for o caso, devemos substitu-la com a
nova. Para fazer isso, iteramos todas as variveis existentes no ambiente e checamos seus nomes. Se
achamos uma correspondncia, deletamos o valor armazenado naquela localizao e armazenamos
l uma cpia do valor da entrada.
Se nenhum valor existente foi encontrado com esse nome, precisamos alocar mais espao para
coloc-lo. Para isso, podemos usar realloc e armazenar uma cpia do lval e seu nome nas
localizaes recm alocadas.
lval* lenv_get(lenv* e, lval* k) {
/* Itera por todos os items no ambiente */
for (int i = 0; i < e->count; i++) {
/* Verifica que a string armazenada casa com a string do simbolo */
/* Se casar, retorna uma copia do valor do mesmo */
if (strcmp(e->syms[i], k->sym) == 0) {
return lval_copy(e->vals[i]);
}
}
/* Caso nenhum simbolo for encontrado, retorna erro */
return lval_err("unbound symbol!");
}
void lenv_put(lenv* e, lval* k, lval* v) {
/* Itera por todos os items no ambiente */
/* Isto eh para ver se a variavel ja existe*/
for (int i = 0; i < e->count; i++) {

http://www.buildyourownlisp.com/contents

72

/* Se a variavel for encontrada, deleta o item naquela posicao */


/* E substitui com a variavel fornecida pelo usuario */
if (strcmp(e->syms[i], k->sym) == 0) {
lval_del(e->vals[i]);
e->vals[i] = lval_copy(v);
return;
}
}
/* Se nenhum registro for encontrado, aloca espaco para um novo */
e->count++;
e->vals = realloc(e->vals, sizeof(lval*) * e->count);
e->syms = realloc(e->syms, sizeof(char*) * e->count);

/* Copia o conteudo do lval e a string do simbolo para o novo local */


e->vals[e->count-1] = lval_copy(v);
e->syms[e->count-1] = malloc(strlen(k->sym)+1);
strcpy(e->syms[e->count-1], k->sym);

Avaliao de Variveis
Nossa funo de avaliao agora depende de algum ambiente. Devemos passar este como um
argumento e us-lo para obter um valor se encontramos um tipo smbolo. Como nosso ambiente
devolve uma cpia do valor, precisamos lembrar de deletar o smbolo de entrada lval.
lval* lval_eval(lenv* e, lval* v) {
if (v->type == LVAL_SYM) {
lval* x = lenv_get(e, v);
lval_del(v);
return x;
}
if (v->type == LVAL_SEXPR) { return lval_eval_sexpr(e, v); }
return v;
}

Como adicionamos um tipo funo, nossa avaliao de S-Expressions tambm precisa mudar. Em
vez de checar por um tipo smbolo, queremos nos certificar que um tipo funo. Se esta condio
for verdadeira, podemos chamar o campo fun do lval usando a mesma notao que as chamadas
de funes normais.
lval* lval_eval_sexpr(lenv* e, lval* v) {
for (int i = 0; i < v->count; i++) {
v->cell[i] = lval_eval(e, v->cell[i]);
}
for (int i = 0; i < v->count; i++) {
if (v->cell[i]->type == LVAL_ERR) { return lval_take(v, i); }
}
if (v->count == 0) { return v; }
if (v->count == 1) { return lval_take(v, 0); }

http://www.buildyourownlisp.com/contents

73

/* Certifica-se que primeiro elemento eh uma funcao depois de avaliar */


lval* f = lval_pop(v, 0);
if (f->type != LVAL_FUN) {
lval_del(v); lval_del(f);
return lval_err("first element is not a function");
}
/* Caso sim, chama a funcao para obter o resultado */
lval* result = f->fun(e, v);
lval_del(f);
return result;
}

Builtins
Agora que nossa avaliao usa o novo tipo funo, precisamos nos assegurar que podemos registrar
todas nossas funes builtins com o ambiente antes de comear o prompt interativo. No momento,
nossas funes builtins no so do tipo correto. Precisamos mudar sua assinatura de tipo para que
elas recebam um ambiente, e mud-las onde apropriado para repassar esse ambiente para outras
chamadas que o necessitem. No vou postar o cdigo aqui para isso, ento v em frente e mude as
assinaturas de tipo das funes builtins para receber um lenv* como seu primeiro argumento
agora. Caso estiver confuso, voc pode olhar o cdigo de exemplo para este captulo.
Como exemplo, podemos usar nossa funo builtin_op para definir builtins separados para
cada uma das funes matemticas que nossa linguagem suporta.
lval* builtin_add(lenv* e, lval* a) {
return builtin_op(e, a, "+");
}
lval* builtin_sub(lenv* e, lval* a) {
return builtin_op(e, a, "-");
}
lval* builtin_mul(lenv* e, lval* a) {
return builtin_op(e, a, "*");
}
lval* builtin_div(lenv* e, lval* a) {
return builtin_op(e, a, "/");
}

Tendo mudado as builtins para o tipo correto, podemos criar uma funo que registra todas nossas
builtins em um ambiente.
Para cada builtin, queremos criar uma funo lval e um smbolo lval com o nome dado. A
seguir, registramos estes com o ambiente usando lenv_put. O ambiente sempre recebe ou retorna
cpias dos valores, ento precisamos lembrar de deletar estes dois lvals depois de registrar, j que
no vamos us-los mais.

http://www.buildyourownlisp.com/contents

74

Se quebrarmos esta tarefa em duas funes, podemos elegantemente registrar todos nossos builtins
com algum ambiente.
void lenv_add_builtin(lenv* e, char* name, lbuiltin func) {
lval* k = lval_sym(name);
lval* v = lval_fun(func);
lenv_put(e, k, v);
lval_del(k); lval_del(v);
}
void lenv_add_builtins(lenv* e) {
/* Funcoes de listas */
lenv_add_builtin(e, "list", builtin_list);
lenv_add_builtin(e, "head", builtin_head);
lenv_add_builtin(e, "tail", builtin_tail);
lenv_add_builtin(e, "eval", builtin_eval);
lenv_add_builtin(e, "join", builtin_join);

/* Funcoes matematicas */
lenv_add_builtin(e, "+", builtin_add);
lenv_add_builtin(e, "-", builtin_sub);
lenv_add_builtin(e, "*", builtin_mul);
lenv_add_builtin(e, "/", builtin_div);

O ltimo passo chamar esta funo antes de criar o prompt interativo. Tambm precisamos
lembrar de deletar o ambiente quando terminarmos.
lenv* e = lenv_new();
lenv_add_builtins(e);
while (1) {
char* input = readline("lispy> ");
add_history(input);
mpc_result_t r;
if (mpc_parse("<stdin>", input, Lispy, &r)) {
lval* x = lval_eval(e, lval_read(r.output));
lval_println(x);
lval_del(x);
mpc_ast_delete(r.output);
} else {
mpc_err_print(r.error);
mpc_err_delete(r.error);
}
free(input);
}
lenv_del(e);

Se tudo estiver funcionando corretamente, podemos brincar um pouco no prompt e verificar se as


funes so realmente um novo tipo de valor agora, e no smbolos.

http://www.buildyourownlisp.com/contents

75

lispy> +
<function>
lispy> eval (head {5 10 11 15})
5
lispy> eval (head {+ - + - * /})
<function>
lispy> (eval (head {+ - + - * /})) 10 20
30
lispy> hello
Error: unbound symbol!
lispy>

Funo Define
Conseguimos registrar nossos builtins como variveis, mas ainda no temos uma maneira de nossos
usurios definir suas prprias variveis.
Isto na verdade um pouco estranho. Precisamos fazer o usurio passar um smbolo para nomear, e
tambm o valor para atribuir a ele. Mas smbolos no aparecem sozinhos. Caso contrrio, a funo
de avaliao tentar recuperar um valor para eles do ambiente.
A nica maneira de passarmos smbolos sem que eles sejam avaliados coloc-los entre {} em
uma Q-expression (expresso citada). Ento vamos usar esta tcnica para nossa funo define. Ela
receber como entrada uma lista de smbolos e alguns outros valores, e atribuir cada um destes
valores a cada um dos smbolos.
Esta funo dever agir como qualquer outro builtin. Primeiro, verifica condies de erro, e em
seguida executa algum comando e retorna um valor. Neste caso, ela primeiramente checa que os
argumentos da entrada so dos tipos corretos. A seguir, itera sobre cada smbolo e valor e os coloca
no ambiente. Se h algum erro, podemos devolv-lo, mas em caso de sucesso, devolvemos a
expresso vazia ().
lval* builtin_def(lenv* e, lval* a) {
LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
"Function 'def' passed incorrect type!");
/* O primeiro argumento eh uma lista de simbolos */
lval* syms = a->cell[0];
/* Verifica se todos elementos da primeira lista sao simbolos */
for (int i = 0; i < syms->count; i++) {
LASSERT(a, syms->cell[i]->type == LVAL_SYM,
"Function 'def' cannot define non-symbol");
}
/* Verifica quantidade correta de simbolos e valores */
LASSERT(a, syms->count == a->count-1,
"Function 'def' cannot define incorrect "
"number of values to symbols");
/* Atribui copias dos valores para simbolos */
for (int i = 0; i < syms->count; i++) {

http://www.buildyourownlisp.com/contents

76

lenv_put(e, syms->cell[i], a->cell[i+1]);


}
lval_del(a);
return lval_sexpr();
}

Precisamos registrar este novo builtin usando nossa funo builtin lenv_add_builtin.
/* Variable Functions */
lenv_add_builtin(e, "def",

builtin_def);

Agora devemos conseguir suportar variveis definidas pelo usurio. Como nossa funo def
recebe uma lista de smbolos, podemos fazer algumas coisas legais armazenando e manipulando
smbolos em listas antes de pass-los para serem definidos. Brinque um pouco no prompt e
verifique que tudo esteja funcionando corretamente. Voc deve obter um comportamento como o
mostrado a seguir. Explore quais outros mtodos complexos so possveis para a definio e
avaliao de variveis. Conseguindo definir funes, vamos ver de verdade algumas das coisas teis
que podem ser feitas com essa abordagem.
lispy>
()
lispy>
()
lispy>
100
lispy>
200
lispy>
300
lispy>
()
lispy>
11
lispy>
()
lispy>
{a b x
lispy>
()
lispy>
{1 2 3
lispy>

def {x} 100


def {y} 200
x
y
+ x y
def {a b} 5 6
+ a b
def {arglist} {a b x y}
arglist
y}
def arglist 1 2 3 4
list a b x y
4}

Comunicando erros
At agora, nossa comunicao de erros no funciona muito bem. Podemos comunicar quando um
erro ocorre, e dar uma vaga noo do que o problema foi, mas no damos ao usurio muita
informao sobre o que exatamente deu errado. Por exemplo, se h um smbolo no vinculado ns
deveramos ser capaz de reportar exatamente qual smbolo est no vinculado. Isto pode ajudar o
usurio a encontrar equvocos, erros de digitao e outros problemas triviais.

http://www.buildyourownlisp.com/contents

77

No seria legal se pudssemos escrever uma funo que pode reportar erros de maneira
semelhante a como printf funciona? Seria ideal se pudssemos passar strings, inteiros, e
outros dados para tornar nossas mensagens de erros mais ricas.
A funo printf uma funo especial em C porque ela recebe um nmero varivel de
argumentos. Podemos criar nossas prprias funes com argumentos variveis, que o que vamos
fazer para tornar melhor nossa comunicao de erros.
Vamos modificar lval_err para agir da mesma maneira como printf, recebendo uma string de
formato, e depois alguns argumentos para casar com essa string.
Para declarar que uma funo recebe argumentos variveis na assinatura de tipo, voc usa a sintaxe
especial de ellipsis (ou, reticncias) ..., que representa o restante dos argumentos.
lval* lval_err(char* fmt, ...);

A seguir, dentro da funo existem funes da biblioteca padro que podemos usar para examinar o
que o cdigo chamador passou.
O primeiro passo criar um struct va_list e inicializ-lo usando va_start, passando o ltimo
argumento nomeado. Para outros propsitos, possvel examinar cada argumento passado usando
va_arg, mas ns vamos passar nossa lista inteira de argumentos variveis diretamente para a
funo vsnprintf. Esta funo funciona como printf, mas escreve em uma string e recebe um
va_list. Tendo feito isso, devemos chamar va_end para liberar quaisquer recursos usados.
A funo vsnprintf joga a sada em uma string, que precisamos alocar antes. Como no
sabemos o tamanho desta string at rodarmos a funo, primeiro vamos alocar um buffer de
caracteres com tamanho 512 e ento realocar em um buffer menor depois que jogarmos a sada
para ele. Se uma mensagem de erro for maior que 512 caracteres vai acabar cortada, mas com sorte
isso no vai acontecer.
Colocando tudo junto, nossa nova funo de erros se parece com isso.
lval* lval_err(char* fmt, ...) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_ERR;
/* Cria um va list e inicializa */
va_list va;
va_start(va, fmt);
/* Aloca 512 bytes de espaco */
v->err = malloc(512);
/* imprime a string de erro com um maximo de 511 caracteres */
vsnprintf(v->err, 511, fmt, va);
/* Realoca para o numero de bytes realmente usado */
v->err = realloc(v->err, strlen(v->err)+1);
/* Limpa nossa va list */

http://www.buildyourownlisp.com/contents

78

va_end(va);
return v;
}

Usando isso podemos comear a adicionar mensagens de erros melhores em nossas funes. Como
exemplo, podemos olhar para lenv_get. Quando um smbolo no pode ser encontrado, em vez de
comunicar um erro genrico, podemos realmente dizer o nome do que no foi encontrado.
return lval_err("Unbound Symbol '%s'", k->sym);

Podemos tambm adaptar nossa macro LASSERT de maneira que possa receber argumentos
variveis tambm. Como ela uma macro e no uma funo padro, a sintaxe um pouco diferente.
No lado esquerdo da definio ns usamos a notao de ellipsis novamente, mas no lado direito
usamos uma varivel especial __VA_ARGS__ para colar o contedo de todos os outros
argumentos.
Precisamos prefixar esta varivel especial com dois sinais hash (jogo-da-velha) ##. Isto se certifica
que ela colada corretamente quando a macro no recebe nenhum argumento extra. Basicamente, o
que isso faz remover a vrgula do comeo , para fazer de conta que nenhum argumento extra foi
passado.
Como talvez possamos usar args na construo da mensagem de erro, precisamos nos certificar
que no o deletemos at que tenhamos criado o valor de erro.
#define LASSERT(args, cond, fmt, ...) \
if (!(cond)) { \
lval* err = lval_err(fmt, ##__VA_ARGS__); \
lval_del(args); \
return err; \
}

Agora podemos atualizar algumas das nossas mensagens de erro para torn-las mais informativas.
Por exemplo, se foi passado um nmero incorreto de argumentos, podemos especificar quantos
eram esperados e quantos foram dados.
LASSERT(a, a->count == 1,
"Function 'head' passed too many arguments. "
"Got %i, Expected %i.",
a->count, 1);

Podemos tambm melhorar nossa comunicao para erros de tipo. Devemos tentar comunicar qual
tipo era esperado por uma funo e qual tipo realmente recebeu. Antes de fazer isso, seria til ter
uma funo que recebesse como entrada uma enumerao de tipo e devolvesse uma representao
string do tipo dado.
char* ltype_name(int t) {
switch(t) {
case LVAL_FUN: return "Function";
case LVAL_NUM: return "Number";
case LVAL_ERR: return "Error";

http://www.buildyourownlisp.com/contents

79

case LVAL_SYM: return "Symbol";


case LVAL_SEXPR: return "S-Expression";
case LVAL_QEXPR: return "Q-Expression";
default: return "Unknown";

}
LASSERT(a, a->cell[0]->type == LVAL_QEXPR,
"Function 'head' passed incorrect type for argument 0. "
"Got %s, Expected %s.",
ltype_name(a->cell[0]->type), ltype_name(LVAL_QEXPR));

V em frente e use LASSERT para comunicar erros com mais profundidade no cdigo. Isto vai
tornar mais fcil a depurao de muitos dos prximos estgios, medida que comecemos a escrever
cdigos complicados usando nossa nova linguagem. Veja se voc consegue usar macros para poupar
digitao e gerar cdigo automaticamente para maneiras comuns de relatar erros.
lispy> + 1 {5 6 7}
Error: Function '+' passed incorrect type for argument 1. Got Q-Expression,
Expected Number.
lispy> head {1 2 3} {4 5 6}
Error: Function 'head' passed incorrect number of arguments. Got 2, Expected 1.
lispy>

Referncia
variables.c

Metas Bnus

Crie uma Macro para ajudar especificamente relatar erros de tipos.


Crie uma Macro para ajudar especificamente relatar erros de argumentos.
Crie uma Macro para ajudar especificamente relatar erros de lista vazia.
Mude a impresso duma funo builtin para que imprime seu nome.
Escreva uma funo para imprimir todos os valores nomeados em um ambiente.
Redefina uma das variveis builtins para algo diferente.
Mude a redefinio de uma das variveis builtins para algo diferente de um erro.
Crie uma funo exit para parar o prompt e sair.

http://www.buildyourownlisp.com/contents

80

Funes Captulo 12
O que uma Funo
Funes so a essncia de toda programao. Nos primeiros dias da cincia da computao elas
representavam um sonho ingnuo. A ideia era que poderiam reduzir a computao em pedaos cada
vez menores de cdigo reusvel. Com o tempo e uma estrutura apropriada para bibliotecas,
eventualmente teramos escrito cdigo para todas as necessidades computacionais. No mais as
pessoas teriam que escrever suas prprias funes, e programao consistiria de um trabalho fcil
de costurar componentes uns aos outros.
Este sonho no se tornou realidade ainda, mas ele persiste, no importa quo falho. Cada nova
tcnica de programao ou paradigma que aparece agita essa ideia um pouco. Eles prometem
melhor reuso de cdigo, melhores abstraes e uma vida mais fcil para todos.
Na realidade, o que cada paradigma oferece so simplesmente abstraes diferentes. H sempre
um trade-off. Para cada nvel mais alto a se pensar sobre programao, alguma pea perdida.
E isto significa que, no importa quo bem voc decide o que manter e o que deixar,
ocasionalmente algum precisar daquela pea que foi perdida. Mas atravs disso tudo, de uma
maneira ou de outra, funes sempre persistiram, e continuamente provaram ser efetivas.
Ns usamos funes em C, ns sabemos o que elas se parecem, mas ns ainda no sabemos
exatamente o que elas so. Aqui vo algumas maneiras de pensar sobre elas.
Uma maneira de pensar sobre funes uma descrio de alguma computao que voc quer
executar depois. Quando voc define uma funo como dizer "quando eu uso este nome, eu quero
que esse tipo de coisa acontea". uma ideia muito prtica de uma funo. muito intuitiva, e
metafrica para a linguagem. Esta a maneira de comandar um humano ou animal. Outro motivo
pelo qual gosto dela que captura a natureza adiada das funes. Funes so definidas uma vez,
mas podem ser chamadas repetidamente depois.
Uma outra maneira de pensar sobre funes como uma caixa preta que recebe alguma entrada e
produz uma sada. Esta ideia sutilmente diferente da anterior. um pouco mais algbrica, e no
fala sobre computao ou comandos. Esta ideia um conceito matemtico, e no est amarrada a
alguma maquina ou linguagem em particular. Em algumas situaes esta ideia excepcionalmente
til. Ela nos permite pensar sobre funes sem se preocupar sobre suas partes internas ou como elas
so computadas exatamente. Podemos ento combinar ou compor funes umas com as outras sem
se preocupar com alguma coisa sutil dar errado. Isto a ideia central por trs de uma abstrao, e
isto que permite camadas de complexidade funcionar juntamente com as outras ao invs de
conflitar. A fora desta ideia pode ser tambm a sua queda. Como ela no menciona nada sobre
computao, ela no lida com algumas preocupaes do mundo real. "Quanto tempo esta funo
leva para rodar?", "Esta funo eficiente?", "Vai modificar o estado do meu programa? Caso
sim, como?".

http://www.buildyourownlisp.com/contents

81

Um terceiro mtodo pensar em funes como computaes parciais. Como o modelo


matemtico, elas podem receber algumas entradas. Estes valores so requeridos antes que a
funo possa completar a computao. por isso que ela chamada parcial. Mas como o
modelo computacional, o corpo da funo consiste de uma computao especificado em
alguma linguagem de comandos. Estas entradas so chamadas variveis no vinculadas
(unbounded variables), e para terminar a computao algum precisa simplesmente forneclas. Como encaixar uma engrenagem em uma mquina previamente rodando sem objetivo, isto
completa tudo que necessrio para a computao rodar, e a mquina roda. A sada dessas
computaes parciais ela mesma uma varivel com um valor desconhecido. Esta sada pode
ser usada como entrada para uma nova funo, e assim uma funo depende de outra.
Uma vantagem dessa ideia sobre o modelo matemtico reconhecermos que funes contm
computao. Vemos que quando a computao roda, algum processo fsico est acontecendo na
mquina. Isto significa que reconhecemos o fato que algumas coisas tomam tempo para decorrer, ou
que a funo pode mudar o estado do programa, ou fazer qualquer outra coisa que no temos muita
certeza.
Todas essas ideias so exploradas no estudo de funes, clculo Lambda. Este um campo que
combina lgica, matemtica e cincia da computao. O nome vem da letra grega Lambda, que
usada na representao de vinculao de variveis (binding variables). Usar o clculo Lambda nos
d uma maneira de definir, compor e construir funes usando uma notao matemtica simples.
Vamos usar todas as ideias anteriores para adicionar funes definidas pelo usurio nossa
linguagem. Lisp j bem adequada a este tipo de diverso, e usando estes conceitos no vai levar
muito trabalho para implementar funes.
O primeiro passo ser escrever uma funo builtin que possa criar funes definidas pelo usurio.
Aqui vai uma ideia sobre como ela pode ser especificada. O primeiro argumento poderia ser uma
lista de smbolos, da mesma forma que a funo def. Estes smbolos podemos chamar de
argumentos formais, tambm conhecidos como variveis no vinculadas. Elas agem como entradas
para nossa computao parcial. O segundo argumento poderia ser uma outra lista. Quando rodamos
a funo, isto vai ser avaliado com nossa funo builtin eval.
Esta funo chamaremos apenas \, (uma homenagem ao clculo Lambda j que o caractere \ se
parece um pouco com um lambda). Para criar uma funo que recebe duas entradas e as soma,
poderamos ento criar algo como isso.
\ {x y} {+ x y}

Podemos chamar a funo colocando-a como o primeiro argumento em uma S-Expression normal.
(\ {x y} {+ x y}) 10 20

Se quisermos nomear esta funo, podemos pass-la para nosso builtin existente def como
qualquer outro valor, e armazen-la no ambiente.
def {add-together} (\ {x y} {+ x y})

http://www.buildyourownlisp.com/contents

82

A seguir, poderemos cham-la se referindo pelo nome.


add-together 10 20

Tipo funo
Para armazenar uma funo como um lval precisamos pensar exatamente do que ela consiste.
Usando a definio anterior, uma funo deve consistir de trs partes. Primeiro a lista de
argumentos formais, que precisamos vincular antes que possamos avaliar a funo. A segunda parte
uma Q-Expression que representa o corpo da funo. Finalmente, requeremos um local para
armazenar os valores atribudos para os argumentos formais. Com sorte, j temos uma estrutura
para armazenar variveis, um ambiente.
Vamos armazenar nossas funes builtins e funes definidas pelo usurio com o mesmo tipo
LVAL_FUN. Isto significa que precisaremos duma maneira de diferenciar internamente entre elas.
Para fazer isso, podemos checar se o apontador de funo lbuiltin NULL ou no. Se no for
NULL, sabemos que o lval alguma funo builtin, caso contrrio sabemos que uma funo do
usurio.
struct lval {
int type;
/* Basic */
long num;
char* err;
char* sym;
/* Function */
lbuiltin builtin;
lenv* env;
lval* formals;
lval* body;
/* Expression */
int count;
lval** cell;
};

Renomeamos o campo lbuiltin de func para builtin. Devemos nos certificar de mudar isto
em todos os locais que usado em nosso cdigo.
Tambm precisamos criar um construtor para as funes lval definidas pelo usurio. Aqui
construmos um ambiente para a funo, e atribumos os valores formals e body para os valores
passados.
lval* lval_lambda(lval* formals, lval* body) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_FUN;

http://www.buildyourownlisp.com/contents

83

/* Seta Builtin para Null */


v->builtin = NULL;
/* Constroi novo ambiente */
v->env = lenv_new();

/* Seta Formals e Body */


v->formals = formals;
v->body = body;
return v;

Como sempre quando mudamos nosso tipo lval, precisamos atualizar as funes para deleo,
cpia, e impresso para lidar com as mudanas. Para avaliao, precisaremos olhar com mais
profundidade.
Para Deleo...
case LVAL_FUN:
if (!v->builtin) {
lenv_del(v->env);
lval_del(v->formals);
lval_del(v->body);
}
break;

Para Cpia...
case LVAL_FUN:
if (v->builtin) {
x->builtin = v->builtin;
} else {
x->builtin = NULL;
x->env = lenv_copy(v->env);
x->formals = lval_copy(v->formals);
x->body = lval_copy(v->body);
}
break;

Para Impresso...
case LVAL_FUN:
if (v->builtin) {
printf("<builtin>");
} else {
printf("(\\ "); lval_print(v->formals);
putchar(' '); lval_print(v->body); putchar(')');
}
break;

Funo Lambda
Podemos adicionar um builtin para nossa funo lambda. Queremos que receba como entrada uma
lista de smbolos, e uma lista que represente o cdigo. Depois disso, ela deve retornar uma funo

http://www.buildyourownlisp.com/contents

84

lval. Definimos j alguns builtins, e este seguir o mesmo formato. Como em def, fazemos
alguma checagem de erro para verificar que o nmero e os tipos dos argumentos (usando algumas
Macros recm definidas). A seguir, apenas extramos os primeiros dois argumentos da lista e os
passamos para nossa funo lval_lambda definida anteriormente.
lval* builtin_lambda(lenv* e, lval* a) {
/* Verifica dois argumentos, cada um deve ser uma Q-Expression */
LASSERT_NUM("\\", a, 2);
LASSERT_TYPE("\\", a, 0, LVAL_QEXPR);
LASSERT_TYPE("\\", a, 1, LVAL_QEXPR);
/* Verifica que a primeira Q-Expression contem apenas Symbols */
for (int i = 0; i < a->cell[0]->count; i++) {
LASSERT(a, (a->cell[0]->cell[i]->type == LVAL_SYM),
"Cannot define non-symbol. Got %s, Expected %s.",
ltype_name(a->cell[0]->cell[i]->type),ltype_name(LVAL_SYM));
}
/* Extrai os primeiros dois argumentos e passa-os para lval_lambda */
lval* formals = lval_pop(a, 0);
lval* body = lval_pop(a, 0);
lval_del(a);
return lval_lambda(formals, body);
}

De onde vieram LASSERT_NUM e LASSERT_TYPE?


Tomei a liberdade de melhorar a comunicao de erros para este captulo. Esta tarefa foi sugerida
como meta bnus do captulo anterior. Ela torna o cdigo bem mais claro que era difcil de ignorar!
Caso esteja planejando completar esta tarefa voc mesmo, agora pode ser uma boa hora de faz-lo.
Caso contrrio, voc pode olhar o cdigo de referncia deste captulo para ver a abordagem que
usei, e integr-la em seu cdigo.

Ambiente Pai
Demos s funes seus prprios ambientes. Neste ambiente colocaremos os valores que seus
argumentos formais so setados. Quando vamos avaliar o corpo da funo, podemos faz-lo neste
ambiente e saber que aquelas variveis tero os valores corretos.
Mas idealmente tambm queremos que estas funes sejam capaz de acessar variveis que esto no
ambiente global, como as nossas funes builtins.
Podemos resolver este problema mudando a definio do nosso ambiente para conter uma
referncia a algum ambiente pai. A seguir, quando queremos avaliar uma funo, podemos setar
este ambiente pai para nosso ambiente global, que tem todas nossas builtins definidas dentro dele.
Quando adicionamos isto ao nosso struct lenv, conceitualmente ser uma referncia a um
ambiente pai, no algum subambiente ou algo do tipo. Por causa disso, no devemos delet-lo

http://www.buildyourownlisp.com/contents

85

quando nosso lenv deletado, ou copi-lo quando nosso lenv copiado.


A maneira que o ambiente pai funciona simples. Se algum chama lenv_get no ambiente e o
smbolo no encontrado, ele vai buscar no ambiente pai para ver se o valor nomeado existe l, e
repetir o processo at que ou a varivel seja encontrada ou no h mais pais. Para significar que um
ambiente no tem pai, setamos a referncia para NULL.
A funo construtora somente requer mudanas bsicas para permitir isto.
struct lenv {
lenv* par;
int count;
char** syms;
lval** vals;
};
lenv* lenv_new(void) {
lenv* e = malloc(sizeof(lenv));
e->par = NULL;
e->count = 0;
e->syms = NULL;
e->vals = NULL;
return e;
}

Para obter um valor de um ambiente precisamos adicionar a busca do ambiente pai caso um smbolo
no for encontrado.
lval* lenv_get(lenv* e, lval* k) {
for (int i = 0; i < e->count; i++) {
if (strcmp(e->syms[i], k->sym) == 0) {
return lval_copy(e->vals[i]);
}
}

/* Caso sem simbolo, verifica no ambiente pai ou devolve erro */


if (e->par) {
return lenv_get(e->par, k);
} else {
return lval_err("Unbound Symbol '%s'", k->sym);
}

Como temos um novo tipo lval que tem seu prprio ambiente precisamos uma funo para copiar
ambientes, para usar quando copiamos structs lval.
lenv* lenv_copy(lenv* e) {
lenv* n = malloc(sizeof(lenv));
n->par = e->par;
n->count = e->count;
n->syms = malloc(sizeof(char*) * n->count);
n->vals = malloc(sizeof(lval*) * n->count);
for (int i = 0; i < e->count; i++) {
n->syms[i] = malloc(strlen(e->syms[i]) + 1);
strcpy(n->syms[i], e->syms[i]);

http://www.buildyourownlisp.com/contents

86

n->vals[i] = lval_copy(e->vals[i]);
}
return n;
}

Ter ambientes pais tambm muda nosso conceito de definir uma varivel.
H duas maneiras que podemos definir uma varivel agora. Podemos ou defini-la no ambiente local,
mais interno, ou podemos defini-lo no ambiente global, mais externo. Vamos adicionar funes para
fazer ambos. Manteremos a funo lenv_put do mesmo jeito. Ele pode ser usado para a definio
no ambiente local. Mas adicionaremos uma nova funo lenv_def para definio no ambiente
global. Ela funciona simplesmente seguindo a cadeia de pais acima, e por fima usa lenv_put para
defini-lo localmente.
void lenv_def(lenv* e, lval* k, lval* v) {
/* Itera ate e nao ter pai */
while (e->par) { e = e->par; }
/* Coloca valor em e */
lenv_put(e, k, v);
}

No momento, esta distino pode parecer intil, mas mais tarde vamos us-la para escrever
resultados parciais de clculos em variveis locais dentro de uma funo. Podemos adicionar outra
builtin para atribuio local. Chamaremos esta de put em C, mas a daremos o smbolo = em Lisp.
Podemos adaptar nossa funo builtin_def e reusar o mesmo cdigo comum, da mesma forma
que fazemos com nossos operadores matemticos.
Em seguida, precisamos registrar estes como builtins.
lenv_add_builtin(e, "def", builtin_def);
lenv_add_builtin(e, "=",
builtin_put);
lval* builtin_def(lenv* e, lval* a) {
return builtin_var(e, a, "def");
}
lval* builtin_put(lenv* e, lval* a) {
return builtin_var(e, a, "=");
}
lval* builtin_var(lenv* e, lval* a, char* func) {
LASSERT_TYPE(func, a, 0, LVAL_QEXPR);
lval* syms = a->cell[0];
for (int i = 0; i < syms->count; i++) {
LASSERT(a, (syms->cell[i]->type == LVAL_SYM),
"Function '%s' cannot define non-symbol. "
"Got %s, Expected %s.", func,
ltype_name(syms->cell[i]->type),
ltype_name(LVAL_SYM));
}
LASSERT(a, (syms->count == a->count-1),
"Function '%s' passed too many arguments for symbols. "

http://www.buildyourownlisp.com/contents

87

"Got %i, Expected %i.", func, syms->count, a->count-1);


for (int i = 0; i < syms->count; i++) {
/* Caso 'def', define globalmente. Caso 'put', define localmente */
if (strcmp(func, "def") == 0) {
lenv_def(e, syms->cell[i], a->cell[i+1]);
}
if (strcmp(func, "=")
== 0) {
lenv_put(e, syms->cell[i], a->cell[i+1]);
}
}
lval_del(a);
return lval_sexpr();
}

Chamando Funes
Precisamos escrever o cdigo que roda quando uma expresso avaliada e uma funo lval
chamada.
Quando o tipo desta funo um builtin, chamamo-la como antes, usando o apontador para funo,
mas precisamos fazer algo separado para nossas funes definidas pelo usurio. Precisamos
vincular cada um dos argumentos passados para cada um dos smbolos no campo formals. Feito
isso, precisamos avaliar o campo body, usar o campo env como um ambiente, e o ambiente
chamador como um pai.
Uma primeira tentativa, sem checagem de erros, pode se parecer com isto:
lval* lval_call(lenv* e, lval* f, lval* a) {
/* Se for Builtin, apenas chame-a */
if (f->builtin) { return f->builtin(e, a); }
/* Atribui cada argumento para cada formal em ordem */
for (int i = 0; i < a->count; i++) {
lenv_put(f->env, f->formals->cell[i], a->cell[i]);
}
lval_del(a);
/* Seta o ambiente pai */
f->env->par = e;
/* Avalia o body */
return builtin_eval(f->env,
lval_add(lval_sexpr(), lval_copy(f->body)));
}

Mas isto no funciona corretamente quando o nmero de argumentos fornecidos e o nmero de


argumentos formais forem diferentes. Nesta situao ela vai falhar.

http://www.buildyourownlisp.com/contents

88

Na verdade este um caso interessante, e nos deixa com algumas opes. Ns poderamos
simplesmente jogar um erro quando a contagem de argumentos fornecidos est incorreta, mas
podemos fazer algo que ainda mais divertido. Quando faltam argumentos a ser fornecidos,
poderamos vincular apenas os primeiros argumentos formais da funo e devolv-la, deixando o
restante no vinculado.
Isto cria uma funo que chamada parcialmente avaliada e reflete nossa ideia anterior de uma
funo ser um tipo de computao parcial. Se comeamos com uma funo que recebe dois
argumentos e passamos apenas um argumento, podemos vincular este primeiro argumento e
retornar uma nova funo com o primeiro argumento formal vinculado, e o segundo permanecendo
vazio.
Esta metfora cria uma imagem bonitinha de como as funes funcionam. Podemos imaginar uma
funo no comeo de uma expresso, repetidamente consumindo entradas diretamente sua direita.
Depois de consumir a primeira entrada sua direita, se ela for completa (no requer mais entradas),
ela a avalia e substitui a si mesma com um novo valor. Caso contrrio, se ainda requer mais
argumentos, ela se substitui com uma outra funo, mais completa, com algumas variveis ligadas.
Este processo se repete at que o valor final para o programa seja criado.
Ento voc pode imaginar funes como um pequeno Pac-Man, no consumindo todas as entradas
de uma vez s, mas iterativamente comendo entradas direita, ficando cada vez maior, at que
fique cheio e exploda para criar algo novo. Isto no exatamente como vamos implementar no
cdigo, mas ainda divertido de imaginar.
lval* lval_call(lenv* e, lval* f, lval* a) {
/* Caso Builtin, entao aplique-a */
if (f->builtin) { return f->builtin(e, a); }
/* Grava contagem de argumentos */
int given = a->count;
int total = f->formals->count;
/* Enquanto ainda tem argumentos a processar */
while (a->count) {
/* Caso acabamos de vinucular todos argumentos formais */
if (f->formals->count == 0) {
lval_del(a); return lval_err(
"Function passed too many arguments. "
"Got %i, Expected %i.", given, total);
}
/* Extrai o primeiro simbolo dos formais */
lval* sym = lval_pop(f->formals, 0);
/* Extrai o proximo argumento da lista */
lval* val = lval_pop(a, 0);
/* Vincula uma copia no ambiente da funcao */
lenv_put(f->env, sym, val);

http://www.buildyourownlisp.com/contents

89

/* Deleta simbolo e valor */


lval_del(sym); lval_del(val);

/* Lista de argumentos estah agora vinculada e pode ser limpa */


lval_del(a);
/* Se todos formais foram vinculados, entao avalia */
if (f->formals->count == 0) {
/* Seta ambiente pai para ambiente de avaliacao */
f->env->par = e;
/* Avalia e devolve */
return builtin_eval(
f->env, lval_add(lval_sexpr(), lval_copy(f->body)));
} else {
/* Caso contrario, retorna funcao parcialmente avaliada */
return lval_copy(f);
}
}

A funo acima faz exatamente o que explicamos, adicionando tambm um tratamento de erros
correto. Primeiro itera sobre os argumentos passados, tentando colocar cada um no ambiente. A
seguir ela verifica se o ambiente est cheio, e nesse caso avalia, caso no, devolve uma cpia de si
mesma com alguns argumentos preenchidos.
Se atualizamos nossa funo de avaliao lval_eval_sexpr para chamar lval_call,
podemos testar nosso novo sistema.
lval* f = lval_pop(v, 0);
if (f->type != LVAL_FUN) {
lval* err = lval_err(
"S-Expression starts with incorrect type. "
"Got %s, Expected %s.",
ltype_name(f->type), ltype_name(LVAL_FUN));
lval_del(f); lval_del(v);
return err;
}
lval* result = lval_call(e, f, v);

Experimente definir algumas funes e testar como avaliaes parciais funcionam.


lispy>
()
lispy>
210
lispy>
(\ {y}
lispy>
()
lispy>
510
lispy>

def {add-mul} (\ {x y} {+ x (* x y)})


add-mul 10 20
add-mul 10
{+ x (* x y)})
def {add-mul-ten} (add-mul 10)
add-mul-ten 50

http://www.buildyourownlisp.com/contents

90

Argumentos Variveis
Definimos algumas de nossas funes builtins de forma que recebam um nmero varivel de
argumentos. Funes como + e join podem receber qualquer nmero de argumentos, e operam
neles logicamente. Devemos achar uma maneira de permitir funes definidas pelo usurio
funcionar com mltiplos argumentos tambm.
Infelizmente no h uma maneira elegante de permitir isso, sem adicionarmos alguma sintaxe
especial. Ento vamos codificar algum sistema fixo na nossa linguagem usando um smbolo
especial &.
Vamos permitir que usurios definam argumentos formais que se paream com {x & xs}, o que
significa que uma funo receber um nico argumento x, seguido de zero ou mais outros
argumentos, unidos em uma lista chamada xs. Esta um pouco parecida com a ellipsis que usamos
para declara argumentos variveis em C.
Quando atribuirmos nosso argumentos formais, vamos procurar por um smbolo & e caso existir,
tomaremos o prximo argumento formal e o atribuiremos quaisquer argumentos fornecidos
restantes que foram passados. importante converter esta lista de argumento em uma QExpression. Precisamos tambm lembrar de checar que & seja seguido por um smbolo real, e se no
for devemos devolver um erro.
Logo depois do primeiro smbolo ser extrado dos argumentos formais no lao while de
lval_call, podemos adicionar este caso especial.
/* Caso especial para tratar '&' */
if (strcmp(sym->sym, "&") == 0) {
/* Verifica que '&' eh seguido de outro simbolo */
if (f->formals->count != 1) {
lval_del(a);
return lval_err("Function format invalid. "
"Symbol '&' not followed by single symbol.");
}

/* Proximo formal deve ser vinculado aos argumentos restantes */


lval* nsym = lval_pop(f->formals, 0);
lenv_put(f->env, nsym, builtin_list(e, a));
lval_del(sym); lval_del(nsym);
break;

Suponha que ao chamar a funo, o usurio no fornea quaisquer argumentos variveis, mas
apenas os primeiros argumentos nomeados. Neste caso, precisamos setar o smbolo seguindo & para
a lista vazia. Adicione este caso especial logo depois de deletarmos a lista de argumentos e antes de
checar para ver se todos os formais foram avaliados.

http://www.buildyourownlisp.com/contents

91

/* Caso '&' sobrou na lista formal, vincula para lista vazia */


if (f->formals->count > 0 &&
strcmp(f->formals->cell[0]->sym, "&") == 0) {
/* Certifica-se que & nao foi passado de maneira invalida */
if (f->formals->count != 2) {
return lval_err("Function format invalid. "
"Symbol '&' not followed by single symbol.");
}
/* Extrai e deleta simbolo '&' */
lval_del(lval_pop(f->formals, 0));
/* Extrai proximo simbolo e cria lista vazia */
lval* sym = lval_pop(f->formals, 0);
lval* val = lval_qexpr();

/* Vincula ao ambiente e deleta */


lenv_put(f->env, sym, val);
lval_del(sym); lval_del(val);

Funes Interessantes
Definio de funo
Lambdas so claramente uma maneira simples e poderosa de definir funes. Mas a sintaxe um
pouco desajeitada. H vrias parnteses, chaves e smbolos envolvidos. Aqui vai uma ideia
interessante. Podemos tentar escrever uma funo que ela prpria define uma funo, usando uma
sintaxe mais simples.
Essencialmente o que queremos uma funo que pode executar dois passos em uma vez s.
Primeiro ela deve criar uma funo, em seguida ela deve defini-la com um nome. Aqui est o
truque. Podemos deixar o usurio fornecer o nome e os argumentos formais juntamente em uma
lista, e ento separar estes e us-los na definio. Aqui est uma funo que faz isso. Ela recebe
como entrada alguns argumentos e um corpo. Ela recebe a cabea dos argumentos como sendo o
nome da funo, e o resto para ser os argumentos formais. Ela passa o corpo diretamente a um
lambda.
\ {args body} {def (head args) (\ (tail args) body)}

Podemos nomear esta funo em algo como fun passando-a para def como de costume.
def {fun} (\ {args body} {def (head args) (\ (tail args) body)})

Isto significa que podemos agora definir funes de maneira muito mais simples e atraente. Para
definir nossa funo add-together mencionada anteriormente podemos fazer como segue.
Funes podem definir funes. Isto certamente algo que nunca poderamos fazer em C. Como
isso legal!

http://www.buildyourownlisp.com/contents

92

fun {add-together x y} {+ x y}

Currying
Em algum momento, funes como + recebem um nmero varivel de argumentos. Em algumas
situaes isto timo, mas e se tivssemos uma lista de argumentos que desejssemos pass-la?
Nesta situao, ela se torna intil.
Novamente podemos tentar criar uma funo para resolver este problema. Se pudermos criar uma
lista no formato que desejamos usar para nossa expresso, podemos usar eval para trat-lo como
tal. Na situao de + podemos acrescentar esta funo na frente da lista e ento executar a
avaliao.
Podemos definir uma funo unpack (do ingls, desempacotar) que faa isto. Ela recebe como
entrada uma funo e uma lista, acrescenta a funo frente da lista e a avalia.
fun {unpack f xs} {eval (join (list f) xs)}

Em algumas situaes podemos encarar o dilema oposto. Temos uma funo que recebe como
entrada uma lista, mas que desejamos cham-la usando argumentos variveis. Neste caso a soluo
ainda mais simples. Usamos o fato que nossa sintaxe & para argumentos variveis empacota
argumentos variveis em uma lista para ns.
fun {pack f & xs} {f xs}

Em algumas linguagens isto chamado currying e uncurrying, respectivamente. O nome


homenagem a Haskell Curry e infelizmente no tem nada a ver com nosso tempero apimentado
favorito.
lispy>
()
lispy>
()
lispy>
18
lispy>
{5}

def {uncurry} pack


def {curry} unpack
curry + {5 6 7}
uncurry head 5 6 7

Por causa da maneira que nossa avaliao parcial funciona, no precisamos pensar em currying
com algum conjunto especfico de argumentos. Podemos pensar em funes elas mesmas sendo em
forma curried ou uncurried (no curried).
lispy>
()
lispy>
()
lispy>
18
lispy>
18

def {add-uncurried} +
def {add-curried} (curry +)
add-curried {5 6 7}
add-uncurried 5 6 7

http://www.buildyourownlisp.com/contents

93

Brinque um pouco e veja quais outras funes interessantes e poderosas voc consegue inventar. No
prximo captulo vamos adicionar condicionais, o que vai realmente comear a fazer nossa
linguagem mais completa. Mas isto no significa que voc no poder inventar outras ideias
interessantes. Nosso Lisp est ficando mais rico.

Referncia
functions.c

Metas Bnus
Defina uma funo Lisp que devolva o primeiro elemento duma lista.
Defina uma funo Lisp que devolva o segundo elemento duma lista.
Defina uma funo Lisp que chame uma funo com dois argumentos na ordem inversa.
Defina uma funo Lisp que chame uma funo com argumentos, e depois passe o
resultado para uma outra funo.
Mude os argumentos variveis para que pelo menos um argumento extra precise ser
fornecido para que seja avaliado.

Condicionais Captulo 13
Fazendo voc mesmo
J chegamos at aqui. Seu conhecimento de C deve ser bom o suficiente para voc se manter nos
prprios ps um pouco mais. Caso esteja se sentindo confiante, este captulo uma oportunidade
perfeita para abrir as asas e tentar alguma coisa voc mesmo. um captulo relativamente curto e
consiste em essencialmente adicionar novas funes builtins para lidar com comparao e
ordenao.
Caso esteja se sentindo positivo, v em frente e tente implementar comparao e ordenao na
sua linguagem agora. Defina algumas novas funes builtins para maior que, menor que, igual
a, e todos os outros operadores de comparao que usamos em C. Tente definir uma funo if
que teste uma condio e ento avalie algum cdigo ou um outro cdigo, dependendo do
resultado. Tendo terminado isso, volte aqui e compare seu trabalho com o meu. Observe as
diferenas e decida quais partes voc prefere.
Se voc ainda se sente inseguro, no se preocupe. Siga adiante e explicarei minha abordagem.

http://www.buildyourownlisp.com/contents

Ordenao
Para fins de simplicidade, vou reusar nosso tipo de dados nmero para representar o resultado das
comparaes. Farei uma regra similar a C, para dizer que qualquer nmero que no seja 0
avaliado como verdadeiro em um comando if, enquanto 0 sempre avalia como falso.
Por isso, nossas funes de ordenao parecem uma verso simplificada de nossas funes
aritmticas. Elas s funcionam em nmeros, e queremos que s funcionem com dois argumentos.
Caso essas condies sejam atendidas, a matemtica simples. Queremos devolver um lval
nmero ou 0 ou 1 dependendo da comparao de igualdade entre os dois lval da entrada.
Podemos usar os operadores de comparao de C para fazer isso. Por fim, devolvemos este
resultado como um valor nmero.
Primeiro verificamos as condies de erro, a seguir comparamos os nmeros em cada argumento
para obter um resultado. Finalmente, devolvemos este resultado como um valor nmero.
lval* builtin_gt(lenv* e, lval* a) {
return builtin_ord(e, a, ">");
}
lval* builtin_lt(lenv* e, lval* a) {
return builtin_ord(e, a, "<");
}
lval* builtin_ge(lenv* e, lval* a) {
return builtin_ord(e, a, ">=");
}
lval* builtin_le(lenv* e, lval* a) {
return builtin_ord(e, a, "<=");
}
lval* builtin_ord(lenv* e, lval* a, char* op) {
LASSERT_NUM(op, a, 2);
LASSERT_TYPE(op, a, 0, LVAL_NUM);
LASSERT_TYPE(op, a, 1, LVAL_NUM);
int r;
if (strcmp(op, ">") == 0) {
r = (a->cell[0]->num > a->cell[1]->num);
}
if (strcmp(op, "<") == 0) {
r = (a->cell[0]->num < a->cell[1]->num);
}
if (strcmp(op, ">=") == 0) {
r = (a->cell[0]->num >= a->cell[1]->num);
}
if (strcmp(op, "<=") == 0) {
r = (a->cell[0]->num <= a->cell[1]->num);
}
lval_del(a);
return lval_num(r);

94

http://www.buildyourownlisp.com/contents

95

Igualdade
Igualdade vai ser diferente de ordenao porque queremos que funcione em mais do que tipos
nmeros. Ser til ver se uma entrada igual uma lista vazia, ou ver se duas funes passadas so
a mesma. Por isso, precisamos definir uma funo que verifique igualdade entre diferentes tipos de
lval.
Esta funo essencialmente verifica que todos os campos que perfazem os dados para um
determinado tipo lval so iguais. Caso todos os campos forem iguais, a coisa toda considerada
igual. Caso contrrio, se houver qualquer diferena, a coisa toda considerada diferente.
int lval_eq(lval* x, lval* y) {
/* Tipos diferentes sao sempre diferentes */
if (x->type != y->type) { return 0; }
/* Compara baseado no tipo */
switch (x->type) {
/* Compara valor numero */
case LVAL_NUM: return (x->num == y->num);
/* Compara valor string */
case LVAL_ERR: return (strcmp(x->err, y->err) == 0);
case LVAL_SYM: return (strcmp(x->sym, y->sym) == 0);
/* Compara se for builtin, senao compara argumentos formais e corpo */
case LVAL_FUN:
if (x->builtin || y->builtin) {
return x->builtin == y->builtin;
} else {
return lval_eq(x->formals, y->formals)
&& lval_eq(x->body, y->body);
}
/* Caso lista, compara cada elemento individual */
case LVAL_QEXPR:
case LVAL_SEXPR:
if (x->count != y->count) { return 0; }
for (int i = 0; i < x->count; i++) {
/* Se qualquer elemento nao for igual, entao a lista inteira eh
diferente */
if (!lval_eq(x->cell[i], y->cell[i])) { return 0; }
}
/* Senao, as listas devem ser iguais */
return 1;
break;
}
return 0;
}

Usando esta funo, a nova funo builtin para comparao de igualdade simples de adicionar.

http://www.buildyourownlisp.com/contents

96

Simplesmente nos certificamos que hajam dois argumentos e que eles so iguais. Armazenamos o
resultado da comparao em um novo lval e o devolvemos.
lval* builtin_cmp(lenv* e, lval* a, char* op) {
LASSERT_NUM(op, a, 2);
int r;
if (strcmp(op, "==") == 0) {
r = lval_eq(a->cell[0], a->cell[1]);
}
if (strcmp(op, "!=") == 0) {
r = !lval_eq(a->cell[0], a->cell[1]);
}
lval_del(a);
return lval_num(r);
}
lval* builtin_eq(lenv* e, lval* a) {
return builtin_cmp(e, a, "==");
}
lval* builtin_ne(lenv* e, lval* a) {
return builtin_cmp(e, a, "!=");
}

Funo If
Para tornar nossos operadores de comparao teis, precisamos uma funo if. Esta funo um
pouco parecida com o operador ternrio em C. Dependendo de uma condio ser verdadeira, ela
avalia uma coisa, e se a condio for falsa, ela avalia outra.
Podemos novamente fazer uso de Q-Expressions para codificar uma computao. Primeiro fazemos
o usurio passar o resultado duma comparao, em seguida fazemos ele passar duas Q-Expressions
representando o cdigo a ser avaliado quando uma condio seja ou verdadeira ou falsa.
lval* builtin_if(lenv* e, lval* a) {
LASSERT_NUM("if", a, 3);
LASSERT_TYPE("if", a, 0, LVAL_NUM);
LASSERT_TYPE("if", a, 1, LVAL_QEXPR);
LASSERT_TYPE("if", a, 2, LVAL_QEXPR);
/* Marca ambas expressoes como avaliaveis */
lval* x;
a->cell[1]->type = LVAL_SEXPR;
a->cell[2]->type = LVAL_SEXPR;
if (a->cell[0]->num) {
/* Se condicao verdadeira, avalia primeira expressao */
x = lval_eval(e, lval_pop(a, 1));
} else {
/* Caso contrario, avalia segunda expressao */
x = lval_eval(e, lval_pop(a, 2));
}
/* Deleta lista de argumentos e devolve */

http://www.buildyourownlisp.com/contents

97

lval_del(a);
return x;

Tudo que nos resta registrar todos estes novos builtins e estamos prontos novamente.
/* Funcoes de comparacao */
lenv_add_builtin(e, "if", builtin_if);
lenv_add_builtin(e, "==", builtin_eq);
lenv_add_builtin(e, "!=", builtin_ne);
lenv_add_builtin(e, ">", builtin_gt);
lenv_add_builtin(e, "<", builtin_lt);
lenv_add_builtin(e, ">=", builtin_ge);
lenv_add_builtin(e, "<=", builtin_le);

D uma brincada para ver se tudo est funcionando corretamente.


lispy>
1
lispy>
0
lispy>
0
lispy>
0
lispy>
1
lispy>
1
lispy>
1
lispy>
()
lispy>
-100

> 10 5
<= 88 5
== 5 6
== 5 {}
== 1 1
!= {} 56
== {1 2 3 {5 6}} {1

{5 6}}

def {x y} 100 200


if (== x y) {+ x y} {- x y}

Funes Recursivas
Introduzindo condicionais tornamos nossa linguagem muito mais poderosa. Isto porque elas
efetivamente nos permitem implementar funes recursivas.
Funes recursivas so aquelas que chama elas mesmas. J estamos acostumadas essas em C para
executar leitura e avaliao de expresses. O motivo que precisamos de condicionais para isso
porque elas nos permitem verificar a situao para a qual desejamos terminar a recurso.
Por exemplo, podemos usar condicionais para implementar uma funo len que nos diz o nmero
de itens numa lista. Se encontramos a lista vazia, simplesmente devolvemos 0. Caso contrrio,
retornamos o comprimento da cauda da lista da entrada usando tail mais 1. Pense um pouco a
respeito por que isso funciona. Isto usa funo len repetidamente at que atinja a lista vazia.
Nesse ponto, ele devolve 0 e soma todos os outros resultados parciais juntos.

http://www.buildyourownlisp.com/contents

98

(fun {len l} {
if (== l {})
{0}
{+ 1 (len (tail l))}
})

Assim como em C, h uma simetria agradvel para esse tipo de funo recursiva. Primeiro fazemos
uma coisa para a lista vazia (o caso base). Em seguida, se recebemos algo maior, retiramos algum
pedao (tipo, a cabea da lista) e fazemos algo com ele, antes de combin-lo com o resto da coisa
com a qual a funo j foi aplicada.
Aqui vai uma outra funo para inverter uma lista. Como antes, ela testa para uma lista vazia, mas
dessa vez ela devolve a prpria lista vazia. Isto faz sentido. O inverso da lista vazia simplesmente
a lista vazia. Mas se receber algo maior que a lista vazia, ela inverte a cauda e joga o resultado em
frente da cabea.
(fun {reverse l} {
if (== l {})
{{}}
{join (reverse (tail l)) (head l)}
})

Vamos usar esta tcnica para construir muitas funes. Isto se deve porque ser a maneira principal
de atingir iterao em nossa linguagem.

Referncia
conditionals.c

Metas Bnus
Crie operadores builtins lgicos or ||, and && e not ! e adicione-os linguagem.
Defina uma funo Lisp recursiva nth que devolva o ensimo item duma lista.
Defina uma funo Lisp recursiva que devolva 1 se o elemento for membro duma lista,
caso contrrio devolva 0.
Defina uma funo Lisp que devolva o ltimo elemento duma lista.
Defina, em Lisp, funes operadoras lgicas como or, and e not.
Adicione um tipo boolean para a linguagem com variveis builtins true e false.

http://www.buildyourownlisp.com/contents

99

Strings Captulo 14
Bibliotecas
Nosso Lisp finalmente funcional. Devemos ser capaz de escrever praticamente qualquer
funo que desejarmos. Podemos criar construes bem complexas usando ela, e mesmo fazer
coisas legais que no so possveis em muitas linguagens pesadas e populares;
Cada vez que atualizamos nosso programa e o rodamos novamente, chato ter que digitar todas
nossas funes. Neste captulo, vamos adicionar a funcionalidade de carregar cdigo de um arquivo
e rod-lo. Isto vai nos permitir comear a construir uma biblioteca padro. Ao longo do caminho,
vamos tambm adicionar suporte para comentrios no cdigo, strings e impresso.

Tipo String
Para o usurio carregar um arquivo, teremos que deix-lo fornecer uma string consistindo do nome
do arquivo. Nossa linguagem suporta smbolos, mas ainda no suporta strings, que podem incluir
espaos e outros caracteres. Precisamos adicionar este possvel tipo lval para especificar os nomes
de arquivos que precisamos.
Comeamos, como em outros captulos, adicionando um item ao nosso enum e adicionando um
item ao nosso lval para representar o tipo do dado.
enum { LVAL_ERR, LVAL_NUM,
LVAL_SYM, LVAL_STR,
LVAL_FUN, LVAL_SEXPR, LVAL_QEXPR };
/* Basico */
long num;
char* err;
char* sym;
char* str;

A seguir, podemos adicionar uma funo para construir lval strings, de maneira similar a como
fizemos para construir smbolos.
lval* lval_str(char* s) {
lval* v = malloc(sizeof(lval));
v->type = LVAL_STR;
v->str = malloc(strlen(s) + 1);
strcpy(v->str, s);
return v;
}

Tambm precisamos adicionar os itens relevantes em nossas funes que lidam com lval.
Para Deleo...

http://www.buildyourownlisp.com/contents

100

case LVAL_STR: free(v->str); break;

Para Cpia...
case LVAL_STR: x->str = malloc(strlen(v->str) + 1);
strcpy(x->str, v->str); break;

Para Igualdade...
case LVAL_STR: return (strcmp(x->str, y->str) == 0);

Para Nome do Tipo...


case LVAL_STR: return "String";

Para Impresso, precisamos fazer um pouco mais. A string que armazenamos internamente
diferente da string que queremos imprimir. Queremos imprimir uma string que um usurio possa
digitar na entrada, usando caracteres de escapa como \n para representar uma quebra de linha.
Por isso precisamos escap-la antes de imprimi-la. Por sorte, podemos usar uma funo mpc que
far isso para ns.
Na funo de impresso, adicionamos o seguinte...
case LVAL_STR:

lval_print_str(v); break;

Onde...
void lval_print_str(lval* v) {
/* Faz uma copia da string */
char* escaped = malloc(strlen(v->str)+1);
strcpy(escaped, v->str);
/* Passe-a pela funcao de escape */
escaped = mpcf_escape(escaped);
/* Imprima-a entre caracteres " */
printf("\"%s\"", escaped);
/* libera a string copiada */
free(escaped);
}

Lendo Strings
Agora precisamos adicionar suporte a interpretar strings. Como de costume, isto requer primeiro
adicionar uma nova regra gramatical chamada string e adicion-la ao nosso analisador sinttico.
A regra que vamos usar que representa uma string vai ser a mesma para as strings em C. Isto quer
dizer que uma string essencialmente uma srie de caracteres de escape, ou caracteres normais,
entre duas aspas duplas "". Podemos especificar isto como uma expresso regular dentro da nossa
string de gramtica como segue.
string

: /\"(\\\\.|[^\"])*\"/ ;

http://www.buildyourownlisp.com/contents

101

Isto parece complicado mas faz bastante sentido quando explicado em partes. L-se dessa forma.
Uma string um caractere ", seguido de zero ou mais de, ou uma barra invertida \\ seguida de
qualquer outro caractere ., ou qualquer coisa que no seja um caractere " ([^\\"]). Finalmente,
termina com outro caractere ".
Tambm precisamos adicionar um caso para lidar com isto na funo lval_read.
if (strstr(t->tag, "string")) { return lval_read_str(t); }

Como a string da entrada introduzida em forma escapada, precisamos criar uma funo
lval_read_str que lida com isso. Esta funo um pouco complicada porque ela precisa fazer
certas coisas: primeiro ela precisa remover os caracteres " de qualquer lado. A seguir, ela precisa
"desescapar" a string, convertendo sequncias de caracteres como \n para os caracteres realmente
codificados. Finalmente, precisa criar um novo lval e limpar qualquer coisa que tenha acontecido
no meio do caminho.
lval* lval_read_str(mpc_ast_t* t) {
/* Corta a aspa do final */
t->contents[strlen(t->contents)-1] = '\0';
/* Copia a string, pulando a primeira aspa */
char* unescaped = malloc(strlen(t->contents+1)+1);
strcpy(unescaped, t->contents+1);
/* Passa pela funcao unescape para desescapar */
unescaped = mpcf_unescape(unescaped);
/* Constroi um novo lval usando a string */
lval* str = lval_str(unescaped);
/* Libera a string e devolve */
free(unescaped);
return str;
}

Se isso tudo funcionar, devemos ser capaz de brincar um pouco com strings no prompt. A seguir,
vamos adicionar funes que realmente faam uso delas.
lispy> "hello"
"hello"
lispy> "hello\n"
"hello\n"
lispy> "hello\""
"hello\""
lispy> head {"hello" "world"}
{"hello"}
lispy> eval (head {"hello" "world"})
"hello"
lispy>

Comentrios
J que estamos adicionando nova sintaxe para a linguagem, podemos tambm dar uma olhada em
comentrios.

http://www.buildyourownlisp.com/contents

102

Da mesma forma que em C, podemos usar comentrios para informar outras pessoas (ou ns
mesmos) sobre o que o cdigo est tentando fazer ou por que ele foi escrito. Em C, comentrios vo
entre /* e */. Comentrios Lisp, por sua vez, comeam com ; e vo at o fim da linha.
Tentei pesquisar por que Lisps usam ; para comentrios, mas parece que as origens disso foram
perdidas nos nevoeiros do tempo. Eu imagino isso como uma pequena rebelio contra as linguagens
imperativas como C e Java, que usam pontos e vrgulas to descarada e frequentemente para
separar/terminar comandos. Comparando a Lisp, todas essas linguagens so apenas comentrios.
Ento em Lisp, um comentrio definido com um ponto e vrgula ; seguido de qualquer nmero de
caracteres que no so caracteres quebra de linha representados ou por \r ou por \n. Podemos
adicionar uma outra expresso regular para definir isso.
comment : /;[^\\r\\n]*/ ;

Da mesma forma que com strings, precisamos criar um novo parser e usar isso para atualizar nossa
linguagem em mpca_lang. Tambm precisamos lembrar de adicionar o parser em
mpc_cleanup, e atualizar o primeiro argumento inteiro para refletir o novo nmero de parsers
passados.
Nossa gramtica final agora se parece com isto.
mpca_lang(MPCA_LANG_DEFAULT,
"
number : /-?[0-9]+/ ;
symbol : /[a-zA-Z0-9_+\\-*\\/\\\\=<>!&]+/ ;
string : /\"(\\\\.|[^\"])*\"/ ;
comment : /;[^\\r\\n]*/ ;
sexpr
: '(' <expr>* ')' ;
qexpr
: '{' <expr>* '}' ;
expr
: <number> | <symbol> | <string>
| <comment> | <sexpr> | <qexpr>;
lispy
: /^/ <expr>* /$/ ;
",
Number, Symbol, String, Comment, Sexpr, Qexpr,

\
\
\
\
\
\
\
\
\
\
Expr, Lispy);

E a funo de limpeza agora se parece com isso.


mpc_cleanup(8,
Number, Symbol, String, Comment,
Sexpr, Qexpr, Expr,
Lispy);

Como comentrios so somente para programadores lendo o cdigo, nossa funo interna para llos consiste em apenas ignor-los. Podemos adicionar uma clusula para lidar com eles de maneira
similar a chaves e parnteses em lval_read.
if (strstr(t->children[i]->tag, "comment")) { continue; }

Comentrios no sero de muito uso no prompt interativo, mas sero bem teis para anotar arquivos
de cdigo.

http://www.buildyourownlisp.com/contents

103

Funo Load
Queremos construir uma funo que possa carregar (load) e avaliar um arquivo quando passada
uma string com seu nome. Para implementar esta funo vamos precisar fazer uso da nossa
gramtica, j que teremos que ler o contedo do arquivo, interpretar e avali-la. Nossa funo load
vai se fiar em nosso mpc_parser* chamado Lispy.
Por isso, da mesma forma que com funes, precisamos declarar adiantado nossos apontadores para
parsers, e coloc-los no topo do arquivo.
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*
mpc_parser_t*

Number;
Symbol;
String;
Comment;
Sexpr;
Qexpr;
Expr;
Lispy;

Nossa funo load ser como qualquer outra builtin. Precisamos comear verificando que o
argumento apenas uma string. Depois usamos a funo mpc_parse_contents para ler o
contedo do arquivo usando uma gramtica. Da mesma forma que mpc_parse, esta funo
analisa o contedo de um arquivo e devolve um objeto mpc_result, que no nosso caso uma
rvore de sintaxe abstrata ou um erro.
Levemente diferente do nosso prompt de comandos, ao analisar a sintaxe de um arquivo no
devemos trat-lo como se fosse uma expresso. Dentro de um arquivo, deixamos usurios listar
mltiplas expresses e avaliar todas elas individualmente. Para obter este comportamento,
precisamos percorrer cada expresso no contedo do arquivo e avali-las uma por uma. Se
houverem erros, devemos imprimi-los e continuar.
Se houver um erro de sintaxe, vamos extrair a mensagem e coloc-la em um lval de erro que
devolveremos. Se no h erros, o valor de retorno para esta builtin pode ser a expresso vazia. O
cdigo completo para isso se parece com o seguinte:
lval* builtin_load(lenv* e, lval* a) {
LASSERT_NUM("load", a, 1);
LASSERT_TYPE("load", a, 0, LVAL_STR);
/* Analisa conteudo do arquivo fornecido */
mpc_result_t r;
if (mpc_parse_contents(a->cell[0]->str, Lispy, &r)) {
/* Le conteudo */
lval* expr = lval_read(r.output);
mpc_ast_delete(r.output);
/* Avalia cada expressao */
while (expr->count) {
lval* x = lval_eval(e, lval_pop(expr, 0));
/* Caso avaliacao gerar erro, imprime-o */
if (x->type == LVAL_ERR) { lval_println(x); }

http://www.buildyourownlisp.com/contents

104

lval_del(x);
}
/* Deleta expressoes e argumentos */
lval_del(expr);
lval_del(a);
/* Devolve lista vazia */
return lval_sexpr();
} else {
/* Pega erro de sintaxe em uma string */
char* err_msg = mpc_err_string(r.error);
mpc_err_delete(r.error);
/* Cria nova mensagem de erro com ela */
lval* err = lval_err("Could not load Library %s", err_msg);
free(err_msg);
lval_del(a);

/* Devolve erro */
return err;

Argumentos da Linha de Comando


Com a capacidade de carregar arquivos, podemos tentar a sorte adicionando uma funcionalidade
tpica em outras linguagens de programao. Quando nomes de arquivos so fornecidos diretamente
como argumentos, podemos tentar rodar estes arquivos. Por exemplo, para rodar um arquivo python
algum poderia escrever python nome_do_arquivo.py.
Estes argumentos da linha de comando so acessveis usando as variveis argc e argv que so
fornecidos funo main. A varivel argc d o nmero de argumentos, e argv especifica cada
string. A argc sempre setada para no mnimo um, onde o primeiro argumento sempre o
comando inteiro invocado.
Isto significa que se argc setada para 1, podemos invocar o interpretador, seno podemos passar
cada outro argumento por nossa funo builtin_load.
/* Fornecida lista de arquivos */
if (argc >= 2) {
/* percorre cada nome de arquivo (comecando de 1) */
for (int i = 1; i < argc; i++) {
/* Lista de argumentos com um unico elemento, o nome do arquivo */
lval* args = lval_add(lval_sexpr(), lval_str(argv[i]));
/* Passa para a builtin load e pega o resultado */
lval* x = builtin_load(e, args);

http://www.buildyourownlisp.com/contents

105

/* Caso resultado for erro, imprima-o */


if (x->type == LVAL_ERR) { lval_println(x); }
lval_del(x);
}

Agora possvel escrever algum programa bsico e tentar invoc-lo usando este mtodo.
lispy example.lspy

Funo Print
Se vamos rodar programas da linha de comando, vamos querer faz-los jogar algo na sada, em vez
de apenas definir funes e outros valores. Podemos adicionar uma funo print ao nosso Lisp
que faz uso da nossa funo existente lval_print.
Esta funo imprime cada argumento separado por um espao e a seguir imprime um caractere
quebra de linha para terminar, devolvendo uma expresso vazia.
lval* builtin_print(lenv* e, lval* a) {
/* Imprime cada argumento, seguido de um espaco */
for (int i = 0; i < a->count; i++) {
lval_print(a->cell[i]); putchar(' ');
}
/* Imprime uma quebra de linha e deleta argumentos */
putchar('\n');
lval_del(a);
}

return lval_sexpr();

Funo Error
Podemos tambm fazer uso das strings para adicionar uma funo para reportar erros. Ela pode
receber como entrada uma string fornecida pelo usurio e fornec-la como uma mensagem de erro
para lval_err.
lval* builtin_error(lenv* e, lval* a) {
LASSERT_NUM("error", a, 1);
LASSERT_TYPE("error", a, 0, LVAL_STR);
/* Constroi Error a partir do primeiro argumento */
lval* err = lval_err(a->cell[0]->str);
/* Deleta argumentos e retorna */
lval_del(a);
return err;
}

http://www.buildyourownlisp.com/contents

106

O ltimo passo registrar estas funes como builtins. Agora finalmente podemos comear a
construir bibliotecas e escrev-las em arquivos.
/* String Functions
lenv_add_builtin(e,
lenv_add_builtin(e,
lenv_add_builtin(e,
lispy>
"Hello
()
lispy>
Error:
lispy>
"Hello
()
lispy>

*/
"load", builtin_load);
"error", builtin_error);
"print", builtin_print);

print "Hello World!"


World!"
error "This is an error"
This is an error
load "hello.lspy"
World!"

Finalizando
Este ser o ltimo captulo em que vamos explicitamente trabalhar na implementao C de Lisp. O
resultado deste captulo ser o estado final da implementao da sua linguagem.
A contagem final do nmero de linhas deve estar em algo perto das 1000 linhas de cdigo. Escrever
esta quantidade de cdigo no trivial. Se voc chegou at aqui, escreveu um programa de verdade
e comeou um projeto decente. As habilidades que voc aprendeu aqui devem ser transferveis, e
dar-lhe a confiana de buscar seus prprios objetivos e metas. Voc agora tem um programa
complexo e belo que voc pode interagir e brincar. Isto algo do que se orgulhar. V em frente e
mostre para seus amigos e a famlia!
No prximo captulo vamos comear a usar nosso Lisp para construir uma biblioteca padro de
funes comuns. Depois disso, descrevo alguns possveis melhoramentos e direes em que a
linguagem pode ser levada. Embora terminado o meu envolvimento, isto realmente apenas o
comeo. Obrigado por seguir at aqui, e boa sorte com qualquer C que voc escreva no futuro!

Referncia
strings.c

Metas Bnus
Adapte a funo builtin join para funcionar com strings.
Adapte a funo builtin head para funcionar com strings.
Adapte a funo builtin tail para funcionar com strings.

http://www.buildyourownlisp.com/contents

107

Crie uma funo builtin read que leia e converta uma string em uma Q-expression.
Crie uma funo builtin show que imprima o contedo das strings como elas so (noescapadas).
Crie um valor especial ok para retornar no lugar de expresses vazias ().
Adicione funes para envolver todas as funes C de manipulao de arquivos como
fopen e fgets.

Biblioteca Padro Captulo 15


Minimalismo
O Lisp que construmos propositalmente mnimo. Adicionamos um nmero pequeno de
estruturas e builtins. Quando escolhemos essas coisas com cuidado, como fizemos, deve nos
permitir adicionar tudo o mais necessrio linguagem.
A motivao para o minimalismo dupla. A primeira vantagem que torna o ncleo da linguagem
simples de depurar e fcil de aprender. Isso um grande benefcio para desenvolvedores e usurios.
Como a Navalha de Occam, quase sempre melhor remover qualquer desperdcio se resultar numa
linguagem igualmente expressiva. A segunda razo que ter uma linguagem pequena
esteticamente mais legal. inteligente, interessante e divertido ver quo pequeno conseguimos
fazer o ncleo da linguagem, e ainda ter algo til do outro lado. Como hackers, que devemos ser
por ora, isto algo que curtimos.

tomos
Ao lidar com condicionais, no adicionamos nenhum tipo booleano nossa linguagem. Por causa
disso, tambm no adicionamos true ou false. Ao invs disso, simplesmente usamos nmeros.
Legibilidade todavia ainda importante, ento podemos definir algumas constantes para representar
estes valores.
Numa observao parecida, muitos lisps usam a palavra nil para representar a lista vazia {}.
Podemos adicionar isso tambm. Estas constantes so s vezes chamadas de tomos porque elas so
fundamentais e constantes.
O usurio no forado a usar estas constantes nomeadas, e podem usar nmeros e listas vazias no
lugar, como quiserem. Esta escolha empodera usurios, algo em que ns acreditamos.
; Atomos
(def {nil} {})
(def {true} 1)
(def {false} 0)

http://www.buildyourownlisp.com/contents

108

Blocos de Construo
J inventamos uma boa quantidade de funes legais que usamos nos exemplos. Uma destas nossa
funo fun que nos permite declarar funes de maneira mais clara. Devemos definitivamente
inclu-la em nossa biblioteca padro.
; Definicoes de funcoes
(def {fun} (\ {f b} {
def (head f) (\ (tail f) b)
}))

Tambm tivemos nossas funes unpack e pack. Estas tambm sero essenciais para usurios.
Devemos inclu-las, juntamente com seus pseudnimos curry e uncurry.
; Desempacota lista para funcao
(fun {unpack f l} {
eval (join (list f) l)
})
; Empacota lista para funcao
(fun {pack f & xs} {f xs})
; Curried and Uncurried calling
(def {curry} unpack)
(def {uncurry} pack)

Digamos que queiramos fazer vrias coisas em ordem. Uma maneira de fazer isto colocar cada
coisa a fazer como argumento uma funo. Sabemos que argumentos so avaliados em ordem da
esquerda para direita, o que essencialmente sequenciar eventos. Para funes como print e
load no nos importamos muito com o que elas avaliam, mas nos importamos com a ordem em
que isso acontece.
Por isso, podemos criar uma funo do que avalia algumas expresses em ordem e retorna a ltima.
Ela se fia na funo last que retorna o ltimo elemento da lista. Vamos defini-la depois.
; Executa varias coisas em sequencia
(fun {do & l} {
if (== l nil)
{nil}
{last l}
})

s vezes queremos guardar os resultados em variveis locais usando o operador =. Quanto estamos
dentro de uma funo, isto vai implicitamente salvar resultados apenas localmente, mas s vezes
queremos abrir um escopo ainda mais local. Para isto, podemos criar uma uno let que cria uma
funo vazia para cdigo acontecer dentro, e avali-lo.
; Abre novo escopo
(fun {let b} {
((\ {_} b) ())
})

http://www.buildyourownlisp.com/contents

109

Podemos usar isto em conjunto com do para nos certificar que variveis no vazam para fora de seu
escopo.
lispy> let {do (= {x} 100) (x)}
100
lispy> x
Error: Unbound Symbol 'x'
lispy>

Operadores Lgicos
No definimos nenhum operadores locais como and e ou em nossa linguagem. Esta pode ser uma
boa coisa para adicionar depois. Por enquanto, podemos usar operadores aritmticos para emul-los.
Pense em como essas funes vo funcionar quando encontrar 0 ou 1 para suas diferentes entradas.
; Funcoes Logicas
(fun {not x}
{- 1 x})
(fun {or x y} {+ x y})
(fun {and x y} {* x y})

Funes Variadas
Aqui esto algumas outras funes variadas que no se encaixam mesmo em qualquer lugar. Veja se
voc consegue adivinhar sua funcionalidade pretendida.
(fun {flip f a b} {f b a})
(fun {ghost & xs} {eval xs})
(fun {comp f g x} {f (g x)})

A funo flip recebe uma funo f e dois argumentos a e b. Ela ento aplica f a e b na ordem
invertida. Isto pode ser til quando queremos que uma funo seja parcialmente avaliada. Se
queremos avaliar parcialmente uma funo apenas passando-a em seu segundo argumento, podemos
usar flip para nos dar uma nova funo que receba os primeiros dois argumentos em ordem
invertida.
Isto significa que caso queira aplicar o segundo argumento de uma funo, voc pode simplesmente
aplicar o primeiro argumento ao flip desta funo.
lispy>
()
lispy>
1
lispy>
()
lispy>
()
lispy>
1

(flip def) 1 {x}


x
def {define-one} ((flip def) 1)
define-one {y}
y

http://www.buildyourownlisp.com/contents

110

lispy>

No consegui pensar em nenhum uso para a funo ghost, mas pareceu interessante. Ela
simplesmente recebe um nmero qualquer de argumentos e os avalia como se fossem a prpria
expresso. Ento ela fica na frente de uma expresso como um fantasma, no interagindo com ou
mudando o comportamento do programa em nada. Se voc puder pensar em algum uso para ela, eu
adoraria ouvir.
lispy> ghost + 2 2
4

A funo comp usara para compor duas funes. Ela recebe como entrada f, g e um argumento
para g. Ela ento aplica este argumento g e aplica o resultado novamente f. Isto pode ser usado
para compor funes em uma nova funo que aplica ambas em srie. Como antes, podemos usar
isto em combinao com avaliao parcial para construir funes complexas a partir de outras mais
simples.
Por exemplo, podemos compor duas funes. Uma nega um nmero e outra desempacota uma lista
de nmeros para os multiplicar com *.
lispy>
4
lispy>
-4
lispy>
(\ {x}
lispy>
()
lispy>
-16
lispy>

(unpack *) {2 2}
- ((unpack *) {2 2})
comp - (unpack *)
{f (g x)})
def {mul-neg} (comp - (unpack *))
mul-neg {2 8}

Funes de Lista
A funo head usada para obter o primeiro elemento duma lista, mas o que ela retorna ainda est
envolto na lista. Caso queiramos pegar o elemento fora desta lista, precisamos extra-lo de alguma
maneira.
Listas com apenas um elemento avaliam para apenas aquele elemento, ento podemos usar a funo
eval para fazer esta extrao. Podemos tambm definir algumas funes utilitrias para ajudar a
extrair o primeiro, segundo ou terceiro elemento de uma lista. Vamos usar mais estas funes mais
tarde.
; First, Second, or
(fun {fst l} { eval
(fun {snd l} { eval
(fun {trd l} { eval

Third
(head
(head
(head

Item in List
l) })
(tail l)) })
(tail (tail l))) })

http://www.buildyourownlisp.com/contents

111

Olhamos brevemente em algumas funes recursivas de listas alguns captulos atrs. Naturalmente
existem muitas mais que podemos definir usando esta tcnica.
Para encontrar o tamanho de uma lista podemos recorrer sobre ela adicionado 1 ao comprimento da
cauda. Para encontrar o ensimo elemento de uma lista podemos executar a operao tail e contar
para baixo at atingirmos 0. Para obter o ltimo elemento de uma lista podemos simplesmente
acessar o elemento do comprimento menos um.
; Comprimento da lista
(fun {len l} {
if (== l nil)
{0}
{+ 1 (len (tail l))}
})
; Nth - enesimo elemento
(fun {nth n l} {
if (== n 0)
{fst l}
{nth (- n 1) (tail l)}
})
; Ultimo elemento
(fun {last l} {nth (- (len l) 1) l})

H muitas outras funes teis que seguem este mesmo padro. Podemos definir funes para
tomar ou descartar os primeiros tantos elementos de uma lista, ou funes para checar se um valor
membro de uma lista.
; Tomar N itens
(fun {take n l} {
if (== n 0)
{nil}
{join (head l) (take (- n 1) (tail l))}
})
; Descartar N itens
(fun {drop n l} {
if (== n 0)
{l}
{drop (- n 1) (tail l)}
})
; Split at N
(fun {split n l} {list (take n l) (drop n l)})
; Element of List
(fun {elem x l} {
if (== l nil)
{false}
{if (== x (fst l)) {true} {elem x (tail l)}}
})

Todas estas funes seguem padres similares. Seria timo se houvesse uma maneira de extrair este

http://www.buildyourownlisp.com/contents

112

padro para que no tenhamos que digit-lo toda vez. Por exemplo, podemos querer uma maneira
de executar uma funo em cada elemento duma lista. Esta funo podemos definir chamando de
map. Ela recebe como entrada uma funo e uma lista. Para cada item na lista ela aplica a funo
para aquele item e o acrescenta na frente da lista. A seguir ela aplica map cauda da lista.
; Apply Function to List
(fun {map f l} {
if (== l nil)
{nil}
{join (list (f (fst l))) (map f (tail l))}
})

Com isto podemos fazer algumas coisas legais que se parecem com laos. Em algumas maneiras,
este conceito mais poderoso que laos. Em vez de pensar sobre executar alguma funo em cada
elemento da lista, podemos pensar sobre agir em todos elementos de uma vez s. Ns mapeamos a
lista em vez de mudar cada elemento.
lispy> map - {5 6 7 8 2 22 44}
{-5 -6 -7 -8 -2 -22 -44}
lispy> map (\ {x} {+ x 10}) {5 2 11}
{15 12 21}
lispy> print {"hello" "world"}
{"hello" "world"}
()
lispy> map print {"hello" "world"}
"hello"
"world"
{() ()}
lispy>

Uma adaptao desta ideia uma funo filter que recebe uma funo condicional e somente
inclui itens de uma lista que correspondam quela condio.
; Aplica Filtro a uma Lista
(fun {filter f l} {
if (== l nil)
{nil}
{join (if (f (fst l)) {head l} {nil}) (filter f (tail l))}
})

Assim que fica na prtica:


lispy> filter (\ {x} {> x 2}) {5 2 11 -7 8 1}
{5 11 8}

Alguns laos no agem exatamente em uma lista, mas acumular algum total ou condensam a lista
em um valor nico. Estes so laos como somatrios e produtrios, e podem ser expressos de
maneira parecida funo len que j definimos.
So chamados folds (dobras) e funcionam assim: supridos com uma funo f, um valor base z e
uma lista l, eles fundem cada elemento na lista com o total, comeando com o valor base.
; Fold Left (Dobra a Esquerda)

http://www.buildyourownlisp.com/contents

113

(fun {foldl f z l} {
if (== l nil)
{z}
{foldl f (f z (fst l)) (tail l)}
})

Usando folds podemos definir as funes sum e product de maneira bem elegante.
(fun {sum l} {foldl + 0 l})
(fun {product l} {foldl * 1 l})

Funes Condicionais
Ao definir nossa funo fun j mostramos quo poderosa nossa linguagem na sua capacidade de
definir funes que se parecem com sintaxe. Um outro exemplo disso encontrado em emular os
comandos C switch e case. Em C, eles so construdos na linguagem, mas para nossa linguagem
podemos defini-los como parte da biblioteca.
Podemos definir uma funo select que recebe zero ou mais listas de dois elementos como
entrada. Para cada lista de dois elementos nos argumentos, ela primeiro avalia o primeiro argumento
do par. Caso for verdadeiro, ento ele avalia e retorna o segundo item, caso contrrio ele executa a
mesma coisa novamente no resto da lista.
(fun {select & cs} {
if (== cs nil)
{error "No Selection Found"}
{if (fst (fst cs)) {snd (fst cs)} {unpack select (tail cs)}}
})

Podemos definir uma funo otherwise (significando, caso contrrio) que sempre avalie como
true. Isto funciona um como a palavra-chave default em C.
; Caso default
(def {otherwise} true)
; Imprime sufixo do dia do mes
(fun {month-day-suffix i} {
select
{(== i 0) "st"}
{(== i 1) "nd"}
{(== i 3) "rd"}
{otherwise "th"}
})

Isto na verdade mais poderoso que o comando switch em C. Em C, no lugar de passar


condies, o valor da entrada comparado apenas para igualdade com um nmero de candidatos
constantes. Ns tambm podemos definir esta funo em nosso Lisp, onde comparamos um valor a
um nmero de candidatos. Nesta funo, recebemos um valor x seguido de zero ou mais listas de
dois elementos novamente. Se o primeiro elemento na lista de dois elementos for igual a x, o

http://www.buildyourownlisp.com/contents

114

segundo elemento avaliado, seno o processo continua lista abaixo.


(fun {case x & cs} {
if (== cs nil)
{error "No Case Found"}
{if (== x (fst (fst cs))) {snd (fst cs)} {
unpack case (join (list x) (tail cs))}}
})

A sintaxe para esta funo se torna bem legal e simples. Tente ver se voc consegue pensar em
outras estruturas de controle ou funes teis que voc gostaria de implementar usando estes tipos
de mtodos.
(fun {day-name x} {
case x
{0 "Monday"}
{1 "Tuesday"}
{2 "Wednesday"}
{3 "Thursday"}
{4 "Friday"}
{5 "Saturday"}
{6 "Sunday"}
})

Fibonacci
Nenhuma biblioteca padro seria completa sem uma definio obrigatria da funo Fibonacci.
Usando todas as coisas que definimos acima, podemos escrever uma funo fib fofinha que
realmente bem legvel e semanticamente clara.
; Fibonacci
(fun {fib n} {
select
{ (== n 0) 0 }
{ (== n 1) 1 }
{ otherwise (+ (fib (- n 1)) (fib (- n 2))) }
})

Este o fim da biblioteca padro que escrevi. Construir uma biblioteca padro uma parte divertida
do projeto de uma linguagem, porque voc pode ser criativo e determinado no que entra e no que
fica fora. Tente inventar alguma coisa que voc curta. Explore o que possvel definir e faa o que
for interessante.

http://www.buildyourownlisp.com/contents

115

Referncia
prelude.lspy

Metas Bnus
Reescreva a funo len usando foldl.
Reescreva a funo elem usando foldl.
Incorpore sua biblioteca padro diretamente dentro da linguagem. Faa carregar ao iniciar.
Escreva alguma documentao para sua biblioteca padro, explicando o que cada uma das
funes fazem.
Escreva alguns programas exemplos usando sua biblioteca padro, para usurios
aprenderem com eles.
Escreva alguns casos de teste para cada uma das funes na sua biblioteca padro.

Projetos Extras Captulo 16


Apenas o Comeo
Embora fizemos bastante com nosso Lisp, ainda est longe de ser uma linguagem de programao
inteiramente completa, pronta para produo. Se voc tentar us-la para qualquer projeto
suficientemente grande, haver uma certa quantidade de problemas que voc vai topar e melhorias
que ter que fazer. Resolver esses problemas seria o que a traria mais para o escopo de uma
linguagem madura.
Aqui esto algumas das questes que voc provavelmente iria encontrar, possveis solues para
estes problemas e algumas outras ideias divertidas para melhorias. Algumas podem precisar de
algumas centenas de linhas de cdigo, outras alguns milhares. A escolha do que atacar sua. Se
voc se afeioar sua linguagem, talvez curta fazer alguns desses projetos.

Tipos Nativos
Atualmente nossa linguagem apenas envolve os tipos nativos C long e char*. Isto bem
limitante caso queira fazer qualquer tipo de computao til. Nossas operaes nestes tipos de
dados so tambm bem limitadas. Idealmente nossa linguagem deveria envolver todos os tipos
nativos C e permitir maneiras de manipul-los. Uma das adies mais importantes seria a
capacidade de manipular nmeros decimais. Para isto voc poderia envolver o tipo double e

http://www.buildyourownlisp.com/contents

116

operaes relevantes. Com mais de um tipo numrico precisamos nos certificar que os operadores
aritmticos como + e - funcionam em todos eles, e em combinao.
Adicionar suporte a tipos nativos deve ser interessante para pessoas desejando fazer computao
com nmeros decimais e de ponto flutuante em suas linguagens.

Tipos Definidos pelo Usurio


Assim como adicionar suporte para tipos nativos, seria bom dar aos usurios a capacidade de
adicionar seus prprios tipos, assim como usamos structs em C. A sintaxe ou maneira de usar isto
sua escolha. Esta uma parte essencial de tornar nossa linguagem usvel para qualquer projeto de
tamanho razovel.
Esta tarefa pode ser interessante para qualquer pessoa com alguma ideia especfica de como eles
gostariam de desenvolver a linguagem, e como gostariam que o projeto final se parea.

Literal para Listas


Alguns Lisps usam colchetes [] para dar uma notao literal para listas de valores avaliados. Isto
acar sinttico para escrever coisas como list 100 (+ 10 20) 300. Em vez disso, ele te
deixa escrever [100 (+ 10 20) 300]. Em algumas situaes isto claramente melhor, mas
isso vai ocupar os caracteres [] que possivelmente poderia ser usados para propsitos mais
interessantes.
Esta deve ser uma adio simples para pessoas querendo experimentar adicionar sintaxe extra.

Interao com Sistema Operacional


Uma parte essencial de erguer uma linguagem d-la capacidades apropriadas para abrir, ler e
escrever arquivos. Isto significa envolver todas as funes C como fread, fwrite, fgetc, etc
em equivalentes Lisp. Esta uma tarefa relativamente tranquilo, mas requer escrever um grande
nmero de funes envolvendo C (wrappers). por isso que no fizemos em nossa linguagem.
Em nota similar, seria timo dar acesso nossa linguagem a quaisquer chamadas ao sistema
operacional que sejam apropriadas. Deveramos d-la capacidade de mudar o diretrio, listar
arquivos em um diretrio e este tipo de coisa. Esta uma tarefa fcil, mas novamente requer
envolver bastantes funes C. essencial para qualquer uso prtico dessa linguagem como uma
linguagem de scripts.
Pessoas desejando fazer uso da sua linguagem para fazer simples tarefas de scripts e manipulao
de strings podem estar interessadas em implementar este projeto.

http://www.buildyourownlisp.com/contents

117

Macros
Muitos outros Lisps permitem voc escrever coisas como (def x 100) para definir o valor 100
para x. Em nosso Lisp isto no funcionaria porque iria tentar avaliar o x para o valor que foi
armazenado em x no ambiente. Em outros Lisps estas funes so chamadas macros, e quando
encontradas elas param a avaliao dos seus argumentos e os manipulam no-avaliados. Elas
permitem escrever coisas que parecem chamadas normais a funes, mas na verdade fazem coisas
complexas e interessantes.
Estas so coisas divertidas de ter em uma linguagem. Elas fazem com que voc possa colocar um
pouco de mgica no funcionamento. Em muitos casos isso pode deixar a sintaxe melhor ou permitir
o usurio no ter que se repetir.
Eu gosto como nossa linguagem lida com coisas como def e if sem recorrer a macros, mas se
voc no gosta como ela funciona atualmente, e quer que seja mais parecida com Lisps
convencionais, isto pode ser algo que voc esteja interessado em implementar.

Tabela Hash de variveis


No momento, quando procuramos nomes de variveis em nossa linguagem, simplesmente fazemos
uma busca linear por todas as variveis no ambiente. Isto se torna cada vez mais ineficiente quanto
mais variveis temos definidas.
Uma maneira mais eficiente de fazer isso implementar uma Tabela Hash. Esta tcnica converte o
nome da varivel em um inteiro e usa isto para indexar em um array de tamanho conhecido para
encontrar o valor associado a este smbolo. Esta uma estrutura de dados muito importante em
programao e vai aparecer em todo lugar por causa da sua performance fantstica sob cargas
pesadas.
Qualquer um interessado em aprender mais sobre estruturas de dados e algoritmos ser espero de
tentar implementar esta estrutura de dados ou uma de suas variaes.

Alocao com Pool


Nosso Lisp muito simples, no rpido. Sua performance relativa algumas linguagens de
scripting como Python e Ruby. A maior parte do overhead de performance em nosso programa vem
do fato que fazer praticamente qualquer coisa requer que construamos e destruamos lval. Por isso,
ns chamamos malloc muito frequentemente. Esta uma funo lenta porque requer que o
sistema operacional faa um pouco de gerenciamento para ns. Ao fazer clculos h bastante
cpias, alocaes e desalocaes de tipos lval.

http://www.buildyourownlisp.com/contents

118

Caso desejarmos reduzir este overhead, precisamos reduzir o nmero de chamadas a malloc. Uma
maneira de fazer isso chamar malloc uma vez no comeo do programa, alocando um grande
"tanque" (pool) de memria. Devemos ento substituir todas nossas chamadas a malloc por
chamadas a alguma funo que corte e distribua esta memria para uso no programa. Isto significa
que estamos emulando um pouco do comportamento do sistema operacional, mas numa maneira
local mais rpida. Esta ideia chamada memory pool allocation (alocao de tanque de memria) e
uma tcnica comum em desenvolvimento de jogos e outras aplicaes sensveis ao desempenho.
Isto pode ser complicado de implementar corretamente, mas conceitualmente no precisa ser
complexo. Caso queira um mtodo rpido de obter grandes ganhos em performance, dar uma olhada
nisso pode lhe interessar.

Coleta de Lixo
Quase todas outras implementaes de Lisps atribuem variveis diferentemente do nosso. Elas no
armazenam uma cpia do valor no ambiente, mas na verdade um apontador (ou referncia) para ele
diretamente. Como esto usando apontadores em vez de cpias, da mesma forma que em C, h
muito menos overhead ao usar grandes estruturas de dados.

Se armazenamos apontadores a valores, no lugar de cpias, precisamos nos certificar que o


dado apontado no excludo antes que algum outro valor tente us-lo. Queremos exclu-lo
quando no h mais nenhuma referncia a ele. Um mtodo para fazer isto, chamado Mark and
Sweep (marca e varre) monitorar estes valores que esto no ambiente, assim como todo valor
que foi alocado. Quando uma varivel colocada no ambiente, ela e tudo o que ela referencia
marcada. A seguir, quando desejamos liberar memria, podemos iterar sobre toda alocao e
deletar qualquer uma que no esteja marcada.
Isto chamado coleta de lixo (em ingls, garbage collection) e uma parte integral de muitas
linguagens de programao. Assim como em alocao com pools, implementar um coletor de lixo
no precisa ser complicado, mas precisa ser feito cuidadosamente, em particular se voc deseja
faz-lo eficiente. Implementar isto seria essencial para fazer esta linguagem prtica para trabalhar
com grande quantidades de dados. Um tutorial particularmente bom em implementar um coletor de
lixo em C pode ser encontrado aqui (em ingls).
Isto deve interessar qualquer um interessado no desempenho da linguagem e desejando mudar a
semntica de como variveis so armazenadas e modificadas na linguagem.

http://www.buildyourownlisp.com/contents

119

Otimizao de Chamada em Cauda (Tail Call Optimisation)


Nossa linguagem de programao usa recurso para fazer seu lao. Isto conceitualmente uma
maneira inteligente de faz-lo, mas na prtica bem pobre. Funes recursivas chamam a si
prprias para coletar todos os resultados parciais de uma computao, e s ento combinam todos
os resultados juntos. Este um jeito bem desperdiador de fazer computao quando resultados
parciais podem ser acumulados como algum total sobre um lao. Isto particularmente
problemtico para laos que sero rodados por muitas, ou infinitas, iteraes.
Algumas funes recursivas podem ser automaticamente convertidas para laos while
correspondentes, que acumulam totais passo a passo, em vez de tudo de uma vez. Esta converso
automtica chamada otimizao de chamada em cauda (ou tail call optimization) e
uma otimizao essencial para programas que fazem bastante iterao com recurso.
Pessoas interessadas em otimizaes de compiladores e as correspondncias entre diferentes formas
de computao podem achar este projeto interessante.

Escopo Lxico
Quando nossa linguagem tenta buscar uma varivel que j foi definida, ela joga um erro. Seria
melhor se ela pudesse dizer quais variveis no esto definidas antes de avaliar o programa. Isto
poderia nos permitir evitar erros de digitao e outros bugs. Encontrar esses problemas antes do
programa rodar chamado escopo lxico, e usa as regras para definies de variveis para tentar
inferir quais variveis esto definidas e quais no esto em cada ponto do programa, sem fazer
qualquer avaliao.
Esta pode ser uma tarefa difcil de fazer corretamente, mas deve ser interessante para qualquer um
que queira fazer sua linguagem de programao mais segura e menos propensa a erros.

Tipagem Esttica
Cada valor no nosso programa tem um tipo associado a ele. Sabemos isto antes de qualquer
avaliao ter acontecido. Nossas funes builtin tambm apenas recebem certos tipos como entrada.
Deveramos ser capazes de usar esta informao para inferir os tipos de novas funes e valores
definidos pelo usurio. Tambm podemos usar esta informao para checar se as funes esto
sendo chamadas com os tipos corretos antes de rodarmos o programa. Isto reduzir quaisquer erros
decorrentes de chamar funes com tipos incorretos antes da avaliao. Esta verificao chamada
de tipagem esttica.
Sistemas de tipos so uma parte interessante e fundamental da cincia da computao. Eles so
atualmente a melhor maneira que conhecemos de detectar erros antes de rodar um programa.

http://www.buildyourownlisp.com/contents

120

Qualquer um interessado em segurana de uma linguagem de programao deve achar este projeto
realmente interessante.

Concluso
Muito obrigado por ler este livro. Espero que voc encontrou algo interessante nas suas pginas. Se
voc curtiu, por favor conte a seus amigos a respeito. Se voc vai continuar a desenvolver sua
linguagem, ento muito boa sorte e espero que voc aprenda muitas coisas mais sobre C, linguagens
de programao e cincia da computao.
Acima de tudo, espero que voc tenha se divertido construindo seu prprio Lisp. At a prxima!

Crditos Construa Seu Prprio Lisp


Agradecimentos Especiais
Agradecimentos especiais a meus amigos e famlia por seu apoio, em particular a Francesca Shaw
por aguentar eu passando todo meu tempo neste projeto, e a Caroline Holden por revisar.
Obrigado a Miran Lipovaca, Frederic Trottier-Hebert, e Jonathan Tang, autores de Learn you a
Haskell, Learn you some Erlang, e Write Yourself a Scheme in 48 Hours pela inspirao, e suas
ideias e pensamentos.

Leitores Beta
Obrigado a todos meus leitores Beta pelo seus valiosos feedback, correes, sugestes, e
encorajamento. Muito obrigado aos usurios Reddit neelaryan, bitsbytesbikes, acesHD, CodyChan,
northClan, da4c30ff, nowords, ozhank, crackez, stubarfoo, viezebanaan, JMagnum86,
uNEV6X29rpf3, fortyninezeronine, skeeto, miketaylr, wonnernaus, Barthalion, codyrioux, sigjuice,
yoshiK, u-n-sky,

Crditos das Imagens (Imagens somente no original)


Muito obrigado a todo mundo que tornaram suas imagens e fotos disponveis sob a licena Creative
Commons. Espero que em fazer este livro disponvel para leitura online gratuitamente, tenha dado
algo em troca pela criatividade e boa vontade da comunidade.
Todas as imagens esto licenciadas sob CC BY 2.0 a menos que especificado diferente.

http://www.buildyourownlisp.com/contents

Ada Lovelace (1815-1852) by Mathematical Association of America


Fridge by sweethappychick1985
Mike Tyson by birzer
Amelia on MacBook Pro by Paulo Ordoveza
smashed Computer by cosmic yard sale
Cover of program, 1897, by Mucha by Mary Margret
German Pointer by Rory Nolan
Reptile Park #1 by Brandon Holton
Octopus Vulgaris (I think?) by Pat David
Felix by andreavallejos
The Xmas tree has been drinking by Kevin Dooley
For understanding recursion... by Andreas.
Walter White by Adam Barhan
Plumbing APIs by Salim Virji
Self Storage. Ghost Mural by Brad Coy
Building site in Berlin by Ingo Ronner
LISP Theory & Practice by Paul Downey
Strawberry Macro by atramos
Mutant, No Explaination by Orin Zebest
Emergence of mysterious Black Box by thierry ehrmann
SCF_MIT_2009 by Ulrick
Curry set by Vera & Jean-Christophe
Our Pug Is Cute When He Is Asleep by VeryMotoMoto
String in the Sun by Syops1st
St John's College Old Library - West Side by ben.gallagher
kid to do list, list, Be happy and go home by Carissa Rogers
Fat luck. by Sascha Erni, .rb
Static Electricity by andrechinn
Ada Lovelace Portrait by Alfred Edward Chalon est licensiado no Domnio Pblico

Traduo
Texto traduzido do original em ingls por Elias Dorneles (twitter: @eliasdorneles).

121

Das könnte Ihnen auch gefallen