De la part d'un traducteur: cet article a été publié sur le blog de l'auteur le 15 mars 2018. À mesure qu'un langage évolue, sa syntaxe peut être différente à l'heure actuelle. Tout ce qui est décrit concerne Zig 0.2.0, la version actuelle du langage est Zig 0.3.0.
J'ai contacté l'auteur de l'article, et il a aimablement fourni un lien vers le référentiel avec la version actuelle des sources du projet sur Zig 0.3.0
Bonjour Écrivons un interprète Brainfuck! "Pourquoi?" "Vous pouvez demander, mais vous ne trouverez pas la réponse ici."
Je vais le faire sur
Zig .

Zig c'est ...
... un nouveau langage de programmation. Il est toujours en version bêta et se développe rapidement. Si vous avez déjà vu le code Zig, le code dans ce post peut vous sembler un peu différent. Il est vraiment différent! Zig 0.2.0 vient de sortir, coïncidant avec la sortie de
LLVM 6 il y a quelques semaines, et comprend de nombreux changements de syntaxe et améliorations générales du langage. Généralement, de nombreux «sorts» ont été remplacés par des mots clés. Voir
ici pour une explication plus approfondie de tous les changements!
Zig est conçu
pour être lisible et relativement intuitif pour ceux qui sont familiers avec les langages compilés et typés tels que C, C ++ et, à certains moments, Rust.
Le code a été compilé et testé avec Zig 0.2.0, qui est disponible dès maintenant
via différents canaux , y compris homebrew, si vous êtes sur OSX: brew install zig.
Commençons
Pour savoir comment fonctionne Brainfuck, voir
ici . Il n'y a presque rien à y apprendre, mais c'est un langage
complet de Turing , ce qui signifie que vous pouvez écrire
n'importe quoi dessus.
J'ai posté le code
ici , au cas où vous voudriez voir le produit final ou des validations anticipées.
Zig est un langage compilé. Lorsque vous compilez un programme, le binaire résultant (si vous compilez un binaire exécutable, pas une bibliothèque) doit avoir une fonction principale qui marque le point d'entrée.
Alors ...
// main.zig fn main() void { }
... et commencez ...
$ zig build-exe main.zig
... donne ...
/zig/std/special/bootstrap.zig:70:33: error: 'main' is private /zigfuck/main.zig:2:1: note: declared here
main doit être déclarée publique pour être visible à l'extérieur du module ...
// main.zig pub fn main() void { }
Laissez le programme brainfuck utiliser un tableau de 30 000 octets comme mémoire, je vais faire un tel tableau.
// main.zig pub fn main() void { const mem: [30000]u8; }
Je peux déclarer une constante (const) ou une variable (var). Ici, j'ai déclaré mem comme un tableau de 30 000 octets (u) non signés (8 bits).
Cela ne compile pas.
/main.zig:3:5: error: variables must be initialized
Un programme C équivalent compilerait normalement: je peux déclarer une variable sans initialisation, mais Zig m'oblige à prendre une décision maintenant, au moment où la variable est déclarée. Peu m'importe ce qui y sera écrit, mais je dois l'indiquer explicitement. Je vais le faire en initialisant la variable avec une valeur non définie (non définie).
// main.zig pub fn main() void { const mem: [30000]u8 = undefined; }
L'initialisation d'une variable avec une valeur non définie ne donne aucune garantie sur la valeur de la variable en mémoire. C'est la même chose qu'une déclaration de variable non initialisée en C, sauf que vous devez l'indiquer explicitement.
Mais peut-être que je me fiche de comment initialiser cette mémoire. Peut-être que je veux avoir la garantie que les zéros ou une valeur arbitraire y sont écrits. Dans ce cas, je devrais également déclarer explicitement ceci:
// main.zig pub fn main() void { const mem = []u8{0} ** 30000; }
Cela peut sembler étrange, mais ** est l'opérateur utilisé pour développer les tableaux. Je déclare un tableau de 0 octet, puis je le développe à 30 000 et j'obtiens la valeur d'initialisation finale de 30 000 zéro octet. Cette opération se produit une fois,
au moment de la compilation . comptime est l'une des grandes idées de Zig, et j'y reviendrai dans l'un des articles suivants.
Écrivons maintenant un programme sur brainfuck qui ne fait qu'incrémenter le premier slot mémoire cinq fois!
pub fn main() void { const mem = []u8{0} ** 30000; const src = "+++++"; }
Dans Zig, les chaînes sont des tableaux d'octets. Je ne devrais pas déclarer src comme un tableau d'octets, car le compilateur l'implique. C'est facultatif, mais si vous le souhaitez, c'est possible:
const src: [5]u8 = "+++++";
Cela compilera très bien. Cependant, ceci:
const src: [6]u8= "+++++";
ne le sera pas.
main.zig:5:22: error: expected type '[6]u8', found '[5]u8'
Une dernière remarque: puisque les chaînes ne sont que des tableaux, elles ne se terminent pas par zéro. Cependant, vous pouvez déclarer une chaîne terminée par un caractère nul C. En tant que littéral, il ressemblera à ceci:
c"Hello I am a null terminated string";
Pour le bien commun ...
Je veux faire
quelque chose avec chaque caractère d'une chaîne. Je peux le faire! Au début de main.zig, j'importe quelques fonctions de la bibliothèque standard:
const warn = @import("std").debug.warn;
import , comme pratiquement tout ce qui commence par le signe @, est une
fonction de compilation intégrée . Ces fonctionnalités sont toujours disponibles dans le monde entier. L'importation ici fonctionne de manière similaire à javascript - vous pouvez importer n'importe quoi en creusant dans l'espace de noms et en extraire toutes les fonctions ou variables accessibles au public. Dans l'exemple ci-dessus, j'importe directement la fonction warn et l'affecte, tout d'un coup, à la constante warn. Maintenant, elle peut être appelée. Il s'agit d'un modèle courant: nous importons directement à partir de l'espace de noms std, puis appelons std.debug.warn () ou l'affectons à la variable warn. Cela ressemble à ceci:
const std = @import("std"); const warn = std.debug.warn;
const warn = @import("std").debug.warn; // main.zig pub fn main() void { const mem = []u8{0} ** 30000; const src = "+++++"; for (src) |c| { warn("{}", c); } }
Pendant le débogage, le développement initial et les tests, je veux juste imprimer quelque chose à l'écran. Zig est
sujet aux erreurs et stdout est également sujet aux erreurs. Je ne veux pas le faire pour le moment, et je peux imprimer directement sur stderr en utilisant warn, que nous avons importé de la bibliothèque standard.
warn prend une chaîne formatée, comme printf en C! Le code ci-dessus imprimera:
4343434343
43 est le code de caractère ascii +. Je peux aussi écrire:
warn("{c}", c);
et obtenez:
+++++
Nous avons donc initialisé l'espace mémoire et écrit le programme. Nous réalisons maintenant la langue elle-même. Je vais commencer par +, et remplacer le corps de la boucle for par switch:
for (src) |c| { switch(c) { '+' => mem[0] += 1 } }
Je reçois deux erreurs:
/main.zig:10:7: error: switch must handle all possibilities switch(c) { ^ /main.zig:11:25: error: cannot assign to constant '+' => mem[0] += 1 ^
Bien sûr, je ne peux pas affecter une nouvelle valeur à une variable, qui est une constante! mem doit être fait une variable ...
var mem = []u8{0} ** 30000;
comme pour les autres erreurs, ma construction de
commutateur devrait savoir quoi faire si le caractère n'est pas +, même si rien n'est à faire. Dans mon cas, c'est exactement ce que je veux. Je remplis ce cas avec un bloc vide:
for (src) |c| { switch(c) { '+' => mem[0] += 1, else => {} } }
Maintenant, je peux compiler le programme. Appelez avertir à la fin et exécutez:
const warn = @import("std").debug.warn; pub fn main() void { var mem = []u8{0} ** 30000; const src = "+++++"; for (src) |c| { switch(c) { '+' => mem[0] += 1, else => {} } } warn("{}", mem[0]); }
Je reçois le numéro 5 imprimé en
stderr , comme je m'y attendais.
Continuons ...
De même, nous soutenons.
switch(c) { '+' => mem[0] += 1, '-' => mem[0] -= 1, else => {} }
Pour utiliser> et <, vous devez utiliser une variable supplémentaire, qui sert de "pointeur" dans la mémoire que j'ai allouée au programme brainfuck de l'utilisateur.
var memptr: u16 = 0;
Puisqu'un 16 bits non signé peut être au maximum de 65 535, il suffit amplement d'indexer 30 000 octets d'espace d'adressage.
en fait, 15 bits nous suffiraient, ce qui nous permet d'adresser 32767 octets. Zig autorise les types avec des largeurs différentes , mais pas encore u15.
vous pouvez réellement faire u15 de cette façon:
const u15 = @IntType(false, 15):
Il est proposé que tout type [iu] \ d + soit valide comme type entier.
Maintenant, au lieu d'utiliser mem [0], je peux utiliser cette variable.
'+' => mem[memptr] += 1, '-' => mem[memptr] -= 1,
<et> simplement incrémenter et décrémenter ce pointeur.
'>' => memptr += 1, '<' => memptr -= 1,
Super Nous pouvons écrire un vrai programme maintenant!
Chèque 1,2,3
Zig a un moteur de test intégré. N'importe où dans n'importe quel fichier, je peux écrire un bloc de test:
test "Name of Test" { // test code }
et exécutez le test à partir de la ligne de commande: zig test $ FILENAME. Les autres blocs de test sont identiques au code normal.
Regardons ceci:
// test.zig test "testing tests" {} zig test test.zig Test 1/1 testing tests...OK
Bien sûr, un test vide est inutile. Je peux utiliser assert pour confirmer l'exécution des tests.
const assert = @import("std").debug.assert; test "test true" { assert(true); } test "test false" { assert(false); }
zig test test.zig "thing.zig" 10L, 127C written :!zig test thing.zig Test 1/2 test true...OK Test 2/2 test false...assertion failure [37;1m_panic.7 [0m: [2m0x0000000105260f34 in ??? (???) [0m [37;1m_panic [0m: [2m0x0000000105260d6b in ??? (???) [0m [37;1m_assert [0m: [2m0x0000000105260619 in ??? (???) [0m [37;1m_test false [0m: [2m0x0000000105260cfb in ??? (???) [0m [37;1m_main.0 [0m: [2m0x00000001052695ea in ??? (???) [0m [37;1m_callMain [0m: [2m0x0000000105269379 in ??? (???) [0m [37;1m_callMainWithArgs [0m: [2m0x00000001052692f9 in ??? (???) [0m [37;1m_main [0m: [2m0x0000000105269184 in ??? (???) [0m [37;1m??? [0m: [2m0x00007fff5c75c115 in ??? (???) [0m [37;1m??? [0m: [2m0x0000000000000001 in ??? (???) [0m
Le test est tombé. Utilisez la commande suivante pour reproduire l'erreur:
./zig-cache/test
La trace de pile sur le coquelicot est toujours en cours de développement.Pour tester cela efficacement, je dois le diviser en morceaux. Commençons par ceci:
fn bf(src: []const u8, mem: [30000]u8) void { var memptr: u16 = 0; for (src) |c| { switch(c) { '+' => mem[memptr] += 1, '-' => mem[memptr] -= 1, '>' => memptr += 1, '<' => memptr -= 1, else => {} } } } pub fn main() void { var mem = []u8{0} ** 30000; const src = "+++++"; bf(src, mem); }
Cela devrait sembler fonctionner, non?
Mais ...
/main.zig:1:29: error: type '[30000]u8' is not copyable; cannot pass by value
cela est décrit sur https://github.com/zig-lang/zig/issues/733 .
Zig est strict à ce sujet. Les types complexes et tous les objets pouvant être redimensionnés ne peuvent pas être transmis par valeur. Cela rend l'allocation de pile prévisible et logique et évite les copies inutiles. Si vous souhaitez utiliser la sémantique du transfert par valeur dans votre programme, vous pouvez l'implémenter vous-même en utilisant votre stratégie d'allocation, mais le langage lui-même ne le prend pas en charge dans des circonstances ordinaires.
Le moyen naturel de contourner cette limitation est de passer un pointeur au lieu d'une valeur (passer par référence). Zig utilise une stratégie différente, les tranches. Une tranche est un pointeur avec une longueur attachée et avec un contrôle pour tomber dans les bordures. La syntaxe de la signature de fonction ressemble à ceci:
fn bf(src: []const u8, mem: []u8) void { ... }
et lors de l'appel de la fonction, cela ressemble à ceci:
bf(src, mem[0..mem.len]);
Notez que j'ai défini la limite supérieure simplement en faisant référence à la longueur du tableau. Il existe une notation abrégée pour de tels cas:
bf(src, mem[0..]);
Maintenant, je peux commencer à écrire des tests qui testent directement la fonction bf (). Je vais ajouter des fonctions de test à la fin du fichier pour l'instant ...
test "+" { var mem = []u8{0}; const src = "+++"; bf(src, mem[0..]); assert(mem[0] == 3); }
Je prends le tableau mem d'un octet et vérifie ensuite ce qui doit arriver (l'octet est incrémenté trois fois). Ça marche!
Test 1/1 +...OK
"-" est coché de la même manière:
test "-" { var mem = []u8{0}; const src = "---"; bf(src, mem[0..]); assert(mem[0] == 253); }
Ça ne marche pas! Lorsque j'essaie de soustraire 1 de 0, j'obtiens ...
Test 2/2 -...integer overflow
mem est un tableau d'octets non signés et la soustraction de 1 à 0 provoque un débordement. Encore une fois, Zig me fait déclarer explicitement ce que je veux. Dans ce cas, je n'ai pas à me soucier du débordement, en fait, je veux que cela se produise, car nous avons affaire à
l'arithmétique modulaire , conformément à la
spécification de brainfuck . Cela signifie que décrémenter une cellule avec le nombre 0 me donnera 255, et un incrément de 255 me donnera 0.
Zig possède plusieurs opérations arithmétiques auxiliaires qui offrent la
sémantique du «wrapping» garanti .
'+' => mem[memptr] +%= 1, '-' => mem[memptr] -%= 1,
Cela résout tout le problème de débordement et fait ce que j'attendais.
Pour tester <et>, je navigue dans un petit tableau et vérifie la valeur de la cellule incrémentée:
test ">" { var mem = []u8{0} ** 5; const src = ">>>+++"; bf(src, mem[0..]); assert(mem[3] == 3); }
et ...
test "<" { var mem = []u8{0} ** 5; const src = ">>>+++<++<+"; bf(src, mem[0..]); assert(mem[3] == 3); assert(mem[2] == 2); assert(mem[1] == 1); }
Dans ce dernier cas, je peux directement comparer le résultat avec un tableau statique en utilisant ...
const mem = std.mem;
Rappelons que j'ai déjà importé std. Dans l'exemple ci-dessous, j'utilise mem.eql dans cet espace de noms:
test "<" { var storage = []u8{0} ** 5; const src = ">>>+++<++<+"; bf(src, storage[0..]); assert(mem.eql(u8, storage, []u8{ 0, 1, 2, 3, 0 })); }
... et rappelez-vous, les littéraux de chaîne, ce ne sont que des tableaux u8 en zig, et je peux y mettre des littéraux hexadécimaux, c'est-à-dire Le code suivant fonctionnera de la même manière!
assert(mem.eql(u8, storage, "\x00\x01\x02\x03\x00"));
Ajoutez le "."! Il imprime simplement en tant que caractère la valeur d'octet dans la cellule vers laquelle pointe le pointeur. J'utilise warn maintenant, mais plus tard je le remplacerai par stdout. C'est facile à faire conceptuellement, mais quelque peu confus dans la mise en œuvre. Je le ferai plus tard!
'.' => warn("{c}", storage[memptr]),
Cycles
[et] - la magie commence ici ....
[- si la valeur de la cellule actuelle est zéro, ignorez les étapes jusqu'au crochet fermant sans exécuter le code.
] - si la valeur de la cellule actuelle n'est pas nulle, revenez à la parenthèse ouvrante et exécutez à nouveau le code.
Cette fois, je vais commencer par un test, je vais les tester ensemble (évidemment, cela n'a aucun sens de les tester séparément). Le premier cas de test - la cellule de stockage [2] doit être vide, bien que la boucle doit l'incrémenter si elle démarre:
test "[] skips execution and exits" { var storage = []u8{0} ** 3; const src = "+++++>[>+++++<-]"; bf(src, storage[0..]); assert(storage[0] == 5); assert(storage[1] == 0); assert(storage[2] == 0); }
et je vais créer des blancs pour l'instruction switch:
'[' => if (storage[memptr] == 0) { }, ']' => if (storage[memptr] == 0) { },
Que faire maintenant? Vous pouvez utiliser une approche naïve. J'incrémente simplement le pointeur src jusqu'à ce que je le trouve]. Mais je ne peux pas utiliser la boucle for en zig pour cela, elle a été créée uniquement pour itérer dans les collections, sans manquer leurs éléments. Une construction appropriée ici est tout:
était:
var memptr: u16 = 0; for (src) |c| { switch(c) { ... } }
est devenu ...
var memptr: u16 = 0; var srcptr: u16 = 0; while (srcptr < src.len) { switch(src[srcptr]) { ... } srcptr += 1; }
Maintenant, je peux réaffecter le pointeur srcptr au milieu du bloc, je vais le faire:
'[' => if (storage[memptr] == 0) { while (src[srcptr] != ']') srcptr += 1; },
Cela satisfait le test "[] ignore l'exécution du code et quitte"
Cela satisfait le test «[] ignore l'exécution et quitte», bien qu'il ne soit pas entièrement fiable, comme nous le verrons.
Qu'en est-il des crochets de fermeture? Je pense que cela peut être écrit simplement par analogie:
test "[] executes and exits" { var storage = []u8{0} ** 2; const src = "+++++[>+++++<-]"; bf(src, storage[0..]); assert(storage[0] == 0); assert(storage[1] == 25); } ']' => if (storage[memptr] != 0) { while (src[srcptr] != '[') srcptr -= 1; },
Vous pouvez voir ce qui se passe ... Une solution naïve avec deux crochets a un défaut fatal et se casse complètement sur les boucles imbriquées. Tenez compte des éléments suivants:
++>[>++[-]++<-]
Le résultat devrait être {2, 0}, mais le premier crochet ouvert se déplace simplement bêtement vers le premier crochet de fermeture, et tout devient compliqué. Vous devez passer au support de fermeture suivant au même niveau d'imbrication. Il est facile d'ajouter un compteur de profondeur et de le suivre lorsque vous avancez le long de la ligne. Nous le faisons dans les deux sens:
'[' => if (storage[memptr] == 0) { var depth:u16 = 1; srcptr += 1; while (depth > 0) { srcptr += 1; switch(src[srcptr]) { '[' => depth += 1, ']' => depth -= 1, else => {} } } }, ']' => if (storage[memptr] != 0) { var depth:u16 = 1; srcptr -= 1; while (depth > 0) { srcptr -= 1; switch(src[srcptr]) { '[' => depth -= 1, ']' => depth += 1, else => {} } } },
et tests associés: notez que src dans les deux tests inclut une boucle interne.
test "[] skips execution with internal braces and exits" { var storage = []u8{0} ** 2; const src = "++>[>++[-]++<-]"; try bf(src, storage[0..]); assert(storage[0] == 2); assert(storage[1] == 0); } test "[] executes with internal braces and exits" { var storage = []u8{0} ** 2; const src = "++[>++[-]++<-]"; try bf(src, storage[0..]); assert(storage[0] == 0); assert(storage[1] == 2); }
Séparément, notez [-] - l'idiome de brainfuck, signifiant «zéro cette cellule». Vous pouvez voir que la valeur de la cellule au début n'a pas d'importance, elle sera décrémentée jusqu'à ce qu'elle atteigne 0, puis l'exécution se poursuivra.
Chemin malchanceux
Je ne comptais pas sur la possibilité que le programme sur bf soit cassé. Que se passe-t-il si je soumets un programme de saisie incorrect à mon interprète? Par exemple, simplement [sans parenthèse fermante, ou <, qui va immédiatement au-delà du tableau de mémoire? (Je peux envelopper le pointeur de mémoire, mais il vaut mieux considérer cela comme une erreur).
Je vais regarder un peu en avant et expliquer toutes les différences dans le code. Je mettrai la fonction d'interpréteur bf dans un fichier séparé et mettrai également la fonctionnalité de recherche et de recherche dans mes propres petites fonctions.
const warn = @import("std").debug.warn; const sub = @import("std").math.sub; fn seekBack(src: []const u8, srcptr: u16) !u16 { var depth:u16 = 1; var ptr: u16 = srcptr; while (depth > 0) { ptr = sub(u16, ptr, 1) catch return error.OutOfBounds; switch(src[ptr]) { '[' => depth -= 1, ']' => depth += 1, else => {} } } return ptr; } fn seekForward(src: []const u8, srcptr: u16) !u16 { var depth:u16 = 1; var ptr: u16 = srcptr; while (depth > 0) { ptr += 1; if (ptr >= src.len) return error.OutOfBounds; switch(src[ptr]) { '[' => depth += 1, ']' => depth -= 1, else => {} } } return ptr; } pub fn bf(src: []const u8, storage: []u8) !void { var memptr: u16 = 0; var srcptr: u16 = 0; while (srcptr < src.len) { switch(src[srcptr]) { '+' => storage[memptr] +%= 1, '-' => storage[memptr] -%= 1, '>' => memptr += 1, '<' => memptr -= 1, '[' => if (storage[memptr] == 0) srcptr = try seekForward(src, srcptr), ']' => if (storage[memptr] != 0) srcptr = try seekBack(src, srcptr), '.' => warn("{c}", storage[memptr]), else => {} } srcptr += 1; } }
Cela rend le commutateur beaucoup plus facile à lire, à mon avis, SeeForward et SeeBack fonctionnent et semblent très similaires, et j'ai été tenté de les transformer en quelque chose de plus intelligent et plus compact, mais à la fin ils font des choses différentes et gèrent les erreurs également de différentes manières. Plus facile à copier et à ajuster, ce sera donc plus clair. J'ajusterai également la fonction de recherche plus tard, à un moment donné, peut-être dans un article ultérieur.
J'ai ajouté des choses importantes! Notez que les trois fonctions renvoient désormais un type! .. Il s'agit de la nouvelle syntaxe pour ce qui était auparavant le type% T (union d'erreur). Cela signifie que la fonction peut renvoyer un certain type ou une erreur. Lorsque j'essaie d'appeler une telle fonction, je dois utiliser try avant d'appeler la fonction, ce qui lève l'erreur dans la pile des appels si l'erreur se produit, ou utiliser catch:
const x = functionCall() catch {}
Où je gère les erreurs dans un bloc catch. Comme écrit, catch peut avaler toutes les erreurs. C'est une mauvaise pratique, mais ici Zig nous oblige à le faire explicitement. Si j'attrape une erreur dans un bloc vide, je déclare soit que je ne pense pas qu'une erreur peut se produire, soit que je n'ai pas besoin de la gérer. En pratique, cela peut être quelque chose comme TODO, et en fait il est très facile de le rendre aussi explicite!
const x = functionCall() catch { @panic("TODO") }
Rappelons qu'un tel cas ne se produira jamais dans le code de production. J'informe le compilateur que je sais ce que je fais. Si une erreur pouvait se produire, je devrais ajouter la gestion des erreurs.
Alors, quelles erreurs dois-je renvoyer à partir de SeeBack ou SeeForward?
Dans SeeBack:
ptr = sub(u16, ptr, 1) catch return error.OutOfBounds;
J'ai remplacé le pointeur de décrémentation pour utiliser la sous-fonction de std lib, qui génère une erreur de débordement en cas de débordement. Je veux attraper cette erreur et retourner à la place l'erreur OutOfBounds, que je crée ici juste en l'utilisant.
Erreurs Zig est essentiellement un tableau de codes d'erreur qui est généré par le compilateur lorsque vous utilisez une erreur. Une sorte d'erreur. Ils sont garantis uniques et peuvent être utilisés comme valeurs dans un bloc de commutation.
Je veux utiliser OutOfBounds ici parce que, sémantiquement, si le pointeur de mémoire devient inférieur à zéro, je demande au runtime d'aller au-delà de l'espace mémoire que j'ai alloué.
de même dans la fonction seekForward:
if (ptr >= src.len) return error.OutOfBounds;
Dans ce cas, si le pointeur est plus grand que src.len, j'attrape l'erreur ici et renvoie la même erreur.
lors de l'appel:
'[' => if (storage[memptr] == 0) srcptr = try seekForward(src, srcptr), ']' => if (storage[memptr] != 0) srcptr = try seekBack(src, srcptr),
J'essaie d'appeler ces fonctions. S'ils sont appelés avec succès, ils sont exécutés correctement et essayez de renvoyer srcptr. S'ils échouent, essayez de terminer la fonction et renvoie une erreur à l'endroit de l'appel à la fonction entière bf.
L'appel peut provenir du principal!
const bf = @import("./bf.zig").bf; // yes, hello const hello_world = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>."; pub fn main() void { storage = []u8{0} ** 30000; bf(hello_world, storage[0..]) catch {}; }
J'avale cette erreur ici, et cela ne devrait pas être fait, mais nous noterons un point important sur la facilité avec laquelle zig peut transmettre des erreurs dans la pile d'appels. Il n'est pas de la responsabilité de la fonction appelante de vérifier chaque cas d'erreur, mais le compilateur force l'appel de chaque fonction qui peut échouer avec un essai. Cela doit toujours être fait, même si les erreurs sont ignorées!
La nouvelle syntaxe try / catch élimine les nombreux sorts comme %% et% que les gens n'aiment pas tellement.
Maintenant, j'ai implémenté 7 des 8 personnages de brainfuck, et cela suffit pour exécuter un programme «significatif».
Un programme significatif
Voici le programme:
// , const fib = "++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++>++++++++++++++++>>+<<[>>>>++++++++++<<[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]>[<+>-]>[-]>>>++++++++++<[->-[>+>>]>[+[-<+>]>+>>]<<<<<]>[-]>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<<.>.>>[>>+<<-]>[>+<<+>-]>[<+>-]<<<-]<<++...";
Courons ...
pub fn main() void { storage = []u8{0} ** 30000; bf(fib, storage[0..]) catch {}; }
le tour est joué!
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 121, 98, 219,
Un souvenir me revient chaque fois que je pense à une série de Fibonacci ... Je l'ai découvert grâce au programme PBS (Public Broadcasting Service, un service américain de télédiffusion non commerciale) dans les années 80, et je m'en souviens toujours. Je pensais que ce serait oublié, mais Youtube est une bonne chose .
Comment puis-je améliorer cela?
J'ai déjà fait allusion à quelques TODO. Je n'aurais pas dû utiliser stderr pour la sortie. Je veux utiliser stdout.
Chaque fois que j'ouvre l'interpréteur, j'ouvre le flux dans stdout et j'imprime dedans:
const io = std.io; ... pub fn bf(src: []const u8, storage: []u8) !void { const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); ... '.' => stdout.print("{c}", storage[memptr]) catch unreachable, ...
Que se passe-t-il ici? io.getStdOut(), ( catch unreachable — , !). , , , print. print , warn, . print , .
, stdout, stdout. Zig , , .
, , ? , , ? , ? , Zig !
, !
const bf = @import("./bf.zig").bf; const warn = @import("std").debug.warn; const serpinsky = "++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[ -<<<[ ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<< ]>.>+[>>]>+ ] "; pub fn main() void { var storage = []u8{0} ** 30000; bf(serpinsky, storage[0..]) catch unreachable; }
, bf , !void. , main. , :
const bf = @import("./bf.zig").bf; const warn = @import("std").debug.warn; const serpinsky = "++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[ -<<<[ ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<< ]>.>+[>>]>+ ] "; pub fn main() void { var storage = []u8{0} ** 30000; bf(serpinsky, storage[0..]) catch |err| switch (err) { }; }
!
/Users/jfo/code/zigfuck/main.zig:7:46: error: error.OutOfBounds not handled in switch shell returned 1
, bf ! , , stdout, bf. , , , try. , , , catch, try, , .
, :
const io = std.io; ... pub fn bf(src: []const u8, storage: []u8) !void { const stdout = &(io.FileOutStream.init(&(io.getStdOut() catch unreachable)).stream); ... '.' => stdout.print("{c}", storage[memptr]) catch unreachable, ...
:
const io = std.io; ... pub fn bf(src: []const u8, storage: []u8) !void { const stdout = &(io.FileOutStream.init(&(try io.getStdOut())).stream); ... '.' => try stdout.print("{c}", storage[memptr]), ...
:
const bf = @import("./bf.zig").bf; const warn = @import("std").debug.warn; const serpinsky = "++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[ -<<<[ ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<< ]>.>+[>>]>+ ] "; pub fn main() void { var storage = []u8{0} ** 30000; bf(serpinsky, storage[0..]) catch |err| switch (err) { }; }
, , !
/Users/jfo/code/zigfuck/main.zig:7:46: error: error.SystemResources not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.OperationAborted not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.IoPending not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.BrokenPipe not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.Unexpected not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.WouldBlock not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.FileClosed not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.DestinationAddressRequired not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.DiskQuota not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.FileTooBig not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.InputOutput not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.NoSpaceLeft not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.AccessDenied not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.OutOfBounds not handled in switch /Users/jfo/code/zigfuck/main.zig:7:46: error: error.NoStdHandles not handled in switch shell returned 1
Zig , ! switch , , , , .
pub fn main() void { var storage = []u8{0} ** 30000; bf(serpinsky, storage[0..]) catch |err| switch (err) { error.OutOfBounds => @panic("Out Of Bounds!"), else => @panic("IO error") }; }
- , , , Zig, ! ,
! !
Todo
, ! , , ",", brainfuck- getc, . , bf. , , Zig. , , , .
Conclusion
, , Zig . Zig , , , , , ++. , , . , , . Zig , , , .
Zig, , 0.2.0 ! , , debug-, , ! --release-fast --release-safe,
.
.
Zig. , 1.0.0, Zig, , , !
, #zig freenode , .