Construção da linguagem hoc
Porque isso é interessante
No capítulo 8 do livro The Unix Programming Environment (UPE), Brian W. Kernighan e Rob Pike mostram como construir uma uma pequena linguagem Turing-completa chamada hoc
(higher-order calculator).
Em UPE, hoc
serve para apresentar as seguintes ferramentas do ambiente UNIX:
- yacc: um gerador de analisador sintático, ou seja, um programa que gera o código-fonte de um parser, a partir da descrição formal de uma linguagem;
- lex: um gerador de analisador léxico, muitas vezes usado em conjunto com yacc;
- make: um utilitário que automatiza tarefas na construção de programas.
Estudar a implementação de hoc
em C é uma boa maneira de aprender como funciona um interpretador por dentro. Um conhecimento básico de C é suficiente para acompanhar este exemplo.
🗒 Desde que o GNU Linux substituiu no mercado os UNIX proprietários, yacc e lex também foram superados por ferramentas livres mais modernas: GNU bison e flex. Em muitos ambientes, ao instalar bison e flex você ganha também atalhos chamados
yacc
elex
que emulam o funcionamento das ferramentas antigas.
Minha motivação
Decidi estudar este exemplo para aprender o básico de lex e yacc, antes de estudar o pacote SLY de David Beazley, que implementa funcionalidade semelhante em Python, usando metaprogramação em vez de geração de código. Meu plano é usar SLY na Oficina de Linguagens de Programação do Garoa Hacker Clube. Como achei o exemplo hoc
muito interessante, e não encontrei o livro UPE em português, resolvi contribuir para a nossa cultura de computação apresentando esse exemplo em nosso idioma.
Características de hoc
- só um tipo de dado: números de ponto flutuante;
- interpretador interativo: pode ser usado como um REPL ou lendo arquivos-fonte;
- operadores aritméticos e funções pré-definidas
sqrt
,log
,sin
etc.; - constantes pré-definidas
PI
,E
,PHI
etc.; - comandos de controle de fluxo:
if-else
,while
; - funções e procedimentos definidos pelo usuário, com recursividade.
Exemplo
Exibir números da sequência de Fibonacci menores do que 1000:
$ ./hoc fib.hoc
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
Código-fonte de fib.hoc
:
proc fib() {
a = 0
b = 1
while (b < $1) {
print b
c = b
b = a+b
a = c
}
print "\n"
}
fib(1000)
No código acima, a variável $1
é o primeiro argumento passado para fib
. A palavra reservada proc
serve para declarar um procedimento: uma sub-rotina que não devolve um valor, assim como um médodo do tipo void
em Java. Para declarar uma função que devolve um número em hoc
, usa-se func
.
🗒 A distinção entre procedimentos e funções é natural para quem já programou em Pascal ou Delphi. Muitas linguagens modernas não separam os dois conceitos claramente. Por exemplo, em Python não há procedimentos, há apenas funções que não devolvem nenhum valor explicitamente mas, implicitamente, devolvem o valor
None
, que a gente ignora.
O que há neste repositório
Os textos em português não são uma tradução direta do original e sim uma recriação com minhas próprias palavras. Em alguns pontos minhas explicações são mais detalhadas, em outros pontos são mais resumidas. O capítulo original tem cerca de 50 páginas, e o assunto é bem amplo, então em vários pontos os autores se desculpam pela abordagem rápida e superficial. Eu certamente não conheço o assunto melhor que eles.
Os códigos em C e yacc são razoavelmente fiéis ao original, exceto por algumas simplificações e também porque eu traduzi todos os identificadores de variáveis e funções que podiam ser traduzidos. Isso torna mais fácil para o leitor saber o que são identificadores nossos, como NUMERO
e aviso
, e o que são identificadores externos que somos obrigados a preservar, como main
ou yylex
.
O diretório /complete
é um fork do repositório richardfearn/hoc no GitHub. Richard Fearn modificou código original de 1984 para compilar e executar em um sistema GNU/Linux em 2012. Em 1/jan/2019, não está acessível o site onde o código original foi publicado (http://netlib.bell-labs.com/cm/cs/upe/).
🗒 Você pode compilar o programa rodando
make
no diretório/complete
, desde que tenha instalado as ferramentas de desenvolvimento do seu sistema, incluindo bison e flex.
O diretório /docs
contém esta página que você está lendo e as explicações das etapas do desenvolvimento de hoc
.
Os demais diretórios contém as 6 etapas da construção de hoc
, como descrito em The Unix Programming Environment.
Índice de páginas
- Etapa 1: calculadora aritmética, expressões computadas imediatamente.
- Etapa 1b: sinal de número negativo e uso do make.
- Etapa 2: variáveis de
a
az
; tratamento de alguns erros. - Etapa 3: variáveis com nomes mais longos, funções e constantes pré-definidas (
sin
,PI
, etc.). - Etapa 4: refatoração implementando linguagem intermediária baseada em pilha.
- Etapa 5: controle de fluxo, blocos delimitados por
{}
e operadores relacionais (>
,>=
, etc.). - Etapa 6: comandos
func
eproc
para definir funções e procedimentos recursivos; entrada e saída de strings além de números.
— LR