Clem é uma linguagem de programação mínima baseada em pilha, com funções de primeira classe. Seu objetivo é escrever um intérprete para a linguagem Clem. Ele deve executar corretamente todos os exemplos incluídos na implementação de referência, disponível aqui .
- Como sempre, lacunas padrão se aplicam.
- A menor entrada pela contagem de bytes vence.
A linguagem Clem
Clem é uma linguagem de programação baseada em pilha com funções de primeira classe. A melhor maneira de aprender o Clem é executar o clem
intérprete sem argumentos. Ele começará no modo interativo, permitindo que você jogue com os comandos disponíveis. Para executar os programas de exemplo, digite clem example.clm
onde exemplo é o nome do programa. Este breve tutorial deve ser suficiente para você começar.
Existem duas classes principais de funções. Funções atômicas e funções compostas. Funções compostas são listas compostas de outras funções compostas e funções atômicas. Observe que uma função composta não pode se conter.
Funções atômicas
O primeiro tipo de função atômica é a constante . Uma constante é simplesmente um valor inteiro. Por exemplo, -10. Quando o intérprete encontra uma constante , ele o empurra para a pilha. Corra clem
agora. Digite -10
no prompt. Você deveria ver
> -10
001: (-10)
>
O valor 001
descreve a posição da função na pilha e (-10)
é a constante que você acabou de inserir. Agora entre +11
no prompt. Você deveria ver
> +11
002: (-10)
001: (11)
>
Observe que (-10)
foi para a segunda posição na pilha e (11)
agora ocupa a primeira. Essa é a natureza de uma pilha! Você notará que -
também é o comando de decremento. Sempre -
ou +
precedendo um número, eles indicam o sinal desse número e não o comando correspondente. Todas as outras funções atômicas são comandos . Existem 14 no total:
@ Rotate the top three functions on the stack
# Pop the function on top of the stack and push it twice
$ Swap the top two functions on top of the stack
% Pop the function on top of the stack and throw it away
/ Pop a compound function. Split off the first function, push what's left,
then push the first function.
. Pop two functions, concatenate them and push the result
+ Pop a function. If its a constant then increment it. Push it
- Pop a function. If its a constant then decrement it. Push it
< Get a character from STDIN and push it to the stack. Pushes -1 on EOF.
> Pop a function and print its ASCII character if its a constant
c Pop a function and print its value if its a constant
w Pop a function from the stack. Peek at the top of the stack. While it is
a non-zero constant, execute the function.
Digitar um comando no prompt executará o comando. Digite #
no prompt (o comando duplicado). Você deveria ver
> #
003: (-10)
002: (11)
001: (11)
>
Observe que o (11) foi duplicado. Agora digite %
no prompt (o comando drop). Você deveria ver
> %
002: (-10)
001: (11)
>
Para enviar um comando para a pilha, basta colocá-lo entre parênteses. Digite (-)
no prompt. Isso empurrará o operador de decremento para a pilha. Você deveria ver
> (-)
003: (-10)
002: (11)
001: (-)
>
Funções compostas
Você também pode incluir várias funções atômicas entre parênteses para formar uma função composta. Quando você insere uma função composta no prompt, ela é enviada para a pilha. Digite ($+$)
no prompt. Você deveria ver
> ($+$)
004: (-10)
003: (11)
002: (-)
001: ($ + $)
>
Tecnicamente, tudo na pilha é uma função composta. No entanto, algumas das funções compostas na pilha consistem em uma única função atômica (nesse caso, as consideraremos como funções atômicas por uma questão de conveniência). Ao manipular funções compostas na pilha, o .
comando (concatenação) é frequentemente útil. Digite .
agora. Você deveria ver
> .
003: (-10)
002: (11)
001: (- $ + $)
>
Observe que a primeira e a segunda funções na pilha foram concatenadas e que a segunda função na pilha vem em primeiro lugar na lista resultante. Para executar uma função que está na pilha (seja atômica ou composta), devemos emitir o w
comando (while). O w
comando exibirá a primeira função na pilha e a executará repetidamente, desde que a segunda função na pilha seja uma constante diferente de zero. Tente prever o que acontecerá se digitarmos w
. Agora digite w
. Você deveria ver
> w
002: (1)
001: (0)
>
É isso que você esperava? Os dois números sentados no topo da pilha foram adicionados e sua soma permanece. Vamos tentar novamente. Primeiro, vamos largar o zero e pressionar 10, digitando %10
. Você deveria ver
> %10
002: (1)
001: (10)
>
Agora digitaremos a função inteira de uma só vez, mas adicionaremos um extra %
no final para eliminar o zero. Digite (-$+$)w%
no prompt. Você deveria ver
> (-$+$)w%
001: (11)
>
(Observe que esse algoritmo funciona apenas se a primeira constante na pilha for positiva).
Cordas
Strings também estão presentes. Eles são principalmente açúcar sintático, mas podem ser bastante úteis. Quando o intérprete encontra uma sequência, ele coloca cada caractere do último ao primeiro na pilha. Digite %
para descartar o 11 do exemplo anterior. Agora, digite 0 10 "Hi!"
no prompt. Ele 0
inserirá um terminador NULL e o 10
caractere de nova linha. Você deveria ver
> 0 10 "Hi!"
005: (0)
004: (10)
003: (33)
002: (105)
001: (72)
>
Digite (>)w
para imprimir caracteres da pilha até encontrarmos o terminador NULL. Você deveria ver
> (>)w
Hi!
001: (0)
>
Conclusões
Espero que isso seja suficiente para você começar com o intérprete. O design da linguagem deve ser relativamente direto. Deixe-me saber se algo está terrivelmente claro :) Algumas coisas foram deixadas intencionalmente vagas: os valores devem ser assinados e pelo menos 16 bits, a pilha deve ser grande o suficiente para executar todos os programas de referência, etc. Muitos detalhes não foram gravados aqui porque uma especificação de linguagem completa seria proibitivamente grande para postar (e ainda não escrevi uma: P). Em caso de dúvida, imite a implementação de referência.