Is there an equivalent of the C '*' format spcifier?

Hey there was working on a code generator and was trying to get some better indentation on the output. I was wondering if there is somethings similar to the ‘*’ specifier in C for the print format options.

i.e. in C:

#include <stdio.h>

int main() { 
    printf("%*s\n", 10, "Hello"); 
}

So that I can maybe do something similar to:

try writer.print("{*s}", .{4*depth, string});

Thanks for the help!

Hey, you might want to refer to the documentation of std.Io.Writer.print function.
https://codeberg.org/ziglang/zig/src/commit/7f36c4c7d3c8f3bfabb49a922a6156e4f1c61f67/lib/std/Io/Writer.zig#L537-L595

Btw, if you are generating zig code, you can also use std.zig.Ast.parse to parse the generated code and then render it formatted with std.zig.Ast.render, this also gives you little sanity check that your generated code passes the AST check.

1 Like

Thanks for the link. Unfortunately I don’t think I can do what I want, because the format has to be comptime known. I want the width parameter to be a function parameter, i.e.

fn genExpr(writer: *io.Writer, depth:u32) !void {
    try writer.print("{s: >[depth]}\n", .{"blah", depth});
}

Why not just:

fn genExpr(writer: *io.Writer, depth: u32) !void {
    try writer.splatByteAll(' ', depth);
    try writer.print("{s}\n", .{"blah"});
}

That’s also effectively what std.zig.Ast.render does (link)

1 Like

I think there are two approaches that work well:

  • pass indent: []const u8 instead of depth where indent can for example subslice into some (possibly static string literal) buffer that only contains the wanted indent character (up to some max depth).

  • or what @Justus2308 described

The first is nice when the indent isn’t just repeated whitespace, for example it can be useful to format tree-like output with connecting lines.

2 Likes

even if your not generating zig, something like what @Cloudef suggested is a good idea.


I tested it in case I forgot the details, but indentation is just not working with slices for some reason, it does still work with other things like numbers.


you can use reference a parameter for almost any part, the exceptions are the alignment (<,^,>) and the fill char.

Remember that the format args are a stuct/tuple, and you reference fields by name/index.

try writer.print("{s: >[1]}\n", .{"blah", depth});
// or
try writer.print("{[blah]s: >[depth]}\n", .{ .blah = "blah", .depth = depth});
8 Likes

Thanks a bunch. I though I had already tried this but I guess not. Thank you everyone else for your help as well

Highlighting this in case you missed it, alignment+fill isnt working at all with slices for some reason. Thats why you it didnt work when you tried it.

but it is supposed to work

1 Like

Seems to work on 0.15.2

It actually is working, just a misunderstanding.

It is not fill with n chars.

it is ensure you print at least n chars, if you print less use this filler char.

the annoying part is I think I had the exact misunderstanding multiple times already, perhaps the docs should be made more clear. They also say it only works on numbers but thats just not true so they need an update anyway

If you want/need the entire expression template to be one comptime string, another option would be to use a custom formatter for intendation:

const Indent = struct {
    depth: u32,

    pub fn format(self: Indent, w: *std.Io.Writer) std.Io.Writer.Error!void {
        return w.splatByteAll(' ', self.depth);
    }
};

fn indent(depth: u32) Indent {
    return .{ .depth = depth };
}

fn genExpr(w: *std.Io.Writer, depth: u32) !void {
    try writer.print("{f}{s}\n", .{ indent(depth), "blah" });
}

Though it’s generally better to prefer explicit writer calls to format strings if possible since the latter can increase compile times quite a bit.