std.io supports printing integers as hex values

remove "unnecessary if statement" error
this "depends on compile variable" code is too hard to validate,
and has false negatives. not worth it right now.

std.str removed, instead use std.mem.

std.mem.eql and std.mem.sliceEql merged and do not require explicit
type argument.
master
Andrew Kelley 2017-02-07 17:19:51 -05:00
parent 92793252ad
commit 8a859afd58
18 changed files with 191 additions and 132 deletions

View File

@ -225,7 +225,6 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/panic.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/rand.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/rand_test.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/sort.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/str.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/test_runner.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/test_runner_libc.zig" DESTINATION "${ZIG_STD_DEST}")
install(FILES "${CMAKE_SOURCE_DIR}/std/test_runner_nolibc.zig" DESTINATION "${ZIG_STD_DEST}")

View File

@ -8536,14 +8536,6 @@ static TypeTableEntry *ir_analyze_instruction_cond_br(IrAnalyze *ira, IrInstruct
if (!ir_resolve_bool(ira, condition, &cond_is_true))
return ir_unreach_error(ira);
if (!cond_br_instruction->base.is_gen && !condition->value.depends_on_compile_var &&
!ir_should_inline(ira->new_irb.exec, cond_br_instruction->base.scope))
{
const char *true_or_false = cond_is_true ? "true" : "false";
ir_add_error(ira, &cond_br_instruction->base,
buf_sprintf("condition is always %s; unnecessary if statement", true_or_false));
}
IrBasicBlock *old_dest_block = cond_is_true ?
cond_br_instruction->then_block : cond_br_instruction->else_block;
@ -9060,7 +9052,7 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
bool ptr_is_const = true;
bool ptr_is_volatile = false;
return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, len_val,
usize, false, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
usize, depends_on_compile_var, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
} else {
ir_add_error_node(ira, source_node,
buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
@ -9084,7 +9076,7 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
bool ptr_is_const = true;
bool ptr_is_volatile = false;
return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, len_val,
usize, false, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
usize, depends_on_compile_var, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
} else {
ir_add_error_node(ira, source_node,
buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),

View File

@ -1,5 +1,4 @@
const io = @import("io.zig");
const str = @import("str.zig");
const math = @import("math.zig");
const mem = @import("mem.zig");
const debug = @import("debug.zig");
@ -95,7 +94,7 @@ pub const Elf = struct {
var magic: [4]u8 = undefined;
%return elf.in_stream.readNoEof(magic);
if (!str.eql(magic, "\x7fELF")) return error.InvalidFormat;
if (!mem.eql(magic, "\x7fELF")) return error.InvalidFormat;
elf.is_64 = switch (%return elf.in_stream.readByte()) {
1 => false,

View File

@ -2,7 +2,6 @@ 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 str = @import("str.zig");
pub const cstr = @import("cstr.zig");
pub const sort = @import("sort.zig");
pub const net = @import("net.zig");

View File

@ -61,8 +61,8 @@ error Unseekable;
error Eof;
const buffer_size = 4 * 1024;
const max_u64_base10_digits = 20;
const max_f64_digits = 65;
const max_int_digits = 65;
pub const OpenRead = 0b0001;
pub const OpenWrite = 0b0010;
@ -100,6 +100,7 @@ pub const OutStream = struct {
Start,
OpenBrace,
CloseBrace,
Hex: bool,
};
/// Calls print and then flushes the buffer.
@ -131,6 +132,12 @@ pub const OutStream = struct {
state = State.Start;
start_index = i + 1;
},
'x' => {
state = State.Hex { false };
},
'X' => {
state = State.Hex { true };
},
else => @compileError("Unknown format character: " ++ c),
},
State.CloseBrace => switch (c) {
@ -140,14 +147,25 @@ pub const OutStream = struct {
},
else => @compileError("Single '}' encountered in format string"),
},
State.Hex => |uppercase| switch (c) {
'}' => {
self.printInt(args[next_arg], 16, uppercase);
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
else => @compileError("Expected '}' after 'x'/'X' in format string"),
},
}
}
comptime {
if (args.len != next_arg) {
@compileError("Unused arguments");
}
if (state != State.Start) {
@compileError("Incomplete format string: " ++ format);
// TODO https://github.com/andrewrk/zig/issues/253
switch (state) {
State.Start => {},
else => @compileError("Incomplete format string: " ++ format),
}
}
if (start_index < format.len) {
@ -159,7 +177,7 @@ pub const OutStream = struct {
pub fn printValue(self: &OutStream, value: var) -> %void {
const T = @typeOf(value);
if (@isInteger(T)) {
return self.printInt(T, value);
return self.printInt(value, 10, false);
} else if (@isFloat(T)) {
return self.printFloat(T, value);
} else if (@canImplicitCast([]const u8, value)) {
@ -172,12 +190,11 @@ pub const OutStream = struct {
}
}
pub fn printInt(self: &OutStream, comptime T: type, x: T) -> %void {
// TODO replace max_u64_base10_digits with math.log10(math.pow(2, @sizeOf(T)))
if (self.index + max_u64_base10_digits >= self.buffer.len) {
pub fn printInt(self: &OutStream, x: var, base: u8, uppercase: bool) -> %void {
if (self.index + max_int_digits >= self.buffer.len) {
%return self.flush();
}
const amt_printed = bufPrintInt(T, self.buffer[self.index...], x);
const amt_printed = bufPrintInt(self.buffer[self.index...], x, base, uppercase);
self.index += amt_printed;
}
@ -448,39 +465,51 @@ fn charToDigit(c: u8, radix: u8) -> %u8 {
return value;
}
pub fn bufPrintInt(comptime T: type, out_buf: []u8, x: T) -> usize {
if (T.is_signed) bufPrintSigned(T, out_buf, x) else bufPrintUnsigned(T, out_buf, x)
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 bufPrintSigned(comptime T: type, out_buf: []u8, x: T) -> usize {
const uint = @intType(false, T.bit_count);
/// Guaranteed to not use more than max_int_digits
pub fn bufPrintInt(out_buf: []u8, x: var, base: u8, uppercase: bool) -> usize {
if (@typeOf(x).is_signed)
bufPrintSigned(out_buf, x, base, uppercase)
else
bufPrintUnsigned(out_buf, x, base, uppercase)
}
fn bufPrintSigned(out_buf: []u8, x: var, base: u8, uppercase: bool) -> usize {
const uint = @intType(false, @typeOf(x).bit_count);
if (x < 0) {
out_buf[0] = '-';
return 1 + bufPrintUnsigned(uint, out_buf[1...], uint(-(x + 1)) + 1);
return 1 + bufPrintUnsigned(out_buf[1...], uint(-(x + 1)) + 1, base, uppercase);
} else {
return bufPrintUnsigned(uint, out_buf, uint(x));
return bufPrintUnsigned(out_buf, uint(x), base, uppercase);
}
}
fn bufPrintUnsigned(comptime T: type, out_buf: []u8, x: T) -> usize {
var buf: [max_u64_base10_digits]u8 = undefined;
fn bufPrintUnsigned(out_buf: []u8, x: var, base: u8, uppercase: bool) -> 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 % 10;
const digit = a % base;
index -= 1;
buf[index] = '0' + u8(digit);
a /= 10;
buf[index] = digitToChar(u8(digit), uppercase);
a /= base;
if (a == 0)
break;
}
const len = buf.len - index;
@memcpy(&out_buf[0], &buf[index], len);
return len;
const src_buf = buf[index...];
mem.copy(u8, out_buf, src_buf);
return src_buf.len;
}
fn parseU64DigitTooBig() {
@ -505,3 +534,19 @@ pub fn openSelfExe(stream: &InStream) -> %void {
else => @compileError("unsupported os"),
}
}
fn bufPrintIntToSlice(buf: []u8, x: var, base: u8, uppercase: bool) -> []u8 {
return buf[0...bufPrintInt(buf, x, base, uppercase)];
}
fn testBufPrintInt() {
@setFnTest(this);
var buf: [max_int_digits]u8 = undefined;
assert(mem.eql(bufPrintIntToSlice(buf, i32(-12345678), 2, false), "-101111000110000101001110"));
assert(mem.eql(bufPrintIntToSlice(buf, i32(-12345678), 10, false), "-12345678"));
assert(mem.eql(bufPrintIntToSlice(buf, i32(-12345678), 16, false), "-bc614e"));
assert(mem.eql(bufPrintIntToSlice(buf, i32(-12345678), 16, true), "-BC614E"));
assert(mem.eql(bufPrintIntToSlice(buf, u32(12345678), 10, true), "12345678"));
}

View File

@ -29,3 +29,40 @@ pub fn shlOverflow(comptime T: type, a: T, b: T) -> %T {
var answer: T = undefined;
if (@shlWithOverflow(T, a, b, &answer)) error.Overflow else answer
}
pub fn log(comptime base: usize, value: var) -> @typeOf(value) {
const T = @typeOf(value);
if (@isInteger(T)) {
if (base == 2) {
return T.bit_count - 1 - @clz(value);
} else {
@compileError("TODO implement log for non base 2 integers");
}
} else if (@isFloat(T)) {
@compileError("TODO implement log for floats");
} else {
@compileError("log expects integer or float, found '" ++ @typeName(T) ++ "'");
}
}
/// x must be an integer or a float
/// Note that this causes undefined behavior if
/// @typeOf(x).is_signed && x == @minValue(@typeOf(x)).
pub fn abs(x: var) -> @typeOf(x) {
const T = @typeOf(x);
if (@isInteger(T)) {
return if (x < 0) -x else x;
} else if (@isFloat(T)) {
@compileError("TODO implement abs for floats");
} else {
@unreachable();
}
}
fn getReturnTypeForAbs(comptime T: type) -> type {
if (@isInteger(T)) {
return @intType(false, T.bit_count);
} else {
return T;
}
}

View File

@ -43,6 +43,9 @@ pub const Allocator = struct {
/// Copy all of source into dest at position 0.
/// dest.len must be >= source.len.
pub fn copy(comptime T: type, dest: []T, source: []const T) {
// TODO instead of manually doing this check for the whole array
// and turning off debug safety, the compiler should detect loops like
// this and automatically omit safety checks for loops
@setDebugSafety(this, false);
assert(dest.len >= source.len);
for (source) |s, i| dest[i] = s;
@ -82,6 +85,23 @@ pub fn sliceAsInt(buf: []u8, is_be: bool, comptime T: type) -> T {
return result;
}
/// Compares two slices and returns whether they are equal.
pub fn eql(a: var, b: var) -> bool {
if (a.len != b.len) return false;
for (a) |item, index| {
if (b[index] != item) return false;
}
return true;
}
fn testStringEquality() {
@setFnTest(this);
assert(eql("abcd", "abcd"));
assert(!eql("abcdef", "abZdef"));
assert(!eql("abcdefg", "abcdef"));
}
fn testSliceAsInt() {
@setFnTest(this);
{

View File

@ -1,5 +1,4 @@
const assert = @import("debug.zig").assert;
const str = @import("str.zig");
const mem = @import("mem.zig");
const math = @import("math.zig");
@ -76,7 +75,7 @@ fn testSort() {
const slice = buf[0...case[0].len];
mem.copy(u8, slice, case[0]);
sort(u8, slice, u8asc);
assert(str.eql(slice, case[1]));
assert(mem.eql(slice, case[1]));
}
const i32cases = [][][]i32 {
@ -93,7 +92,7 @@ fn testSort() {
const slice = buf[0...case[0].len];
mem.copy(i32, slice, case[0]);
sort(i32, slice, i32asc);
assert(str.sliceEql(i32, slice, case[1]));
assert(mem.eql(slice, case[1]));
}
}
@ -114,6 +113,6 @@ fn testSortDesc() {
const slice = buf[0...case[0].len];
mem.copy(i32, slice, case[0]);
sort(i32, slice, i32desc);
assert(str.sliceEql(i32, slice, case[1]));
assert(mem.eql(slice, case[1]));
}
}

View File

@ -1,21 +0,0 @@
const assert = @import("debug.zig").assert;
pub fn eql(a: []const u8, b: []const u8) -> bool {
sliceEql(u8, a, b)
}
pub fn sliceEql(comptime T: type, a: []const T, b: []const T) -> bool {
if (a.len != b.len) return false;
for (a) |item, index| {
if (b[index] != item) return false;
}
return true;
}
fn testStringEquality() {
@setFnTest(this);
assert(eql("abcd", "abcd"));
assert(!eql("abcdef", "abZdef"));
assert(!eql("abcdefg", "abcdef"));
}

View File

@ -1,5 +1,5 @@
const assert = @import("std").debug.assert;
const str = @import("std").str;
const mem = @import("std").mem;
fn arrays() {
@setFnTest(this);
@ -63,10 +63,10 @@ fn nestedArrays() {
const array_of_strings = [][]u8 {"hello", "this", "is", "my", "thing"};
for (array_of_strings) |s, i| {
if (i == 0) assert(str.eql(s, "hello"));
if (i == 1) assert(str.eql(s, "this"));
if (i == 2) assert(str.eql(s, "is"));
if (i == 3) assert(str.eql(s, "my"));
if (i == 4) assert(str.eql(s, "thing"));
if (i == 0) assert(mem.eql(s, "hello"));
if (i == 1) assert(mem.eql(s, "this"));
if (i == 2) assert(mem.eql(s, "is"));
if (i == 3) assert(mem.eql(s, "my"));
if (i == 4) assert(mem.eql(s, "thing"));
}
}

View File

@ -1,5 +1,5 @@
const assert = @import("std").debug.assert;
const str = @import("std").str;
const mem = @import("std").mem;
const io = @import("std").io;
const ET = enum {
@ -8,8 +8,8 @@ const ET = enum {
pub fn print(a: &const ET, buf: []u8) -> %usize {
return switch (*a) {
ET.SINT => |x| { io.bufPrintInt(i32, buf, x) },
ET.UINT => |x| { io.bufPrintInt(u32, buf, x) },
ET.SINT => |x| { io.bufPrintInt(buf, x, 10, false) },
ET.UINT => |x| { io.bufPrintInt(buf, x, 10, false) },
}
}
};
@ -22,8 +22,8 @@ fn enumWithMembers() {
var buf: [20]u8 = undefined;
assert(%%a.print(buf) == 3);
assert(str.eql(buf[0...3], "-42"));
assert(mem.eql(buf[0...3], "-42"));
assert(%%b.print(buf) == 2);
assert(str.eql(buf[0...2], "42"));
assert(mem.eql(buf[0...2], "42"));
}

View File

@ -1,5 +1,5 @@
const assert = @import("std").debug.assert;
const str = @import("std").str;
const mem = @import("std").mem;
pub fn foo() -> %i32 {
const x = %return bar();
@ -28,8 +28,8 @@ fn gimmeItBroke() -> []const u8 {
fn errorName() {
@setFnTest(this);
assert(str.eql(@errorName(error.AnError), "AnError"));
assert(str.eql(@errorName(error.ALongerErrorName), "ALongerErrorName"));
assert(mem.eql(@errorName(error.AnError), "AnError"));
assert(mem.eql(@errorName(error.ALongerErrorName), "ALongerErrorName"));
}
error AnError;
error ALongerErrorName;

View File

@ -1,5 +1,4 @@
const assert = @import("std").debug.assert;
const str = @import("std").str;
fn compileTimeRecursion() {
@setFnTest(this);

View File

@ -1,6 +1,6 @@
const std = @import("std");
const assert = std.debug.assert;
const str = std.str;
const mem = std.mem;
fn continueInForLoop() {
@setFnTest(this);
@ -24,7 +24,7 @@ fn forLoopWithPointerElemVar() {
var target: [source.len]u8 = undefined;
@memcpy(&target[0], &source[0], source.len);
mangleString(target);
assert(str.eql(target, "bcdefgh"));
assert(mem.eql(target, "bcdefgh"));
}
fn mangleString(s: []u8) {
for (s) |*c| {

View File

@ -1,5 +1,5 @@
const assert = @import("std").debug.assert;
const str = @import("std").str;
const mem = @import("std").mem;
const cstr = @import("std").cstr;
// normal comment
@ -144,7 +144,7 @@ fn first4KeysOfHomeRow() -> []const u8 {
fn ReturnStringFromFunction() {
@setFnTest(this);
assert(str.eql(first4KeysOfHomeRow(), "aoeu"));
assert(mem.eql(first4KeysOfHomeRow(), "aoeu"));
}
const g1 : i32 = 1233 + 1;
@ -210,31 +210,31 @@ fn emptyFn() {}
fn hexEscape() {
@setFnTest(this);
assert(str.eql("\x68\x65\x6c\x6c\x6f", "hello"));
assert(mem.eql("\x68\x65\x6c\x6c\x6f", "hello"));
}
fn stringConcatenation() {
@setFnTest(this);
assert(str.eql("OK" ++ " IT " ++ "WORKED", "OK IT WORKED"));
assert(mem.eql("OK" ++ " IT " ++ "WORKED", "OK IT WORKED"));
}
fn arrayMultOperator() {
@setFnTest(this);
assert(str.eql("ab" ** 5, "ababababab"));
assert(mem.eql("ab" ** 5, "ababababab"));
}
fn stringEscapes() {
@setFnTest(this);
assert(str.eql("\"", "\x22"));
assert(str.eql("\'", "\x27"));
assert(str.eql("\n", "\x0a"));
assert(str.eql("\r", "\x0d"));
assert(str.eql("\t", "\x09"));
assert(str.eql("\\", "\x5c"));
assert(str.eql("\u1234\u0069", "\xe1\x88\xb4\x69"));
assert(mem.eql("\"", "\x22"));
assert(mem.eql("\'", "\x27"));
assert(mem.eql("\n", "\x0a"));
assert(mem.eql("\r", "\x0d"));
assert(mem.eql("\t", "\x09"));
assert(mem.eql("\\", "\x5c"));
assert(mem.eql("\u1234\u0069", "\xe1\x88\xb4\x69"));
}
fn multilineString() {
@ -246,7 +246,7 @@ fn multilineString() {
\\three
;
const s2 = "one\ntwo)\nthree";
assert(str.eql(s1, s2));
assert(mem.eql(s1, s2));
}
fn multilineCString() {
@ -295,7 +295,7 @@ const some_mem : [100]u8 = undefined;
fn memAlloc(comptime T: type, n: usize) -> %[]T {
return (&T)(&some_mem[0])[0...n];
}
fn memFree(comptime T: type, mem: []T) { }
fn memFree(comptime T: type, memory: []T) { }
fn castUndefined() {
@ -344,8 +344,8 @@ fn pointerDereferencing() {
fn callResultOfIfElseExpression() {
@setFnTest(this);
assert(str.eql(f2(true), "a"));
assert(str.eql(f2(false), "b"));
assert(mem.eql(f2(true), "a"));
assert(mem.eql(f2(false), "b"));
}
fn f2(x: bool) -> []u8 {
return (if (x) fA else fB)();
@ -562,8 +562,8 @@ fn typeName() {
@setFnTest(this);
comptime {
assert(str.eql(@typeName(i64), "i64"));
assert(str.eql(@typeName(&usize), "&usize"));
assert(mem.eql(@typeName(i64), "i64"));
assert(mem.eql(@typeName(&usize), "&usize"));
}
}

20
test/cases/void.zig Normal file
View File

@ -0,0 +1,20 @@
const assert = @import("std").debug.assert;
const Foo = struct {
a: void,
b: i32,
c: void,
};
fn compareVoidWithVoidCompileTimeKnown() {
@setFnTest(this);
comptime {
const foo = Foo {
.a = {},
.b = 1,
.c = {},
};
assert(foo.a == {});
}
}

View File

@ -471,21 +471,17 @@ const io = @import("std").io;
pub fn main(args: [][]u8) -> %void {
const array = []u8 {9, 8, 7, 6};
for (array) |item| {
%%io.stdout.printInt(@typeOf(item), item);
%%io.stdout.printf("\n");
%%io.stdout.printf("{}\n", item);
}
for (array) |item, index| {
%%io.stdout.printInt(@typeOf(index), index);
%%io.stdout.printf("\n");
%%io.stdout.printf("{}\n", index);
}
const unknown_size: []u8 = array;
for (unknown_size) |item| {
%%io.stdout.printInt(@typeOf(item), item);
%%io.stdout.printf("\n");
%%io.stdout.printf("{}\n", item);
}
for (unknown_size) |item, index| {
%%io.stdout.printInt(@typeOf(index), index);
%%io.stdout.printf("\n");
%%io.stdout.printf("{}\n", index);
}
}
)SOURCE", "9\n8\n7\n6\n0\n1\n2\n3\n9\n8\n7\n6\n0\n1\n2\n3\n");
@ -1124,13 +1120,6 @@ fn get() -> usize { global_var }
".tmp_source.zig:3:8: note: called from here");
add_compile_fail_case("unnecessary if statement", R"SOURCE(
fn f() {
if (true) { }
}
)SOURCE", 1, ".tmp_source.zig:3:9: error: condition is always true; unnecessary if statement");
add_compile_fail_case("addition with non numbers", R"SOURCE(
const Foo = struct {
field: i32,
@ -1588,25 +1577,6 @@ fn derp() {
}
)SOURCE", 1, ".tmp_source.zig:7:13: error: cannot assign to constant");
add_compile_fail_case("compare void with void is compile time known", R"SOURCE(
const Foo = struct {
a: void,
b: i32,
c: void,
};
fn f() {
const foo = Foo {
.a = {},
.b = 1,
.c = {},
};
if (foo.a != {}) {
@unreachable();
}
}
)SOURCE", 1, ".tmp_source.zig:14:15: error: condition is always false; unnecessary if statement");
add_compile_fail_case("return from defer expression", R"SOURCE(
pub fn testTrickyDefer() -> %void {
defer canFail() %% {};

View File

@ -32,4 +32,5 @@ const test_try = @import("cases/try.zig");
const test_typedef = @import("cases/typedef.zig");
const test_undefined = @import("cases/undefined.zig");
const test_var_args = @import("cases/var_args.zig");
const test_void = @import("cases/void.zig");
const test_while = @import("cases/while.zig");