memo88の2020年ブックマークランキング

blog.sushi.money

やってみました。調べてないけど9割方セルクマ 😅

ブクマしてくださった方、ありがとうございます。

memo88の2020年ブックマークランキングベスト24(累計30ブックマーク)

# タイトル
1位 きしださんのかわいいリレーショナルデータベースをRubyで写経した - memo88
2位 リレー式論理回路シミュレータを自作して1bit CPUまで動かした - memo88
3位 Rubyで素朴な自作言語のコンパイラを作った - memo88
4位 LibreOffice Drawのodgファイルから図形の情報を抜き出して使う - memo88
5位 kairo-gokko 製作メモ - memo88
6位 Ruby/Racc: パースに失敗した位置(行、桁)を得る - memo88
7位 素朴な自作言語のコンパイラをGoに移植した - memo88
8位 kairo-gokko (35) 1bit CPU 2 - memo88
9位 kairo-gokko (34) 1bit CPU 1 - memo88
10位 素朴な自作言語のコンパイラをPHPに移植した - memo88
11位 HiveQL テスト 自動化 メモ - memo88
12位 kairo-gokko (33) Dフリップフロップ 2 - memo88
13位 DockerでDigdagサーバを動かす(クライアントの簡単な動作確認用、PostgreSQL不使用) - memo88
14位 素朴な自作言語のコンパイラをPerlに移植した - memo88
15位 素朴な自作言語のコンパイラをCに移植した - memo88
16位 失敗したタスクの情報をdigdagコマンドとシェルスクリプトで取得する - memo88
17位 Ubuntu 18.04にJupyter NotebookとIRubyをインストール(pyenv, rbenv を使用) - memo88
18位 hive.server2.enable.doAs がよく分からなかったので Apache Bigtop で調べてみた - memo88
19位 素朴な自作言語のコンパイラをJavaに移植した - memo88
20位 正規表現エンジン(ロブ・パイクのバックトラック実装)をRubyで写経した - memo88
21位 素朴な自作言語のコンパイラをTypeScript(Deno)に移植した - memo88
22位 素朴な自作言語のコンパイラをDartに移植した - memo88
23位 素朴な自作言語のコンパイラをC♭に移植した - memo88
24位 素朴な自作言語のコンパイラをPythonに移植した - memo88

generated by 年間ブックマークランキングジェネレーター

素朴な自作言語のコンパイラをGoに移植した


移植一覧に戻る


やっつけなので汚いです。ライフゲームコンパイルが通ったのでヨシ、というレベルのものです。

github.com


移植元

memo88.hatenablog.com

ベースになっているバージョン: tag:50 のあたり

(2021-06-20 追記) ステップ60 の修正まで反映しました

メモ

  • Go は 3, 4 年前にちょっとしたCLIツールを作った程度
  • C の後だとすごい楽ちん
  • メソッドも便利
  • ほんとは練習のためにもっとスライスを使いたかったけど、あまり使わないままできあがってしまった

今回の実験コーナー。

これまで Dart への移植 のときに set を不要にし、 Java への移植 のときに call を不要にし、 Perl への移植 のときに call_set を不要にしてきました。

今回はこれらを全部適用して、さらに Python への移植 のときにやった、 Ruby っぽい見た目への変更も加えました。

trial ブランチ でやってみました。

vgコードはこんな感じ。 var を除けばこれはもう Ruby ですね。

def calc_next_gen(current_val, count)
  # 注目しているセルの次世代の生死
  var next_val = 0;

  case
  when (current_val == 0)
    case
    when (count == 3)
      next_val = 1;
    end
  when (0 == 0)
    case
    when (count == 2)
      next_val = 1;
    when (count == 3)
      next_val = 1;
    end
  end
  
  return next_val;
end

def main()
  var w = 5; # 盤面の幅
  var h = 5; # 盤面の高さ

  # 初期状態の設定
  vram_set(w, 1, 0, 1);
  vram_set(w, 2, 1, 1);
  vram_set(w, 0, 2, 1);
  vram_set(w, 1, 2, 1);
  vram_set(w, 2, 2, 1);

  var gen_limit = 0;
  var gen = 1;
  while (gen != gen_limit)
    make_next_gen(w, h);
    replace_with_buf();
    gen = gen + 1;
  end
end

Zig: コマンドライン引数を受け取って整数(i32)に変換する

Zig 昨日触りはじめたばかりでまだぜんぜん分かってません。


まずは std.os.argvコマンドライン引数を取得します。

pkv は確認用のユーティリティ関数で、 print key value のつもり。

// arg_to_i_v1.zig

const std = @import("std");

fn pkv(k: []const u8, v: var) !void {
    std.debug.warn("{} ({})\n", .{k, v});
}

pub fn main() !void {
    try pkv("std.os.argv", std.os.argv);
    try pkv("std.os.argv type", @TypeOf(std.os.argv) == [][*:0]u8);

    const arg1: [*:0]u8 = std.os.argv[1];
    try pkv("arg1", arg1);
}
$ zig-0.6.0 run arg_to_i_v1.zig -- 42
std.os.argv ([*:0]u8@7ffc55805dd8)
std.os.argv type (true)
arg1 (42)

コマンドライン引数が文字列(というかバイト列)の配列として取得できたので、次はこれを i32 に変換します。

標準ライブラリに std.fmt.parseInt という関数があったので適当に使ってみました。

// arg_to_i_v2.zig

const std = @import("std");

pub fn main() !void {
    const arg1: [*:0]u8 = std.os.argv[1];

    const n: i32 = try std.fmt.parseInt(
        i32,  // 変換後の型
        arg1, // 変換元の文字列
        10    // 基数
    );
}

これは型が違うためエラーになります。

./arg_to_i_v2.zig:14:5: error: expected type '[]const u8', found '[*:0]u8'
    arg1, // 変換元の文字列

ここで詰まったので、一度落ち着いて、型について見てみることにしました。


std.os.argv の型は [][*:0]u8 、 その要素(それぞれの引数)の型は [*:0]u8


[]Foo は Foo のスライス。 この表記はスライスだ、と丸呑みしてもよいですが、 これが出てきたら [] の中が空なのでコンパイル時には要素数(長さ)が決まらない(実行時に決まる) ……みたいなことを考えればいいんでしょうか。


[*]Foo は Foo の配列へのポインタ型。 うーん、この表記は覚えるしかなさそう。 Cの配列のように先頭と要素の型に関する情報は持っているが、要素数の情報は持たない。


[A:B]Foo は要素数が A で終端の要素(sentinel)が B である Foo の配列を表す型。

たとえば、[4:0]u8 は要素数が 4 で、終端の要素が 0 である u8 の配列の型、となる。

[ 11, 22, 33, 44, 0 ]
                  ^ ここ(xs[要素数])が 0

で、 [*:0]Foo は何なのか。 要素数* とは??   と思ってしまいましたが、 これは「終端が0である配列」へのポインタ型、と読めばいいようです。たぶん。 終端が 0 であることは分かっているが要素数は分からない配列の先頭を差すポインタの型。

[len:sentinel] のパターンに当てはめるのではなく、この場合は [*]Foo に終端の情報 :0 が加わっている、と捉えればよいのでしょうか。


あと残っているのは []const u8const ですね。

const を外して []u8 だけだったら何かというと、えーとこれは「u8 のスライス」ですね。

ただの []Foo だと要素を書き換えられるスライスですが、 []const Foo だと要素を書き換えられないスライスになるようです。

var xs: [3]u8 = [3]u8{ 'a', 'b', 'c' };

const slice: []u8 = xs[0..xs.len];
slice[0] = 'A';

const unmodifiable_slice: []const u8 = xs[0..xs.len];
unmodifiable_slice[0] = 'A';
//=> error: cannot assign to constant

std.fmt.parseInt[]const u8 を要求しているということは、つまり 「std.fmt.parseInt では渡されたスライスの内容を変更しません(できません)よ」 ということを引数の型で表明しているのだと思います。

std.fmt.parseInt を使う側は「呼び出した関数の中で引数で渡したものの内容が書き換えられたりしないか?」という心配をしなくて済む、ありがたい、ということでしょう。


他の例としては、たとえば OutStream の write 関数や print 関数に渡す文字列の型も []const u8 になっています。

https://github.com/ziglang/zig/blob/0.6.0/lib/std/io/out_stream.zig

pub fn write(self: Self, bytes: []const u8) Error!usize { ... }
pub fn print(self: Self, comptime format: []const u8, args: var) Error!void { ... }

ここまで分かったので、次にすべきなのは [*:0]u8 から []const u8 への変換です。

// こういう形に持っていきたい:
const slice: []const u8 = ??? // [*:0]u8 から変換する
const n: i32 = try std.fmt.parseInt(i32, slice, 10);

枝葉を無視して「配列からスライスへの変換」と考えてみます。

配列からスライスを作る基本的な方法はこう。

const xs: [3]u8 = [3]u8{ 'a', 'b', 'c' };
const slice: []u8 = xs[0..xs.len];

ですが、今必要なのは [*:0]u8 から []const u8 への変換なので、 その形に持って行きたい。

変換後は []const u8 にしたいので、とりあえず const を付けてみます。

const xs: [3]u8 = [3]u8{ 'a', 'b', 'c' };
const slice: []const u8 = xs[0..xs.len];
//             ^^^^^

えーとそれから次は……この例だと変換元が [3]u8 と要素数を指定していますが、これを [*:0]u8 にしたいので…… :0 を付けてみましょうか。

const xs: [3:0]u8 = [3:0]u8{ 'a', 'b', 'c' };
const slice: []const u8 = xs[0..xs.len];

ここから [*:0]u8 にすると、スライスに変換するときに必要な xs.len が取れなくなります。

const xs: [*:0]u8 = ...;
const slice: []const u8 = xs[0..???];

[*:0]u8 ということは、要素数は分からないけれど終端が 0 であることは分かっています。 なので、要素数を知りたければ 0 の位置を探せばいいんじゃないでしょうか。

次のような関数を書いてみました。

fn countChars(chars: [*:0]u8) usize {
    var i: usize = 0;
    while (true) {
        if (chars[i] == 0) {
            return i;
        }
        i += 1;
    }
}

これを使って、 [*:0]u8 な配列を作って []const u8 に変換するサンプルコードを書いてみました。

// arg_to_i_v3.zig 
// ... snip ...

pub fn main() !void {
    var xs: [5:0]u8 = [5:0]u8{ 'a', 'b', 'c', 0, 'd' };
    try pkv("xs", xs);
    try pkv("xs.len", xs.len);

    const xs2: [*:0]u8 = &xs;
    try pkv("xs2", xs2);
    // try pkv("xs2.len", xs2.len);
    //   => error: type '[*:0]u8' does not support field access
    const slice = xs2[0..countChars(xs2)];
    try pkv("slice", slice);
    try pkv("slice.len", slice.len);
}
$ zig-0.6.0 run arg_to_i_v3.zig 
xs (abcd)
xs.len (5)
xs2 (abc)
slice (abc)
slice.len (3)

できました。 ためしに途中の要素 xs[3] を 0 にしてみたため abc までで打ち切られていますが、 終端が 0 だということになっているのでこれはこれでよいでしょう。


ここまでできたらあとは parseInt に渡すだけです。 まとめると下記のようになりました。

// arg_to_i_v4.zig

const std = @import("std");

fn pkv(k: []const u8, v: var) !void {
    std.debug.warn("{} ({})\n", .{k, v});
}

fn countChars(chars: [*:0]u8) usize {
    var i: usize = 0;
    while (true) {
        if (chars[i] == 0) {
          return i;
        }
        i += 1;
    }
}

pub fn main() !void {
    const arg1: [*:0]u8 = std.os.argv[1];

    const arg1_unmodifiable_slice: []const u8 = arg1[0..countChars(arg1)];

    const n: i32 = try std.fmt.parseInt(
        i32,
        arg1_unmodifiable_slice,
        10
    );

    try pkv("n", n);
}
$ zig-0.6.0 run arg_to_i_v4.zig -- 42
n (42)

上記の countChars に相当する関数は標準ライブラリにあるんじゃないかと思って調べてみましたが、ちょっと分かりませんでした。

が、テストコードの中に strlen という関数があるのは見つけました。

https://github.com/ziglang/zig/blob/0.6.0/test/stage1/behavior/const_slice_child.zig#L35

fn strlen(ptr: [*]const u8) usize {
    var count: usize = 0;
    while (ptr[count] != 0) : (count += 1) {}
    return count;
}

だいたい同じことやってますね。

参考

この記事を読んだ人は(ひょっとしたら)こちらも読んでいます

zenn.dev

memo88.hatenablog.com

Zig: 1バイトごとに読み書きするだけのcatコマンドを書いてみた

Zig(ziglang) で標準入力から1バイト読んで標準出力に書くのを繰り返すだけの素朴な cat コマンドを書いてみました。

Zig はさっき触り始めたばかりで右も左も分からない状態です。


<追記 2022-07-17>

v0.8.0 で InStream, OutStream がそれぞれ reader, writer という名前に変わりました。

Proposal: Rename InStream and OutStream · Issue #4917 · ziglang/zig

この変更にあわせて、下記のように修正する必要があります。v0.9.1 でコンパイル+実行できることを確認しました。

- const outstream = std.io.getStdOut().outStream();
+ const outstream = std.io.getStdOut().writer();

- const instream = std.io.getStdIn().inStream();
+ const instream = std.io.getStdIn().reader();

<追記おわり>


const std = @import("std");

pub fn main() !void {
    const outstream = std.io.getStdOut().outStream();
    const instream = std.io.getStdIn().inStream();

    while (true) {
        const byte = instream.readByte() catch |err| switch (err) {
            error.EndOfStream => {
                break;
            },
            else => |e| {
                return e;
            },
        };

        try outstream.writeByte(byte);
    }
}

実行。

$ head -5 cat.zig | zig-0.6.0 run cat.zig | cat -A
const std = @import("std");$
$
pub fn main() !void {$
    const outstream = std.io.getStdOut().outStream();$
    const instream = std.io.getStdIn().inStream();$

リファレンスの探し方がよく分かってなくて GitHub でソースとコメントを見た方が速かった。

zig/in_stream.zig at 0.6.0 · ziglang/zig
https://github.com/ziglang/zig/blob/0.6.0/lib/std/io/in_stream.zig


0.6.0 では in_stream.zig だったのが master(現在は 4fbf9f7f79c8e0df)では reader.zig に変わっていたりします。

2020-09-21 追記

関数呼び出しや条件分岐などの要素が加わったサンプルとして、 CR, LF, タブ, EOF を分かりやすく表示するバージョンを書いてみました。 入力が UTF-8 である想定です。

const std = @import("std");
const outstream = std.io.getStdOut().outStream();

fn printByte(byte: u8) !void {
    if (byte == '\t') {
        try outstream.print("<TAB>", .{});
    } else if (byte == '\r') {
        try outstream.print("<CR>", .{});
    } else if (byte == '\n') {
        try outstream.print("<LF>", .{});
        try outstream.writeByte(byte);
    } else {
        try outstream.writeByte(byte);
    }
}

pub fn main() !void {
    const instream = std.io.getStdIn().inStream();

    while (true) {
        const byte = instream.readByte() catch |err| switch (err) {
            error.EndOfStream => {
                break;
            },
            else => |e| {
                return e;
            },
        };

        try printByte(byte);
    }

    try outstream.print("<EOF>\n", .{});
}

実行例:

$ export PS1='--------\n\n$ '
--------

$ cat sample.txt 
ab      cd
あいうえお
0123--------

$ cat -A sample.txt 
ab^Icd^M$
M-cM-^AM-^BM-cM-^AM-^DM-cM-^AM-^FM-cM-^AM-^HM-cM-^AM-^J^M$
0123--------

$ cat sample.txt | zig-0.6.0 run cat_v2.zig 
ab<TAB>cd<CR><LF>
あいうえお<CR><LF>
0123<EOF>
--------

この記事を読んだ人は(ひょっとしたら)こちらも読んでいます

cat コマンドを改造して育てていくとコンパイラが作れます。

zenn.dev