Thanks for using Compiler Explorer
Sponsors
Jakt
C++
Ada
Algol68
Analysis
Android Java
Android Kotlin
Assembly
C
C3
Carbon
C with Coccinelle
C++ with Coccinelle
C++ (Circle)
CIRCT
Clean
Clojure
CMake
CMakeScript
COBOL
C++ for OpenCL
MLIR
Cppx
Cppx-Blue
Cppx-Gold
Cpp2-cppfront
Crystal
C#
CUDA C++
D
Dart
Elixir
Erlang
Fortran
F#
GLSL
Go
Haskell
HLSL
Helion
Hook
Hylo
IL
ispc
Java
Julia
Kotlin
LLVM IR
LLVM MIR
Modula-2
Mojo
Nim
Numba
Nix
Objective-C
Objective-C++
OCaml
Odin
OpenCL C
Pascal
Pony
PTX
Python
Racket
Raku
Ruby
Rust
Sail
Snowball
Scala
Slang
Solidity
Spice
SPIR-V
Swift
LLVM TableGen
Toit
Triton
TypeScript Native
V
Vala
Visual Basic
Vyper
WASM
Yul (Solidity IR)
Zig
Javascript
GIMPLE
Ygen
sway
zig source #1
Output
Compile to binary object
Link to binary
Execute the code
Intel asm syntax
Demangle identifiers
Verbose demangling
Filters
Unused labels
Library functions
Directives
Comments
Horizontal whitespace
Debug intrinsics
Compiler
zig 0.10.0
zig 0.11.0
zig 0.12.0
zig 0.12.1
zig 0.13.0
zig 0.14.0
zig 0.14.1
zig 0.15.1
zig 0.15.2
zig 0.2.0
zig 0.3.0
zig 0.4.0
zig 0.5.0
zig 0.6.0
zig 0.7.0
zig 0.7.1
zig 0.8.0
zig 0.9.0
zig trunk
Options
Source code
const std = @import("std"); const math = std.math; const assert = std.debug.assert; const mem = std.mem; const builtin = @import("builtin"); const errol = @import("fmt/errol.zig"); const lossyCast = std.math.lossyCast; export fn newAtoi(value: u32, str: [*]u8, base: u8) [*]u8 { _ = formatIntBuf(str[0..33], value, base, false, FormatOptions{}); return str; } fn oldAtoi(value: u32, str: [*]u8, base: u8) [*]u8 { _ = std.fmt.formatIntBuf(str[0..33], value, base, false, std.fmt.FormatOptions{}); return str; } pub const default_max_depth = 3; pub const Alignment = enum { Left, Center, Right, }; pub const FormatOptions = struct { precision: ?usize = null, width: ?usize = null, alignment: ?Alignment = null, fill: u8 = ' ', }; pub fn Generator(comptime Out: type) type { return union(enum) { /// Newly initialized or completed. inactive: void, /// Currently running. This is not a reliable check and exists to help prevent accidental concurrent writes. running: void, /// Async function yielded a value that has not been consumed yet. /// This exists because "priming the generator" dumps in a value without a next(). yielded: YieldResult, /// Previously yielded result was consumed. next() will resume the suspended frame. consumed: YieldResult, const YieldResult = struct { frame: anyframe, out: Out, }; pub fn init() @This() { return .{ .inactive = {} }; } pub fn yield(self: *@This(), out: Out) void { const state = std.meta.activeTag(self.*); assert(state == .inactive or state == .running); suspend { self.* = .{ .yielded = .{ .frame = @frame(), .out = out, }, }; } } fn consume(self: *@This(), result: YieldResult) Out { self.* = .{ .consumed = result }; return result.out; } pub fn next(self: *@This()) ?Out { switch (self.*) { .running => unreachable, // Generator is already running. Probably a concurrency bug. .inactive => return null, .yielded => |result| return self.consume(result), .consumed => |orig| { // Copy elision footgun const copy = orig; self.* = .{ .running = {} }; resume copy.frame; switch (self.*) { .inactive, .consumed => unreachable, // Bad state. Probably a concurrency bug. .yielded => |result| return self.consume(result), .running => { self.* = .{ .inactive = {} }; return null; }, } }, } } }; } fn nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, comptime next_arg: *comptime_int) comptime_int { if (maybe_pos_arg) |pos_arg| { used_pos_args.* |= 1 << pos_arg; return pos_arg; } else { const arg = next_arg.*; next_arg.* += 1; return arg; } } fn peekIsAlign(comptime fmt: []const u8) bool { // Should only be called during a state transition to the format segment. comptime assert(fmt[0] == ':'); inline for (([_]u8{ 1, 2 })[0..]) |i| { if (fmt.len > i and (fmt[i] == '<' or fmt[i] == '^' or fmt[i] == '>')) { return true; } } return false; } /// Renders fmt string with args, calling output with slices of bytes. /// If `output` returns an error, the error is returned from `format` and /// `output` is not called again. /// /// The format string must be comptime known and may contain placeholders following /// this format: /// `{[position][specifier]:[fill][alignment][width].[precision]}` /// /// Each word between `[` and `]` is a parameter you have to replace with something: /// /// - *position* is the index of the argument that should be inserted /// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below) /// - *fill* is a single character which is used to pad the formatted text /// - *alignment* is one of the three characters `<`, `^` or `>`. they define if the text is *left*, *center*, or *right* aligned /// - *width* is the total width of the field in characters /// - *precision* specifies how many decimals a formatted number should have /// /// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when /// all parameters after the separator are omitted. /// Only exception is the *fill* parameter. If *fill* is required, one has to specify *alignment* as well, as otherwise /// the digits after `:` is interpreted as *width*, not *fill*. /// /// The *specifier* has several options for types: /// - `x` and `X`: /// - format the non-numeric value as a string of bytes in hexadecimal notation ("binary dump") in either lower case or upper case /// - output numeric value in hexadecimal notation /// - `s`: print a pointer-to-many as a c-string, use zero-termination /// - `B` and `Bi`: output a memory size in either metric (1000) or power-of-two (1024) based notation. works for both float and integer values. /// - `e`: output floating point value in scientific notation /// - `d`: output numeric value in decimal notation /// - `b`: output integer value in binary notation /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. /// - `*`: output the address of the value instead of the value itself. /// /// If a formatted user type contains a function of the type /// ``` /// fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, context: var, comptime Errors: type, output: fn (@TypeOf(context), []const u8) Errors!void) Errors!void /// ``` /// with `?` being the type formatted, this function will be called instead of the default implementation. /// This allows user types to be formatted in a logical manner instead of dumping all fields of the type. /// /// A user type may be a `struct`, `union` or `enum` type. pub fn format( generator: *Generator([]const u8), comptime fmt: []const u8, args: var, ) void { const ArgSetType = @IntType(false, 32); if (@typeInfo(@TypeOf(args)) != .Struct) { @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); } if (args.len > ArgSetType.bit_count) { @compileError("32 arguments max are supported per format call"); } const State = enum { Start, Positional, CloseBrace, Specifier, FormatFillAndAlign, FormatWidth, FormatPrecision, }; comptime var start_index = 0; comptime var state = State.Start; comptime var next_arg = 0; comptime var maybe_pos_arg: ?comptime_int = null; comptime var used_pos_args: ArgSetType = 0; comptime var specifier_start = 0; comptime var specifier_end = 0; comptime var options = FormatOptions{}; inline for (fmt) |c, i| { switch (state) { .Start => switch (c) { '{' => { if (start_index < i) { generator.yield(fmt[start_index..i]); } start_index = i; specifier_start = i + 1; specifier_end = i + 1; maybe_pos_arg = null; state = .Positional; options = FormatOptions{}; }, '}' => { if (start_index < i) { generator.yield(fmt[start_index..i]); } state = .CloseBrace; }, else => {}, }, .Positional => switch (c) { '{' => { state = .Start; start_index = i; }, ':' => { state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; specifier_end = i; }, '0'...'9' => { if (maybe_pos_arg == null) { maybe_pos_arg = 0; } maybe_pos_arg.? *= 10; maybe_pos_arg.? += c - '0'; specifier_start = i + 1; if (maybe_pos_arg.? >= args.len) { @compileError("Positional value refers to non-existent argument"); } }, '}' => { const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); if (arg_to_print >= args.len) { @compileError("Too few arguments"); } formatType( args[arg_to_print], fmt[0..0], options, generator, default_max_depth, ); state = .Start; start_index = i + 1; }, else => { state = .Specifier; specifier_start = i; }, }, .CloseBrace => switch (c) { '}' => { state = .Start; start_index = i; }, else => @compileError("Single '}' encountered in format string"), }, .Specifier => switch (c) { ':' => { specifier_end = i; state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; }, '}' => { const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); formatType( args[arg_to_print], fmt[specifier_start..i], options, generator, default_max_depth, ); state = .Start; start_index = i + 1; }, else => {}, }, // Only entered if the format string contains a fill/align segment. .FormatFillAndAlign => switch (c) { '<' => { options.alignment = Alignment.Left; state = .FormatWidth; }, '^' => { options.alignment = Alignment.Center; state = .FormatWidth; }, '>' => { options.alignment = Alignment.Right; state = .FormatWidth; }, else => { options.fill = c; }, }, .FormatWidth => switch (c) { '0'...'9' => { if (options.width == null) { options.width = 0; } options.width.? *= 10; options.width.? += c - '0'; }, '.' => { state = .FormatPrecision; }, '}' => { const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); formatType( args[arg_to_print], fmt[specifier_start..specifier_end], options, generator, default_max_depth, ); state = .Start; start_index = i + 1; }, else => { @compileError("Unexpected character in width value: " ++ [_]u8{c}); }, }, .FormatPrecision => switch (c) { '0'...'9' => { if (options.precision == null) { options.precision = 0; } options.precision.? *= 10; options.precision.? += c - '0'; }, '}' => { const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); formatType( args[arg_to_print], fmt[specifier_start..specifier_end], options, generator, default_max_depth, ); state = .Start; start_index = i + 1; }, else => { @compileError("Unexpected character in precision value: " ++ [_]u8{c}); }, }, } } comptime { // All arguments must have been printed but we allow mixing positional and fixed to achieve this. var i: usize = 0; inline while (i < next_arg) : (i += 1) { used_pos_args |= 1 << i; } if (@popCount(ArgSetType, used_pos_args) != args.len) { @compileError("Unused arguments"); } if (state != State.Start) { @compileError("Incomplete format string: " ++ fmt); } } if (start_index < fmt.len) { generator.yield(fmt[start_index..]); } } pub fn formatType( value: var, comptime fmt: []const u8, options: FormatOptions, generator: *Generator([]const u8), comptime max_depth: comptime_int, ) void { if (max_depth < 0) { // This shouldn't ever be reached as we account for it later in the function. // But the compiler analysis phase gets confused and generates infinite versions of this function if there's no check. @compileError("max_depth less than 0"); } if (comptime std.mem.eql(u8, fmt, "*")) { return formatPtr(@TypeOf(value).Child, @ptrToInt(value), generator); } const T = @TypeOf(value); switch (@typeInfo(T)) { .ComptimeInt, .Int, .Float => { return formatValue(value, fmt, options, generator); }, .Void => { return generator.yield("void"); }, .Bool => { return generator.yield(if (value) "true" else "false"); }, .Optional => { if (value) |payload| { return formatType(payload, fmt, options, generator, max_depth); } else { return generator.yield("null"); } }, .ErrorUnion => { if (value) |payload| { return formatType(payload, fmt, options, generator, max_depth); } else |err| { return formatType(err, fmt, options, generator, max_depth); } }, .ErrorSet => { generator.yield("error."); return generator.yield(@errorName(value)); }, .Enum => { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } generator.yield(@typeName(T)); generator.yield("."); return generator.yield(@tagName(value)); }, .Union => |union_info| { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } if (union_info.tag_type) |UnionTagType| { generator.yield(@typeName(T)); if (max_depth == 0) { return generator.yield("{ ... }"); } generator.yield("{ ."); generator.yield(@tagName(@as(UnionTagType, value))); generator.yield(" = "); inline for (union_info.fields) |u_field| { if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { formatType(@field(value, u_field.name), "", options, generator, max_depth - 1); } } generator.yield(" }"); } else { return formatPtr(T, @ptrToInt(&value), generator); } }, .Struct => |struct_info| { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } generator.yield(@typeName(T)); if (max_depth == 0) { return generator.yield("{ ... }"); } generator.yield("{"); inline for (struct_info.fields) |field, i| { if (i == 0) { generator.yield(" ."); } else { generator.yield(", ."); } generator.yield(field.name); generator.yield(" = "); formatType(@field(value, field.name), "", options, generator, max_depth - 1); } generator.yield(" }"); }, .Pointer => |ptr_info| switch (ptr_info.size) { .One => switch (@typeInfo(ptr_info.child)) { builtin.TypeId.Array => |info| { if (info.child == u8) { return formatText(value, fmt, options, generator); } return formatPtr(T.Child, @ptrToInt(value), generator); }, builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { return formatType(value.*, fmt, options, generator, max_depth); }, else => return formatPtr(T.Child, @ptrToInt(value), generator), }, .Many => { if (ptr_info.child == u8) { if (fmt.len > 0 and fmt[0] == 's') { const len = mem.len(u8, value); return formatText(value[0..len], fmt, options, generator); } } return formatPtr(T.Child, @ptrToInt(value), generator); }, .Slice => { if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { return formatText(value, fmt, options, generator); } if (ptr_info.child == u8) { return formatText(value, fmt, options, generator); } return formatPtr(ptr_info.child, @ptrToInt(value.ptr), generator); }, .C => { return formatPtr(T.Child, @ptrToInt(value), generator); }, }, .Array => |info| { const Slice = @Type(builtin.TypeInfo{ .Pointer = .{ .size = .Slice, .is_const = true, .is_volatile = false, .is_allowzero = false, .alignment = @alignOf(info.child), .child = info.child, .sentinel = null, }, }); return formatType(@as(Slice, &value), fmt, options, generator, max_depth); }, .Fn => { return formatPtr(T, @ptrToInt(value), generator); }, .Type => return generator.yield(@typeName(T)), else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), } } fn formatPtr(comptime T: type, ptr: usize, generator: *Generator([]const u8)) void { generator.yield(@typeName(T)); generator.yield("@"); return formatInt(ptr, 16, false, FormatOptions{}, generator); } fn formatValue( value: var, comptime fmt: []const u8, options: FormatOptions, generator: *Generator([]const u8), ) void { if (comptime std.mem.eql(u8, fmt, "B")) { return formatBytes(value, options, 1000, generator); } else if (comptime std.mem.eql(u8, fmt, "Bi")) { return formatBytes(value, options, 1024, generator); } const T = @TypeOf(value); switch (@typeId(T)) { .Float => return formatFloatValue(value, fmt, options, generator), .Int, .ComptimeInt => return formatIntValue(value, fmt, options, generator), else => comptime unreachable, } } pub fn formatIntValue( value: var, comptime fmt: []const u8, options: FormatOptions, generator: *Generator([]const u8), ) void { comptime var radix = 10; comptime var uppercase = false; const int_value = if (@TypeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); break :blk @as(Int, value); } else value; if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { radix = 10; uppercase = false; } else if (comptime std.mem.eql(u8, fmt, "c")) { if (@TypeOf(int_value).bit_count <= 8) { return formatAsciiChar(@as(u8, int_value), options, generator); } else { @compileError("Cannot print integer that is larger than 8 bits as a ascii"); } } else if (comptime std.mem.eql(u8, fmt, "b")) { radix = 2; uppercase = false; } else if (comptime std.mem.eql(u8, fmt, "x")) { radix = 16; uppercase = false; } else if (comptime std.mem.eql(u8, fmt, "X")) { radix = 16; uppercase = true; } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } return formatInt(int_value, radix, uppercase, options, generator); } fn formatFloatValue( value: var, comptime fmt: []const u8, options: FormatOptions, generator: *Generator([]const u8), ) void { if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { return formatFloatScientific(value, options, generator); } else if (comptime std.mem.eql(u8, fmt, "d")) { return formatFloatDecimal(value, options, generator); } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } } pub fn formatText( bytes: []const u8, comptime fmt: []const u8, options: FormatOptions, generator: *Generator([]const u8), ) void { if (fmt.len == 0) { generator.yield(bytes); } else if (comptime std.mem.eql(u8, fmt, "s")) { return formatBuf(bytes, options, generator); } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { for (bytes) |c| { formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, generator); } return; } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } } pub fn formatAsciiChar( c: u8, options: FormatOptions, generator: *Generator([]const u8), ) void { if (std.ascii.isPrint(c)) return generator.yield(@as(*const [1]u8, &c)); @panic("FIXME"); // return format(context, Errors, output, "\\x{x:0<2}", .{c}); } pub fn formatBuf( buf: []const u8, options: FormatOptions, generator: *Generator([]const u8), ) void { generator.yield(buf); const width = options.width orelse 0; var leftover_padding = if (width > buf.len) (width - buf.len) else return; const pad_byte: u8 = options.fill; while (leftover_padding > 0) : (leftover_padding -= 1) { generator.yield(@as(*const [1]u8, &pad_byte)); } } // // Print a float in scientific notation to the specified precision. Null uses full precision. // // It should be the case that every full precision, printed value can be re-parsed back to the // // same type unambiguously. pub fn formatFloatScientific( value: var, options: FormatOptions, generator: *Generator([]const u8), ) void { var x = @floatCast(f64, value); // Errol doesn't handle these special cases. if (math.signbit(x)) { generator.yield("-"); x = -x; } if (math.isNan(x)) { return generator.yield("nan"); } if (math.isPositiveInf(x)) { return generator.yield("inf"); } if (x == 0.0) { generator.yield("0"); if (options.precision) |prec_orig| { // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 const precision = prec_orig; if (precision != 0) { generator.yield("."); var i: usize = 0; while (i < precision) : (i += 1) { generator.yield("0"); } } } else { generator.yield(".0"); } generator.yield("e+00"); return; } var buffer: [32]u8 = undefined; var float_decimal = errol.errol3(x, buffer[0..]); if (options.precision) |prec_orig| { // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 const precision = prec_orig; errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); generator.yield(float_decimal.digits[0..1]); // {e0} case prints no `.` if (precision != 0) { generator.yield("."); var printed: usize = 0; if (float_decimal.digits.len > 1) { const num_digits = math.min(float_decimal.digits.len, precision + 1); generator.yield(float_decimal.digits[1..num_digits]); printed += num_digits - 1; } while (printed < precision) : (printed += 1) { generator.yield("0"); } } } else { generator.yield(float_decimal.digits[0..1]); generator.yield("."); if (float_decimal.digits.len > 1) { const num_digits = if (@TypeOf(value) == f32) math.min(@as(usize, 9), float_decimal.digits.len) else float_decimal.digits.len; generator.yield(float_decimal.digits[1..num_digits]); } else { generator.yield("0"); } } generator.yield("e"); const exp = float_decimal.exp - 1; if (exp >= 0) { generator.yield("+"); if (exp > -10 and exp < 10) { generator.yield("0"); } formatInt(exp, 10, false, FormatOptions{ .width = 0 }, generator); } else { generator.yield("-"); if (exp > -10 and exp < 10) { generator.yield("0"); } formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, generator); } } // Print a float of the format x.yyyyy where the number of y is specified by the precision argument. // By default floats are printed at full precision (no rounding). pub fn formatFloatDecimal( value: var, options: FormatOptions, generator: *Generator([]const u8), ) void { var x = @as(f64, value); // Errol doesn't handle these special cases. if (math.signbit(x)) { generator.yield("-"); x = -x; } if (math.isNan(x)) { return generator.yield("nan"); } if (math.isPositiveInf(x)) { return generator.yield("inf"); } if (x == 0.0) { generator.yield("0"); if (options.precision) |prec_orig| { // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 const precision = prec_orig; if (precision != 0) { generator.yield("."); var i: usize = 0; while (i < precision) : (i += 1) { generator.yield("0"); } } else { generator.yield(".0"); } } else { generator.yield("0"); } return; } // non-special case, use errol3 var buffer: [32]u8 = undefined; var float_decimal = errol.errol3(x, buffer[0..]); if (options.precision) |prec_orig| { // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 const precision = prec_orig; errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal); // exp < 0 means the leading is always 0 as errol result is normalized. var num_digits_whole = if (float_decimal.exp > 0) @intCast(usize, float_decimal.exp) else 0; // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this. var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len); if (num_digits_whole > 0) { // We may have to zero pad, for instance 1e4 requires zero padding. generator.yield(float_decimal.digits[0..num_digits_whole_no_pad]); var i = num_digits_whole_no_pad; while (i < num_digits_whole) : (i += 1) { generator.yield("0"); } } else { generator.yield("0"); } // {.0} special case doesn't want a trailing '.' if (precision == 0) { return; } generator.yield("."); // Keep track of fractional count printed for case where we pre-pad then post-pad with 0's. var printed: usize = 0; // Zero-fill until we reach significant digits or run out of precision. if (float_decimal.exp <= 0) { const zero_digit_count = @intCast(usize, -float_decimal.exp); const zeros_to_print = math.min(zero_digit_count, precision); var i: usize = 0; while (i < zeros_to_print) : (i += 1) { generator.yield("0"); printed += 1; } if (printed >= precision) { return; } } // Remaining fractional portion, zero-padding if insufficient. assert(precision >= printed); if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) { generator.yield(float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); return; } else { generator.yield(float_decimal.digits[num_digits_whole_no_pad..]); printed += float_decimal.digits.len - num_digits_whole_no_pad; while (printed < precision) : (printed += 1) { generator.yield("0"); } } } else { // exp < 0 means the leading is always 0 as errol result is normalized. var num_digits_whole = if (float_decimal.exp > 0) @intCast(usize, float_decimal.exp) else 0; // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this. var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len); if (num_digits_whole > 0) { // We may have to zero pad, for instance 1e4 requires zero padding. generator.yield(float_decimal.digits[0..num_digits_whole_no_pad]); var i = num_digits_whole_no_pad; while (i < num_digits_whole) : (i += 1) { generator.yield("0"); } } else { generator.yield("0"); } // Omit `.` if no fractional portion if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) { return; } generator.yield("."); // Zero-fill until we reach significant digits or run out of precision. if (float_decimal.exp < 0) { const zero_digit_count = @intCast(usize, -float_decimal.exp); var i: usize = 0; while (i < zero_digit_count) : (i += 1) { generator.yield("0"); } } generator.yield(float_decimal.digits[num_digits_whole_no_pad..]); } } pub fn formatBytes( value: var, options: FormatOptions, comptime radix: usize, generator: *Generator([]const u8), ) void { if (value == 0) { return generator.yield("0B"); } const mags_si = " kMGTPEZY"; const mags_iec = " KMGTPEZY"; const magnitude = switch (radix) { 1000 => math.min(math.log2(value) / comptime math.log2(1000), mags_si.len - 1), 1024 => math.min(math.log2(value) / 10, mags_iec.len - 1), else => unreachable, }; const new_value = lossyCast(f64, value) / math.pow(f64, lossyCast(f64, radix), lossyCast(f64, magnitude)); const suffix = switch (radix) { 1000 => mags_si[magnitude], 1024 => mags_iec[magnitude], else => unreachable, }; formatFloatDecimal(new_value, options, generator); if (suffix == ' ') { return generator.yield("B"); } const buf = switch (radix) { 1000 => &[_]u8{ suffix, 'B' }, 1024 => &[_]u8{ suffix, 'i', 'B' }, else => unreachable, }; return generator.yield(buf); } pub fn formatInt( value: var, base: u8, uppercase: bool, options: FormatOptions, generator: *Generator([]const u8), ) void { const int_value = if (@TypeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); break :blk @as(Int, value); } else value; if (@TypeOf(int_value).is_signed) { return formatIntSigned(int_value, base, uppercase, options, generator); } else { return formatIntUnsigned(int_value, base, uppercase, options, generator); } } fn formatIntSigned( value: var, base: u8, uppercase: bool, options: FormatOptions, generator: *Generator([]const u8), ) void { const new_options = FormatOptions{ .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, .precision = options.precision, .fill = options.fill, }; const uint = @IntType(false, @TypeOf(value).bit_count); if (value < 0) { generator.yield("-"); const new_value = @intCast(uint, -(value + 1)) + 1; return formatIntUnsigned(new_value, base, uppercase, new_options, generator); } else if (options.width == null or options.width.? == 0) { return formatIntUnsigned(@intCast(uint, value), base, uppercase, options, generator); } else { generator.yield("+"); const new_value = @intCast(uint, value); return formatIntUnsigned(new_value, base, uppercase, new_options, generator); } } fn formatIntUnsigned( value: var, base: u8, uppercase: bool, options: FormatOptions, generator: *Generator([]const u8), ) void { assert(base >= 2); var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined; const min_int_bits = comptime math.max(@TypeOf(value).bit_count, @TypeOf(base).bit_count); const MinInt = @IntType(@TypeOf(value).is_signed, min_int_bits); var a: MinInt = value; var index: usize = buf.len; while (true) { const digit = a % base; index -= 1; buf[index] = digitToChar(@intCast(u8, digit), uppercase); a /= base; if (a == 0) break; } const digits_buf = buf[index..]; const width = options.width orelse 0; const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0; if (padding > index) { const zero_byte: u8 = options.fill; var leftover_padding = padding - index; while (true) { generator.yield(@as(*const [1]u8, &zero_byte)); leftover_padding -= 1; if (leftover_padding == 0) break; } mem.set(u8, buf[0..index], options.fill); return generator.yield(&buf); } else { const padded_buf = buf[index - padding ..]; mem.set(u8, padded_buf[0..padding], options.fill); return generator.yield(padded_buf); } } pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { var index: usize = 0; var generator = Generator([]const u8).init(); _ = async formatInt(value, base, uppercase, options, &generator); while (generator.next()) |bytes| { mem.copy(u8, out_buf[index..], bytes); index += bytes.len; } return index; } pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T { if (!T.is_signed) return parseUnsigned(T, buf, radix); if (buf.len == 0) return @as(T, 0); if (buf[0] == '-') { return math.negate(try parseUnsigned(T, buf[1..], radix)); } else if (buf[0] == '+') { return parseUnsigned(T, buf[1..], radix); } else { return parseUnsigned(T, buf, radix); } } fn digitToChar(digit: u8, uppercase: bool) u8 { return switch (digit) { 0...9 => digit + '0', 10...35 => digit + ((if (uppercase) @as(u8, 'A') else @as(u8, 'a')) - 10), else => unreachable, }; }
Become a Patron
Sponsor on GitHub
Donate via PayPal
Compiler Explorer Shop
Source on GitHub
Mailing list
Installed libraries
Wiki
Report an issue
How it works
Contact the author
CE on Mastodon
CE on Bluesky
Statistics
Changelog
Version tree