Saída de todos os programas de interrupção (escreva um intérprete paralelo)

26

O objetivo desse desafio é (eventualmente) gerar todos os programas de parada possíveis em um idioma de sua escolha. No início, isso pode parecer impossível, mas você pode fazer isso com uma escolha muito cuidadosa da ordem de execução.

Abaixo está um diagrama ASCII para ilustrar isso. Deixe as colunas representarem uma numeração de todos os programas possíveis (cada programa é um número finito de símbolos de um alfabeto finito). Deixe cada linha representar uma etapa singular na execução desse programa. Um Xrepresenta a execução executada por esse programa naquele intervalo de tempo.

 step#  p1 p2 p3 p4 p5 p6
     1  X  X  X  X  X  X
     2  X  X  X  X  X  
     3  X  X     X  X
     4  X  X     X  X
     5  X  X     X
     6     X     X
     7     X     X
     8     X     X
     9     X     X
     ∞     X     X

Como você pode ver, os programas 2 e 4 não param. Se você os executasse um de cada vez, seu controlador ficaria preso no loop infinito que é o programa 2 e nunca produziria os programas 3 e além.

Em vez disso, você usa uma abordagem de encaixe . As letras representam uma possível ordem de execução para as primeiras 26 etapas. Os *s são locais onde o programa foi interrompido e produzido. Os .passos são ainda não executados.

 step#  p1 p2 p3 p4 p5 p6
     1  A  C  F  J  N  R  V
     2  B  E  I  M  Q  *  Z
     3  D  H  *  P  U
     4  G  L     T  Y
     5  K  O     X
     6  *  S     .
     7     W     .
     8     .     .
     9     .     .
     ∞     .     .

Requisitos para o idioma de destino

O idioma de destino (o que está sendo interpretado em paralelo) deve estar completo em Turing. Fora isso, pode ser qualquer idioma que seja completo em Turing, incluindo subconjuntos completos em Turing de idiomas muito maiores. Você também é livre para interpretar coisas como regras do sistema de etiquetas cíclicas. Você também pode criar um idioma para testar, desde que possa mostrar por que o Turing está completo.

Por exemplo, se você optar por testar o cérebro, então é melhor testar apenas o []-+<>subconjunto, pois a entrada não é suportada e a saída é descartada (veja abaixo).

Quando se trata do programa "controller" (no qual você está jogando golfe), não há requisitos especiais. Aplicam-se restrições de idioma normal.

Como criar uma lista infinita de programas

A maioria das linguagens de programação pode ser representada como uma série de símbolos de um alfabeto finito. Nesse caso, é relativamente fácil enumerar uma lista de todos os programas possíveis em ordem crescente. O alfabeto que você usa deve ser representativo dos requisitos do idioma de destino. Na maioria dos casos, isso é ASCII imprimível. Se o seu idioma suportar Unicode como um recurso adicional, você não deve testar todas as combinações possíveis de caracteres Unicode, apenas ASCII. Se o seu idioma usar apenas []-+<>, não teste as várias combinações de caracteres ASCII de "comentário". Idiomas como o APL teriam seus próprios alfabetos especiais.

Se o seu idioma é melhor descrito de alguma maneira não alfabética, como Fractran ou Turing Machines, existem outros métodos igualmente válidos para gerar uma lista de todos os possíveis programas válidos.

Interpretando uma lista crescente de programas

A parte principal desse desafio é escrever um intérprete paralelo para uma lista crescente de programas. Existem algumas etapas básicas para isso:

  • Adicione um número finito de programas à lista
  • Interprete cada programa da lista individualmente por um período finito de tempo. Isso pode ser realizado executando uma etapa de instrução para cada um. Salve todos os estados.
  • Remova todos os programas de finalização / de lançamento de erros da lista
  • Saída dos programas com interrupção limpa *
  • Adicione mais alguns programas à lista
  • Simule cada programa, alternando a execução de programas mais antigos de onde parou
  • Remova todos os programas de finalização / de lançamento de erros da lista
  • Saída dos programas com interrupção limpa *
  • repetir

* Você só deve emitir programas que parem corretamente. Isso significa que não houve erros de sintaxe ou exceções não detectadas lançadas durante a execução. Os programas que solicitam entrada também devem ser finalizados sem a saída deles. Se um programa produz saída, você não deve encerrá-la, apenas jogue a saída fora.

Mais regras

  • Você não deve gerar novos encadeamentos para conter os programas testados, pois isso descarrega o trabalho de paralelização no SO host / outro software.
  • Editar: para fechar possíveis brechas futuras, você não tem permissão para eval(ou qualquer função relacionada) parte do código do programa testado . Você pode eval bloquear um código a partir do código do intérprete. (A resposta BF-in-Python ainda é válida sob essas regras.)
  • Isso é
  • O idioma em que você enviou seu envio não precisa ser o mesmo que você está testando / produzindo.
  • Você deve assumir que sua memória disponível é ilimitada.
  • Ao provar a integridade de Turing, você pode assumir que a entrada está codificada no programa e a saída pode ser lida no estado interno do programa.
  • Se o seu programa se exibir, provavelmente está errado ou é poliglota.
PhiNotPi
fonte
7
Levei muito tempo para perceber o porquê"If your program outputs itself, it is probably wrong or a polyglot."
trichoplax 4/15
1
Que possamos assumir que a memória disponível é ilimitada (eu não acho que isso é possível de outro modo)
KSab
1
@KSab Sim, e definitivamente não é possível de outra maneira.
PhiNotPi
1
Desafio de acompanhamento ( muito mais difícil): produza todos os programas sem interrupção.
Milo Brandt
1
É aceitável produzir o mesmo programa mais de uma vez?

Respostas:

9

subleq OISC em Python, 317 269 ​​bytes

import collections
g=0
P={}
while 1:
    P[g]=[[0],collections.defaultdict(int,enumerate(list(int(x)for x in reversed(str(g)))))]
    g+=1
    for o,[a,p]in P.items():
        i=a[0]
        p[i+p[i+1]]-=p[i+p[i]]
        if p[i+p[i+1]]<=0:a[0]+=p[i+2]
        else:a[0]+=3
        if a[0]<0:print o;del P[o]

https://esolangs.org/wiki/Subleq

Um programa subleq é uma lista extensível de números inteiros (p) e um ponteiro de instrução (i). Essa variante subleq usa endereçamento relativo, o que a página de discussão do wiki sugere que é necessário para garantir a integridade com valores limitados. Cada tick, a operação p[i+p[i+1]]-=p[i+p[i]]é executada e, i+=p[i+2]se o resultado da operação for <= 0, caso contrário i+=3. Se i for negativo, o programa será interrompido.

Essa implementação testa todos os programas cujo estado inicial é composto por números inteiros não negativos de um dígito (0-9) com um ponteiro de instrução inicial de 0.

Output:
21 (which represents the program [1 2 0 0 0 0 0...]
121
161
221
271
351
352
461
462
571
572
681
682
791
792

A saída é revertida, por razões de golfe. As especificações acima podem ser atualizadas ao contrário, mas não coincidem com o código usado na implementação, por isso não a descrevi dessa maneira.

EDIT: O primeiro programa que exibe crescimento simples e ilimitado é 14283, que diminui o valor no local da memória 6 e grava um 0 explícito (em oposição ao 0 implícito em todas as células) na próxima célula negativa, a cada três marcações.

Sparr
fonte
9

Tag cíclico bit a bit em CJam, 98 87 84 77 bytes

L{Z):Z2b1>_,,1>\f{/([\:~]a2*}+{)~[\({1+(:X+\_0=Xa*+}{0+\1>}?_{]a+}{];p}?}%1}g

Como esse é um loop infinito, você não pode testá-lo diretamente no intérprete online. Contudo, aqui está uma versão alternativa que lê o número de iterações do STDIN para você brincar. Para testar o programa completo, você precisará do interpretador Java .

BCT é uma variante minimalista de Cyclic Tag Systems . Um programa é definido por duas cadeias binárias: uma lista (cíclica) de instruções e um estado inicial. Para facilitar minha vida ao imprimir os programas, defini minha própria notação: cada uma das cadeias é fornecida como uma matriz de números inteiros no estilo CJam, e todo o programa é cercado [[...]], por exemplo

[[[0 0 1 1] [0 1 1 1 0]]]

Também estou proibindo estados iniciais vazios ou listas de instruções vazias.

As instruções no BCT são interpretadas da seguinte forma:

  • Se a instrução for 0 , remova o bit inicial do estado atual.
  • Se a instrução for 1, leia outro bit da lista de instruções, chame isso X. Se o bit inicial do estado atual for 1, acrescenteX ao estado atual, caso contrário, não faça nada.

Se o estado atual ficar vazio, o programa será interrompido.

Os primeiros programas de interrupção são

[[[0] [0]]]
[[[0] [1]]]
[[[0 0] [0]]]
[[[0] [0 0]]]
[[[0 0] [1]]]
[[[0] [0 1]]]
[[[0 1] [0]]]
[[[0] [1 0]]]
[[[0 1] [1]]]
[[[0] [1 1]]]

Se você quiser ver mais, confira a versão no intérprete on-line que eu vinculei acima.

Explicação

Aqui está como o código funciona. Para acompanhar o encaixe, sempre teremos uma matriz na pilha que contém todos os programas. Cada programa é um par de uma representação interna do código do programa (como [[0 1 0] [1 0]]), bem como o estado atual do programa. Usaremos apenas o último para fazer o cálculo, mas precisaremos lembrar do primeiro para imprimir o programa quando ele parar. Esta lista de programas é simplesmente inicializada em uma matriz vazia comL .

O restante do código é um loop infinito {...1}g que primeiro adiciona um ou mais programas a esta lista e calcula uma etapa em cada programa. Os programas interrompidos são impressos e removidos da lista.

Estou enumerando os programas contando um número binário. O dígito inicial é retirado para garantir que também possamos obter todos os programas com 0s iniciais. Para cada representação binária truncada, eu envio um programa para cada divisão possível entre instruções e estado inicial. Por exemplo, se o contador estiver atualmente em 42, sua representação binária é 101010. Nos livramos da liderança 1e empurramos todas as separações não vazias:

[[[0] [1 0 1 0]]]
[[[0 1] [0 1 0]]]
[[[0 1 0] [1 0]]]
[[[0 1 0 1] [0]]]

Como não queremos instruções ou estados vazios, iniciamos o contador em 4, o que fornece [[[0] [0]]]. Essa enumeração é feita pelo seguinte código:

Z):Z    e# Push Z (initially 3), increment, and store in Z.
2b1>    e# Convert to base 2, remove initial digit.
_,      e# Duplicate and get the number of bits N.
,1>     e# Turn into a range [1 .. N-1].
\       e# Swap the range and the bit list.
f{      e# Map this block onto the range, copying in the bit list on each iteration.
  /     e#   Split the bit list by the current value of the range.
  (     e#   Slice off the first segment from the split.
  [     
    \:~ e#   Swap with the other segments and flatten those.
  ]     e#   Collect both parts in an array.
  a2*   e#   Make an array that contains the program twice, as the initial state is the
        e#   same as the program itself.
}
+       e# Add all of these new programs to our list on the stack.

O restante do código mapeia um bloco para a lista de programas, que executa uma etapa da computação BCT na segunda metade desses pares e remove o programa se ele parar:

)~     e# Remove the second half of the pair and unwrap it.
[      e# We need this to wrap the instructions and current state back in an array
       e# again later.
\(     e# Bring the instruction list to the top and remove the leading bit.
{      e# If it's a 1...
  1+   e#   Append a 1 again (the instructions are cyclic).
  (:X+ e#   Remove the next bit, store it in X and also append it again.
  \_0= e#   Bring the current state to the top, get its first bit.
  Xa*+ e#   Append X if that bit was 1 or nothing otherwise.
}{     e# Else (if it's a 0...)
  0+   e#   Append a 0 again (the instructions are cyclic).
  \1>  e#   Discard the leading bit from the current state.
}?
_      e# Duplicate the current state.
{      e# If it's non-empty...
  ]a+  e#   Wrap instructions and state in an array and add them to the program
       e#   pair again.
}{     e# Else (if it's empty)...
  ];p  e# Discard the instructions and the current state and print the program.
}?
Martin Ender
fonte
Bom (+1). Alguns bytes podem ser salvos usando o fato de o BCT ser Turing completo, mesmo que restrito a usar apenas 1 como a sequência de dados inicial (seu "estado"). Por exemplo, interprete cada número inteiro positivo sucessivo em binário como 1P, execute P em 1 e produza P se a execução terminar (seguindo novamente). (É claro que qualquer P que comece com 0 estaria na lista, pois excluiria imediatamente a sequência de dados inicial.)
res
8

Brainfuck em Python, 567 bytes

Uma solução relativamente simples, pois Brainfuck dificilmente é a linguagem mais difícil de se escrever para um intérprete.

Esta implementação do Brainfuck tem o ponteiro de dados começando em 0, apenas com valor positivo (considerado um erro se tentar ir para a esquerda de 0). As células de dados podem assumir valores de 0 a 255 e quebrar. As 5 instruções válidas são ><+[](- desnecessárias devido à embalagem).

Acho que a saída está correta agora, no entanto, é difícil ter certeza de que está imprimindo todas as soluções possíveis, para que eu possa estar perdendo alguma.

o="><+]["
A="[]if b%s1<0else[(p,a+1,b%s1,t+[0],h)]"
C="[(p,h[a]+1,b,t,h)if t[b]%s0else(p,a+1,b,t,h)]"
I=lambda s,i:i*">"if""==s else o[o.find(s[0])+i-5]+I(s[1:],i*o.find(s[0])>3)
s="";l=[]
while 1:
 s=I(s,1)
 r=[];h={}
 for i in range(len(s)):
    if s[i]=="[":r+=[i]
    if s[i]=="]":
     if r==[]:break
     h[r[-1]]=i;h[i]=r[-1];r=r[:-1]
 else:
    if r==[]:
     l+=[(s,0,0,[0],h)];i=0
     while i<len(l):
        p,a,b,t,h=l[i]
        if a>=len(p):print p;l[i:i+1]=[]
        else:l[i:i+1]=eval([A%("+","+"),A%("-","-"),"[(p,a+1,b,t[:b]+[(t[b]+1)%256]+t[b+1:],h)]",C%">",C%"=="][o.find(p[a])]);i+=1

Os primeiros resultados:

>
+
>>
+>
><
>+
++
[]
>>>
+>>

E uma lista dos primeiros 2000: http://pastebin.com/KQG8PVJn

E, finalmente, uma lista dos primeiros 2000 resultados com []eles: http://pastebin.com/iHWwJprs
(todo o resto é trivial, desde que válido)

Observe que a saída não está em uma ordem classificada, embora possa parecer assim para muitos deles, pois os programas que levam mais tempo serão impressos posteriormente.

KSab
fonte
1
Tanto o simples [-]como o [+]definitivo devem aparecer porque o conteúdo do loop é simplesmente ignorado (sem quebra automática).
PhiNotPi
@ SP3000 A [-]e [+]foi um bug que agora deve fixo e eu tenho atualizado com as configurações
KSab
1
Por que você está apoiando .? Não é necessário que um subconjunto de BF completo de Turing e a saída sejam ignorados de qualquer maneira. Além disso, como você está agrupando os valores das células, acho que você precisa apenas de um -e +.
Martin Ender
@ MartinBüttner Parece que eu não entendi a pergunta; Eu não li a parte 'turing complete subcon'. No entanto, isso não torna o desafio quase equivalente à maioria dos idiomas? Você não poderia simplesmente fazer uma substituição de 1 para 1 pelo Brainfuck (ou talvez algo ainda mais simples), por exemplo, o código c aqui: en.wikipedia.org/wiki/Brainfuck#Commands .
KSab
2
Dê uma olhada em stackoverflow.com/questions/1053931/… particularmente na entrada OISC. Além disso, verifique a regra 110 da CA e os sistemas de etiquetas cíclicas. Há muito espaço para a escolha criativa de uma "linguagem" completa para esse desafio.
Sparr
5

barras em Python, 640 498 bytes

g=2
P={}
while 1:
    b=bin(g)[3:]
    P[b]=[[0],['',''],[b]]
    g+=1
    for d,[a,b,c]in P.items():
        s=c[0]
        if a[0]:
            if s.count(b[0]):s=s.replace(b[0],b[1],1)
            else:a[0]=0
        else:
            if s[0]=='0':
                if len(s)==1:del P[d];continue
                s=s[2:]
            else:
                b[0]=b[1]=''
                a[0]=1
                t=p=0
                while t<2:
                    p+=1
                    if p>=len(s):break
                    if s[p]=='0':
                        if p+1>=len(s):break
                        b[t]+=s[p+1]
                        p+=1
                    else:t+=1
                if t<2:del P[d];continue
        c[0]=s
        if len(s)==0:print d;del P[d]

https://esolangs.org/wiki////

Um programa de barras é uma string, nesse intérprete limitada aos caracteres '/' e '\'. Nesta implementação, / é '1' e \ é '0' para permitir algum golfe com o uso do bin do python (x). Quando o intérprete encontra a \, o próximo caractere é gerado e os dois caracteres são removidos. Quando encontra um /, ele procura e substitui padrões como / search / replace / incluindo caracteres de escape dentro dos padrões (\\ representa \ e \ / representa /). Essa operação de substituição é executada na sequência repetidamente até que a sequência de pesquisa não esteja mais presente e a interpretação continua desde o início novamente. O programa pára quando está vazio. Um programa será eliminado se houver um conjunto não fechado de / padrões ou um \ sem nenhum caracter após.

Example output and explanations:
01 outputs '1' and halts
00 outputs '0' and halts
0101 outputs '11' and halts
0100 ...
0001
0000
010101
010100
010001
010000 ...
101110 replaces '1' with '', leaving '00', which outputs '0' and halts
Sparr
fonte
4

Treehugger em Java, 1.299 1.257 1.251 1.207 1.203 1.201 1.193 1.189 bytes

import java.util.*;class I{static class N{N l,r;byte v;}static class T extends Stack<N>{{push(new N());}void p(){pop();if(size()==0)p();}int i,h;char[]s;}static void s(T t){if(t.i>=t.s.length){t.h=1;return ;}char c=t.s[t.i];if(c=='<'){if(t.peek().l==null)t.peek().l=new N();t.push(t.peek().l);}if(c=='>'){if(t.peek().r==null)t.peek().r=new N();t.push(t.peek().r);}if(c=='^')t.p();if(c=='+')t.peek().v++;if(c=='-')t.peek().v--;if(c=='['&&t.peek().v==0){int i=1;while(i>0){t.i++;if(t.s[t.i]==']')i--;if(t.s[t.i]=='[')i++;}return;}if(c==']'&&t.peek().v!=0){int i=1;while(i>0){t.i--;if(t.s[t.i]==']')i++;if(t.s[t.i]=='[')i--;}return;}t.i++;}static char[]n(char[]a){String b="<^>[+-]";int q=a.length;for(int i=q-1;i>=0;i--){int j=b.indexOf(a[i]);if(j<6){a[i]=b.charAt(j+1);return a;}a[i]='<';}a=Arrays.copyOf(a,q+1);a[q]='<';return a;}public static void main(String[]a){List<T>z=new ArrayList<T>();char[]c={};while(true){T t=new T();t.s=c;if(b(c))z.add(t);c=n(c.clone());for(T u:z)try{s(u);if(u.h>0){z.remove(u);System.out.println(u.s);break;}}catch(Exception e){z.remove(u);break ;}}}static boolean b(char[]c){int i=0;for(char d:c){if(d=='[')i++;if(d==']')i--;if(i<0)return 0>0;}return i==0;}}
SuperJedi224
fonte
4

BrachylogProblema de pós-correspondência , 10 bytes

≜;?{~c}ᵐ\d

Experimente online!

Função que é um gerador que gera todos os possíveis problemas de pós-correspondência para os quais as soluções de força bruta acabam sendo interrompidas. (As soluções de força bruta para o problema de pós-correspondência são conhecidas por serem uma operação completa de Turing.) O link TIO contém um cabeçalho que converte um gerador em um programa completo e imprime cada saída imediatamente à medida que é gerada (assim, quando o TIO mata devido ao consumo de mais de 60 segundos de tempo de execução, a saída produzida até o momento é visível).

Isso usa uma formulação do problema em que as cadeias são dadas como cadeias de dígitos, os zeros à esquerda não são permitidos, exceto por 0si só, as soluções para o problema envolvendo zeros à frente não são aceitas e uma cadeia de dígitos pode ser representada como o número ou menos o número. Claramente, nada disso afeta a integridade de Turing do idioma (porque não há necessidade de um problema de correspondência de postagem para usar o dígito zero).

Este programa funciona através da geração de todas as soluções possíveis para os problemas e, em seguida, trabalhando para trás para encontrar os programas originais que são resolvidos por eles. Como tal, um programa individual pode ser produzido várias vezes. Não está claro se isso invalida a resposta ou não; observe que todos os programas de interrupção serão exibidos pelo menos uma vez (de fato, infinitas vezes, como qualquer programa que tenha soluções tem infinitas soluções), e os programas sem interrupção nunca serão exibidos.

Explicação

≜;?{~c}ᵐ\d
≜           Brute-force all numbers:
 ;?           Pair {the number} with {itself}
   {  }ᵐ      For each pair element:
    ~c          Brute-force a partition of that element into substrings
        \     such that the two elements each have the same number of substrings
        \     and group together corresponding substrings
         d    and remove duplicated pairs {to produce a possible output}

fonte
2

"Roxo sem E / S" no Ceilão, 662

import ceylon.language{l=variable,I=Integer,m=map,S=String}class M(S d){l value t=m{*d*.hash.indexed};l I a=0;l I b=0;l I i=0;I g(I j)=>t[j]else 0;value f=m{97->{a},98->{b},65->{g(a)},66->{g(b)},105->{i},49->{1}};value s=m{97->((I v)=>a=v),98->((I v)=>b=v),65->((I v)=>t=m{a->v,*t}),66->((I v)=>t=m{b->v,*t}),105->((I v)=>i=v)};I&I(I)x{throw Exception(d);}I h(I v)=>f[v]?.first else x;shared void p(){(s[g(i)]else x)(h(g(i+1))-h(g(i+2)));i+=3;}}shared void run(){value a='!'..'~';{S*}s=expand(loop<{S*}>{""}((g)=>{for(c in a)for(p in g)p+"``c``"}));l{M*}r={};for(p in s){r=[M(p),*r];for(e in r){try{e.p();}catch(x){print(x.message);r=r.filter(not(e.equals));}}}}

O roxo é uma linguagem de instrução única modificadora e foi solicitada a interpretação aqui . Como entrada e saída não são relevantes para esta tarefa, eu removi o osignificado do símbolo do intérprete, de modo que os (potencialmente) símbolos válidos são apenas a, b, A,B , ie 1(o último apenas para leitura, não para escrita).

Mas como o Purple é auto-modificável (e usa seu código-fonte como dados), potencialmente também programas que contenham outros caracteres são úteis, então eu optei por permitir todos os caracteres ASCII imprimíveis (que não sejam em branco) no código (outros podem ser útil também, mas não são impressos com tanta facilidade).

(Você pode modificar o intérprete para usar como sequência de caracteres permitidos como argumento da linha de comando - alterne a definição de linha comentada a abaixo. Em seguida, o comprimento se tornará 686 bytes.)

Meu intérprete "paralelo" cria assim todas as seqüências finitas desses caracteres (em tamanho crescente e ordem lexicográfica) e tenta cada um deles.

O roxo será interrompido sem erro sempre que o comando a ser lido da fita para execução não for válido - portanto, não há programas inválidos e muitos, muitos interrompidos. (A maioria das paradas, mesmo na primeira etapa, apenas alguns dos programas com extensão 3 chegam à segunda etapa (e serão interrompidas); os primeiros sem interrupção têm extensão 6.

Eu acho que o primeiro programa sem interrupção na ordem tentada pelo meu intérprete é aaaiaa, que na primeira etapa define o aregistro como 0 (o que já era), e na segunda e em todas as etapas seguintes, o ponteiro da instrução volta para 0, fazendo com que seja executadoiaa novamente.

Reutilizei parte do código escrito para o meu intérprete de roxo "padrão" , mas, devido à remoção da entrada e saída, meu intérprete paralelo se torna um pouco mais curto que isso, incluindo a lógica adicional para executar vários programas ao mesmo tempo.

Aqui está uma versão comentada e formatada:

// Find (enumerate) all halting programs in (a non-I/O subset of) Purple.
//
// Question:  /codegolf//q/51273/2338
// My answer: /codegolf//a/65820/2338

// We use a turing-complete subset of the Purple language,
// with input and output (i.e. the `o` command) removed.

import ceylon.language {
    l=variable,
    I=Integer,
    m=map,
    S=String
}

// an interpreting machine.
class M(S d) {
    // The memory tape, as a Map<Integer, Integer>.
    // We can't modify the map itself, but we
    // can replace it by a new map when update is needed.
    l value t = m {
        // It is initialized with the code converted to Integers.
        // We use `.hash` instead of `.integer` because it is shorter.
        *d*.hash.indexed
    };

    // three registers
    l I a = 0;
    l I b = 0;
    l I i = 0;

    // get value from memory
    I g(I j) =>
            t[j] else 0;

    // Map of "functions" for fetching values.
    // We wrap the values in iterable constructors for lazy evaluation
    //  – this is shorter than using (() => ...).
    // The keys are the (Unicode/ASCII) code points of the mapped
    // source code characters.
    value f = m {
        // a
        97 -> { a },
        // b
        98 -> { b },
        // A
        65 -> { g(a) },
        // B
        66 -> { g(b) },
        // i
        105 -> { i },
        // 1
        49 -> { 1 }
    };

    // Map of functions for "storing" results.
    // The values are void functions taking an Integer,
    // the keys are the ASCII/Unicode code points of the corresponding
    // source code characters.
    value s = m {
        // a
        97 -> ((I v) => a = v),
        // b
        98 -> ((I v) => b = v),
        // Modification of the memory works by replacing the map with
        // a new one.
        // This is certainly not runtime-efficient, but shorter than
        // importing ceylon.collections.HashMap.
        // A
        65 -> ((I v) => t = m { a->v, *t }),
        // B
        66 -> ((I v) => t = m { b->v, *t }),
        // i
        105 -> ((I v) => i = v)
    };


    // Exit the interpretation, throwing an exception with the machine's
    // source code as the message.  The return type is effectively `Nothing`,
    // but shorter (and fits the usages).
    I&I(I) x {
        throw Exception(d);
    }

    // accessor function for the f map
    I h(I v) =>
            f[v]?.first else x;

    // a single step
    shared void p() {
        (s[g(i)] else x)(h(g(i + 1)) - h(g(i + 2)));
        i += 3;
    }
}

// the main entry point
shared void run() {
    // the alphabet of "Purple without I/O".
    value a = '!'..'~';
    //// possible alternative to use a command line argument:
    // value a = process.arguments[0] else '!'..'~';

    // an iterable consisting of all programs in length + lexicographic order
    {S*} s =
            // `expand` creates a single iterable (of strings, in this case)
            // from an iterable of iterables (of strings).
             expand(
        // `loop` creates an iterable by applying the given function
        // on the previous item, repeatedly.
        // So here we start with the iterable of length-zero strings,
        // and in each iteration create an iterable of length `n+1` strings
        // by concatenating the length `n` strings with the alphabet members.
        loop<{S*}>{ "" }((g) =>
                {
                    for (c in a)
                        for (p in g)
                            p + "``c``"
                }));

    // This is a (variable) iterable of currently running machines.
    // Initially empty.
    l {M*} r = {};

    // iterate over all programs ...
    for(p in s) {
        // Create a machine with program `p`, include it
        //  in the list of running machines.
        //
        // We use a sequence constructor here instead of
        //  an iterable one (i.e. `r = {M(p, *r)}` to prevent
        // a stack overflow when accessing the deeply nested
        // lazy iterable.
        r = [M(p), *r];
        // iterate over all running machines ...
        for(e in r) {
            try {
                // run a step in machine e.
                e.p();
            } catch(x) {
                // exception means the machine halted.
                // print the program
                print(x.message);
                // remove the machine from the list for further execution
                r = r.filter(not(e.equals));
            }
        }
        // print(r.last);
    }
}
Paŭlo Ebermann
fonte
2

Cálculo do combinador SK em Haskell , 249 bytes

data C=H|S|K|C:$C deriving(Eq,Show)
n(a:$b)=m a*n b
n a=m a
m S=1
m K=1
m(S:$a)=n a
m _=0
f H=[S,K,S:$H,K:$H,S:$H:$H]
f a=[S:$b:$c:$d|b:$d:$(c:$e)<-[a],d==e,n b*n c*n d>0]++[K:$a:$H|n a>0]++do b:$c<-[a];[d:$c|d<-f b]++[b:$d|n b>0,d<-f c]
l=H:(f=<<l)

Experimente online!

Como funciona

As regras de avaliação de chamada por valor para o cálculo do combinador SK são as seguintes:

(a) S xyzxz ( yz ), para x , y , z em forma normal;
(b) K xyx , para x , y na forma normal;
(c) xyxy , se xx ′;
(d) xyxy ′, para x na forma normal, se yy ' .

Como estamos interessados ​​apenas em interromper o comportamento, expandimos um pouco a linguagem introduzindo um símbolo H que não está na forma normal, mas ao qual todas as formas normais “avaliam”:

(a) S xyzxz ( yz ), para x , y , z em forma normal;
(b ′) K x H ↦ x , para x na forma normal;
(c) xyxy , se xx ′;
(d) xyxy ′, para x na forma normal, se yy ′ ;
(e) S = H;
(f) K = H;
(g) SH2H;
(h) KH = H;
i) SHH ↦ H.

Consideramos que qualquer aplicativo H x é um erro em tempo de execução a ser tratado como se fosse um loop infinito, e ordenamos avaliações para que nenhum H seja produzido por (e) - (i), exceto em um contexto em que será ignorado (nível superior, qualquer K x ☐ , qualquer K x ignorado, qualquer S x ☐ ignorado por x na forma normal, qualquer S☐H ignorado). Dessa forma, não afetamos o comportamento de interrupção dos termos usuais que faltam H.

Os benefícios dessas regras modificadas são que todo termo normalizável possui um caminho de avaliação exclusivo para H e que todo termo tem um número finito de possíveis pré-imagens em ↦. Portanto, em vez de usar a abordagem de encaixe, podemos fazer uma pesquisa abrangente muito mais eficiente de todos os caminhos de avaliação reversa de H.

nverifica se um termo está na forma normal, fencontra todas as pré-imagens possíveis de um termo e lé uma lista infinita e preguiçosa de termos normalizáveis ​​produzidos pela pesquisa pela primeira vez em H.

Anders Kaseorg
fonte