ゞグはどのように機胜したすか

翻蚳者からこの投皿は、2018幎3月15日に著者のブログに公開されたした。 蚀語が進化するに぀れお、その構文は珟圚異なる堎合がありたす。 説明されおいるものはすべおZig 0.2.0に関連し、蚀語の珟圚のバヌゞョンはZig 0.3.0です。



私は投皿の䜜者に連絡し、圌は芪切にZig 0.3.0のプロゞェクト゜ヌスの珟圚のバヌゞョンでリポゞトリぞのリンクを提䟛したした。



こんにちは Brainfuckむンタヌプリタヌを䜜成したしょう 「なぜ」ず尋ねるこずはできたすが、ここでは答えが芋぀かりたせん。



Zigでそれを行いたす。



ゞグは...



...新しいプログラミング蚀語。 ただベヌタ版であり、急速に開発䞭です。 Zigコヌドを以前に芋たこずがあれば、この投皿のコヌドはあなたずは少し違うように芋えるかもしれたせん。 圌は本圓に違う Zig 0.2.0がリリヌスされたばかりで、数週間前のLLVM 6のリリヌスに合わせお、倚くの構文の倉曎ず䞀般的な蚀語の改善が含たれおいたす。 ほずんどの堎合、倚くの「スペル」はキヌワヌドに眮き換えられおいたす。 すべおの倉曎の詳现に぀いおは、 こちらをご芧ください



Zigは読みやすいように蚭蚈されおおり 、C、C ++、およびRustなどのコンパむルされ型付けされた蚀語に粟通しおいる人にずっおは比范的盎感的です。



コヌドは、Zig 0.2.0でコンパむルおよびテストされおいたす。これは、OSXを䜿甚しおいる堎合、homebrewを含むさたざたなチャネルからすぐに入手できたす。brewinstall zig。



さあ始めたしょう



Brainfuckの仕組みに぀いおは、 こちらをご芧ください 。 そこで孊ぶこずはほずんどありたせんが、 チュヌリング完党な蚀語であるため、 䜕でも曞くこずができたす。



最終補品や初期のコミットを芋たい堎合に備えお、 ここにコヌドを投皿したした。



Zigはコンパむルされた蚀語です。 プログラムをコンパむルするずき、結果のバむナリラむブラリではなく実行可胜バむナリをコンパむルする堎合には、゚ントリポむントをマヌクするメむン関数が必芁です。



だから...



// main.zig fn main() void { }
      
      





...そしお開始...



 $ zig build-exe main.zig
      
      





...配る...



 /zig/std/special/bootstrap.zig:70:33: error: 'main' is private /zigfuck/main.zig:2:1: note: declared here
      
      





モゞュヌルの倖から芋えるようにするには、mainをpublicずしお宣蚀する必芁がありたす...



 // main.zig pub fn main() void { }
      
      





Brainfuckプログラムが30,000バむトの配列をメモリずしお䜿甚するようにしたす。このような配列を䜜成したす。



 // main.zig pub fn main() void { const mem: [30000]u8; }
      
      





定数constたたは倉数varを宣蚀できたす。 ここでは、memを30,000の笊号なしuバむト8ビットの配列ずしお宣蚀したした。



これはコンパむルされたせん。



 /main.zig:3:5: error: variables must be initialized
      
      





同等のCプログラムは正垞にコンパむルされたす。初期化せずに倉数を宣蚀できたすが、倉数が宣蚀された時点で、Zigがすぐに決定を䞋すように匷制したす。 䜕が曞き蟌たれるかは気にしないかもしれたせんが、これを明瀺的に瀺す必芁がありたす。 これを行うには、倉数を未定矩の倀未定矩で初期化したす。



 // main.zig pub fn main() void { const mem: [30000]u8 = undefined; }
      
      





未定矩の倀で倉数を初期化しおも、メモリ内の倉数の倀に関する保蚌はありたせん。 これは、明瀺的に指定する必芁があるこずを陀いお、Cの初期化されおいない倉数宣蚀ず同じです。



しかし、このメモリを初期化する方法は気にしないかもしれたせん。 おそらく、れロたたは任意の倀がそこに曞き蟌たれるこずを保蚌したいのです。 この堎合、これも明瀺的に述べる必芁がありたす。



 // main.zig pub fn main() void { const mem = []u8{0} ** 30000; }
      
      





奇劙に思えるかもしれたせんが、**は配列の展開に䜿甚される挔算子です。 0バむトの配列を宣蚀し、それを30,000に拡匵し、30,000バむトの最終初期化倀を取埗したす。 この操䜜は、 コンパむル時に 1回発生したす 。 comptimeはZigの玠晎らしいアむデアの1぀であり、次の投皿の1぀でそれに戻りたす。



では、最初のメモリスロットを5回増やすこず以倖は䜕もしないBrainfuckのプログラムを䜜成したしょう。



 pub fn main() void { const mem = []u8{0} ** 30000; const src = "+++++"; }
      
      





Zigでは、文字列はバむト配列です。 コンパむラがこれを暗黙指定しおいるため、srcをバむト配列ずしお宣蚀しないでください。 これはオプションですが、必芁な堎合は可胜です。



 const src: [5]u8 = "+++++";
      
      





これで問題なくコンパむルできたす。 ただし、これ



 const src: [6]u8= "+++++";
      
      





なりたせん。



 main.zig:5:22: error: expected type '[6]u8', found '[5]u8'
      
      





もう1぀の泚意文字列は単なる配列であるため、れロで終わるこずはありたせん。 ただし、ヌル終了文字列Cを宣蚀できたす。リテラルずしおは、次のようになりたす。



 c"Hello I am a null terminated string";
      
      





䞀般的な利益のために...



文字列のすべおの文字で䜕かをしたい。 できるよ main.zigの冒頭で、暙準ラむブラリからいく぀かの関数をむンポヌトしたす。



 const warn = @import("std").debug.warn;
      
      





importは、@蚘号で始たるほがすべおのものず同様に、 組み蟌みコンパむラヌ関数です。 そのような機胜は垞にグロヌバルに利甚可胜です。 ここでのむンポヌトは、javascriptず同様に機胜したす。ネヌムスペヌスを掘り䞋げお、そこから公開されおいる関数たたは倉数を抜出するこずで、䜕でもむンポヌトできたす。 䞊蚘の䟋では、warn関数を盎接むンポヌトし、突然、warn定数に割り圓おたす。 今、圌女を呌び出すこずができたす。 これは䞀般的なパタヌンです。std名前空間から盎接むンポヌトしおから、std.debug.warnを呌び出すか、warn倉数に割り圓おたす。 次のようになりたす。



 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); } }
      
      





デバッグおよび初期開発ずテスト䞭に、画面に䜕かを印刷したいだけです。 Zigぱラヌを起こしやすく 、stdoutも゚ラヌを起こしやすいです。 今はこれをやりたくありたせん。暙準ラむブラリからむンポヌトしたwarnを䜿甚しおstderrに盎接印刷できたす。



warnはCのprintfのようなフォヌマットされた文字列を受け取りたす 䞊蚘のコヌドは印刷されたす



 4343434343
      
      





43はASCII文字コヌド+です。 私も曞くこずができたす



 warn("{c}", c);
      
      





取埗



 +++++
      
      





そこで、メモリ空間を初期化し、プログラムを䜜成したした。 今、私たちは蚀語そのものを実珟しおいたす。 +で開始し、forルヌプの本䜓をスむッチに眮き換えたす。



 for (src) |c| { switch(c) { '+' => mem[0] += 1 } }
      
      





次の2぀の゚ラヌが衚瀺されたす。



 /main.zig:10:7: error: switch must handle all possibilities switch(c) { ^ /main.zig:11:25: error: cannot assign to constant '+' => mem[0] += 1 ^
      
      





もちろん、倉数に新しい倀を割り圓おるこずはできたせん。これは定数です memは倉数にする必芁がありたす...



 var mem = []u8{0} ** 30000;
      
      





他の゚ラヌず同様に、私のスむッチ構成は、䜕もする必芁がない堎合でも、文字が+でない堎合に䜕をすべきかを知っおいる必芁がありたす。 私の堎合、これはたさに私が望むものです。 このケヌスを空のブロックで埋めたす



 for (src) |c| { switch(c) { '+' => mem[0] += 1, else => {} } }
      
      





これでプログラムをコンパむルできたす。 最埌に譊告を呌び出しお実行したす



 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]); }
      
      





予想どおり、 stderrに数字の5が出力されたす。



続けたしょう...



同様に、サポヌトしたす。



 switch(c) { '+' => mem[0] += 1, '-' => mem[0] -= 1, else => {} }
      
      





>ず<を䜿甚するには、远加の倉数を䜿甚する必芁がありたす。この倉数は、ナヌザヌBrainfuckプログラムに割り圓おたメモリ内の「ポむンタヌ」ずしお機胜したす。



 var memptr: u16 = 0;
      
      





笊号なし16ビットは最倧65535になる可胜性があるため、30,000バむトのアドレス空間をむンデックスするには十分です。



実際、15ビットで十分なので、32767バむトをアドレス指定できたす。 Zigでは、 異なる幅の型を䜿甚できたすが、ただu15は䜿甚できたせん。



実際にこの方法でu15を実行できたす。



 const u15 = @IntType(false, 15):
      
      





[iu] \ d +型は敎数型ずしお有効であるこずが提案されおいたす。


mem [0]を䜿甚する代わりに、この倉数を䜿甚できたす。



 '+' => mem[memptr] += 1, '-' => mem[memptr] -= 1,
      
      





<and>このポむンタを単玔に増枛したす。



 '>' => memptr += 1, '<' => memptr -= 1,
      
      





いいね これで実際のプログラムを曞くこずができたす



1,2,3を確認したす



Zigにはテスト゚ンゞンが組み蟌たれおいたす。 任意のファむルのどこにでもテストブロックを蚘述できたす。



 test "Name of Test" { // test code }
      
      





コマンドラむンからテストを実行したすzig test $ FILENAME。 残りのテストブロックは、通垞のコヌドず同じです。



これを芋おみたしょう



 // test.zig test "testing tests" {} zig test test.zig Test 1/1 testing tests...OK
      
      





もちろん、空のテストは圹に立ちたせん。 assertを䜿甚しお、テストの実行を実際に確認できたす。



 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
      
      





テストは萜ちたした。 次のコマンドを䜿甚しお、゚ラヌを再珟したす。



 ./zig-cache/test
      
      





ポピヌのスタックトレヌスはただ開発䞭です。



これを効率的にテストするには、分解する必芁がありたす。 これから始めたしょう



 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); }
      
      





うたくいくはずですよね



しかし...



 /main.zig:1:29: error: type '[30000]u8' is not copyable; cannot pass by value
      
      





これはhttps://github.com/zig-lang/zig/issues/733で説明されおいたす 。


Zigはこれに぀いお厳栌です。 耇合型、およびサむズ倉曎可胜なすべおのオブゞェクトは、倀で枡すこずはできたせん。 これにより、スタックの割り圓おが予枬可胜か぀論理的になり、䞍芁なコピヌが回避されたす。 プログラムで倀による転送のセマンティクスを䜿甚する堎合は、割り圓お戊略を䜿甚しお自分で実装できたすが、通垞の環境では蚀語自䜓がこれをサポヌトしおいたせん。



この制限を回避する自然な方法は、倀の代わりにポむンタヌを枡すこずです参照枡し。 Zigは別の戊略であるスラむスを䜿甚したす。 スラむスは、長さが付加されたポむンタヌであり、境界に収たるかどうかをチェックしたす。 関数シグネチャの構文は次のようになりたす。



 fn bf(src: []const u8, mem: []u8) void { ... }
      
      





関数を呌び出すず、次のようになりたす。



 bf(src, mem[0..mem.len]);
      
      





配列の長さを参照するだけで䞊限を定矩したこずに泚意しおください。 そのような堎合には、衚蚘の省略圢がありたす。



 bf(src, mem[0..]);
      
      





これで、bf関数を盎接テストするテストの䜜成を開始できたす。 今のずころ、ファむルの最埌にテスト機胜を远加したす...



 test "+" { var mem = []u8{0}; const src = "+++"; bf(src, mem[0..]); assert(mem[0] == 3); }
      
      





mem配列を1バむトから取埗し、次に䜕が起こるかを確認したすバむトは3回むンクリメントされたす。 うたくいく



 Test 1/1 +...OK
      
      





「-」は同じ方法でチェックされたす。



 test "-" { var mem = []u8{0}; const src = "---"; bf(src, mem[0..]); assert(mem[0] == 253); }
      
      





動䜜したせん 0から1を枛算しようずするず、...



 Test 2/2 -...integer overflow
      
      





memは笊号なしバむトの配列であり、0から1を匕くずオヌバヌフロヌが発生したす。 繰り返しになりたすが、Zigは私が欲しいものを明瀺的に宣蚀させたす。 この堎合、私はオヌバヌフロヌに぀いお心配する必芁はありたせん。実際、私たちはBrainfuckの仕様に埓っおモゞュラヌ挔算を扱っおいるので、オヌバヌフロヌが起こるこずを望みたす。 ぀たり、数倀0のセルをデクリメントするず255が埗られ、255の増分で0が埗られたす。



Zigには、保蚌された「ラッピング」のセマンティクスを提䟛するいく぀かの補助的な算術挔算がありたす。



 '+' => mem[memptr] +%= 1, '-' => mem[memptr] -%= 1,
      
      





これにより、オヌバヌフロヌの問題党䜓が解決され、期埅どおりに動䜜したす。



<and>をテストするために、小さな配列をナビゲヌトし、むンクリメントされたセルの倀を確認したす。



 test ">" { var mem = []u8{0} ** 5; const src = ">>>+++"; bf(src, mem[0..]); assert(mem[3] == 3); }
      
      





そしお...



 test "<" { var mem = []u8{0} ** 5; const src = ">>>+++<++<+"; bf(src, mem[0..]); assert(mem[3] == 3); assert(mem[2] == 2); assert(mem[1] == 1); }
      
      





埌者の堎合、...を䜿甚しお静的配列ず結果を盎接比范できたす...



 const mem = std.mem;
      
      





すでにstdをむンポヌトしおいるこずを思い出しおください。 以䞋の䟋では、この名前空間でmem.eqlを䜿甚しおいたす。



 test "<" { var storage = []u8{0} ** 5; const src = ">>>+++<++<+"; bf(src, storage[0..]); assert(mem.eql(u8, storage, []u8{ 0, 1, 2, 3, 0 })); }
      
      





...そしお、文字列リテラルを思い出しおください。これらはzigのu8配列であり、16進リテラルを入れるこずができたす。 次のコヌドも同じように機胜したす



 assert(mem.eql(u8, storage, "\x00\x01\x02\x03\x00"));
      
      





「。」を远加しおください ポむンタが指すセルのバむト倀を文字ずしお単に出力したす。 珟圚warnを䜿甚しおいたすが、埌でstdoutに眮き換えたす。 これは抂念的には簡単ですが、実装では倚少混乱したす。 埌でやりたす



 '.' => warn("{c}", storage[memptr]),
      
      





サむクル

[および]-魔法はここから始たりたす....



[-珟圚のセルの倀がれロの堎合、コヌドを実行せずに閉じ括匧ぞのステップをスキップしたす。

]-珟圚のセルの倀がれロでない堎合、開き括匧に戻り、コヌドを再床実行したす。



今回はテストから始めたすが、䞀緒にテストしたす明らかに、個別にテストするのは意味がありたせん。 最初のテストケヌス-ストレヌゞ[2]セルは空である必芁がありたすが、開始するずルヌプが増加したす。



 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); }
      
      





switchステヌトメントの空癜を䜜成したす。



 '[' => if (storage[memptr] == 0) { }, ']' => if (storage[memptr] == 0) { },
      
      





今䜕をしたすか 玠朎なアプロヌチを䜿甚できたす。 srcポむンタを芋぀けるたでむンクリメントしたす]。 しかし、私はこのためにゞグでforルヌプを䜿甚するこずはできたせん。これは、コレクションを反埩するためだけに䜜成されたもので、芁玠を倱うこずはありたせん。 ここで適切な構成は次のずおりです。



だった



 var memptr: u16 = 0; for (src) |c| { switch(c) { ... } }
      
      





なった...



 var memptr: u16 = 0; var srcptr: u16 = 0; while (srcptr < src.len) { switch(src[srcptr]) { ... } srcptr += 1; }
      
      





これで、ブロックの䞭倮でsrcptrポむンタヌを再割り圓おできたす。これを行いたす。



 '[' => if (storage[memptr] == 0) { while (src[srcptr] != ']') srcptr += 1; },
      
      





これは、「[]はコヌドの実行をスキップしお終了する」ずいうテストを満たしたす。

これは、「[]が実行をスキップしお終了する」ずいうテストを満たしたすが、完党に信頌できるわけではありたせんが、埌で説明したす。



閉じ括匧に぀いおはどうですか 私はそれを単に類掚によっお曞けるず信じおいたす



 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; },
      
      





あなたは䜕が起こるかを芋るこずができたす... 2぀のブラケットを持぀玠朎な解決策は臎呜的な欠陥を持ち、ネストされたルヌプで完党に壊れたす。 以䞋を考慮しおください。



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





結果は{2、0}になりたすが、最初の開き括匧は単玔に愚かに最初の閉じ括匧に移動し、すべおが混乱したす。 同じレベルのネストで、次の閉じ括匧にゞャンプする必芁がありたす。 深床カりンタヌを远加しお、ラむンに沿っお前進するに぀れお远跡するのは簡単です。 私たちは䞡方向でそれを行いたす



 '[' => 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 => {} } } },
      
      





および関連するテスト䞡方のテストのsrcには内郚ルヌプが含たれおいるこずに泚意しおください。



 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); }
      
      





別に、[-]に泚意しおください-「このセルをれロ」を意味するBrainfuckのむディオム。 セルが最初にどの倀を持っおいたかは関係なく、0に達するたでデクリメントされ、実行が続行されたす。


䞍運な道



bfのプログラムが壊れる可胜性を期埅しおいたせんでした。 間違った入力プログラムをむンタヌプリタヌに送信するずどうなりたすか たずえば、単に[閉じかっこ、たたは<なしで、すぐにメモリ配列を超えたすか メモリポむンタをラップするこずはできたすが、これを゚ラヌず芋なすこずをお勧めしたす。



少し先を芋お、コヌドのすべおの違いを説明したす。 bfむンタヌプリタヌ関数を別のファむルに配眮し、seekBackおよびseekForward機胜を独自の小さな関数に配眮したす。



 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; } }
      
      





これにより、スむッチは読みやすくなり、seekForwardずseekBackの倖芳ず動䜜は非垞に䌌おおり、私はそれらをよりスマヌトでコンパクトなものにリファクタリングしたいず思いたしたが、最終的には異なるこずをしお゚ラヌを凊理したすたた、さたざたな方法で。 コピヌず調敎が簡単なので、より明確になりたす。 たた、おそらく次の投皿のいずれかで、ある時点でseekForwardを調敎したす。



重芁なものを远加したした 3぀の関数がすべお型を返すようになりたした..これは、以前はtypeT゚ラヌ結合だったものの新しい構文です。 これは、関数が特定のタむプたたぱラヌを返すこずができるこずを意味したす。 そのような関数を呌び出そうずするず、関数を呌び出す前にtryを䜿甚する必芁がありたす。これは、゚ラヌが発生した堎合に呌び出しスタックに゚ラヌをスロヌするか、catchを䜿甚したす。



 const x = functionCall() catch {}
      
      





catchブロックで゚ラヌを凊理する堎所。 曞いたように、catchぱラヌを飲み蟌む可胜性がありたす。 これは悪い習慣ですが、ここではZigによっお明瀺的に行われたす。 空のブロックで゚ラヌをキャッチするず、゚ラヌが発生する可胜性があるずは思わないか、゚ラヌを凊理する必芁がないず断蚀したす。 実際には、TODOのようなものになる可胜性がありたす。実際、明瀺的にするこずも非垞に簡単です。



 const x = functionCall() catch { @panic("TODO") }
      
      





このようなケヌスは本番コヌドでは決しお起こらないこずを思い出しおください。 自分が䜕をしおいるかを知っおいるこずをコンパむラヌに通知しおいたす。 ゚ラヌが発生する可胜性がある堎合、゚ラヌ凊理を远加する必芁がありたす。



それで、seekBackたたはseekForwardから返される゚ラヌは䜕ですか



seekBackの堎合



 ptr = sub(u16, ptr, 1) catch return error.OutOfBounds;
      
      





デクリメントポむンタヌを眮き換えお、std libのサブ関数を䜿甚したす。この関数は、オヌバヌフロヌが発生した堎合にオヌバヌフロヌ゚ラヌをスロヌしたす。 この゚ラヌをキャッチしお、代わりにOutOfBounds゚ラヌを返したいので、それを䜿甚しおここで䜜成したす。



゚ラヌZigは基本的に、゚ラヌを䜿甚するずきにコンパむラヌによっお生成される゚ラヌコヌドの配列です。 これらは䞀意であるこずが保蚌されおおり、switchブロックの倀ずしお䜿甚できたす。


ここでOutOfBoundsを䜿甚したいのは、意味的に、メモリポむンタが0未満になった堎合、ランタむムに割り圓おたメモリ領域を超えるように芁求するためです。



同様に、seekForward関数で



 if (ptr >= src.len) return error.OutOfBounds;
      
      





この堎合、ポむンタヌがsrc.lenより倧きい堎合、ここで゚ラヌをキャッチし、同じ゚ラヌを返したす。



呌び出すずき



 '[' => if (storage[memptr] == 0) srcptr = try seekForward(src, srcptr), ']' => if (storage[memptr] != 0) srcptr = try seekBack(src, srcptr),
      
      





これらの関数を呌び出そうずしたす。 正垞に呌び出された堎合、正しく実行され、srcptrが返されたす。 倱敗した堎合、tryは関数を終了し、bf関数党䜓が呌び出された堎所に゚ラヌを返したす。



呌び出しはメむンからの堎合がありたす



 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 {}; }
      
      





ここでこの゚ラヌを飲み蟌みたすが、そうすべきではありたせんが、ゞグがどのように簡単に゚ラヌを呌び出しスタックに枡すこずができるかに぀いおの重芁なポむントに泚意しおください。 ゚ラヌのすべおのケヌスをチェックするのは呌び出し偎関数の責任ではありたせんが、コンパむラヌはtryで呌び出しに倱敗する可胜性のある各関数を匷制したす。 ゚ラヌが無芖されおも、これは垞に行われなければなりたせん



新しいtry / catch構文は、人々がそれほど嫌う%%やのような倚くの呪文を排陀したす。


今、私は8人䞭7人のBrainfuckキャラクタヌを実装したした。これは「意味のある」プログラムを実行するのに十分です。



意味のあるプログラム



プログラムは次のずおりです。



 //   ,   const fib = "++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++>++++++++++++++++>>+<<[>>>>++++++++++<<[->+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]>[<+>-]>[-]>>>++++++++++<[->-[>+>>]>[+[-<+>]>+>>]<<<<<]>[-]>>[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]<[++++++++++++++++++++++++++++++++++++++++++++++++.[-]]<<<++++++++++++++++++++++++++++++++++++++++++++++++.[-]<<<<<<<.>.>>[>>+<<-]>[>+<<+>-]>[<+>-]<<<-]<<++...";
      
      





起動...



 pub fn main() void { storage = []u8{0} ** 30000; bf(fib, storage[0..]) catch {}; }
      
      





出来䞊がり



 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 121, 98, 219,
      
      





フィボナッチシリヌズのこずを考えるたびに思い出が1぀返っおきたす... 80幎代のPBS公共攟送サヌビス、アメリカの非営利テレビ攟送サヌビス番組でそれを知りたした。 忘れられるず思いたしたが、 Youtubeは玠晎らしいものです。


どうすればこれを改善できたすか



私はすでにいく぀かのTODOをほのめかしたした。 出力にstderrを䜿甚するべきではありたせん。 stdoutを䜿甚したい。



むンタヌプリタヌを開くたびに、stdoutでストリヌムを開き、印刷したす。



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





ここで䜕が起こっおいたすか私は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は返されるため゚ラヌを生成するこずがあるこずを知っおいたすメむン関数の呌び出し偎でこの゚ラヌを飲み蟌みたす。運呜を受け入れお正しいこずをする準備ができたら、次のような゚ラヌをキャッチできたす。



 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および補助関数から発生したため、おなじみのはずですしかし、bfで飲み蟌んだstdoutによっお生成された゚ラヌを芋るず想像しおみたしょう。それらを飲み蟌む代わりに、tryを䜿甚しおチェヌンを抌し䞊げる必芁がありたす。キャッチせずに゚ラヌを生成する関数の呌び出しを䜿甚しお、tryを䜿甚するこずを思い出しおください。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を䜿甚するず、これらの゚ラヌを必芁に応じお凊理できるようになりたす。゚ラヌ倀に基づいお切り替えを行い、必芁であればケヌスを凊理し、スキップしたい堎合はスキップしたす。



 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がいかにスマヌトであるかを実蚌したいだけですそしお、゚ラヌが発生するず、スタックトレヌスの代わりに゚ラヌトレヌスを取埗したすかっこいい



藀堂



むンタヌプリタヌを䜿甚しおさたざたな改善を行うこずができたすもちろん、実際にすべおの゚ラヌを正しく凊理する必芁がありたす。たた、brainfuckではgetc関数ずしお機胜し、実行時にプログラムにデヌタを入力できる「、」挔算子を実装する必芁がありたす。たた、ハヌドコヌドされたbf゜ヌスコヌドを䜿甚する代わりに、゜ヌスファむルをバッファに読み蟌んで解釈できるようにする必芁がありたす。厳密には必芁ではない改善もいく぀かありたすが、Zigの機胜の䞀郚を瀺す堎合がありたす。投皿の最埌にそれらをすべおダンプするのではなく、それらを郚分に分割し、将来の投皿で公開したす。



おわりに



この半完成のミニチュアプロゞェクトが、Zigコヌドがどのように芋え、䜕に䜿甚できるかに぀いおの掞察を䞎えおくれるこずを願っおいたす。 Zigはスむスのナむフではありたせん。すべおに最適なツヌルではありたせん。特定のこずに焊点を合わせおおり、CたたはC ++の代わりにたたは䞀緒に䜿甚できる実甚的なシステム蚀語であるこずに焊点を圓おおいたす。これにより、メモリ䜿甚量、メモリ管理、および゚ラヌ凊理に慎重にアプロヌチしたした。リ゜ヌスが限られおいる環境では、これは䟿利な機胜であり、バグではありたせん。 Zigは決定論的であり、あいたいさはなく、埓来は困難であった環境で信頌性の高いコヌドの蚘述を促進しようずしたす。



Zigの構文ず機胜のごく䞀郚を説明したしたが、バヌゞョン0.2.0以降の蚀語には倚くの興味深い倉曎がありたす私が曞いたコヌドはすべおデバッグモヌドでコンパむルされたす。これはセキュリティチェックに最適で、コンパむル時間を短瞮しお反埩を高速化するのに最適です。 --release-fastモヌドず--release-safeモヌドがあり、今埌さらに倚くのモヌドが远加されたす。これらのモヌドの違いず説明に぀いおは、こちらをご芧ください。



Zigの開発のスピヌドず方向には垞に驚かされたす。倚くはただ動いおいたすが、バヌゞョン1.0.0のリリヌスたではそうであり、Zigを詊しおみるこずにしたのであれば、たくさんの良いアむデアがあるこずを芚えおおいおください。



詊しおみお、質問があればい぀でもfreenodeで#zigに参加しおください。



All Articles