Выведите каждую программу остановки (напишите параллельный интерпретатор)

26

Цель этой задачи - (в конечном итоге) вывести все возможные программы остановки на выбранном вами языке. Поначалу это может показаться невозможным, но вы можете сделать это с помощью очень тщательного выбора порядка выполнения.

Ниже приведена диаграмма ASCII, чтобы проиллюстрировать это. Пусть столбцы представляют нумерацию каждой возможной программы (каждая программа представляет собой конечное число символов из конечного алфавита). Пусть каждая строка представляет отдельный шаг в выполнении этой программы. XПредставляют исполнение в исполнении этой программы в то время шага.

 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

Как вы можете сказать, программы 2 и 4 не останавливаются. Если бы вы выполняли их по одному, ваш контроллер застрял бы в бесконечном цикле, который является программой 2, и никогда не выводил бы программы 3 и выше.

Вместо этого вы используете подход ласточкин хвост . Буквы представляют возможный порядок выполнения для первых 26 шагов. Это *места, где эта программа остановлена ​​и выведена. Это .шаги, которые еще не были выполнены.

 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     .     .
     ∞     .     .

Требования к целевому языку

Целевой язык (который интерпретируется параллельно) должен быть завершен по Тьюрингу. Кроме этого, это может быть любой язык, полный по Тьюрингу, включая подмножества по Тьюрингу из гораздо более крупных языков. Вы также можете свободно интерпретировать такие вещи, как системные правила циклических тегов. Вам также разрешается создавать язык для тестирования, если вы можете показать, почему он завершен по Тьюрингу.

Например, если вы выберете тестирование brainfuck, то лучше всего протестировать только []-+<>подмножество, так как ввод не поддерживается, а вывод просто отбрасывается (см. Ниже).

Когда дело доходит до программы «контроллер» (которую вы играете в гольф), особых требований нет. Нормальные языковые ограничения применяются.

Как создать бесконечный список программ

Большинство языков программирования могут быть представлены в виде последовательности символов из конечного алфавита. В этом случае сравнительно легко перечислить список всех возможных программ в порядке увеличения длины. Используемый алфавит должен соответствовать требованиям целевого языка. В большинстве случаев это печатная версия ASCII. Если ваш язык поддерживает Unicode в качестве дополнительной функции, вам не следует тестировать каждую возможную комбинацию символов Unicode, только ASCII. Если ваш язык использует только []-+<>то, не проверяйте различные комбинации символов ASCII «комментарий». Такие языки, как APL, будут иметь свои собственные специальные алфавиты.

Если ваш язык лучше всего описывается неалфавитным способом, например, Fractran или Turing Machines, то существуют другие одинаково допустимые методы создания списка всех возможных допустимых программ.

Интерпретация постоянно растущего списка программ

Ключевой частью этой задачи является написание параллельного интерпретатора для растущего списка программ. Есть несколько основных шагов для этого:

  • Добавить конечное количество программ в список
  • Интерпретировать каждую программу в списке индивидуально в течение ограниченного периода времени. Это может быть достигнуто путем выполнения одного шага инструкции для каждого. Сохранить все состояния.
  • Удалить все завершающие / генерирующие ошибки программы из списка
  • Вывести полностью остановленные * программы
  • Добавьте еще несколько программ в список
  • Моделируйте каждую программу по очереди, продолжая выполнение старых программ, где она остановилась
  • Удалить все завершающие / генерирующие ошибки программы из списка
  • Вывести полностью остановленные * программы
  • повторение

* Вы должны выводить только программы, которые останавливаются чисто. Это означает, что во время выполнения не было никаких синтаксических ошибок или необработанных исключений. Программы, которые запрашивают ввод, также должны быть завершены без их вывода. Если программа производит вывод, вы не должны его прекратить, просто выбросьте вывод.

Больше правил

  • Вы не должны создавать новые потоки, содержащие тестируемые программы, так как это переносит работу по распараллеливанию на основную ОС / другое программное обеспечение.
  • Редактировать: чтобы закрыть возможные будущие лазейки, вам не разрешено eval(или любая связанная функция) часть кода тестируемой программы. Вы можете eval кодовый блок из кода переводчика. (Ответ BF-in-Python все еще действителен в соответствии с этими правилами.)
  • Это
  • Язык, на котором вы пишете свою заявку , не обязательно должен совпадать с языком, который вы тестируете / выводите.
  • Вы должны предположить, что ваша доступная память не ограничена.
  • При доказательстве полноты по Тьюрингу вы можете предположить, что входные данные жестко закодированы в программе, а выходные данные могут быть считаны из внутреннего состояния программы.
  • Если ваша программа выводит себя сама, это, вероятно, неправильно или полиглот.
PhiNotPi
источник
7
Мне потребовалось слишком много времени, чтобы понять, почему"If your program outputs itself, it is probably wrong or a polyglot."
trichoplax
1
Можно предположить, что доступная память не ограничена (иначе я не думаю, что это возможно)
KSab
1
@KSab Да, и это определенно невозможно иначе.
PhiNotPi
1
Последующее задание ( намного сложнее): Выводить все программы без остановки.
Майло Брандт
1
Допустимо ли выводить одну и ту же программу более одного раза?

Ответы:

9

Subleq OISC в Python, 317 269 ​​байт

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

Программа subleq - это расширяемый список целых чисел (p) и указатель инструкции (i). Этот вариант subleq использует относительную адресацию, которая, как предполагает вики-страница обсуждения, необходима для обеспечения полноты при ограниченных значениях. Каждый тик выполняется операция p[i+p[i+1]]-=p[i+p[i]], а затем, i+=p[i+2]если результат операции был <= 0, в противном случае i+=3. Если я когда-либо отрицательный, программа останавливается.

Эта реализация проверяет каждую программу, чье начальное состояние состоит из неотрицательных целых чисел (0-9) с начальным указателем команд 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

Выходная информация обратная, по соображениям игры в гольф. Вышеприведенная спецификация может быть переформулирована в обратном порядке, но тогда не будет соответствовать коду, используемому в реализации, поэтому я не описал ее таким образом.

РЕДАКТИРОВАТЬ: Первая программа, которая демонстрирует простой неограниченный рост - это 14283, который уменьшает значение в ячейке памяти 6 и записывает явный 0 (в отличие от неявного 0 в каждой ячейке) в следующую отрицательную ячейку, каждые три тика.

Sparr
источник
9

Побитовый циклический тег в CJam, 98 87 84 77 байт

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

Поскольку это бесконечный цикл, вы не можете напрямую проверить это в онлайн-переводчике. Тем не менее, вот альтернативная версия, которая считывает количество итераций из STDIN, с которыми вы можете поиграть. Чтобы протестировать полную программу, вам потребуется интерпретатор Java .

BCT - это минималистский вариант систем циклических меток . Программа определяется двумя двоичными строками: (циклическим) списком инструкций и начальным состоянием. Чтобы упростить мою жизнь при печати программ, я определил свою собственную запись: каждая из строк дана как массив целых чисел в стиле CJam, и вся программа заключена в [[...]], например,

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

Я также запрещаю пустые начальные состояния или пустые списки команд.

Инструкции в BCT интерпретируются следующим образом:

  • Если инструкция есть 0, удалите ведущий бит из текущего состояния.
  • Если инструкция есть 1, прочитайте другой бит из списка инструкций, вызовите это X. Если начальный бит из текущего состояния равен 1, добавьте Xк текущему состоянию, в противном случае ничего не делайте.

Если текущее состояние становится пустым, программа останавливается.

Первые несколько программ остановки

[[[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]]]

Если вы хотите увидеть больше, ознакомьтесь с версией в онлайн-переводчике, на которую я ссылался выше.

объяснение

Вот как работает код. Чтобы отследить ласточкин хвост, у нас всегда будет массив в стеке, который содержит все программы. Каждая программа является парой внутреннего представления программного кода (например [[0 1 0] [1 0]]), а также текущего состояния программы. Мы будем использовать только последнее для выполнения вычислений, но нам нужно будет помнить первое, чтобы напечатать программу, как только она остановится. Этот список программ просто инициализируется в пустой массив с L.

Остальная часть кода представляет собой бесконечный цикл, {...1}gкоторый сначала добавляет одну или несколько программ в этот список и вычисляет один шаг для каждой программы. Программы, которые останавливаются, распечатываются и удаляются из списка.

Я перечисляю программы, считая двоичное число. Начальная цифра убирается, чтобы мы могли получить все программы с ведущими нулями. Для каждого такого усеченного двоичного представления я нажимаю одну программу для каждого возможного разделения между инструкциями и начальным состоянием. Например, если счетчик в данный момент равен 42, его двоичное представление равно 101010. Мы избавляемся от лидирующих 1и выталкиваем все непустые разбиения:

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

Так как нам не нужны пустые инструкции или состояния, мы запускаем счетчик с 4, что дает [[[0] [0]]]. Это перечисление выполняется с помощью следующего кода:

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.

Остальная часть кода отображает блок в список программ, который выполняет один шаг вычисления BCT для второй половины этих пар и удаляет программу, если она останавливается:

)~     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.
}?
Мартин Эндер
источник
Ницца (+1). Некоторые байты могут быть сохранены, используя тот факт, что BCT завершен по Тьюрингу, даже если он ограничен использованием только 1 в качестве начальной строки данных (вашего «состояния»). Например, интерпретируйте каждое последующее положительное целое число в двоичном виде как 1P, затем выполняйте P на 1 и выводите P, если выполнение прекращается (повторное согласование). (Конечно, любой P, начинающийся с 0, будет в списке, так как это немедленно удалит исходную строку данных.)
res
8

Brainfuck в Python, 567 байтов

Относительно простое решение, поскольку Brainfuck едва ли не самый сложный язык для написания интерпретатора.

Эта реализация Brainfuck имеет указатель данных, начинающийся с 0, допускается принимать только положительное значение (считается ошибкой, если он пытается перейти влево от 0). Ячейки данных могут принимать значения от 0 до 255 и переноситься. 5 действительных инструкций ><+[]( -ненужны из-за упаковки).

Я думаю, что теперь все правильно, но трудно быть уверенным, что он печатает все возможные решения, поэтому я могу пропустить некоторые.

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

Первые несколько выходов:

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

И список первых 2000: http://pastebin.com/KQG8PVJn

И, наконец, список первых 2000 выходов с []ними: http://pastebin.com/iHWwJprs
(все остальные тривиальны, пока они действительны)

Обратите внимание, что выходные данные не в отсортированном порядке, хотя для многих из них может показаться, что программы, которые занимают больше времени, будут напечатаны позже.

KSab
источник
1
И голые, [-]и [+]должны определенно появляться, потому что содержимое цикла просто пропускается (без переноса).
PhiNotPi
@ Sp3000 [-]и [+]была ошибка , которая должна теперь фиксированной и я обновил с настройками
KSab
1
Почему вы поддерживаете .? Это не обязательно для подмножества BF, полного по Тьюрингу, и выход должен быть проигнорирован в любом случае. Кроме того, поскольку вы оборачиваете значения ячеек вокруг, я думаю, что вам нужен только один из -и +.
Мартин Эндер
@ MartinBüttner Я, кажется, неправильно понял вопрос; Я не читал часть «полное подмножество Тьюринга». Однако разве это не делает проблему почти эквивалентной для (большинства) языков? Не могли бы вы просто сделать замену 1: 1 Brainfuck (или, может быть, что-то еще проще), например, код c здесь: en.wikipedia.org/wiki/Brainfuck#Commands .
KSab
2
Взгляните на stackoverflow.com/questions/1053931/… особенно на запись в OISC. Также обратите внимание на правило 110 CA и Циклические теговые системы. В этой задаче есть много возможностей для творческого выбора полного «языка» Тьюринга.
Спарр
5

косые черты в Python, 640 498 байт

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////

Программа слэша - это строка, в этом интерпретаторе ограниченная символами '/' и '\'. В этой реализации / - это «1», а «\» - это «0», чтобы можно было играть в гольф с использованием корзины (x) python. Когда интерпретатор встречает \, выводится следующий символ, и оба символа удаляются. Когда он встречает /, он ищет шаблоны поиска и замены как / search / replace /, включая экранированные символы в шаблонах (\\ представляет \ и \ / представляет /). Эта операция замены затем выполняется над строкой несколько раз, пока строка поиска больше не присутствует, затем интерпретация продолжается с самого начала. Программа останавливается, когда она пуста. Программа будет убита, если есть открытый набор / шаблонов или \ без символа после него.

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
источник
4

Treehugger на Java, 1,299 1,257 1,251 1,207 1,203 1,201 1,193 1,189 байт

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
источник
4

BrachylogПроблема почтовой корреспонденции , 10 байт

≜;?{~c}ᵐ\d

Попробуйте онлайн!

Функция, которая является генератором, который генерирует все возможные проблемы почтовой корреспонденции, для которых в конечном итоге решения о грубой перестановке останавливаются. (Известно, что грубое решение проблемы почтовой корреспонденции является завершающей по Тьюрингу операцией.) Ссылка TIO содержит заголовок, который преобразует генератор в полную программу и распечатывает каждый вывод сразу после его создания (таким образом, когда TIO убивает программа из-за того, что потребляет более 60 секунд времени выполнения, вывод, полученный до сих пор, виден).

При этом используется формулировка задачи, в которой строки задаются в виде строк цифр, начальные нули не допускаются, кроме как для 0себя, решения проблемы, включающей в себя начальные нули, не принимаются, а строка цифр может быть представлена ​​либо как число. или минус число. Ясно, что ни одно из этого не имеет никакого влияния на полноту по Тьюрингу языка (потому что нет необходимости в проблеме корреспонденции после использования цифры ноль вообще).

Эта программа работает путем генерации всех возможных решений проблем, а затем работает в обратном направлении, чтобы найти оригинальные программы, которые решаются ими. Таким образом, отдельная программа может выводиться много раз. Неясно, отменяет ли это ответ или нет; обратите внимание, что все программы остановки будут в конечном итоге выводиться по крайней мере один раз (на самом деле, бесконечно много раз, поскольку любая программа, имеющая решения, имеет бесконечно много решений), а программы без остановки никогда не будут выводиться.

объяснение

≜;?{~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}

источник
2

"Фиолетовый без ввода / вывода" на Цейлоне, 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смысл символа из интерпретатора, таким образом, чтобы (потенциально) действительные символы просто a, b, A, B, iи 1(последняя только для чтения, а не для записи).

Но поскольку Purple самоизменяется (и использует свой исходный код в качестве данных), потенциально полезны также программы, содержащие другие символы, поэтому я решил разрешить в коде все печатаемые (не пробельные) символы ASCII (другие могут быть полезно, но не так легко печатается).

(Вы можете изменить интерпретатор так, чтобы он вместо этого принимал в качестве строки разрешенных символов в качестве аргумента командной строки - переключите закомментированную строку, определяющую aниже. Тогда длина становится 686 байтов.)

Мой «параллельный» интерпретатор, таким образом, создает все конечные строки из этих символов (в возрастающей длине и в лексикографическом порядке) и пробует каждый из них.

Purple будет останавливаться без ошибок всякий раз, когда команда, которая будет считана с ленты для выполнения, недействительна - таким образом, нет недопустимых программ и много-много остановок. (Большинство останавливается даже на первом шаге, только некоторые из программ с длиной 3 переходят на второй шаг (и затем останавливаются), первые не останавливающие программы имеют длину 6.

Я думаю, что самая первая не прерывающая программа в том порядке, который испробовал мой интерпретатор, - это то aaaiaa, что на первом шаге aрегистр устанавливается в 0 (каким он уже был), а на втором и каждом последующем шаге указатель команды возвращается к 0, вызывая его iaaснова выполнить .

Я повторно использовал часть кода, написанного для моего интерпретатора «стандартного» Purple , но из-за удаления ввода и вывода мой параллельный интерпретатор становится даже немного короче этого, включая дополнительную логику для выполнения нескольких программ одновременно.

Вот закомментированная и отформатированная версия:

// 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);
    }
}
Пауло Эберманн
источник
2

Комбинаторное исчисление SK в Haskell , 249 байтов

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)

Попробуйте онлайн!

Как это работает

Правила оценки по значению для исчисления комбинатора SK следующие:

(а) S xyzxz ( yz ), для x , y , z в нормальной форме;
(б) K xyx , для x , y в нормальной форме;
(c) xyxy , если xx ′;
(d) xyxy ′ для x в нормальной форме, если yy ′ .

Поскольку нас интересует только остановка поведения, мы немного расширяем язык, вводя символ H, который не находится в нормальной форме, но которому «оценивают» все нормальные формы:

(а) S xyzxz ( yz ), для x , y , z в нормальной форме;
(b ') K x H ↦ x для x в нормальной форме;
(c) xyxy , если xx ′;
(d) xyxy ′ для x в нормальной форме, если yy ′ ;
(е) S ↦ H;
(е) K ↦ H;
(ж) SH ↦ H;
(h) KH ↦ H;
(я) SHH ↦ H.

Мы считаем, что любое приложение H x является ошибкой во время выполнения и обрабатывается так, как если бы это был бесконечный цикл, и мы упорядочиваем оценки таким образом, чтобы (e) - (i) не производил H, кроме как в контексте, где игнорируется (верхний уровень, любой K x ☐, любой игнорируемый K☐, любой игнорируемый S x ☐ для x в нормальной форме, любой игнорируемый S☐H). Таким образом, мы не влияем на поведение при остановке обычных терминов, не содержащих H.

Преимущества этих измененных правил состоят в том, что каждый нормализуемый термин имеет уникальный путь оценки к H, и что каждый термин имеет конечное число возможных прообразов в ↦. Таким образом, вместо использования подхода ласточкин хвост, мы можем сделать намного более эффективный поиск в ширину всех путей обратной оценки из H.

nпроверяет, находится ли термин в нормальной форме, fнаходит все возможные прообразы термина и lявляется ленивым бесконечным списком нормализуемых терминов, полученных в результате поиска в ширину из H.

Андерс Касеорг
источник