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.
fonte
Respostas:
Haskell,
931921875isso ainda não foi totalmente jogado, mas provavelmente nunca será. Ainda assim, já é mais curto que todas as outras soluções.
Vou jogar isso mais em breve.Não me apetece jogar mais do que isso.provavelmente tem alguns erros sutis porque não brinquei com a implementação de referência C.
esta solução usa o tipo
StateT [String] IO ()
para armazenar um programa clem "executável". a maioria do programa é um analisador que analisa o "programa executável".para executar esse uso
r "<insert clem program here>"
.fonte
Python,
16841281 caracteresTemos todo o material básico de golfe feito. Ele executa todos os programas de exemplo e corresponde à saída caractere por caractere.
Teste :
Reúna clemint.py , clemtest_data.py , clemtest.py e um
clem
binário compilado em um diretório e executeclemtest.py
.Expansão :
A versão mais não destruída é essa . Siga junto com esse.
S
é a pilha principal. Cada item da pilha é uma lista de três, um dos seguintes:Para as constantes,
f
é uma função que empurra a constante para a pilha. Para os atmoics,f
é uma função que executa uma das operações (por exemplo-
,+
). Para os compostos,fs
é uma lista de itens.xec
executa um item. Se é uma constante ou um atômico, apenas executa a função. Se é um composto, se ainda não houve recursão, ele executa cada função. Assim, a execução(10 20 - 30)
irá executar cada uma das funções10
,20
,-
e30
, deixando10 19 30
na pilha. Se houve recursão, ela simplesmente envia a função composta para a pilha. Por exemplo, ao executar(10 20 (3 4) 30)
, o resultado deve ser10 20 (3 4) 30
, não10 20 3 4 30
.Aninhar era um pouco complicado. O que você faz durante a leitura
(1 (2 (3 4)))
? A solução é ter uma pilha de pilhas. Em cada nível de aninhamento, uma nova pilha é empurrada na pilha de pilhas e todas as operações de envio vão para essa pilha. Além disso, se houver um aninhamento, as funções atômicas são pressionadas em vez de executadas. Portanto, se você vê10 20 (- 30) 40
,10
é pressionado, então20
, uma nova pilha é criada,-
e30
é empurrada para a nova pilha, e)
sai da nova pilha, a transforma em um item e a empurra para a pilha um nível abaixo. , não um composto com a constante, porque então e não funciona. Não tenho certeza se isso é baseado em princípios, mas é assim que funciona ...endnest()
alças)
. Foi um pouco complicado, pois existe um caso especial em que apenas um item foi enviado e estamos retornando à pilha principal. Ou seja,(10)
deve empurrar a constante10
-
+
Meu intérprete é um processador caractere por caractere - ele não cria tokens -, portanto, números, strings e comentários foram um pouco irritantes de se lidar. Há uma pilha separada
N
, para um número que está sendo processado no momento, e sempre que um caractere que não é um número é processado, tenho que ligarendnum()
para ver se devo primeiro completar esse número e colocá-lo na pilha. Quer estejamos em uma string ou em um comentário, é mantido o controle por variáveis booleanas; quando uma corda é fechada, empurra todas as entranhas da pilha. Os números negativos também exigiam um tratamento especial.É sobre isso para a visão geral. O resto estava implementando todas as built-ins, e certificando-se de fazer cópias de profundidade de
+
,-
e#
.fonte
C 837
Obrigado ao @ceilingcat por encontrar uma versão muito melhor (e mais curta)
Isso trata tudo como strings simples - todos os itens da pilha são strings, mesmo as constantes são strings.
Experimente online!
Uma versão com menos golfe do meu original (ao contrário da versão com golfe, essa imprime a pilha quando termina se não estiver vazia e usa um parâmetro -e para que você possa especificar o script na linha de comando em vez de ler em um arquivo):
fonte