Concevez un ordinateur à un jeu d'instructions!

31

Remarque: je suis prêt à donner une prime à toute réponse que je trouve intéressante.

Votre défi consiste à concevoir un ordinateur à un jeu d'instructions complet de Turing (OISC):

Un OISC est une machine abstraite qui utilise une seule instruction, ce qui évite d'avoir à utiliser un opcode en langage machine. Avec un choix judicieux pour l'instruction unique et des ressources infinies données, un OISC est capable d'être un ordinateur universel de la même manière que les ordinateurs traditionnels qui ont plusieurs instructions.

Voici quelques exemples de commandes uniques qui font un OISC complet de Turing.

Règles:

Vous devez fournir une interprétation ou une preuve de celle-ci

Vous devez fournir un interprète pour votre langue. Cet interpréteur ne doit être limité que par la mémoire / l'heure (par exemple, il ne doit pas avoir de restrictions imposées par l'utilisateur). Si vous ne fournissez pas d'interprète pour votre langue (pour une raison autre que la paresse), vous devez prouver qu'il est possible d'en rédiger un. Un interprète doit être possible .

Vous devez prouver son intégralité Turing

Vous devez inclure une preuve formelle que votre langue est Turing-complete. Un moyen simple de le faire est de prouver qu'il peut interpréter ou avoir le même comportement qu'un autre langage complet de Turing. La langue la plus élémentaire à interpréter serait Brainf ** k .

Par exemple, un langage normal qui a toutes les mêmes commandes que Brainf ** k (et le même manque de restrictions de mémoire imposées par l'utilisateur) est Turing-complete car tout ce qui peut être implémenté dans Brainf ** k peut être implémenté dans la langue .

Voici une liste de langages Turing-complete très simples à implémenter.

Exigences supplémentaires de l'OISC

  • Cet OISC ne doit avoir qu'une seule instruction - il ne peut pas avoir plusieurs instructions, l'une d'entre elles le rendant Turing-complet.

  • Votre OISC peut utiliser n'importe quelle syntaxe que vous aimez. Vous devez définir dans votre réponse ce qu'est une instruction, ce qui est des données et ce qu'est un no-op (par exemple un espace blanc). Sois créatif!

  • Les arguments ne doivent pas simplement être des entiers. Par exemple, /// est un bel exemple d'OISC complet de Turing.

  • La façon dont les entrées et les sorties sont prises et données vous appartient. La plupart des OISC implémentent les E / S via des emplacements de mémoire spécifiques, mais il peut y avoir d'autres moyens de le faire, et vous êtes encouragés à en trouver un.

  • Une réponse valide doit fournir un exemple de code dans votre OISC, soit en l'incluant dans le message, soit en établissant un lien vers un défi simple résolu dans la langue.

Vote

Votants, n'oubliez pas de ne pas voter de façon ennuyeuse. Exemples:

  • Lenguage - équivalents
  • Une implémentation d'un OISC existant (répondeurs, veuillez créer le vôtre!)
  • Un "OISC" dans lequel le premier argument spécifie une commande à appeler ( exemple )

Cependant, vous devriez voter pour des soumissions créatives intéressantes, telles que:

  • Un OISC basé sur une équation mathématique
  • Un ZISC complet de Turing basé sur un réseau de neurones
  • Un OISC dans lequel les E / S de sortie se produisent autrement que dans certains emplacements de mémoire

Gagnant

Comme pour le , la réponse avec le plus de votes gagne! Bonne chance!

MD XF
la source
10
Qu'est-ce qu'une "instruction"? Et comment les comptons-nous?
Wheat Wizard
1
@NoOneIsHere j'aimerais en savoir assez pour voter xD
Brian H.
2
J'ai rétrogradé cela. Je pense que c'est une idée très intéressante, mais vous n'expliquez pas exactement ce qu'est un OISC et comment confirmer quelque chose. J'ai fait de BF un OISC, mais c'est clairement contraire à l'esprit de la question, mais techniquement valable.
NoOneIsHere
1
@MDXF je ne pense pas que vous obtenez ///: il a une commande de substitution, et il a des commandes d'impression, ce qui n'est pas seulement un effet secondaire de la commande de substitution
Destructible Lemon
1
@NoOneIsHere Parce que concours de popularité . Oui, c'est valide, mais il a un mauvais score (décompte des voix), donc il ne gagnera pas.
user202729

Réponses:

20

XOISC

Cet OISC est basé sur le combinateur X de Fokker qui est défini comme suit:

X=λf .f (λg h x .g x (h x)) (λa b c .a)

XSKIX

S=X (X X)K=X XI=S K K=X (X X) (X X) (X X)

Comment fonctionne XOISC

n

  • nf1fNf1 (f2 ((fN X)))

Une fois qu'il ne reste plus d'instructions, XOISC poussera tous les arguments de ligne de commande (le cas échéant) vers la pile, par exemple:

[s1,, sMstack before, a1,, aNarguments]

((((s1 s2)) sM) a1))aN


Étant donné que la seule instruction dans XOISC ne prend qu'un seul argument (décalage de mémoire), il n'y a aucune raison d'utiliser même un nom pour cette instruction. Un fichier source valide sera donc constitué uniquement d'entiers séparés par des sauts de ligne ou des espaces blancs, comme par exemple:

0 0 2 0 1 0 1

Essayez-le en ligne!

Exemple

Prenons l'exemple ci-dessus (la pile s'agrandit vers la droite):

0pop 0 and apply (ie. push single X):[X]0again simply push X:[X, X]2pop 2 (a,b) and push a (b X):[X (X X)]0simply push X:[X (X X), X]1pop 1 (a) and push a X:[X (X X), X X]0simply push X:[X (X X), X X, X]1pop 1 (a) and push a X:[X (X X), X X, X X]

((X (X X)) (X X)) (X X)X (X X) (X X) (X X)S K K

Turing exhaustivité

Idée de preuve

X

X

((X (X X)) (X X)) (X X)

  • X0
  • ensuite, nous sommes dans un nouveau niveau de parenthèses, nous avons donc seulement besoin d'un 0
  • maintenant deux parenthèses se ferment, nous devons donc faire apparaître 2 éléments: 2
  • encore une fois, nous sommes dans un nouveau niveau de parenthèses, nous avons donc besoin d'un 0
  • deux parenthèses, refermer à nouveau un 2
  • et pareil encore

Nous nous retrouvons donc avec un programme XOISC différent (mais sémantiquement équivalent):

0 0 2 0 2 0 2 Essayez-le en ligne!

X

Preuve formelle

Étant donné que le SKI-calcul est Turing complet, nous devons montrer deux choses:

  1. X
  2. X

La première partie - prouver les trois égalités dans l'introduction - est très fastidieuse et encombrante, elle n'est pas non plus très intéressante. Donc, au lieu de le mettre dans cet article, vous pouvez le trouver ici * .

X

XXf gfg

0XF1FNG1GKfgf g

F1FN G1GK1 (GK+1)fggff g

Interprète

Contributions

Étant donné que le calcul lambda non typé nous oblige à définir nos propres types de données pour tout ce que nous voulons et que cela est lourd, l'interprète connaît les chiffres de l' Église - cela signifie que lorsque vous fournissez des entrées, il transformera automatiquement les nombres en leur chiffre d'église correspondant.

À titre d'exemple, voici un programme qui multiplie deux nombres: Essayez-le en ligne!

Vous pouvez également fournir des fonctions comme arguments en utilisant les indices De Bruijn , par exemple le Scombinateur \\\(3 1 (2 1))(ou λλλ(3 1 (2 1))). Toutefois , elle reconnaît aussi le S, K, Iet bien sûr XCombinator.

Sortie

Par défaut, l'interpréteur vérifie si la sortie code un entier, si c'est le cas, elle affichera le nombre correspondant (en plus du résultat). Pour plus de commodité, il y a le -bdrapeau qui dit à l'interpréteur d'essayer de faire correspondre un booléen à la place (voir le dernier exemple).

Assembleur

Bien sûr, tout langage de bas niveau a besoin d'un assembleur qui lui convertit un langage de haut niveau, vous pouvez simplement utiliser n'importe quelle entrée (voir ci-dessus) et le traduire en programme XOISC en utilisant le -adrapeau, essayez-le en ligne! **


* Dans le cas où le lien est en baisse, il y a une copie en commentaire HTML dans cet article.

** Il en résulte un programme qui teste la primalité, essayez-le en ligne!

ბიმო
la source
1
Y a-t-il une raison pour laquelle vous avez choisi le combinateur X au lieu du combinateur Iota?
Esolanging Fruit
1
@EsolangingFruit: Oui, il y a aussi plusieurs autres options, au final j'ai opté pour celle-ci car elle utilise le moins d'applications pour construire SK. Il semblait que ce serait le meilleur (tbh je n'ai pas fait de comparaison moi-même).
ბიმო
1
Btw. il y a une belle comparaison de plusieurs combinateurs dans le papier lié si vous êtes intéressé.
ბიმო
19

Dessiner

Draw est un OISC agissant sur une grille 2D, marquant les carrés d'une manière similaire à la machine Wang B. Cependant, pour garder le langage aussi simple et OISC-y que possible, toutes les instructions (dont il y a un grand total d'un) marquent le carré qui vient d'être foulé et, pour pouvoir s'arrêter, marcher sur un carré marqué termine le programme.

Le programme se compose d'une séquence de lignes contenant un identifiant de ligne (chaîne arbitraire n'incluant pas # ni espace), deux entiers ( xet y) et deux autres identificateurs de ligne ( aet b).

Le programme s'exécute comme suit: en
commençant par la ligne identifiée comme startavec le pointeur pointant vers la position (0, 0), déplacez le pointeur de la quantité indiquée par xet yet marquez le carré sur lequel le pointeur se trouve maintenant (sauf si le carré est déjà marqué, auquel cas l'exécution se termine). Ensuite, passez à la ligne asi au moins l'un des carrés directement adjacents est également marqué, et à la ligne bsinon.

Les interprètes sont encouragés à produire le résultat final de la grille sous la forme d'une sorte d'image, de toile, etc.

Complétude de Turing

Draw est Turing-complete car il est possible de compiler une version modifiée (appelée Alternate) d'une machine Minsky dans la langue.

Alternate agit de manière similaire à une machine Minsky à deux compteurs, mais il existe une grande restriction sur les commandes: les commandes doivent alterner entre le ciblage du premier et du deuxième compteur. Pour contourner cette modification, a été ajouté une commande supplémentaire: nop. Cette commande ne change pas du tout le compteur ciblé, ce qui permet de "compléter" les modifications consécutives sur un compteur, satisfaisant la restriction décrite ci-dessus. Cela signifie également que le registre à modifier n'a pas à être donné et, pour une instruction donnée, peut être directement déduit des instructions à partir desquelles l'exécution peut y accéder.

Exemple: cette machine Minsky

1 inc A 2
2 inc A 3
3 dec A 3 4
4 halt

se transforme en ce programme alternatif:

1 inc 2
2 nop 3
3 inc 4
4 nop 5
5 dec 6 8
6 nop 5
7 halt
8 halt

Cette restriction est nécessaire en raison de la façon dont le programme Draw éventuel gère les registres, c'est-à-dire qu'il ne les différencie pas du tout. Au lieu de cela, le programme Draw copie simplement le registre qui n'a pas été modifié par l'instruction précédente, en le modifiant en fonction de l'instruction en cours d'exécution.

Ensuite, le programme alternatif est directement traduit en Draw comme suit:

Le programme démarre avec ce bloc.

start 0 0 a a
a 3 0 b b
b -3 1 c c
c 3 0 d d
d -3 2 e e
e 3 0 f f
f 3 -3 i1_a i1_a

inc, decEt nopsont traduits dans presque la même manière que l'autre. Dans tous les cas, il n'y a pas de différence entre changer le premier ou le deuxième registre (comme expliqué ci-dessus). Voici un incrément, équivalent à inc 2:

i1_y 0 -2 i1_z i1_y
i1_z 3 -1 i1_a i1_a
i1_a -5 1 i1_b i1_b
i1_b 0 2 i1_c i1_c
i1_c 0 2 i1_d i1_e
i1_d 0 2 i1_d i1_f

i1_e 5 0 i2_z i2_y
i1_f 5 0 i2_z i2_y

Modifiez les nombres dans les i1_xparties à l'index de l'instruction en cours, et dans les i2_xparties à l'index de la prochaine instruction à exécuter.

L' nopinstruction peut être traduite comme telle:

i1_y 0 -2 i1_z i1_y
i1_z 3 -1 i1_a i1_a
i1_a -5 1 i1_b i1_b
i1_b 0 2 i1_c i1_c
i1_c 0 2 i1_d i1_e
i1_d 0 2 i1_d i1_f

i1_e 5 -2 i2_z i2_y
i1_f 5 -2 i2_z i2_y

Ceci est un décrément:

i1_y 0 -2 i1_z i1_y
i1_z 3 -1 i1_a i1_a
i1_a -5 1 i1_b i1_b
i1_b 0 2 i1_c i1_c
i1_c 0 2 i1_d i1_e
i1_d 0 2 i1_d i1_f

i1_e 5 -2 i3_z i3_y
i1_f 5 -4 i2_z i2_y

i3_x fait référence à l'instruction à appeler si le compteur est déjà 1.

Arrêt:

i1_y 0 0 0 0
i1_z 0 0 0 0

Changez les étiquettes de manière appropriée et enchaînez tout simplement ensemble. Faire cela pour l'exemple ci-dessus donne le programme Draw dans le référentiel d'en haut.

Interprètes

Il existe actuellement deux interprètes, tous deux écrits en Python. Ils peuvent être trouvés sur le référentiel GitHub de Draw .

  1. draw.py : Cet interpréteur est destiné à la ligne de commande et prend la source du programme comme argument. Après chaque étape, il sort la commande qui a été exécutée et l'emplacement du pointeur d'instruction; une fois le programme arrêté, il imprime le nombre de cellules marquées.
  2. draw_golly.py : Cette version utilise Golly pour la sortie graphique plus facile et dans le mauvais but , en prenant la source via une boîte popup lors du démarrage du script. Golly peut être un peu capricieux avec Python, alors assurez-vous que Python 2 est installé (et ne mélangez pas Golly 32 bits avec Python 64 bits ou vice versa). La sortie est fournie via la grille de cellules intégrée de Golly.

L'image suivante est un exemple de sortie du deuxième interpréteur. L'exécution de l'exemple de programme dans le référentiel donne ceci (ou similaire):

ivzem
la source
1
Incroyable! Félicitations d'avoir trouvé une façon unique de relever le défi.
MD XF
Votre langue n'a pas du tout besoin de s'arrêter pour être complète. La règle 110 ne prend pas fin, mais elle est néanmoins complète.
Akangka
+1 pour Golly, le meilleur simulateur d'automates cellulaires jamais créé.
HighlyRadioactive
14

-3

Voici l'essentiel.

Mémoire

La mémoire est une carte de bandes, où les clés sont des chaînes et les valeurs sont des entiers de taille arbitraire.

De plus, il existe un ensemble d'étiquettes sur lesquelles le programme peut accéder.

Il y a une pile, qui contient les opérandes, qui sont des chaînes.

Il y a un offest, qui contrôle où dans les bandes de la mémoire il peut accéder.

The One Instruction

-. Tout d'abord, il sort une chaîne LABELde la pile. Si cela LABELn'est pas défini comme une étiquette, il définit l'étiquette et efface la source de cette étiquette (c'est-à-dire d'où elle a été poussée) et l'instruction en cours. Sinon, il effectue le calcul suivant, en utilisant les deux premières valeurs, Aet B:

if mem[A] < mem[B]:
    jump to LABEL
if mem[A] != mem[B]:
    mem[A]--
else:
    mem[B]++

Notez que s'il y a des arguments en excès ou des arguments insuffisants, le programme affichera une erreur, montrant l'état du programme.

Le décalage peut être modifié en accédant à la valeur de ..

Exemple de code

X-

i i X-
i i X-
i i X-
i i X-
i i X-
i i X-
i i X-

Cela définit la variable isur 7, en incrémentant les 7temps.

X-

i i X-
i i X-
i i X-
LOOP-
    a a X-
    a a X-
    j i LOOP-

Cela multiplie i+1par la constante 2.

Preuve d'exhaustivité de Turing

Sans tenir compte des tailles int de C ++ (c'est-à-dire, en supposant qu'elles soient infinies), -3 est Turing Complete par réduction à 3-cell brainfuck . Je peux ignorer cette taille car il peut être écrit un interpréteur pour -3 sur un ordinateur avec une mémoire infinie qui a des cellules arbitrairement grandes.

Je crois également que tout BCT peut être écrit en tant que programme -3.

Conor O'Brien
la source
Comme j'aime améliorer mon contenu, s'il vous plaît, une explication concernant le vote négatif serait appréciée
Conor O'Brien