break off some of std.io into std.fmt, generalize printf

closes #250
master
Andrew Kelley 2017-03-09 19:12:15 -05:00
parent c62db5721c
commit 47f267d25f
6 changed files with 379 additions and 296 deletions

View File

@ -210,6 +210,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/elf.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/empty.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/endian.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/errno.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/fmt.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/hash_map.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/index.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/io.zig" DESTINATION "${ZIG_STD_DEST}")

View File

@ -1,5 +1,6 @@
const std = @import("std");
const io = std.io;
const fmt = std.fmt;
const Rand = std.rand.Rand;
const os = std.os;
@ -23,7 +24,7 @@ pub fn main(args: [][]u8) -> %void {
return err;
};
const guess = io.parseUnsigned(u8, line_buf[0...line_len - 1], 10) %% {
const guess = fmt.parseUnsigned(u8, line_buf[0...line_len - 1], 10) %% {
%%io.stdout.printf("Invalid number.\n");
continue;
};

334
std/fmt.zig Normal file
View File

@ -0,0 +1,334 @@
const math = @import("math.zig");
const debug = @import("debug.zig");
const assert = debug.assert;
const mem = @import("mem.zig");
const max_f64_digits = 65;
const max_int_digits = 65;
const State = enum { // TODO put inside format function and make sure the name and debug info is correct
Start,
OpenBrace,
CloseBrace,
Integer,
IntegerWidth,
Character,
};
/// Renders fmt string with args, calling output with slices of bytes.
/// Return false from output function and output will not be called again.
/// Returns false if output ever returned false, true otherwise.
pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
comptime fmt: []const u8, args: ...) -> bool
{
comptime var start_index = 0;
comptime var state = State.Start;
comptime var next_arg = 0;
comptime var radix = 0;
comptime var uppercase = false;
comptime var width = 0;
comptime var width_start = 0;
inline for (fmt) |c, i| {
switch (state) {
State.Start => switch (c) {
'{' => {
// TODO if you make this an if statement with && then it breaks
if (start_index < i) {
if (!output(context, fmt[start_index...i]))
return false;
}
state = State.OpenBrace;
},
'}' => {
if (start_index < i) {
if (!output(context, fmt[start_index...i]))
return false;
}
state = State.CloseBrace;
},
else => {},
},
State.OpenBrace => switch (c) {
'{' => {
state = State.Start;
start_index = i;
},
'}' => {
if (!formatValue(args[next_arg], context, output))
return false;
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'd' => {
radix = 10;
uppercase = false;
width = 0;
state = State.Integer;
},
'x' => {
radix = 16;
uppercase = false;
width = 0;
state = State.Integer;
},
'X' => {
radix = 16;
uppercase = true;
width = 0;
state = State.Integer;
},
'c' => {
state = State.Character;
},
else => @compileError("Unknown format character: " ++ []u8{c}),
},
State.CloseBrace => switch (c) {
'}' => {
state = State.Start;
start_index = i;
},
else => @compileError("Single '}' encountered in format string"),
},
State.Integer => switch (c) {
'}' => {
if (!formatInt(args[next_arg], radix, uppercase, width, context, output))
return false;
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'0' ... '9' => {
width_start = i;
state = State.IntegerWidth;
},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.IntegerWidth => switch (c) {
'}' => {
width = comptime %%parseUnsigned(usize, fmt[width_start...i], 10);
if (!formatInt(args[next_arg], radix, uppercase, width, context, output))
return false;
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'0' ... '9' => {},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.Character => switch (c) {
'}' => {
if (!formatAsciiChar(args[next_arg], context, output))
return false;
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
}
}
comptime {
if (args.len != next_arg) {
@compileError("Unused arguments");
}
if (state != State.Start) {
@compileError("Incomplete format string: " ++ fmt);
}
}
if (start_index < fmt.len) {
if (!output(context, fmt[start_index...]))
return false;
}
return true;
}
pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool {
const T = @typeOf(value);
if (@isInteger(T)) {
return formatInt(value, 10, false, 0, context, output);
} else if (@isFloat(T)) {
@compileError("TODO implement formatFloat");
} else if (@canImplicitCast([]const u8, value)) {
const casted_value = ([]const u8)(value);
return output(context, casted_value);
} else if (T == void) {
return output(context, "void");
} else {
@compileError("Unable to format type '" ++ @typeName(T) ++ "'");
}
}
pub fn formatAsciiChar(c: u8, context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool {
return output(context, (&c)[0...1]);
}
pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize,
context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
{
if (@typeOf(value).is_signed) {
return formatIntSigned(value, base, uppercase, width, context, output);
} else {
return formatIntUnsigned(value, base, uppercase, width, context, output);
}
}
fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize,
context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
{
const uint = @intType(false, @typeOf(value).bit_count);
if (value < 0) {
const minus_sign: u8 = '-';
if (!output(context, (&minus_sign)[0...1]))
return false;
const new_value = uint(-(value + 1)) + 1;
const new_width = if (width == 0) 0 else (width - 1);
return formatIntUnsigned(new_value, base, uppercase, new_width, context, output);
} else if (width == 0) {
return formatIntUnsigned(uint(value), base, uppercase, width, context, output);
} else {
const plus_sign: u8 = '+';
if (!output(context, (&plus_sign)[0...1]))
return false;
const new_value = uint(value);
const new_width = if (width == 0) 0 else (width - 1);
return formatIntUnsigned(new_value, base, uppercase, new_width, context, output);
}
}
fn formatIntUnsigned(value: var, base: u8, uppercase: bool, width: usize,
context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
{
// max_int_digits accounts for the minus sign. when printing an unsigned
// number we don't need to do that.
var buf: [max_int_digits - 1]u8 = undefined;
var a = value;
var index: usize = buf.len;
while (true) {
const digit = a % base;
index -= 1;
buf[index] = digitToChar(u8(digit), uppercase);
a /= base;
if (a == 0)
break;
}
const digits_buf = buf[index...];
const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0;
if (padding > index) {
const zero_byte: u8 = '0';
var leftover_padding = padding - index;
while (true) {
if (!output(context, (&zero_byte)[0...1]))
return false;
leftover_padding -= 1;
if (leftover_padding == 0)
break;
}
mem.set(u8, buf[0...index], '0');
return output(context, buf);
} else {
const padded_buf = buf[index - padding...];
mem.set(u8, padded_buf[0...padding], '0');
return output(context, padded_buf);
}
}
pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, width: usize) -> usize {
var context = FormatIntBuf {
.out_buf = out_buf,
.index = 0,
};
_ = formatInt(value, base, uppercase, width, &context, formatIntCallback);
return context.index;
}
const FormatIntBuf = struct {
out_buf: []u8,
index: usize,
};
fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) -> bool {
mem.copy(u8, context.out_buf[context.index...], bytes);
context.index += bytes.len;
return true;
}
pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) -> %T {
var x: T = 0;
for (buf) |c| {
const digit = %return charToDigit(c, radix);
x = %return math.mulOverflow(T, x, radix);
x = %return math.addOverflow(T, x, digit);
}
return x;
}
error InvalidChar;
fn charToDigit(c: u8, radix: u8) -> %u8 {
const value = switch (c) {
'0' ... '9' => c - '0',
'A' ... 'Z' => c - 'A' + 10,
'a' ... 'z' => c - 'a' + 10,
else => return error.InvalidChar,
};
if (value >= radix)
return error.InvalidChar;
return value;
}
fn digitToChar(digit: u8, uppercase: bool) -> u8 {
return switch (digit) {
0 ... 9 => digit + '0',
10 ... 35 => digit + ((if (uppercase) u8('A') else u8('a')) - 10),
else => @unreachable(),
};
}
fn testBufPrintInt() {
@setFnTest(this);
var buffer: [max_int_digits]u8 = undefined;
const buf = buffer[0...];
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, 0), "-101111000110000101001110"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 10, false, 0), "-12345678"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, false, 0), "-bc614e"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, true, 0), "-BC614E"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(12345678), 10, true, 0), "12345678"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(666), 10, false, 6), "000666"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 6), "001234"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 1), "1234"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(42), 10, false, 3), "+42"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-42), 10, false, 3), "-42"));
}
fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, width: usize) -> []u8 {
return buf[0...formatIntBuf(buf, value, base, uppercase, width)];
}
fn testParseU64DigitTooBig() {
@setFnTest(this);
parseUnsigned(u64, "123a", 10) %% |err| {
if (err == error.InvalidChar) return;
@unreachable();
};
@unreachable();
}
fn testParseUnsignedComptime() {
@setFnTest(this);
comptime {
assert(%%parseUnsigned(usize, "2", 10) == 2);
}
}

View File

@ -1,20 +1,21 @@
pub const rand = @import("rand.zig");
pub const io = @import("io.zig");
pub const os = @import("os.zig");
pub const math = @import("math.zig");
pub const cstr = @import("cstr.zig");
pub const sort = @import("sort.zig");
pub const net = @import("net.zig");
pub const list = @import("list.zig");
pub const hash_map = @import("hash_map.zig");
pub const mem = @import("mem.zig");
pub const debug = @import("debug.zig");
pub const fmt = @import("fmt.zig");
pub const hash_map = @import("hash_map.zig");
pub const io = @import("io.zig");
pub const list = @import("list.zig");
pub const math = @import("math.zig");
pub const mem = @import("mem.zig");
pub const net = @import("net.zig");
pub const os = @import("os.zig");
pub const rand = @import("rand.zig");
pub const sort = @import("sort.zig");
pub const linux = switch(@compileVar("os")) {
Os.linux => @import("linux.zig"),
else => null_import,
else => empty_import,
};
pub const darwin = switch(@compileVar("os")) {
Os.darwin => @import("darwin.zig"),
else => null_import,
else => empty_import,
};
const null_import = @import("empty.zig");
pub const empty_import = @import("empty.zig");

View File

@ -11,6 +11,7 @@ const assert = debug.assert;
const os = @import("os.zig");
const mem = @import("mem.zig");
const Buffer0 = @import("cstr.zig").Buffer0;
const fmt = @import("fmt.zig");
pub const stdin_fileno = 0;
pub const stdout_fileno = 1;
@ -61,8 +62,6 @@ error Unseekable;
error Eof;
const buffer_size = 4 * 1024;
const max_f64_digits = 65;
const max_int_digits = 65;
pub const OpenRead = 0b0001;
pub const OpenWrite = 0b0010;
@ -81,173 +80,46 @@ pub const OutStream = struct {
}
pub fn write(self: &OutStream, bytes: []const u8) -> %void {
var src_bytes_left = bytes.len;
var src_index: usize = 0;
const dest_space_left = self.buffer.len - self.index;
while (src_bytes_left > 0) {
const copy_amt = math.min(dest_space_left, src_bytes_left);
@memcpy(&self.buffer[self.index], &bytes[src_index], copy_amt);
while (src_index < bytes.len) {
const dest_space_left = self.buffer.len - self.index;
const copy_amt = math.min(dest_space_left, bytes.len - src_index);
mem.copy(u8, self.buffer[self.index...], bytes[src_index...src_index + copy_amt]);
self.index += copy_amt;
assert(self.index <= self.buffer.len);
if (self.index == self.buffer.len) {
%return self.flush();
}
src_bytes_left -= copy_amt;
src_index += copy_amt;
}
}
const State = enum { // TODO put inside printf function and make sure the name and debug info is correct
Start,
OpenBrace,
CloseBrace,
Integer,
IntegerWidth,
Character,
};
/// Calls print and then flushes the buffer.
pub fn printf(self: &OutStream, comptime format: []const u8, args: ...) -> %void {
comptime var start_index = 0;
comptime var state = State.Start;
comptime var next_arg = 0;
comptime var radix = 0;
comptime var uppercase = false;
comptime var width = 0;
comptime var width_start = 0;
inline for (format) |c, i| {
switch (state) {
State.Start => switch (c) {
'{' => {
if (start_index < i) %return self.write(format[start_index...i]);
state = State.OpenBrace;
},
'}' => {
if (start_index < i) %return self.write(format[start_index...i]);
state = State.CloseBrace;
},
else => {},
},
State.OpenBrace => switch (c) {
'{' => {
state = State.Start;
start_index = i;
},
'}' => {
%return self.printValue(args[next_arg]);
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'd' => {
radix = 10;
uppercase = false;
width = 0;
state = State.Integer;
},
'x' => {
radix = 16;
uppercase = false;
width = 0;
state = State.Integer;
},
'X' => {
radix = 16;
uppercase = true;
width = 0;
state = State.Integer;
},
'c' => {
state = State.Character;
},
else => @compileError("Unknown format character: " ++ []u8{c}),
},
State.CloseBrace => switch (c) {
'}' => {
state = State.Start;
start_index = i;
},
else => @compileError("Single '}' encountered in format string"),
},
State.Integer => switch (c) {
'}' => {
%return self.printInt(args[next_arg], radix, uppercase, width);
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'0' ... '9' => {
width_start = i;
state = State.IntegerWidth;
},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.IntegerWidth => switch (c) {
'}' => {
width = comptime %%parseUnsigned(usize, format[width_start...i], 10);
%return self.printInt(args[next_arg], radix, uppercase, width);
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'0' ... '9' => {},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.Character => switch (c) {
'}' => {
%return self.printAsciiChar(args[next_arg]);
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
}
}
comptime {
if (args.len != next_arg) {
@compileError("Unused arguments");
}
if (state != State.Start) {
@compileError("Incomplete format string: " ++ format);
}
}
if (start_index < format.len) {
%return self.write(format[start_index...format.len]);
}
%return self.print(format, args);
%return self.flush();
}
pub fn printValue(self: &OutStream, value: var) -> %void {
const T = @typeOf(value);
if (@isInteger(T)) {
return self.printInt(value, 10, false, 0);
} else if (@isFloat(T)) {
return self.printFloat(T, value);
} else if (@canImplicitCast([]const u8, value)) {
const casted_value = ([]const u8)(value);
return self.write(casted_value);
} else if (T == void) {
return self.write("void");
} else {
@compileError("Unable to print type '" ++ @typeName(T) ++ "'");
}
/// Does not flush the buffer.
pub fn print(self: &OutStream, comptime format: []const u8, args: ...) -> %void {
var context = PrintContext {
.self = self,
.result = {},
};
_ = fmt.format(&context, printOutput, format, args);
return context.result;
}
pub fn printInt(self: &OutStream, x: var, base: u8, uppercase: bool, width: usize) -> %void {
if (self.index + max_int_digits >= self.buffer.len) {
%return self.flush();
}
const amt_printed = bufPrintInt(self.buffer[self.index...], x, base, uppercase, width);
self.index += amt_printed;
}
pub fn printAsciiChar(self: &OutStream, c: u8) -> %void {
if (self.index + 1 >= self.buffer.len) {
%return self.flush();
}
self.buffer[self.index] = c;
self.index += 1;
const PrintContext = struct {
self: &OutStream,
result: %void,
};
fn printOutput(context: &PrintContext, bytes: []const u8) -> bool {
context.self.write(bytes) %% |err| {
context.result = err;
return false;
};
return true;
}
pub fn flush(self: &OutStream) -> %void {
@ -506,90 +378,6 @@ pub const InStream = struct {
}
};
pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) -> %T {
var x: T = 0;
for (buf) |c| {
const digit = %return charToDigit(c, radix);
x = %return math.mulOverflow(T, x, radix);
x = %return math.addOverflow(T, x, digit);
}
return x;
}
error InvalidChar;
fn charToDigit(c: u8, radix: u8) -> %u8 {
const value = switch (c) {
'0' ... '9' => c - '0',
'A' ... 'Z' => c - 'A' + 10,
'a' ... 'z' => c - 'a' + 10,
else => return error.InvalidChar,
};
if (value >= radix)
return error.InvalidChar;
return value;
}
fn digitToChar(digit: u8, uppercase: bool) -> u8 {
return switch (digit) {
0 ... 9 => digit + '0',
10 ... 35 => digit + ((if (uppercase) u8('A') else u8('a')) - 10),
else => @unreachable(),
};
}
/// Guaranteed to not use more than max_int_digits
pub fn bufPrintInt(out_buf: []u8, x: var, base: u8, uppercase: bool, width: usize) -> usize {
if (@typeOf(x).is_signed)
bufPrintSigned(out_buf, x, base, uppercase, width)
else
bufPrintUnsigned(out_buf, x, base, uppercase, width)
}
fn bufPrintSigned(out_buf: []u8, x: var, base: u8, uppercase: bool, width: usize) -> usize {
const uint = @intType(false, @typeOf(x).bit_count);
if (x < 0) {
out_buf[0] = '-';
const new_value = uint(-(x + 1)) + 1;
const new_width = if (width == 0) 0 else (width - 1);
return 1 + bufPrintUnsigned(out_buf[1...], new_value, base, uppercase, new_width);
} else if (width == 0) {
return bufPrintUnsigned(out_buf, uint(x), base, uppercase, width);
} else {
out_buf[0] = '+';
const new_value = uint(x);
const new_width = if (width == 0) 0 else (width - 1);
return 1 + bufPrintUnsigned(out_buf[1...], new_value, base, uppercase, new_width);
}
}
fn bufPrintUnsigned(out_buf: []u8, x: var, base: u8, uppercase: bool, width: usize) -> usize {
// max_int_digits accounts for the minus sign. when printing an unsigned
// number we don't need to do that.
var buf: [max_int_digits - 1]u8 = undefined;
var a = x;
var index: usize = buf.len;
while (true) {
const digit = a % base;
index -= 1;
buf[index] = digitToChar(u8(digit), uppercase);
a /= base;
if (a == 0)
break;
}
const src_buf = buf[index...];
const padding = if (width > src_buf.len) (width - src_buf.len) else 0;
mem.set(u8, out_buf[0...padding], '0');
mem.copy(u8, out_buf[padding...], src_buf);
return src_buf.len + padding;
}
pub fn openSelfExe(stream: &InStream) -> %void {
switch (@compileVar("os")) {
Os.linux => {
@ -602,45 +390,3 @@ pub fn openSelfExe(stream: &InStream) -> %void {
else => @compileError("unsupported os"),
}
}
fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, width: usize) -> []u8 {
return buf[0...bufPrintInt(buf, value, base, uppercase, width)];
}
fn testParseU64DigitTooBig() {
@setFnTest(this);
parseUnsigned(u64, "123a", 10) %% |err| {
if (err == error.InvalidChar) return;
@unreachable();
};
@unreachable();
}
fn testParseUnsignedComptime() {
@setFnTest(this);
comptime {
assert(%%parseUnsigned(usize, "2", 10) == 2);
}
}
fn testBufPrintInt() {
@setFnTest(this);
var buffer: [max_int_digits]u8 = undefined;
const buf = buffer[0...];
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, 0), "-101111000110000101001110"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 10, false, 0), "-12345678"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, false, 0), "-bc614e"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, true, 0), "-BC614E"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(12345678), 10, true, 0), "12345678"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(666), 10, false, 6), "000666"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 6), "001234"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 1), "1234"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(42), 10, false, 3), "+42"));
assert(mem.eql(u8, bufPrintIntToSlice(buf, i32(-42), 10, false, 3), "-42"));
}

View File

@ -1,6 +1,6 @@
const assert = @import("std").debug.assert;
const mem = @import("std").mem;
const io = @import("std").io;
const fmt = @import("std").fmt;
const ET = enum {
SINT: i32,
@ -8,8 +8,8 @@ const ET = enum {
pub fn print(a: &const ET, buf: []u8) -> %usize {
return switch (*a) {
ET.SINT => |x| { io.bufPrintInt(buf, x, 10, false, 0) },
ET.UINT => |x| { io.bufPrintInt(buf, x, 10, false, 0) },
ET.SINT => |x| { fmt.formatIntBuf(buf, x, 10, false, 0) },
ET.UINT => |x| { fmt.formatIntBuf(buf, x, 10, false, 0) },
}
}
};