@@ -7,6 +7,7 @@ const std = @import("std.zig");
7
7
const math = std .math ;
8
8
const assert = std .debug .assert ;
9
9
const mem = std .mem ;
10
+ const meta = std .meta ;
10
11
const builtin = @import ("builtin" );
11
12
const errol = @import ("fmt/errol.zig" );
12
13
const lossyCast = std .math .lossyCast ;
@@ -78,43 +79,48 @@ pub fn format(
78
79
args : anytype ,
79
80
) ! void {
80
81
const ArgSetType = u32 ;
81
- if (@typeInfo (@TypeOf (args )) != .Struct ) {
82
- @compileError ("Expected tuple or struct argument, found " ++ @typeName (@TypeOf (args )));
82
+
83
+ const ArgsType = @TypeOf (args );
84
+ // XXX: meta.trait.is(.Struct)(ArgsType) doesn't seem to work...
85
+ if (@typeInfo (ArgsType ) != .Struct ) {
86
+ @compileError ("Expected tuple or struct argument, found " ++ @typeName (ArgsType ));
83
87
}
84
- if (args .len > @typeInfo (ArgSetType ).Int .bits ) {
88
+
89
+ const fields_info = meta .fields (ArgsType );
90
+ if (fields_info .len > @typeInfo (ArgSetType ).Int .bits ) {
85
91
@compileError ("32 arguments max are supported per format call" );
86
92
}
87
93
88
94
comptime var arg_state : struct {
89
95
next_arg : usize = 0 ,
90
- used_args : ArgSetType = 0 ,
91
- args_len : usize = args .len ,
96
+ used_args : usize = 0 ,
97
+ args_len : usize = fields_info .len ,
92
98
93
99
fn hasUnusedArgs (comptime self : * @This ()) bool {
94
- return ( @popCount (ArgSetType , self .used_args ) != self .args_len ) ;
100
+ return @popCount (ArgSetType , self .used_args ) != self .args_len ;
95
101
}
96
102
97
- fn nextArg (comptime self : * @This (), comptime pos_arg : ? usize ) comptime_int {
98
- const next_idx = pos_arg orelse blk : {
103
+ fn nextArg (comptime self : * @This (), comptime arg_index : ? usize ) comptime_int {
104
+ const next_index = arg_index orelse init : {
99
105
const arg = self .next_arg ;
100
106
self .next_arg += 1 ;
101
- break :blk arg ;
107
+ break :init arg ;
102
108
};
103
109
104
- if (next_idx >= self .args_len ) {
110
+ if (next_index >= self .args_len ) {
105
111
@compileError ("Too few arguments" );
106
112
}
107
113
108
114
// Mark this argument as used
109
- self .used_args |= 1 << next_idx ;
115
+ self .used_args |= 1 << next_index ;
110
116
111
- return next_idx ;
117
+ return next_index ;
112
118
}
113
119
} = .{};
114
120
115
121
comptime var parser : struct {
116
122
buf : []const u8 = undefined ,
117
- pos : usize = 0 ,
123
+ pos : comptime_int = 0 ,
118
124
119
125
// Returns a decimal number or null if the current character is not a
120
126
// digit
@@ -159,13 +165,21 @@ pub fn format(
159
165
return null ;
160
166
}
161
167
168
+ fn maybe (comptime self : * @This (), comptime val : u8 ) bool {
169
+ if (self .pos < self .buf .len and self .buf [self .pos ] == val ) {
170
+ self .pos += 1 ;
171
+ return true ;
172
+ }
173
+ return false ;
174
+ }
175
+
162
176
// Returns the n-th next character or null if that's past the end
163
177
fn peek (comptime self : * @This (), comptime n : usize ) ? u8 {
164
178
return if (self .pos + n < self .buf .len ) self .buf [self .pos + n ] else null ;
165
179
}
166
180
} = .{};
167
181
168
- comptime var options : FormatOptions = .{};
182
+ var options : FormatOptions = .{};
169
183
170
184
@setEvalBranchQuota (2000000 );
171
185
@@ -203,7 +217,7 @@ pub fn format(
203
217
if (i >= fmt .len ) break ;
204
218
205
219
if (fmt [i ] == '}' ) {
206
- @compileError ("missing opening {" );
220
+ @compileError ("Missing opening {" );
207
221
}
208
222
209
223
// Get past the {
@@ -216,7 +230,7 @@ pub fn format(
216
230
comptime const fmt_end = i ;
217
231
218
232
if (i >= fmt .len ) {
219
- @compileError ("missing closing }" );
233
+ @compileError ("Missing closing }" );
220
234
}
221
235
222
236
// Get past the }
@@ -230,15 +244,29 @@ pub fn format(
230
244
parser .pos = 0 ;
231
245
232
246
// Parse the positional argument number
233
- comptime var opt_pos_arg = comptime parser .number ();
247
+ comptime const opt_pos_arg = init : {
248
+ if (comptime parser .maybe ('[' )) {
249
+ comptime const arg_name = parser .until (']' );
250
+
251
+ if (! comptime parser .maybe (']' )) {
252
+ @compileError ("Expected closing ]" );
253
+ }
254
+
255
+ break :init comptime meta .fieldIndex (ArgsType , arg_name ) orelse
256
+ @compileError ("No argument with name '" ++ arg_name ++ "'" );
257
+ } else {
258
+ break :init comptime parser .number ();
259
+ }
260
+ };
234
261
235
262
// Parse the format specifier
236
- comptime var specifier_arg = comptime parser .until (':' );
263
+ comptime const specifier_arg = comptime parser .until (':' );
237
264
238
265
// Skip the colon, if present
239
266
if (comptime parser .char ()) | ch | {
240
- if (ch != ':' )
241
- @compileError ("expected : or }, found '" ++ [1 ]u8 {ch } ++ "'" );
267
+ if (ch != ':' ) {
268
+ @compileError ("Expected : or }, found '" ++ [1 ]u8 {ch } ++ "'" );
269
+ }
242
270
}
243
271
244
272
// Parse the fill character
@@ -270,26 +298,57 @@ pub fn format(
270
298
}
271
299
272
300
// Parse the width parameter
273
- comptime var opt_width_arg = comptime parser .number ();
274
- options .width = opt_width_arg ;
301
+ options .width = init : {
302
+ if (comptime parser .maybe ('[' )) {
303
+ comptime const arg_name = parser .until (']' );
304
+
305
+ if (! comptime parser .maybe (']' )) {
306
+ @compileError ("Expected closing ]" );
307
+ }
308
+
309
+ comptime const index = meta .fieldIndex (ArgsType , arg_name ) orelse
310
+ @compileError ("No argument with name '" ++ arg_name ++ "'" );
311
+ const arg_index = comptime arg_state .nextArg (index );
312
+
313
+ break :init @field (args , fields_info [arg_index ].name );
314
+ } else {
315
+ break :init comptime parser .number ();
316
+ }
317
+ };
275
318
276
319
// Skip the dot, if present
277
320
if (comptime parser .char ()) | ch | {
278
- if (ch != '.' )
279
- @compileError ("expected . or }, found '" ++ [1 ]u8 {ch } ++ "'" );
321
+ if (ch != '.' ) {
322
+ @compileError ("Expected . or }, found '" ++ [1 ]u8 {ch } ++ "'" );
323
+ }
280
324
}
281
325
282
326
// Parse the precision parameter
283
- comptime var opt_precision_arg = comptime parser .number ();
284
- options .precision = opt_precision_arg ;
327
+ options .precision = init : {
328
+ if (comptime parser .maybe ('[' )) {
329
+ comptime const arg_name = parser .until (']' );
330
+
331
+ if (! comptime parser .maybe (']' )) {
332
+ @compileError ("Expected closing ]" );
333
+ }
334
+
335
+ comptime const arg_i = meta .fieldIndex (ArgsType , arg_name ) orelse
336
+ @compileError ("No argument with name '" ++ arg_name ++ "'" );
337
+ const arg_to_use = comptime arg_state .nextArg (arg_i );
338
+
339
+ break :init @field (args , fields_info [arg_to_use ].name );
340
+ } else {
341
+ break :init comptime parser .number ();
342
+ }
343
+ };
285
344
286
345
if (comptime parser .char ()) | ch | {
287
- @compileError ("extraneous trailing character '" ++ [1 ]u8 {ch } ++ "'" );
346
+ @compileError ("Extraneous trailing character '" ++ [1 ]u8 {ch } ++ "'" );
288
347
}
289
348
290
349
const arg_to_print = comptime arg_state .nextArg (opt_pos_arg );
291
350
try formatType (
292
- args [arg_to_print ],
351
+ @field ( args , fields_info [arg_to_print ]. name ) ,
293
352
specifier_arg ,
294
353
options ,
295
354
writer ,
@@ -1803,3 +1862,22 @@ test "sci float padding" {
1803
1862
try testFmt ("center-pad: *3.141e+00*\n " , "center-pad: {e:*^11.3}\n " , .{number });
1804
1863
try testFmt ("right-pad: 3.141e+00**\n " , "right-pad: {e:*<11.3}\n " , .{number });
1805
1864
}
1865
+
1866
+ test "named arguments" {
1867
+ try testFmt ("hello world!" , "{} world{c}" , .{ "hello" , '!' });
1868
+ try testFmt ("hello world!" , "{[greeting]} world{[punctuation]c}" , .{ .punctuation = '!' , .greeting = "hello" });
1869
+ try testFmt ("hello world!" , "{[1]} world{[0]c}" , .{ '!' , "hello" });
1870
+ }
1871
+
1872
+ test "runtime width specifier" {
1873
+ var width : usize = 9 ;
1874
+ try testFmt ("~~hello~~" , "{:~^[1]}" , .{ "hello" , width });
1875
+ try testFmt ("~~hello~~" , "{:~^[width]}" , .{ .string = "hello" , .width = width });
1876
+ }
1877
+
1878
+ test "runtime precision specifier" {
1879
+ var number : f32 = 3.1415 ;
1880
+ var precision : usize = 2 ;
1881
+ try testFmt ("3.14e+00" , "{:1.[1]}" , .{ number , precision });
1882
+ try testFmt ("3.14e+00" , "{:1.[precision]}" , .{ .number = number , .precision = precision });
1883
+ }
0 commit comments