EDIT: Como alguns de vocês suspeitavam, houve um erro no intérprete oficial: a ordem da composição .
foi invertida. Eu tinha duas versões do intérprete e usei a errada aqui. Os exemplos também foram escritos para esta versão incorreta. Corrigi o intérprete no repositório e os exemplos abaixo. A descrição de >
também era um pouco ambígua, então eu corrigi isso. Além disso, desculpas por isso demorar tanto, eu fui pego em algumas coisas da vida real.
EDIT2: Meu intérprete teve um erro cuja implementação .
foi refletida nos exemplos (eles contavam com um comportamento indefinido). O problema está resolvido.
Introdução
Shift é uma linguagem de programação funcional esotérica que criei há alguns anos, mas publiquei hoje. É baseado em pilha, mas também possui currying automático como Haskell.
Especificação
Existem dois tipos de dados no Shift:
- Funções, que possuem uma aridade positiva arbitrária (número de entradas) e que retornam uma lista de saídas. Por exemplo, uma função que duplica sua única entrada possui aridade 1 e uma função que troca suas duas entradas tem aridade 2.
- Espaços em branco, todos idênticos e não têm outro objetivo além de não serem funções.
Um programa Shift consiste em zero ou mais comandos , cada um com um único caractere ASCII. Existem 8 comandos no total:
!
( aplicar ) exibe uma funçãof
e um valorx
da pilha e aplicaf
- se ax
. Sef
tiver arity 1, a listaf(x)
será adicionada à frente da pilha. Se houver aridaden > 1
, uma nova(n-1)
função -aryg
é enviada para a pilha. É preciso entradas e retornos .x1,x2,...,xn-1
f(x,x1,x2,...,xn-1)
?
(em branco ) coloca um espaço em branco na pilha.+
( clone ) envia para a pilha uma função unária que duplica sua entrada: qualquer valorx
é mapeado para[x,x]
.>
( shift ) empurra para a pilha uma função unária que assume uman
função -aryf
e retorna uma(n+1)
função -aryg
que ignora seu primeiro argumentox
, chamaf
os restantes e se alinhax
à frente do resultado. Por exemplo,shift(clone)
é uma função binária que recebe entradasa,b
e retornos[a,b,b]
./
( fork ) empurra para a pilha uma função ternária que aceita três entradasa,b,c
e retorna[b]
sea
estiver em branco ou[c]
não.$
( Chamada ) empurra para a pilha uma função binária que aparece uma funçãof
e um valorx
, e aplica-sef
ax
exatamente como!
faz..
( cadeia ) empurra para a pilha uma função binária que exibe duas funçõesf
eg
retorna sua composição: uma funçãoh
que tem a mesma aridadef
e que recebe suas entradas normalmente, aplicaf
- se a elas e depois aplica - se totalmenteg
ao resultado (chamadas tantas vezes quanto dita a sua aridade), com itens não utilizados da saída dof
restante no resultado deh
. Por exemplo, suponha quef
seja uma função binária que clone seu segundo argumento eg
seja chamada . Se a pilha contém[f,g,a,b,c]
e nós o fazemos.!!
, então ela contém[chain(f,g),a,b,c]
; se fizermos a!!
seguir,f
é aplicado primeiroa,b
, produzindo[a,b,b]
, entãog
é aplicado aos dois primeiros elementos disso, já que sua aridade é 2, produzindo[a(b),b]
, e a pilha finalmente será[a(b),b,c]
.@
( digamos ) empurra uma função unária que simplesmente retorna sua entrada e imprime0
se estivesse em branco e1
se fosse uma função.
Observe que todos os comandos, exceto !
simplesmente enviam um valor para a pilha, não há como executar entradas e a única maneira de produzir qualquer coisa é usar @
. Um programa é interpretado avaliando os comandos um por um, imprimindo 0
s ou 1
s sempre que "say" é chamado e saindo. Qualquer comportamento não descrito aqui (aplicar um espaço em branco, aplicar uma pilha de comprimento 0 ou 1, chamar "cadeia" em um espaço em branco etc.) é indefinido: o intérprete pode travar, falhar silenciosamente, solicitar informações ou qualquer outra coisa.
A tarefa
Sua tarefa é escrever um intérprete para o Shift. Deve levar do STDIN, da linha de comando ou do argumento da função um programa Shift a ser interpretado e imprimir em STDOUT ou retornar a saída resultante (possivelmente infinita) de 0
s e 1
s. Se você escreve uma função, deve poder acessar as saídas de tamanho infinito de alguma forma (gerador em Python, lista lenta em Haskell, etc). Como alternativa, você pode pegar outra entrada, um número n
e retornar pelo menos n
caracteres da saída, se for maior que n
.
A menor contagem de bytes vence e as brechas padrão não são permitidas.
Casos de teste
Este programa Shift imprime 01
:
?@!@@!
Começando pela esquerda: pressione um espaço em branco, pressione dizer e aplique a palavra no espaço em branco. Isso gera 0
. Em seguida, pressione say duas vezes e aplique a segunda palavra à primeira. Isso gera 1
.
Este programa faz um loop para sempre, não produzindo saída:
$+.!!+!!
Empurre a chamada e o clone e aplique a cadeia a eles (precisamos de dois !
s, pois a cadeia é uma função binária). Agora a pilha contém uma função que pega um argumento, o duplica e chama a primeira cópia no segundo. Com +!!
, duplicamos essa função e a chamamos por si mesma.
Este programa imprime 0010
:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Empurre um espaço em branco e diga . Em seguida, componha uma função binária que copie seu segundo argumento b
, copie o primeiro a
e o componha consigo mesmo, depois aplique a composição à cópia de b
, retornando [a(a(b)),b]
. Aplique para dizer e em branco, depois diga para os dois elementos restantes na pilha.
Este programa é impresso 0
. Para cada um !!!
que você anexa, ele imprime um adicional 0
.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Empurre um espaço em branco e diga . Em seguida, componha uma função ternária que tome f,g,x
como entradas e retornos [f,f,g,g(x)]
. Clone essa função e aplique-a a si mesma, digamos , e ao espaço em branco. Este aplicativo não altera a pilha, para que possamos aplicar a função novamente quantas vezes quisermos.
Este programa imprime a sequência infinita 001011011101111...
, onde o número de 1
s sempre aumenta em um:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
O repositório contém uma versão anotada.
f(x1, x2, ..., xn)
e g(y1, y2, ..., ym)
. A chamada .
aparece nos dois e pressiona uma função h(z1, z2, ..., zn)
. Agora você pode consumir todos esses argumentos gradualmente fazendo curry com ele !
. Após n
essas aplicações, a função restante tinha apenas um argumento e, nesse ponto, calcula f(z1, z2, ..., zn)
(ou seja, f
aplica-se a todos os argumentos que você inseriu), o que gera alguns novos valores e, em seguida, consome imediatamente os m
valores da pilha e os chama g
.
.
funciona exatamente como Martin descreveu, exceto que, se f
retornar uma lista menor que m
valores, o resultado será indefinido (a composição tem aridade n
, portanto, não pode ser consumido mais argumentos da pilha). Essencialmente, a saída de f
é usada como uma pilha temporária, na qual g
são pressionados e aplicados os m
tempos de utilização !
, e o resultado disso é adicionado à pilha principal.