Skip to content

Commit 4b3e9d5

Browse files
chore: wip
1 parent d54042e commit 4b3e9d5

7 files changed

Lines changed: 533 additions & 14 deletions

File tree

src/parser/tokenizer.zig

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,19 @@ pub const Tokenizer = struct {
893893
in_single_quote = false;
894894
}
895895
} else if (in_double_quote) {
896-
if (c == '"' and (self.pos == 0 or self.input[self.pos - 1] != '\\')) {
896+
if (c == '\\' and self.pos + 1 < self.input.len) {
897+
// Skip escaped character inside double quotes entirely
898+
// This correctly handles \\" (escaped backslash before quote)
899+
if (c == '\n') {
900+
self.line += 1;
901+
self.column = 1;
902+
} else {
903+
self.column += 1;
904+
}
905+
self.pos += 1;
906+
// The escaped char will be handled by the newline/column
907+
// tracking below, then we continue
908+
} else if (c == '"') {
897909
in_double_quote = false;
898910
}
899911
} else {

src/shell/printf_builtin.zig

Lines changed: 190 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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
432614
pub fn printWithEscapes(str: []const u8) !void {
433615
var i: usize = 0;

0 commit comments

Comments
 (0)