0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Zigで実行時に指定された文字列のフィールドの値を取得する方法

Posted at

ZigRTTI(RunTime Type Information)を備えていないため、無理です。あきらめましょう。


Zigで実行時に指定された文字列のフィールドの値を取得する方法



                                            終  
                                         制作・著作
                                          ━━━━━
                                          ⓀⓉⓏ 

いやそれでもなんとかしたいっす

妥協案として、指定された文字列に対して、フィールドにアクセスする関数を対応づけたルックアップテーブルを引くという方法が挙げられる。

このようなルックアップテーブルをコンパイル時に作成する手段として、標準ライブラリでstd.StaticStringMapが提供されている1.

この際、キーとなる文字列は、フィールド名と一致するようコンパイル時に検証されていると安心である。

ということで、以下のような型を作成した。

zig 0.14のnightlyで確認しています。
zigは、しばしば破壊的変更が行われてれいるため、将来のバージョンでは使えないかもしれないことにご注意を

fn BindMap(comptime InputType: type) type {
    return struct {
        pub const Fn = *const fn (in: InputType, out: *std.ArrayList([]const u8)) anyerror!void;
        pub const KV = struct {std.meta.FieldEnum(InputType), Fn};
        
        pub fn init(comptime kvs: []const KV) std.StaticStringMap(Fn) {
            const fields = std.meta.fields(InputType);
            comptime std.debug.assert(kvs.len == fields.len);

            comptime var map_kvs: [kvs.len](struct []const u8, Fn}) = undefined;
            for (kvs, 0..) |kv, i| {
                map_kvs[i] = .{ @tagName(kv[0]), kv[1] };
            }

            return std.StaticStringMap(Fn).initComptime(map_kvs);
        }
    };
}

これは、文字列で指定されたフィールドの値をstd.ArrayListに放り込むことを目的としたものである。

ポイントは、

  • init関数でルックアップテーブルのキーとする型をstd.meta.FieldEnumにし、フィールド名として存在しない名前で構成した場合はコンパイルエラーにさせる
  • kvsの要素数と、型のフィールドの要素数をコンパイル時に要請する
    • 一部のフィールドだけを対象とするのであれば不要

また、StaticStringMap.initに渡す引数の型はanytypeとなっているが、lenで要素数が取れ、[]で要素アクセスできればよく、実は配列やスライスを渡しても構わなかったりする。

使い方

以下の例をご参照あれ

const std = @import("std");

// BindMapは上記の定義をそのまま使う

const Foo = struct {
  a: []const u8,
  b: []const u8,
  c: []const u8,
};

const Map = BindMap(Foo).init(&.{
    .{.a, Binder.getA},
    .{.b, Binder.getB},
    .{.c, Binder.getC},
});

const Binder = struct {
    fn getA(in: Foo, out: *std.ArrayList([]const u8)) !void {
        try out.append(in.a);
    }
    fn getB(in: Foo, out: *std.ArrayList([]const u8)) !void {
        try out.append(in.b);
    }
    fn getC(in: Foo, out: *std.ArrayList([]const u8)) !void {
        try out.append(in.c);
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer {
        _ = gpa.deinit();
    }
    const allocator = gpa.allocator();

    const foo: Foo = .{
        .a = "hoge",
        .b = "uge",
        .c = "moge",
    };
    
    const fields: []const []const u8 = &.{"a", "c"};

    var out = std.ArrayList([]const u8).init(allocator);
    defer out.deinit();

    for (fields) |field| {
        const binder = Map.get(field);
        std.debug.assert(binder != null);
        try binder.?(foo, &out);
    }
}

実例

あらかじめzig-clap2 を使用して構成した引数情報のうち、一部を起動する子プロセスに渡したく模索して妥協案として行きつきました。

const Symbol = []const u8;
const FilePath = []const u8;

const GenerateSetting = struct {
    source_dir_path: []FilePath,
    output_dir_path: FilePath,
    watch: bool,
};

const Binder = struct {
    // 設定を保持する型にclapで使用するenumを持たせている
    const ArgId = GenerateSetting.ArgId(.{});
    // そのenumにclapのパーザに渡すclap.Param(ArgId)のスライスを持たせている
    const decls = ArgId.Decls;
    
    fn bindSourceDir(setting: GenerateSetting, args: *std.ArrayList(Symbol)) !void {
        const decl = comptime findDecl(ArgId, decls, .source_dir_path);
        try args.append("--" ++ decl.names.long.?);

        for (setting.source_dir_path) |path| {
            try args.append(path);
        }
    }
    fn bindOutputDir(setting: GenerateSetting, args: *std.ArrayList(Symbol)) !void {
        const decl = comptime findDecl(ArgId, decls, .output_dir_path);
        try args.append("--" ++ decl.names.long.?);

        try args.append(setting.output_dir_path);
    }
    fn bindWatchMode(setting: GenerateSetting, args: *std.ArrayList(Symbol)) !void {
        if (setting.watch) {
            const decl = comptime findDecl(ArgId, decls, .watch);
            try args.append("--" ++ decl.names.long.?);
        }
    }
};

fn findDecl(comptime Id: type, comptime decls: []const clap.Param(Id), comptime id: Id) clap.Param(Id) {
    inline for (decls) |decl| {
        if (decl.id == id) {
            std.debug.assert(decl.names.long != null);
            return decl;
        }
    }

    @compileError(std.fmt.comptimePrint("Not contained CLI arg setting: {}",.{id}));
}

const GenerateConfigMap = 
    ConfigBindMap(GenerateSetting).init(&.{
        .{.source_dir_path, Binder.bindSourceDir},
        .{.output_dir_path, Binder.bindOutputDir},
        .{.watch, Binder.bindWatchMode},
    })
;
  1. zig 0.12.0までは、ComptimeStringMapだったが、0.13.0でリネームされた

  2. https://github.com/Hejsil/zig-clap

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?