Zenで利用できる文字列のフォーマットを説明します。大きく分けると2つのトピックがあります。
1.はZenのソースコード内で文字列を作成したい場合に利用します。std.debug.warn
はこちらに属します。2.は文字列のデータを、別の形式に変換する際に利用します。例えば、ユーザーから入力された文字列の"1"
を整数型の1
に変換する処理です。
これらの文字列を処理するコードは、std.fmt
モジュール内に実装されています。std.debug
モジュールやstd.io
モジュールは、std.fmt
モジュールを利用して文字列をフォーマットした後、対象のファイル (標準出力、標準エラーを含む) に文字列を書き込みます。
本書内では、std.debug.warn
関数を用いて標準エラーに文字を出力するコードを多く掲載しています。このwarn
関数はテンプレート文字列と可変長の引数から、フォーマットされた文字列を標準エラーに出力します。
例えば、次のようなコードを書くと、Hello World
と標準エラーに出力されます。
std.debug.warn("Hello {}\n", "World");
ここで、最初の文字列Hello {}\n
はテンプレート文字列と呼ばれます。テンプレート文字列内の{}
はプレースホルダであり、テンプレート文字列に続く引数がこの位置に表示されることを意味します。
このテンプレート文字列と可変長引数から、文字列を作り出す処理は、std.fmt.format
関数が担っています。
std.fmt.format
関数を利用して文字列をフォーマットして出力するAPIには、以下のものがあります。
std.fmt.bufPrint
: 与えたバッファにフォーマットされた文字列を出力しますstd.fmt.allocPrint
: 与えたアロケータからバッファを確保し、フォーマットされた文字列を出力しますstd.debug.warn
: 標準エラーにフォーマットされた文字列を出力しますstd.debug.printError
: warn
と同様ですが、error:
のプレフィックスが付き、赤色の文字で出力しますstd.debug.printHint
: warn
と同様ですが、hint:
のプレフィックスが付き、シアン色の文字で出力しますstd.debug.printInfo
: warn
と同様ですが、info:
のプレフィックスが付き、緑色の文字で出力しますプログラム内でフォーマットされた文字列を扱いたい場合に利用します。
bufPrint
は[]u8
のバッファを与え、そのバッファに文字列を出力します。バッファの長さが不足する場合、EndOfStream
エラーを返します。
examples/ch13-io/format/src/format_api.zen:6:13
test "bufPrint" {
var buf: [16]u8 = undefined;
const formatted = try fmt.bufPrint(&buf, "Hello {}", "World");
equalSlices(u8, "Hello World", formatted);
const e = fmt.bufPrint(&buf, "too long string for the buffer");
err(error.EndOfStream, e);
}
allocPrint
はバッファの代わりにアロケータを第一引数で与えます。メモリ確保に失敗すると、OutOfMemory
エラーを返します。
フォーマットされた文字列を標準エラーに出力します。warn
以外は、それぞれプレフィックスが付き、メッセージの文字が着色されます。
std.fs.File
のインスタンスを対象に、フォーマットされた文字列を出力します。例えば、標準出力にフォーマットされた文字列を出力する場合、次のようにします。
examples/ch13-io/format/src/format_api.zen:15:18
pub fn main() !void {
const stdout = try std.fs.getStdOut();
try std.fs.write.print(stdout, "Hello {}\n", "World");
}
$ zen run format_api.zen
Hello World
プレースホルダ{}
はテンプレート文字列内に複数個置くことができます。後ろに続く可変長引数が順番に埋められていきます。
examples/ch13-io/format/src/placeholder.zen:3:5
test "placeholder" {
// output: 0, one, 2.0e+00
std.debug.warn("{}, {}, {}\n", @is(u32, 0), "one", f64(2.0));
Zenのプリミティブ型は、標準ライブラリ内でデフォルトのフォーマットで出力されます。ユーザー定義の構造体も、デフォルトでは次のように各フィールドが出力されます。
examples/ch13-io/format/src/placeholder.zen:51:60
test "format struct print" {
const Struct = struct {
x: f64,
y: f64,
};
const s = Struct { .x = 1.0, .y = 2.0 };
// output: Struct{ .x = 1.0e+00, .y = 2.0e+00 }
std.debug.warn("{}\n", s);
}
タグ付き共用体も同様に、ヴァリアントとその値が出力されます。
examples/ch13-io/format/src/placeholder.zen:62:76
test "format tagged union" {
const Value = union(enum) {
String: []u8,
F64: f64,
U32: u32,
};
const string = Value { .String = &"hello" };
// output: Value{ .String = hello }
std.debug.warn("{}\n", string);
const float = Value { .F64 = 1.2345 };
// output: Value{ .F64 = 1.2345e+00 }
std.debug.warn("{}\n", float);
}
エラー共用体は、エラー型の値が格納されている場合はエラーの種別、そうでない場合、格納されている値が出力されます。
examples/ch13-io/format/src/placeholder.zen:78:90
test "format error union print" {
const Error = error {
AnError,
};
const err: Error!u32 = Error.AnError;
// output: error.AnError
std.debug.warn("{}\n", err);
const value: Error!u32 = @is(u32, 1);
// output: 1
std.debug.warn("{}\n", value);
}
ポインタはプリミティブ型と構造体とで、振る舞いが異なります。プリミティブ型は{}
で型名@16進数表記アドレスの形式で出力されます。
examples/ch13-io/format/src/placeholder.zen:92:97
test "format pointer print" {
const x: u32 = 0;
// `@`に続くアドレスは実行ごとに異なります
// output: u32@20c65c
std.debug.warn("{}\n", &x);
}
構造体のポインタ型は、{}
では通常の構造体の値と同じ出力が、{*}
で型名@16進数表記アドレスの形式で出力されます。
examples/ch13-io/format/src/placeholder.zen:99:110
test "format struct pointer print" {
const Struct = struct {
x: f64,
y: f64,
};
const s = Struct { .x = 1.0, .y = 2.0 };
// output: Struct{ .x = 1.0e+00, .y = 2.0e+00 }
std.debug.warn("{}\n", &s);
// output: Struct@20c230
std.debug.warn("{*}\n", &s);
}
プレースホルダ内には、様々なオプションを記述することができます。
{n}
でn
番目の位置にある引数を指定します。引数は0
から数えます。
examples/ch13-io/format/src/placeholder.zen:7:8
// output: zero, one, zero, one
std.debug.warn("{}, {}, {0}, {1}\n", "zero", "one");
数値を桁数を指定して出力します。{:n}
でn
に桁数を指定します。
examples/ch13-io/format/src/placeholder.zen:12:13
// output: |01234|00001|
std.debug.warn("|{:5}|{:5}|\n", @is(u32, 1234), @is(u32, 1));
小数点以下第何位まで出力するかを指定します。{:.n}
でn
に桁数を指定します。
examples/ch13-io/format/src/placeholder.zen:17:18
// output: 3.14159
std.debug.warn("{:.5}\n", f64(3.14159265359));
プレースホルダ{}
内には、文字列を出力する際の書式指定を記述することが可能です。
examples/ch13-io/format/src/placeholder.zen:22:48
// {c}: 一文字のASCII文字として表示
// output: A
std.debug.warn("{c}\n", u8(65));
// {b}: 2進数として表示
// output: 11110001001000000
std.debug.warn("{b}\n", @is(u32, 123456));
// {x}: 小文字の16進数として表示
// output: 1e240
std.debug.warn("{x}\n", @is(u32, 123456));
// {X}: 大文字の16進数として表示
// output: 1E240
std.debug.warn("{X}\n", @is(u32, 123456));
// {e}: 浮動小数点を指数形式で表示
// output: 1.23456e+02
std.debug.warn("{e}\n", f64(123.456));
// {d}: 整数 / 浮動小数点を10進数として表示
// output: 123.456
std.debug.warn("{d}\n", f64(123.456));
// {s}: NULL文字終端された文字列 (C言語の文字列) を表示
// output: hello
std.debug.warn("{s}\n", c"hello");
構造体のフォーマット出力を自分で定義することも可能です。その場合、構造体に所定のシグネチャを持つpub
で公開されたformat
メソッドを定義します。
例えば、次のような座標値x
とy
をフィールドにもつPoint
構造体を、(x, y) = (xの値, yの値)
のような形式でフォーマット出力したいとします。
const Point = struct {
x: f64, y: f64,
};
format
メソッドの実装は、次のようになります。引数が多くてわかりずらい部分がありますが、重要なことは、プレースホルダーの情報が渡されるので、その情報を元にstd.fmt.format
を呼び出す、ということです。
examples/ch13-io/format/src/user_define.zen:4:22
const Point = struct {
x: f64, y: f64,
pub fn format(
self: Point,
comptime fmt_spec: []const u8,
options: fmt.FormatOptions,
context: var,
comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void,
) Errors!void {
return fmt.format(context,
Errors,
output,
"(x, y) = ({d:.3}, {d:.3})", // テンプレート文字列
self.x,
self.y);
}
};
std.fmt.format
の第4引数はテンプレート文字列で、第5引数以降はテンプレート文字列のプレースホルダーを埋めるための引数です。
このPoint
構造体の値をフォーマット出力すると、自身で定義した形式でフォーマットされて出力されます。
examples/ch13-io/format/src/user_define.zen:24:28
test "user defined format" {
const point = Point { .x = 1.0, .y = 2.0 };
// output: (x, y) = (1.000, 2.000)
std.debug.warn("{}\n", point);
}
文字列を解析し、整数型や浮動小数点型に変換します。コマンドラインから入力を受け付けたり、ファイルからデータを読み取る場合に利用できます。これを実現する標準ライブラリ関数は、std.fmt
モジュールにある次の関数です。
parseInt
: 符号付き整数の形式で表現された文字列を、任意の整数型に変換しますparseUnsigned
: 符号なし整数の形式で表現された文字列を、任意の整数型に変換しますparseFloat
: 文字列を任意の浮動小数点型に変換しますparseInt
の関数シグネチャは、fn parseInt(comptime destType: type, string: []const u8, radix: u8) !destType
です。第一引数がターゲットとする整数型、第二引数が文字列スライス、第三引数が基数です。基数は、2〜36進数の中から選択できます。
parseInt
で正常に整数値が得られる使用例は次の通りです。ターゲットとする整数型には、符号付き / 符号ないずれも選択でき、任意ビット幅を指定できます。また、数字の前に+
/ -
が入っていてもかまいません
examples/ch13-io/format/src/parse_int.zen:6:27
test "parseInt" {
const a = try fmt.parseInt(i32, "10", 10);
ok(a == 10);
const b = try fmt.parseInt(i32, "+10", 10);
ok(b == 10);
const c = try fmt.parseInt(i32, "-10", 10);
ok(c == -10);
const d = try fmt.parseInt(u32, "10", 10);
ok(d == 10);
const e = try fmt.parseInt(u32, "1001", 2);
ok(e == 9);
const f = try fmt.parseInt(u32, "FF", 16);
ok(f == 255);
const g = try fmt.parseInt(u5, "31", 10);
ok(g == 31);
}
ターゲットとする整数型が符号なしの場合には、-
が入った文字列は変換が失敗し、error.InvalidCharacter
が返ってきます。スペース () を含む、数値として変換できない文字列がある場合も、
error.InvalidCharacter
が返ってきます。文字列を変換した結果が、ターゲットとする整数型内に収まらない場合も変換に失敗し、error.Overflow
が返ってきます。
examples/ch13-io/format/src/parse_int.zen:29:38
test "fail to parseInt" {
const invalid_minus_sign = fmt.parseInt(u32, "-10", 10);
err(error.InvalidCharacter, invalid_minus_sign);
const invalid_char = fmt.parseInt(u8, " 10", 10);
err(error.InvalidCharacter, invalid_char);
const overflow = fmt.parseInt(u8, "256", 10);
err(error.Overflow, overflow);
}
parseUnsigned
は-
から始まる文字列を受け付けないことを除き、parseInt
と同等です。
parseFloat
の関数シグネチャは、fn parseFloat(comptime destType: type, s: []const u8) !destType
です。第一引数はターゲットとする浮動小数点型で、f16
/ f32
/ f64
/ f128
のいずれかです。第二引数は文字列スライスです。
parseFloat
の利用例を以下に示します。文字列は指数表現でも変換できます。nan
や+inf
/ -inf
も変換可能です。
examples/ch13-io/format/src/parse_int.zen:40:74
test "parseFloat" {
const approxEq = std.math.approxEq;
const epsilon = 1e-7;
// ターゲットとして指定できる浮動小数点型
inline for ([_]type{ f16, f32, f64, f128 }) |floatType| {
const zero = try fmt.parseFloat(floatType, "0");
ok(zero == 0.0);
const plus_zero = try fmt.parseFloat(floatType, "+0");
const minus_zero = try fmt.parseFloat(floatType, "-0");
ok(plus_zero == 0.0);
ok(minus_zero == 0.0);
const pi = try fmt.parseFloat(floatType, "3.141");
const minus_pi = try fmt.parseFloat(floatType, "-3.141");
ok(approxEq(floatType, pi, 3.141, epsilon));
ok(approxEq(floatType, minus_pi, -3.141, epsilon));
// 指数表現
const exp = try fmt.parseFloat(floatType, "1.23456e+2");
ok(approxEq(floatType, exp, 123.456, epsilon));
}
// NaNも変換可能
// NaN同士は比較できないため、ビットレベルで同じことをテスト
const nan = try fmt.parseFloat(f64, "NaN");
ok(@bitCast(u64, nan) == @bitCast(u64, std.math.nan(f64)));
// `inf` / `-inf`も変換可能
const inf = try fmt.parseFloat(f64, "inf");
const minus_inf = try fmt.parseFloat(f64, "-inf");
ok(inf == std.math.inf(f64));
ok(minus_inf == -std.math.inf(f64));
}
☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.