@@ -91,20 +91,38 @@ pub fn builtinPrintf(shell: *Shell, cmd: *types.ParsedCommand) !void {
9191 }
9292 }
9393
94- // Parse width
95- while (j < format .len and format [j ] >= '0' and format [j ] <= '9' ) {
96- width = width * 10 + (format [j ] - '0' );
94+ // Parse width - support * (take from next argument)
95+ if (j < format .len and format [j ] == '*' ) {
96+ if (arg_idx < cmd .args .len ) {
97+ width = @intCast (@max (std .fmt .parseInt (i64 , cmd .args [arg_idx ], 10 ) catch 0 , 0 ));
98+ arg_idx += 1 ;
99+ did_consume_arg = true ;
100+ }
97101 j += 1 ;
102+ } else {
103+ while (j < format .len and format [j ] >= '0' and format [j ] <= '9' ) {
104+ width = width * 10 + (format [j ] - '0' );
105+ j += 1 ;
106+ }
98107 }
99108
100- // Parse precision
109+ // Parse precision - support .* (take from next argument)
101110 if (j < format .len and format [j ] == '.' ) {
102111 j += 1 ;
103112 precision = 0 ;
104113 has_precision = true ;
105- while (j < format .len and format [j ] >= '0' and format [j ] <= '9' ) {
106- precision = precision * 10 + (format [j ] - '0' );
114+ if (j < format .len and format [j ] == '*' ) {
115+ if (arg_idx < cmd .args .len ) {
116+ precision = @intCast (@max (std .fmt .parseInt (i64 , cmd .args [arg_idx ], 10 ) catch 0 , 0 ));
117+ arg_idx += 1 ;
118+ did_consume_arg = true ;
119+ }
107120 j += 1 ;
121+ } else {
122+ while (j < format .len and format [j ] >= '0' and format [j ] <= '9' ) {
123+ precision = precision * 10 + (format [j ] - '0' );
124+ j += 1 ;
125+ }
108126 }
109127 }
110128
@@ -212,6 +230,30 @@ pub fn builtinPrintf(shell: *Shell, cmd: *types.ParsedCommand) !void {
212230 did_consume_arg = true ;
213231 }
214232 i = j + 1 ;
233+ } else if (spec == 'e' or spec == 'E' ) {
234+ // Scientific notation
235+ if (arg_idx < cmd .args .len ) {
236+ const num = std .fmt .parseFloat (f64 , cmd .args [arg_idx ]) catch 0.0 ;
237+ try printfScientific (num , width , precision , left_justify , spec == 'E' );
238+ arg_idx += 1 ;
239+ did_consume_arg = true ;
240+ }
241+ i = j + 1 ;
242+ } else if (spec == 'g' or spec == 'G' ) {
243+ // Shortest representation (float or scientific)
244+ if (arg_idx < cmd .args .len ) {
245+ const num = std .fmt .parseFloat (f64 , cmd .args [arg_idx ]) catch 0.0 ;
246+ // Use scientific if exponent < -4 or >= precision
247+ const abs_num = @abs (num );
248+ if (abs_num != 0 and (abs_num < 0.0001 or abs_num >= std .math .pow (f64 , 10.0 , @floatFromInt (precision )))) {
249+ try printfScientific (num , width , if (precision > 0 ) precision - 1 else 0 , left_justify , spec == 'G' );
250+ } else {
251+ try printfFloat (num , width , precision , left_justify );
252+ }
253+ arg_idx += 1 ;
254+ did_consume_arg = true ;
255+ }
256+ i = j + 1 ;
215257 } else if (spec == '%' ) {
216258 // Escaped %
217259 try IO .print ("%" , .{});
@@ -302,6 +344,72 @@ pub fn builtinPrintf(shell: *Shell, cmd: *types.ParsedCommand) !void {
302344 continue ;
303345 }
304346 },
347+ 'u' = > {
348+ // Unicode escape \uHHHH (1-4 hex digits) -> UTF-8
349+ var codepoint : u21 = 0 ;
350+ var hex_count : usize = 0 ;
351+ var k : usize = i + 2 ;
352+ while (k < format .len and hex_count < 4 ) : (k += 1 ) {
353+ const c = format [k ];
354+ const digit : u21 = if (c >= '0' and c <= '9' )
355+ c - '0'
356+ else if (c >= 'a' and c <= 'f' )
357+ c - 'a' + 10
358+ else if (c >= 'A' and c <= 'F' )
359+ c - 'A' + 10
360+ else
361+ break ;
362+ codepoint = codepoint * 16 + digit ;
363+ hex_count += 1 ;
364+ }
365+ if (hex_count > 0 ) {
366+ var utf8_buf : [4 ]u8 = undefined ;
367+ const utf8_len = std .unicode .utf8Encode (codepoint , & utf8_buf ) catch 0 ;
368+ if (utf8_len > 0 ) {
369+ try IO .print ("{s}" , .{utf8_buf [0.. utf8_len ]});
370+ }
371+ i = k ;
372+ continue ;
373+ } else {
374+ try IO .print ("{c}" , .{format [i ]});
375+ i += 1 ;
376+ continue ;
377+ }
378+ },
379+ 'U' = > {
380+ // Unicode escape \UHHHHHHHH (1-8 hex digits) -> UTF-8
381+ var codepoint : u21 = 0 ;
382+ var hex_count : usize = 0 ;
383+ var k : usize = i + 2 ;
384+ while (k < format .len and hex_count < 8 ) : (k += 1 ) {
385+ const c = format [k ];
386+ const digit : u32 = if (c >= '0' and c <= '9' )
387+ c - '0'
388+ else if (c >= 'a' and c <= 'f' )
389+ c - 'a' + 10
390+ else if (c >= 'A' and c <= 'F' )
391+ c - 'A' + 10
392+ else
393+ break ;
394+ const new_cp = @as (u32 , codepoint ) * 16 + digit ;
395+ if (new_cp > 0x10FFFF ) break ; // Max Unicode codepoint
396+ codepoint = @intCast (new_cp );
397+ hex_count += 1 ;
398+ }
399+ if (hex_count > 0 ) {
400+ var utf8_buf : [4 ]u8 = undefined ;
401+ const utf8_len = std .unicode .utf8Encode (codepoint , & utf8_buf ) catch 0 ;
402+ if (utf8_len > 0 ) {
403+ try IO .print ("{s}" , .{utf8_buf [0.. utf8_len ]});
404+ }
405+ i = k ;
406+ continue ;
407+ } else {
408+ try IO .print ("{c}" , .{format [i ]});
409+ i += 1 ;
410+ continue ;
411+ }
412+ },
305413 else = > try IO .print ("{c}" , .{format [i ]}),
306414 }
307415 i += 2 ;
@@ -353,14 +461,26 @@ pub fn printfInt(num: i64, width: usize, zero_pad: bool, left_justify: bool) !vo
353461 const str = std .fmt .bufPrint (& buf , "{d}" , .{num }) catch return ;
354462 if (width > 0 and str .len < width ) {
355463 const pad = width - str .len ;
356- const pad_char : u8 = if (zero_pad and ! left_justify ) '0' else ' ' ;
357464 if (left_justify ) {
358465 try IO .print ("{s}" , .{str });
359466 var p : usize = 0 ;
360467 while (p < pad ) : (p += 1 ) try IO .print (" " , .{});
468+ } else if (zero_pad ) {
469+ // Zero-pad: place zeros after the sign but before the digits
470+ // e.g., printf "%05d" -1 -> "-0001" (not "000-1")
471+ if (num < 0 ) {
472+ try IO .print ("-" , .{});
473+ var p : usize = 0 ;
474+ while (p < pad ) : (p += 1 ) try IO .print ("0" , .{});
475+ try IO .print ("{s}" , .{str [1.. ]}); // digits without the minus
476+ } else {
477+ var p : usize = 0 ;
478+ while (p < pad ) : (p += 1 ) try IO .print ("0" , .{});
479+ try IO .print ("{s}" , .{str });
480+ }
361481 } else {
362482 var p : usize = 0 ;
363- while (p < pad ) : (p += 1 ) try IO .print ("{c} " , .{pad_char });
483+ while (p < pad ) : (p += 1 ) try IO .print (" " , .{});
364484 try IO .print ("{s}" , .{str });
365485 }
366486 } else {
@@ -428,6 +548,68 @@ pub fn printfFloat(num: f64, width: usize, precision: usize, left_justify: bool)
428548 }
429549}
430550
551+ /// Helper for printf - format float in scientific notation (%e / %E)
552+ pub fn printfScientific (num : f64 , width : usize , precision : usize , left_justify : bool , uppercase : bool ) ! void {
553+ // Compute mantissa and exponent manually
554+ var buf : [128 ]u8 = undefined ;
555+ const abs_num = @abs (num );
556+ var exp_val : i32 = 0 ;
557+ var mantissa = abs_num ;
558+
559+ if (abs_num != 0.0 and ! std .math .isNan (abs_num ) and ! std .math .isInf (abs_num )) {
560+ exp_val = @intFromFloat (@floor (std .math .log10 (abs_num )));
561+ mantissa = abs_num / std .math .pow (f64 , 10.0 , @floatFromInt (exp_val ));
562+ // Normalize: ensure 1.0 <= mantissa < 10.0
563+ if (mantissa >= 10.0 ) {
564+ mantissa /= 10.0 ;
565+ exp_val += 1 ;
566+ } else if (mantissa < 1.0 and mantissa > 0.0 ) {
567+ mantissa *= 10.0 ;
568+ exp_val -= 1 ;
569+ }
570+ }
571+
572+ if (num < 0 ) mantissa = - mantissa ;
573+
574+ // Format mantissa with precision
575+ const mant_str = switch (precision ) {
576+ 0 = > std .fmt .bufPrint (& buf , "{d:.0}" , .{mantissa }) catch return ,
577+ 1 = > std .fmt .bufPrint (& buf , "{d:.1}" , .{mantissa }) catch return ,
578+ 2 = > std .fmt .bufPrint (& buf , "{d:.2}" , .{mantissa }) catch return ,
579+ 3 = > std .fmt .bufPrint (& buf , "{d:.3}" , .{mantissa }) catch return ,
580+ 4 = > std .fmt .bufPrint (& buf , "{d:.4}" , .{mantissa }) catch return ,
581+ 5 = > std .fmt .bufPrint (& buf , "{d:.5}" , .{mantissa }) catch return ,
582+ else = > std .fmt .bufPrint (& buf , "{d:.6}" , .{mantissa }) catch return ,
583+ };
584+
585+ // Format exponent
586+ var exp_buf : [16 ]u8 = undefined ;
587+ const e_char : u8 = if (uppercase ) 'E' else 'e' ;
588+ const exp_str = if (exp_val >= 0 )
589+ std .fmt .bufPrint (& exp_buf , "{c}+{d:0>2}" , .{ e_char , exp_val }) catch return
590+ else
591+ std .fmt .bufPrint (& exp_buf , "{c}-{d:0>2}" , .{ e_char , - exp_val }) catch return ;
592+
593+ // Combine and apply width
594+ var full_buf : [160 ]u8 = undefined ;
595+ const full = std .fmt .bufPrint (& full_buf , "{s}{s}" , .{ mant_str , exp_str }) catch return ;
596+
597+ if (width > 0 and full .len < width ) {
598+ const pad = width - full .len ;
599+ if (left_justify ) {
600+ try IO .print ("{s}" , .{full });
601+ var p : usize = 0 ;
602+ while (p < pad ) : (p += 1 ) try IO .print (" " , .{});
603+ } else {
604+ var p : usize = 0 ;
605+ while (p < pad ) : (p += 1 ) try IO .print (" " , .{});
606+ try IO .print ("{s}" , .{full });
607+ }
608+ } else {
609+ try IO .print ("{s}" , .{full });
610+ }
611+ }
612+
431613/// Helper for printf %b - print string with escape interpretation
432614pub fn printWithEscapes (str : []const u8 ) ! void {
433615 var i : usize = 0 ;
0 commit comments