Skip to content

Instantly share code, notes, and snippets.

@MichaelVoelkel
Created August 4, 2022 12:53
Show Gist options
  • Save MichaelVoelkel/57d07547372a6fc44371f7e8721b3adb to your computer and use it in GitHub Desktop.
Save MichaelVoelkel/57d07547372a6fc44371f7e8721b3adb to your computer and use it in GitHub Desktop.
Zig Format put split into functions pseudo code
pub fn format(
writer: anytype,
comptime fmt: []const u8,
args: anytype,
) !void {
const ArgsType = @TypeOf(args);
const args_type_info = @typeInfo(ArgsType);
_checkPreconditions(...);
@setEvalBranchQuota(2000000);
comptime var arg_state: ArgState = .{ .args_len = fields_info.len };
comptime var i = 0;
inline while (i < fmt.len) {
const start_index = i;
_proceedToAnyBrace(...);
comptime var end_index = i;
comptime var unescape_brace = false;
encountered_double_braces = _handleDoubleBracesIfEncountered(...);
// Write out the literal
_writeOutLiteral(...);
// We've already skipped the other brace, restart the loop
next_step = _proceedAfterLiteralEnd(...);
comptime switch next_step {.skip_loop: continue, .break: break, .proceed: {}};
const fmt_begin = i;
const fmt_end = _findAndSkipClosingBrace(...);
const placeholder = comptime parsePlaceholder(fmt[fmt_begin..fmt_end].*);
const arg_pos = _getPlaceholderPos(...);
const width = _getPlaceholderWidth(...);
const precision = _getPlaceholderPrecision(...);
const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
@compileError("too few arguments");
_formatTheType(...);
}
_assertUnusedArgs(...);
}
pub fn _checkPreconditions(...) {
if (args_type_info != .Struct) {
@compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
}
const fields_info = args_type_info.Struct.fields;
if (fields_info.len > max_format_args) {
@compileError("32 arguments max are supported per format call");
}
}
fn _proceedToAnyBrace(...) {
inline while (i < fmt.len) : (i += 1) {
switch (fmt[i]) {
'{', '}' => break,
else => {},
}
}
}
fn _handleDoubleBracesIfEncountered(...) {
if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
unescape_brace = true;
// Make the first brace part of the literal...
end_index += 1;
// ...and skip both
i += 2;
}
}
fn _writeOutLiteral(...) {
// evtl. Kommentar zu der Bedingung hinzufügen im Funktionsnamen, aber wsl unnötig
if (start_index != end_index) {
try writer.writeAll(fmt[start_index..end_index]);
}
}
fn _proceedAfterLiteralEnd(...) {
if (unescape_brace) return .skip_loop;
if (i >= fmt.len) return .break;
if (fmt[i] == '}') {
@compileError("missing opening {");
}
// Get past the {
comptime assert(fmt[i] == '{');
i += 1;
return .proceed;
}
fn _findAndSkipClosingBrace(...) {
inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
if (i >= fmt.len) {
@compileError("missing closing }");
}
comptime assert(fmt[i] == '}'); // überhaupt noch notwendig?
i += 1;
return i;
}
fn _getPlaceholderPos(...) {
return comptime switch (placeholder.arg) {
.none => null,
.number => |pos| pos,
.named => |arg_name| meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'"),
};
}
fn _getArgWidth(...) {
// wieso ist das eigentlich kein comptime switch, das für placeholder arg schon?
return switch (placeholder.width) {
.none => null,
.number => |v| v,
.named => |arg_name| blk: {
const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'");
_ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
break :blk @field(args, arg_name);
},
};
}
fn _getPlaceholderPrecision(...) {
return switch (placeholder.precision) {
.none => null,
.number => |v| v,
.named => |arg_name| blk: {
const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'");
_ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
break :blk @field(args, arg_name);
},
};
}
fn _formatTheType(...) {
try formatType(
@field(args, fields_info[arg_to_print].name),
placeholder.specifier_arg,
FormatOptions{
.fill = placeholder.fill,
.alignment = placeholder.alignment,
.width = width,
.precision = precision,
},
writer,
default_max_depth,
);
}
fn _assertUnusedArgs(...) {
if (comptime arg_state.hasUnusedArgs()) {
const missing_count = arg_state.args_len - @popCount(ArgSetType, arg_state.used_args);
switch (missing_count) {
0 => unreachable,
1 => @compileError("unused argument in '" ++ fmt ++ "'"),
else => @compileError((comptime comptimePrint("{d}", .{missing_count})) ++ " unused arguments in '" ++ fmt ++ "'"),
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment