@@ -7,6 +7,7 @@ const std = @import("std.zig");
77const math = std .math ;
88const assert = std .debug .assert ;
99const mem = std .mem ;
10+ const meta = std .meta ;
1011const builtin = @import ("builtin" );
1112const errol = @import ("fmt/errol.zig" );
1213const lossyCast = std .math .lossyCast ;
@@ -82,43 +83,48 @@ pub fn format(
8283 args : anytype ,
8384) ! void {
8485 const ArgSetType = u32 ;
85- if (@typeInfo (@TypeOf (args )) != .Struct ) {
86- @compileError ("Expected tuple or struct argument, found " ++ @typeName (@TypeOf (args )));
86+
87+ const ArgsType = @TypeOf (args );
88+ // XXX: meta.trait.is(.Struct)(ArgsType) doesn't seem to work...
89+ if (@typeInfo (ArgsType ) != .Struct ) {
90+ @compileError ("Expected tuple or struct argument, found " ++ @typeName (ArgsType ));
8791 }
88- if (args .len > @typeInfo (ArgSetType ).Int .bits ) {
92+
93+ const fields_info = meta .fields (ArgsType );
94+ if (fields_info .len > @typeInfo (ArgSetType ).Int .bits ) {
8995 @compileError ("32 arguments max are supported per format call" );
9096 }
9197
9298 comptime var arg_state : struct {
9399 next_arg : usize = 0 ,
94- used_args : ArgSetType = 0 ,
95- args_len : usize = args .len ,
100+ used_args : usize = 0 ,
101+ args_len : usize = fields_info .len ,
96102
97103 fn hasUnusedArgs (comptime self : * @This ()) bool {
98- return ( @popCount (ArgSetType , self .used_args ) != self .args_len ) ;
104+ return @popCount (ArgSetType , self .used_args ) != self .args_len ;
99105 }
100106
101- fn nextArg (comptime self : * @This (), comptime pos_arg : ? usize ) comptime_int {
102- const next_idx = pos_arg orelse blk : {
107+ fn nextArg (comptime self : * @This (), comptime arg_index : ? usize ) comptime_int {
108+ const next_index = arg_index orelse init : {
103109 const arg = self .next_arg ;
104110 self .next_arg += 1 ;
105- break :blk arg ;
111+ break :init arg ;
106112 };
107113
108- if (next_idx >= self .args_len ) {
114+ if (next_index >= self .args_len ) {
109115 @compileError ("Too few arguments" );
110116 }
111117
112118 // Mark this argument as used
113- self .used_args |= 1 << next_idx ;
119+ self .used_args |= 1 << next_index ;
114120
115- return next_idx ;
121+ return next_index ;
116122 }
117123 } = .{};
118124
119125 comptime var parser : struct {
120126 buf : []const u8 = undefined ,
121- pos : usize = 0 ,
127+ pos : comptime_int = 0 ,
122128
123129 // Returns a decimal number or null if the current character is not a
124130 // digit
@@ -163,13 +169,21 @@ pub fn format(
163169 return null ;
164170 }
165171
172+ fn maybe (comptime self : * @This (), comptime val : u8 ) bool {
173+ if (self .pos < self .buf .len and self .buf [self .pos ] == val ) {
174+ self .pos += 1 ;
175+ return true ;
176+ }
177+ return false ;
178+ }
179+
166180 // Returns the n-th next character or null if that's past the end
167181 fn peek (comptime self : * @This (), comptime n : usize ) ? u8 {
168182 return if (self .pos + n < self .buf .len ) self .buf [self .pos + n ] else null ;
169183 }
170184 } = .{};
171185
172- comptime var options : FormatOptions = .{};
186+ var options : FormatOptions = .{};
173187
174188 @setEvalBranchQuota (2000000 );
175189
@@ -207,7 +221,7 @@ pub fn format(
207221 if (i >= fmt .len ) break ;
208222
209223 if (fmt [i ] == '}' ) {
210- @compileError ("missing opening {" );
224+ @compileError ("Missing opening {" );
211225 }
212226
213227 // Get past the {
@@ -220,7 +234,7 @@ pub fn format(
220234 comptime const fmt_end = i ;
221235
222236 if (i >= fmt .len ) {
223- @compileError ("missing closing }" );
237+ @compileError ("Missing closing }" );
224238 }
225239
226240 // Get past the }
@@ -234,15 +248,29 @@ pub fn format(
234248 parser .pos = 0 ;
235249
236250 // Parse the positional argument number
237- comptime var opt_pos_arg = comptime parser .number ();
251+ comptime const opt_pos_arg = init : {
252+ if (comptime parser .maybe ('[' )) {
253+ comptime const arg_name = parser .until (']' );
254+
255+ if (! comptime parser .maybe (']' )) {
256+ @compileError ("Expected closing ]" );
257+ }
258+
259+ break :init comptime meta .fieldIndex (ArgsType , arg_name ) orelse
260+ @compileError ("No argument with name '" ++ arg_name ++ "'" );
261+ } else {
262+ break :init comptime parser .number ();
263+ }
264+ };
238265
239266 // Parse the format specifier
240- comptime var specifier_arg = comptime parser .until (':' );
267+ comptime const specifier_arg = comptime parser .until (':' );
241268
242269 // Skip the colon, if present
243270 if (comptime parser .char ()) | ch | {
244- if (ch != ':' )
245- @compileError ("expected : or }, found '" ++ [1 ]u8 {ch } ++ "'" );
271+ if (ch != ':' ) {
272+ @compileError ("Expected : or }, found '" ++ [1 ]u8 {ch } ++ "'" );
273+ }
246274 }
247275
248276 // Parse the fill character
@@ -274,26 +302,57 @@ pub fn format(
274302 }
275303
276304 // Parse the width parameter
277- comptime var opt_width_arg = comptime parser .number ();
278- options .width = opt_width_arg ;
305+ options .width = init : {
306+ if (comptime parser .maybe ('[' )) {
307+ comptime const arg_name = parser .until (']' );
308+
309+ if (! comptime parser .maybe (']' )) {
310+ @compileError ("Expected closing ]" );
311+ }
312+
313+ comptime const index = meta .fieldIndex (ArgsType , arg_name ) orelse
314+ @compileError ("No argument with name '" ++ arg_name ++ "'" );
315+ const arg_index = comptime arg_state .nextArg (index );
316+
317+ break :init @field (args , fields_info [arg_index ].name );
318+ } else {
319+ break :init comptime parser .number ();
320+ }
321+ };
279322
280323 // Skip the dot, if present
281324 if (comptime parser .char ()) | ch | {
282- if (ch != '.' )
283- @compileError ("expected . or }, found '" ++ [1 ]u8 {ch } ++ "'" );
325+ if (ch != '.' ) {
326+ @compileError ("Expected . or }, found '" ++ [1 ]u8 {ch } ++ "'" );
327+ }
284328 }
285329
286330 // Parse the precision parameter
287- comptime var opt_precision_arg = comptime parser .number ();
288- options .precision = opt_precision_arg ;
331+ options .precision = init : {
332+ if (comptime parser .maybe ('[' )) {
333+ comptime const arg_name = parser .until (']' );
334+
335+ if (! comptime parser .maybe (']' )) {
336+ @compileError ("Expected closing ]" );
337+ }
338+
339+ comptime const arg_i = meta .fieldIndex (ArgsType , arg_name ) orelse
340+ @compileError ("No argument with name '" ++ arg_name ++ "'" );
341+ const arg_to_use = comptime arg_state .nextArg (arg_i );
342+
343+ break :init @field (args , fields_info [arg_to_use ].name );
344+ } else {
345+ break :init comptime parser .number ();
346+ }
347+ };
289348
290349 if (comptime parser .char ()) | ch | {
291- @compileError ("extraneous trailing character '" ++ [1 ]u8 {ch } ++ "'" );
350+ @compileError ("Extraneous trailing character '" ++ [1 ]u8 {ch } ++ "'" );
292351 }
293352
294353 const arg_to_print = comptime arg_state .nextArg (opt_pos_arg );
295354 try formatType (
296- args [arg_to_print ],
355+ @field ( args , fields_info [arg_to_print ]. name ) ,
297356 specifier_arg ,
298357 options ,
299358 writer ,
@@ -1937,3 +1996,22 @@ test "null" {
19371996 const inst = null ;
19381997 try testFmt ("null" , "{}" , .{inst });
19391998}
1999+
2000+ test "named arguments" {
2001+ try testFmt ("hello world!" , "{} world{c}" , .{ "hello" , '!' });
2002+ try testFmt ("hello world!" , "{[greeting]} world{[punctuation]c}" , .{ .punctuation = '!' , .greeting = "hello" });
2003+ try testFmt ("hello world!" , "{[1]} world{[0]c}" , .{ '!' , "hello" });
2004+ }
2005+
2006+ test "runtime width specifier" {
2007+ var width : usize = 9 ;
2008+ try testFmt ("~~hello~~" , "{:~^[1]}" , .{ "hello" , width });
2009+ try testFmt ("~~hello~~" , "{:~^[width]}" , .{ .string = "hello" , .width = width });
2010+ }
2011+
2012+ test "runtime precision specifier" {
2013+ var number : f32 = 3.1415 ;
2014+ var precision : usize = 2 ;
2015+ try testFmt ("3.14e+00" , "{:1.[1]}" , .{ number , precision });
2016+ try testFmt ("3.14e+00" , "{:1.[precision]}" , .{ .number = number , .precision = precision });
2017+ }
0 commit comments