forked from orangeduck/BuildYourOwnLisp
-
Notifications
You must be signed in to change notification settings - Fork 1
/
chapter4_interactive_prompt.html
284 lines (179 loc) · 18 KB
/
chapter4_interactive_prompt.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
<h1>Um Prompt Interativo <small>• Capítulo 4</small></h1>
<h2>Leia, Avalie, Imprima</h2> <hr/>
<div class='pull-right alert alert-warning' style="margin: 15px; text-align: center;">
<img src="/static/img/reptile.png" alt="reptile" class="img-responsive" width="187px" height="273px"/>
<p><small>Réptil • Soa quase igual <em>REPL</em></small></p>
</div>
<p>À medida que construímos nossa linguagem de programação precisaremos de alguma maneira de interagir com ela. C usa um compilador, onde você muda o seu programa, recompile e roda-o. Seria bom se pudéssemos fazer algo melhor, para interagir com a linguagem de maneira dinâmica, de maneira que possamos testar seu comportamento em diversas condições bem rapidamente. Para isso, construiremos algo chamado <em>prompt interativo</em>.</p>
<p>Isso é um programa que solicita (em inglês, <em>prompts</em>) alguma entrada ao usuário, e quando esta for fornecida, ele responde de volta com alguma mensagem. Usar isso será a maneira mais fácil de testar nossa linguagem de programação e ver como ela age. Este sistema é também chamado de <em>REPL</em>, que significa <em>read</em>-<em>evaluate</em>-<em>print</em> <em>loop</em> (laço leia-avalie-imprima). É uma maneira comum de interagir com uma linguagem de programação, que você talvez tenha usado antes em linguagens como <em>Python</em> ou <em>Ruby</em>.</p>
<p>Antes de construir um <em>REPL</em> completo, começaremos 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 usuário e avaliá-la, como se fosse um verdadeiro programa Lisp.</p>
<h2>Um prompt interativo</h2> <hr/>
<p>Para a construção básica, queremos escrever um laço que repetidamente escreve uma mensagem, e então espera por alguma entrada. Para obter entrada do usuário, podemos usar uma função chamada <code>fgets</code>, que lê qualquer entrada até uma quebra de linha. Precisamos de algum lugar para armazenar essa entrada do usuário. Para isso vamos declarar um <em>buffer de entrada</em> com tamanho constante.</p>
<p>Uma vez que tenhamos a entrada do usuário armazenada, podemos imprimi-la de volta usando uma função chamada <code>printf</code>.</p>
<pre><code data-language='c'>#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;
}</code></pre>
<div class="alert alert-warning">
<p><strong>O que é aquele texto em verde fraco?</strong></p>
<p>O código acima contem <em>comentários</em>. Estes são seções do código entre símbolos <code>/*</code> <code>*/</code>, que são ignorados pelo compilador, mas são usados para informar a pessoa lendo o que está acontecendo. Preste atenção neles!</p>
</div>
<p>Vamos repassar esse programa com um pouco mais de profundidade.</p>
<p>A linha <code>static char input[2048];</code> 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 usuário que é digitada na linha de comando. A palavra chave <code>static</code> torna essa variável local a este arquivo, e a seção <code>[2048]</code> é o que declara o seu tamanho.</p>
<p>Nós escrevemos um laço infinito usando <code>while (1)</code>. Em um bloco condicional, <code>1</code> sempre avalia como verdadeiro. Por isso, comandos dentro desse laço vão executar para sempre.</p>
<p>Para imprimir nosso prompt, usamos a função <code>fputs</code>. Esta é uma pequena variação de <code>puts</code> que não acrescenta um caractere de quebra de linha. Usamos a função <code>fgets</code> para obter entrada do usuário da linha de comando. Ambas funções requerem um arquivo para onde escrever, ou de onde ler. Para isso, fornecemos as variáveis especiais <code>stdin</code> e <code>stdout</code>. Estas são declaradas em <code><stdio.h></code> e são variáveis de arquivo especiais representando a entrada e a saída da linha de comando. Quando passamos essa variável, a função <code>fgets</code> espera o usuário digitar uma linha de texto, e quando ele o faz ela armazena-a no buffer <code>input</code>, incluindo o caractere quebra de linha. Para que a <code>fgets</code> não leia dados demais, também fornecemos o tamanho do buffer <code>2048</code>.</p>
<p>Para ecoar a mensagem de volta ao usuário, usamos a função <code>printf</code>. Esta é uma função que provê uma maneira de imprimir mensagens consistindo de vários elementos. Ela casa argumentos com padrões dentro da string fornecida. Por exemplo, em nosso caso, podemos ver o padrão <code>%s</code> na string dada. Isto significa que ele será substituído por qualquer que seja o próximo argumento passado a seguir, interpretado como uma string.</p>
<p>Para mais informação sobre esses diferentes padrões, veja a <a href="http://en.cppreference.com/w/c/io/printf">documentação</a> sobre <code>printf</code>.</p>
<div class="alert alert-warning">
<p><strong>Como é que vou saber sobre funções como <code>fgets</code> e <code>printf</code>?</strong></p>
<p>Não é imediatamente óbvio como saber sobre essas funções padrão, e quando usá-las. Ao confrontar com um problema, é necessário alguma experiência para saber quando este já foi resolvido para você por funções de bibliotecas.</p>
<p>Por sorte C tem uma biblioteca padrão bem pequena e quase todas elas podem ser aprendidas na prática. Se quiser fazer algo que seja bem básico, ou fundamental, vale a pena olhar a <a href="http://en.cppreference.com/w/c">documentação de referência</a> para a biblioteca padrão e checar se existem quaisquer funções incluídas que fazem o que você quer.</p>
</div>
<h2>Compilação</h2> <hr/>
<p>Você pode compilar isso com o mesmo comando que foi usado no segundo capítulo.</p>
<pre><code>cc -std=c99 -Wall prompt.c -o prompt</code></pre>
<p>Depois de compilá-lo, você deve tentar rodá-lo. Você pode usar <code>Ctrl+c</code> para sair do programa quando terminar. Se tudo deu certo, seu programa deve rodar mais ou menos como isso:</p>
<pre><code data-language='lispy'>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!
No You're a Stop being so rude!
lispy></code></pre>
<h2>Editando a entrada</h2> <hr/>
<p>Se você está trabalhando no Linux ou Mac, notará um comportamento estranho caso tente usar as setas do teclado para tentar editar a entrada.</p>
<pre><code data-language='lispy'>Lispy Version 0.0.0.0.3
Press Ctrl+c to Exit
lispy> hel^[[D^[[C
</code></pre>
<p>Usar as setas está criando estes caracteres estranhos <code>^[[D</code> ou <code>^[[C</code>, 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.</p>
<p>No Windows este comportamento é o padrão. No Linux e no Mac isso é providenciado por uma biblioteca chamada <code>editline</code>. No Linux e no Mac, precisamos substituir nossas chamadas para <code>fputs</code> e <code>fgets</code> com chamadas para as funções que esta biblioteca fornece.</p>
<p>Caso esteja desenvolvendo no Windows e queira simplesmente continuar, fique à vontade para pular para o fim deste capítulo já que as próximas seções podem não ser relevantes.</p>
<h3>Usando a Editline</h3>
<p>A biblioteca <code>editline</code> fornece duas funções que vamos usar chamadas <code>readline</code> (leia linha) e <code>add_history</code> (adicione histórico). A primeira função, <code>readline</code> é usada para ler entrada de algum prompt, permitindo editar a entrada. A segunda função <code>add_history</code> nos permite gravar o histórico das entradas de maneira que elas possam ser obtidas com as setas para cima e para baixo.</p>
<p>Nós substituímos <code>fputs</code> e <code>fgets</code> com chamadas para essas funções para obter o seguinte:</p>
<pre><code data-language='c'>#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> ");
/* 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;
}</code></pre>
<p>Nós <em>incluímos</em> alguns novos <em>cabeçalhos</em> (headers). Há o <code>#include <stdlib.h></code>, que nos dá acesso à função <code>free</code> usada mais tarde no código. Também adicionamos <code>#include <editline/readline.h></code> e <code>#include <editline/history.h></code> que nos dá acesso às funções <code>editline</code>, <code>readline</code> e <code>add_history</code>.</p>
<p>Em lugar de solicitar a entrada, e pegá-la com <code>fgets</code>, fazemos ambos em uma vez usando apenas <code>readline</code>. O resultado disso nós passamos para <code>add_history</code> para gravá-lo. Finalmente o imprimimos como antes, usando <code>printf</code>.</p>
<p>Diferentemente da <code>fgets</code>, a função <code>readline</code> retira o caractere quebra-de-linha à direita da entrada, então precisamos adicioná-lo na chamada à função <code>printf</code>. Também precisamos deletar a entrada nos dada pela função <code>readline</code> usando <code>free</code>. Isto é porque diferentemente da <code>fgets</code>, que escreve para um buffer existente, a função <code>readline</code> aloca nova memória quando é chamada. Quando devemos liberar memória é algo que vamos cobrir em profundidade em capítulos posteriores.</p>
<h3>Compilando com a Editline</h3>
<p>Se tentar compilar isto imediatamente com o comando anterior você obterá um erro. Isto é porque primeiro precisamos instalar a biblioteca <code>editline</code> no seu computador.</p>
<pre><code>fatal error: editline/readline.h: No such file or directory #include <editline/readline.h></code></pre>
<p>No <strong>Mac</strong> a biblioteca <code>editline</code> vem com <em>Command Line Tools</em> (ferramentas da linha de comando). Instruções para instalação podem ser encontradas no <a href="/chapter2_installation">Capítulo 2</a>. Você pode ainda receber um erro sobre o cabeçalho de histórico (history header) não ser encontrado. Neste caso, remova a linha <code>#include <editline/history.h></code>, pois esta pode não ser necessária.</p>
<p>No <strong>Linux</strong> você pode instalar a <em>editline</em> com <code>sudo apt-get install libedit-dev</code>. No Fedora, pode usar o comando <code>su -c "yum install libedit-dev*"</code>.</p>
<p>Tendo instalado a <em>editline</em>, você pode tentar compilar novamente. Dessa vez obterá um erro diferente.</p>
<pre><code>undefined reference to `readline'
undefined reference to `add_history'
</code></pre>
<p>Isto significa que você não <em>vinculou</em> (em inglês, <em>linked</em>) seu programa com a <code>editline</code>. Este processo de vinculação (ligação, ou <em>linking</em>) permite ao compilador diretamente embutir chamadas para a <code>editline</code> ao seu programa. Você pode fazê-lo ligar adicionando a opção <code>-ledit</code> ao seu comando de compilar, antes da opção de saída <code>-o</code>).</p>
<pre><code>cc -std=c99 -Wall prompt.c -ledit -o prompt</code></pre>
<p>Rode isso e verifique se agora consegue editar a entrada à medida que você digita.</p>
<div class="alert alert-warning">
<p><strong>Ainda não está funcionando!</strong></p>
<p>Alguns sistemas podem ter pequenas variações em como instalar, incluir e vincular com <code>editline</code>. Por exemplo, no Arch Linux o cabeçalho history da editline é <code>histedit.h</code>. Caso esteja tendo problemas, pesquise online e veja se consegue encontrar instruções específicas para sua distribuição em como instalar e usar a biblioteca <code>editline</code>. Se isso não der certo, busque por instruções para a biblioteca <code>readline</code>, que é um substituto para a editline. No Mac ela pode ser instalada usando HomeBrew ou MacPorts.</p>
</div>
<h2>O pré-processador C</h2> <hr/>
<p>Para um projeto tão pequeno, pode ser tranquilo ter que programar diferentemente dependendo de qual sistema operacional estamos usando, mas se queremos enviar o código fonte a um amigo em um sistema operacional diferente para me ajudar com a programação isso vai causar problemas. Num mundo ideal eu gostaria que meu código fonte fosse capaz de compilar não importa onde ou em qual computador ele está sendo compilado. Este é um problema geral em C, e é chamado <em>portabilidade</em>. Nem sempre há uma solução fácil ou correta.</p>
<div class='pull-right alert alert-warning' style="margin: 15px; text-align: center;">
<img src="/static/img/octopus.png" alt="octopus" class="img-responsive" width="266px" height="268px"/>
<p><small>Octopus • Soa quase igual Octothorpe <code>#</code></small></p>
</div>
<p>Mas C fornece um mecanismo para ajudar, chamado <em>o pré-processador</em>.</p>
<p>O pré-processador é um programa que roda antes do compilador. Ele tem uma gama de propósitos, e nós já o estamos usando sem saber. Qualquer linha que comece com um caractere octothorpe/jogo-da-velha/sustenido <code>#</code> é um comando do pré-processador. Nós o estamos usando para incluir (<em>include</em>) arquivos de cabeçalho, nos dando acesso às funções da biblioteca padrão e outras.</p>
<p>Um outro uso do pré-processador é detectar qual sistema operacional o código está sendo compilado, e usar isto para emitir código diferente.</p>
<p>Isto é exatamente como nós vamos fazê-lo. Se estivermos rodando Windows, vamos deixar o pré-processador emitir código com algumas funções que preparei imitando <code>readline</code> e <code>add_history</code>, senão vamos incluir os cabeçalhos da <code>editline</code> e usá-los.</p>
<p>Para declarar qual código o compilador deve emitir, podemos envolvê-lo em comandos do pré-processador <code>#ifdef</code>, <code>#else</code>, e <code>#endif</code>. Estes são como uma função <code>if</code> que acontece antes do código ser compilado. Todo o conteúdo do arquivo desde o primeiro <code>#ifdef</code> ao próximo <code>#else</code> é usado se a condição for verdadeira, caso contrário todo o conteúdo desde o <code>#else</code> até o último <code>#endif</code> será usado no lugar. Colocando isso em torno das nossas funções imitadoras, e de nossos cabeçalhos da editline, o código que será emitido deverá compilar no Windows, Linux ou Mac.</p>
<pre><code data-language='c'>#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;
}</code></pre>
<h2>Referência</h2> <hr/>
<references />
<h2>Metas bônus</h2> <hr/>
<div class="alert alert-warning">
<ul class="list-group">
<li class="list-group-item">› Mude o prompt de <code>lispy></code> para algo de sua escolha.</li>
<li class="list-group-item">› Mude o que é ecoado de volta ao usuário.</li>
<li class="list-group-item">› Adicione uma mensagem extra na informação de versão e como sair.</li>
<li class="list-group-item">› O que <code>\n</code> significa naquelas strings?</li>
<li class="list-group-item">› Que outros padrões podem ser usados com <code>printf</code>?</li>
<li class="list-group-item">› O que acontece quando você passa para <code>printf</code> uma variável que não casa com o padrão?</li>
<li class="list-group-item">› O que faz o comando do pré-processador <code>#ifndef</code>?</li>
<li class="list-group-item">› O que faz o comando do pré-processador<code>#define</code>?</li>
<li class="list-group-item">› Se <code>_WIN32</code> está definido no Windows, o que está definido no Linux ou Mac?</li>
</ul>
</div>
<h2>Navegação</h2>
<table class="table" style='table-layout: fixed;'>
<tr>
<td class="text-left"><a href="chapter3_basics"><h4>‹ O Básico</h4></a></td>
<td class="text-center"><a href="contents"><h4>• Conteúdo •</h4></a></td>
<td class="text-right"><a href="chapter5_languages"><h4>Linguagens ›</h4></a></td>
</tr>
</table>