From d8ba1bc12054712dec731db0c4062a5df0d627c6 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Tue, 17 Apr 2018 22:58:10 +1200 Subject: [PATCH 01/58] Improve fmt float-printing - Fix errors printing very small numbers - Add explicit scientific output mode - Add rounding based on a specific precision for both decimal/exp modes. - Test and confirm exp/decimal against libc for all f32 values. Various changes to better match libc. --- std/fmt/errol/index.zig | 76 ++++++- std/fmt/index.zig | 433 +++++++++++++++++++++++++++++++++++----- 2 files changed, 453 insertions(+), 56 deletions(-) diff --git a/std/fmt/errol/index.zig b/std/fmt/errol/index.zig index 42287bd25..8204a6f0f 100644 --- a/std/fmt/errol/index.zig +++ b/std/fmt/errol/index.zig @@ -12,13 +12,79 @@ pub const FloatDecimal = struct { exp: i32, }; +pub const RoundMode = enum { + // Round only the fractional portion (e.g. 1234.23 has precision 2) + Decimal, + // Round the entire whole/fractional portion (e.g. 1.23423e3 has precision 5) + Scientific, +}; + +/// Round a FloatDecimal as returned by errol3 to the specified fractional precision. +/// All digits after the specified precision should be considered invalid. +pub fn roundToPrecision(float_decimal: &FloatDecimal, precision: usize, mode: RoundMode) void { + // The round digit refers to the index which we should look at to determine + // whether we need to round to match the specified precision. + var round_digit: usize = 0; + + switch (mode) { + RoundMode.Decimal => { + if (float_decimal.exp >= 0) { + round_digit = precision + usize(float_decimal.exp); + } else { + // if a small negative exp, then adjust we need to offset by the number + // of leading zeros that will occur. + const min_exp_required = usize(-float_decimal.exp); + if (precision > min_exp_required) { + round_digit = precision - min_exp_required; + } + } + }, + RoundMode.Scientific => { + round_digit = 1 + precision; + }, + } + + // It suffices to look at just this digit. We don't round and propagate say 0.04999 to 0.05 + // first, and then to 0.1 in the case of a {.1} single precision. + + // Find the digit which will signify the round point and start rounding backwards. + if (round_digit < float_decimal.digits.len and float_decimal.digits[round_digit] - '0' >= 5) { + assert(round_digit >= 0); + + var i = round_digit; + while (true) { + if (i == 0) { + // Rounded all the way past the start. This was of the form 9.999... + // Slot the new digit in place and increase the exponent. + float_decimal.exp += 1; + + // Re-size the buffer to use the reserved leading byte. + const one_before = @intToPtr(&u8, @ptrToInt(&float_decimal.digits[0]) - 1); + float_decimal.digits = one_before[0..float_decimal.digits.len + 1]; + float_decimal.digits[0] = '1'; + return; + } + + i -= 1; + + const new_value = (float_decimal.digits[i] - '0' + 1) % 10; + float_decimal.digits[i] = new_value + '0'; + + // must continue rounding until non-9 + if (new_value != 0) { + return; + } + } + } +} + /// Corrected Errol3 double to ASCII conversion. pub fn errol3(value: f64, buffer: []u8) FloatDecimal { const bits = @bitCast(u64, value); const i = tableLowerBound(bits); if (i < enum3.len and enum3[i] == bits) { const data = enum3_data[i]; - const digits = buffer[0..data.str.len]; + const digits = buffer[1..data.str.len + 1]; mem.copy(u8, digits, data.str); return FloatDecimal { .digits = digits, @@ -98,7 +164,11 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal { } // digit generation - var buf_index: usize = 0; + + // We generate digits starting at index 1. If rounding a buffer later then it may be + // required to generate a preceeding digit in some cases (9.999) in which case we use + // the 0-index for this extra digit. + var buf_index: usize = 1; while (true) { var hdig = u8(math.floor(high.val)); if ((high.val == f64(hdig)) and (high.off < 0)) @@ -128,7 +198,7 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal { buf_index += 1; return FloatDecimal { - .digits = buffer[0..buf_index], + .digits = buffer[1..buf_index], .exp = exp, }; } diff --git a/std/fmt/index.zig b/std/fmt/index.zig index 7bb982911..5d749bb4b 100644 --- a/std/fmt/index.zig +++ b/std/fmt/index.zig @@ -4,7 +4,7 @@ const debug = std.debug; const assert = debug.assert; const mem = std.mem; const builtin = @import("builtin"); -const errol3 = @import("errol/index.zig").errol3; +const errol = @import("errol/index.zig"); const max_int_digits = 65; @@ -22,6 +22,8 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context), IntegerWidth, Float, FloatWidth, + FloatScientific, + FloatScientificWidth, Character, Buf, BufWidth, @@ -87,6 +89,9 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context), 's' => { state = State.Buf; }, + 'e' => { + state = State.FloatScientific; + }, '.' => { state = State.Float; }, @@ -133,9 +138,33 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context), '0' ... '9' => {}, else => @compileError("Unexpected character in format string: " ++ []u8{c}), }, + State.FloatScientific => switch (c) { + '}' => { + try formatFloatScientific(args[next_arg], null, context, Errors, output); + next_arg += 1; + state = State.Start; + start_index = i + 1; + }, + '0' ... '9' => { + width_start = i; + state = State.FloatScientificWidth; + }, + else => @compileError("Unexpected character in format string: " ++ []u8{c}), + }, + State.FloatScientificWidth => switch (c) { + '}' => { + width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable); + try formatFloatScientific(args[next_arg], width, context, Errors, output); + next_arg += 1; + state = State.Start; + start_index = i + 1; + }, + '0' ... '9' => {}, + else => @compileError("Unexpected character in format string: " ++ []u8{c}), + }, State.Float => switch (c) { '}' => { - try formatFloatDecimal(args[next_arg], 0, context, Errors, output); + try formatFloatDecimal(args[next_arg], null, context, Errors, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -199,7 +228,7 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@ return formatInt(value, 10, false, 0, context, Errors, output); }, builtin.TypeId.Float => { - return formatFloat(value, context, Errors, output); + return formatFloatScientific(value, null, context, Errors, output); }, builtin.TypeId.Void => { return output(context, "void"); @@ -257,81 +286,237 @@ pub fn formatBuf(buf: []const u8, width: usize, } } -pub fn formatFloat(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { +// 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, maybe_precision: ?usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { var x = f64(value); // Errol doesn't handle these special cases. - if (math.isNan(x)) { - return output(context, "NaN"); - } if (math.signbit(x)) { try output(context, "-"); x = -x; } + + if (math.isNan(x)) { + return output(context, "nan"); + } if (math.isPositiveInf(x)) { - return output(context, "Infinity"); + return output(context, "inf"); } if (x == 0.0) { - return output(context, "0.0"); + try output(context, "0"); + + if (maybe_precision) |precision| { + if (precision != 0) { + try output(context, "."); + var i: usize = 0; + while (i < precision) : (i += 1) { + try output(context, "0"); + } + } + } else { + try output(context, ".0"); + } + + try output(context, "e+00"); + return; } var buffer: [32]u8 = undefined; - const float_decimal = errol3(x, buffer[0..]); - try output(context, float_decimal.digits[0..1]); - try output(context, "."); - if (float_decimal.digits.len > 1) { - const num_digits = if (@typeOf(value) == f32) - math.min(usize(9), float_decimal.digits.len) - else - float_decimal.digits.len; - try output(context, float_decimal.digits[1 .. num_digits]); + var float_decimal = errol.errol3(x, buffer[0..]); + + if (maybe_precision) |precision| { + errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); + + try output(context, float_decimal.digits[0..1]); + + // {e0} case prints no `.` + if (precision != 0) { + try output(context, "."); + + var printed: usize = 0; + if (float_decimal.digits.len > 1) { + const num_digits = math.min(float_decimal.digits.len, precision + 1); + try output(context, float_decimal.digits[1 .. num_digits]); + printed += num_digits - 1; + } + + while (printed < precision) : (printed += 1) { + try output(context, "0"); + } + } } else { - try output(context, "0"); + try output(context, float_decimal.digits[0..1]); + try output(context, "."); + if (float_decimal.digits.len > 1) { + const num_digits = if (@typeOf(value) == f32) + math.min(usize(9), float_decimal.digits.len) + else + float_decimal.digits.len; + + try output(context, float_decimal.digits[1 .. num_digits]); + } else { + try output(context, "0"); + } } - if (float_decimal.exp != 1) { - try output(context, "e"); - try formatInt(float_decimal.exp - 1, 10, false, 0, context, Errors, output); + try output(context, "e"); + const exp = float_decimal.exp - 1; + + if (exp >= 0) { + try output(context, "+"); + if (exp > -10 and exp < 10) { + try output(context, "0"); + } + try formatInt(exp, 10, false, 0, context, Errors, output); + } else { + try output(context, "-"); + if (exp > -10 and exp < 10) { + try output(context, "0"); + } + try formatInt(-exp, 10, false, 0, context, Errors, output); } } -pub fn formatFloatDecimal(value: var, precision: usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { +// 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, maybe_precision: ?usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void { var x = f64(value); // Errol doesn't handle these special cases. - if (math.isNan(x)) { - return output(context, "NaN"); - } if (math.signbit(x)) { try output(context, "-"); x = -x; } + + if (math.isNan(x)) { + return output(context, "nan"); + } if (math.isPositiveInf(x)) { - return output(context, "Infinity"); + return output(context, "inf"); } if (x == 0.0) { - return output(context, "0.0"); + try output(context, "0"); + + if (maybe_precision) |precision| { + if (precision != 0) { + try output(context, "."); + var i: usize = 0; + while (i < precision) : (i += 1) { + try output(context, "0"); + } + } else { + try output(context, ".0"); + } + } else { + try output(context, "0"); + } + + return; } + // non-special case, use errol3 var buffer: [32]u8 = undefined; - const float_decimal = errol3(x, buffer[0..]); + var float_decimal = errol.errol3(x, buffer[0..]); - const num_left_digits = if (float_decimal.exp > 0) usize(float_decimal.exp) else 1; + if (maybe_precision) |precision| { + errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal); - try output(context, float_decimal.digits[0 .. num_left_digits]); - try output(context, "."); - if (float_decimal.digits.len > 1) { - const num_valid_digtis = if (@typeOf(value) == f32) math.min(usize(7), float_decimal.digits.len) - else - float_decimal.digits.len; + // exp < 0 means the leading is always 0 as errol result is normalized. + var num_digits_whole = if (float_decimal.exp > 0) usize(float_decimal.exp) else 0; - const num_right_digits = if (precision != 0) - math.min(precision, (num_valid_digtis-num_left_digits)) - else - num_valid_digtis - num_left_digits; - try output(context, float_decimal.digits[num_left_digits .. (num_left_digits + num_right_digits)]); + // 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. + try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]); + + var i = num_digits_whole_no_pad; + while (i < num_digits_whole) : (i += 1) { + try output(context, "0"); + } + } else { + try output(context , "0"); + } + + // {.0} special case doesn't want a trailing '.' + if (precision == 0) { + return; + } + + try output(context, "."); + + // 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 = 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) { + try output(context, "0"); + printed += 1; + } + + if (printed >= precision) { + return; + } + } + + // Remaining fractional portion, zero-padding if insufficient. + debug.assert(precision >= printed); + if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) { + try output(context, float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); + return; + } else { + try output(context, float_decimal.digits[num_digits_whole_no_pad ..]); + printed += float_decimal.digits.len - num_digits_whole_no_pad; + + while (printed < precision) : (printed += 1) { + try output(context, "0"); + } + } } else { - try output(context, "0"); + // exp < 0 means the leading is always 0 as errol result is normalized. + var num_digits_whole = if (float_decimal.exp > 0) 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. + try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]); + + var i = num_digits_whole_no_pad; + while (i < num_digits_whole) : (i += 1) { + try output(context, "0"); + } + } else { + try output(context , "0"); + } + + // Omit `.` if no fractional portion + if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) { + return; + } + + try output(context, "."); + + // Zero-fill until we reach significant digits or run out of precision. + if (float_decimal.exp < 0) { + const zero_digit_count = usize(-float_decimal.exp); + + var i: usize = 0; + while (i < zero_digit_count) : (i += 1) { + try output(context, "0"); + } + } + + try output(context, float_decimal.digits[num_digits_whole_no_pad ..]); } } @@ -598,32 +783,81 @@ test "fmt.format" { // TODO get these tests passing in release modes // https://github.com/zig-lang/zig/issues/564 if (builtin.mode == builtin.Mode.Debug) { + { + var buf1: [32]u8 = undefined; + const value: f32 = 1.34; + const result = try bufPrint(buf1[0..], "f32: {e}\n", value); + assert(mem.eql(u8, result, "f32: 1.34000003e+00\n")); + } { var buf1: [32]u8 = undefined; const value: f32 = 12.34; - const result = try bufPrint(buf1[0..], "f32: {}\n", value); - assert(mem.eql(u8, result, "f32: 1.23400001e1\n")); + const result = try bufPrint(buf1[0..], "f32: {e}\n", value); + assert(mem.eql(u8, result, "f32: 1.23400001e+01\n")); } { var buf1: [32]u8 = undefined; const value: f64 = -12.34e10; - const result = try bufPrint(buf1[0..], "f64: {}\n", value); - assert(mem.eql(u8, result, "f64: -1.234e11\n")); + const result = try bufPrint(buf1[0..], "f64: {e}\n", value); + assert(mem.eql(u8, result, "f64: -1.234e+11\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 9.999960e-40; + const result = try bufPrint(buf1[0..], "f64: {e}\n", value); + assert(mem.eql(u8, result, "f64: 9.99996e-40\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 1.409706e-42; + const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); + assert(mem.eql(u8, result, "f64: 1.40971e-42\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = @bitCast(f32, u32(814313563)); + const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); + assert(mem.eql(u8, result, "f64: 1.00000e-09\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = @bitCast(f32, u32(1006632960)); + const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); + assert(mem.eql(u8, result, "f64: 7.81250e-03\n")); + } + { + // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. + // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. + var buf1: [32]u8 = undefined; + const value: f64 = @bitCast(f32, u32(1203982400)); + const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); + assert(mem.eql(u8, result, "f64: 1.00001e+05\n")); } { var buf1: [32]u8 = undefined; const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64); - assert(mem.eql(u8, result, "f64: NaN\n")); + assert(mem.eql(u8, result, "f64: nan\n")); + } + { + var buf1: [32]u8 = undefined; + const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64); + assert(mem.eql(u8, result, "f64: -nan\n")); } { var buf1: [32]u8 = undefined; const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64); - assert(mem.eql(u8, result, "f64: Infinity\n")); + assert(mem.eql(u8, result, "f64: inf\n")); } { var buf1: [32]u8 = undefined; const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64); - assert(mem.eql(u8, result, "f64: -Infinity\n")); + assert(mem.eql(u8, result, "f64: -inf\n")); + } + { + var buf1: [64]u8 = undefined; + const value: f64 = 1.52314e+29; + const result = try bufPrint(buf1[0..], "f64: {.}\n", value); + assert(mem.eql(u8, result, "f64: 152314000000000000000000000000\n")); } { var buf1: [32]u8 = undefined; @@ -635,20 +869,20 @@ test "fmt.format" { var buf1: [32]u8 = undefined; const value: f32 = 1234.567; const result = try bufPrint(buf1[0..], "f32: {.2}\n", value); - assert(mem.eql(u8, result, "f32: 1234.56\n")); + assert(mem.eql(u8, result, "f32: 1234.57\n")); } { var buf1: [32]u8 = undefined; const value: f32 = -11.1234; const result = try bufPrint(buf1[0..], "f32: {.4}\n", value); // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). - // -11.12339... is truncated to -11.1233 - assert(mem.eql(u8, result, "f32: -11.1233\n")); + // -11.12339... is rounded back up to -11.1234 + assert(mem.eql(u8, result, "f32: -11.1234\n")); } { var buf1: [32]u8 = undefined; const value: f32 = 91.12345; - const result = try bufPrint(buf1[0..], "f32: {.}\n", value); + const result = try bufPrint(buf1[0..], "f32: {.5}\n", value); assert(mem.eql(u8, result, "f32: 91.12345\n")); } { @@ -657,7 +891,100 @@ test "fmt.format" { const result = try bufPrint(buf1[0..], "f64: {.10}\n", value); assert(mem.eql(u8, result, "f64: 91.1234567890\n")); } + { + var buf1: [32]u8 = undefined; + const value: f64 = 0.0; + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 5.700; + const result = try bufPrint(buf1[0..], "f64: {.0}\n", value); + assert(mem.eql(u8, result, "f64: 6\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 9.999; + const result = try bufPrint(buf1[0..], "f64: {.1}\n", value); + assert(mem.eql(u8, result, "f64: 10.0\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 1.0; + const result = try bufPrint(buf1[0..], "f64: {.3}\n", value); + assert(mem.eql(u8, result, "f64: 1.000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 0.0003; + const result = try bufPrint(buf1[0..], "f64: {.8}\n", value); + assert(mem.eql(u8, result, "f64: 0.00030000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 1.40130e-45; + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 9.999960e-40; + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00000\n")); + } + // libc checks + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(916964781))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00001\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(925353389))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00001\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(1036831278))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.10000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(1065353133))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 1.00000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(1092616192))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 10.00000\n")); + } + // libc differences + { + var buf1: [32]u8 = undefined; + // This is 0.015625 exactly according to gdb. We thus round down, + // however glibc rounds up for some reason. This occurs for all + // floats of the form x.yyyy25 on a precision point. + const value: f64 = f64(@bitCast(f32, u32(1015021568))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.01563\n")); + } + // std-windows-x86_64-Debug-bare test case fails + { + // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 + // also rounds to 630 so I'm inclined to believe libc is not + // optimal here. + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(1518338049))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 18014400656965630.00000\n")); + } } } From e5175d432ef01e078ef247ea0a781243219ddfb6 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Mon, 23 Apr 2018 17:18:05 +1200 Subject: [PATCH 02/58] Fix release float printing errors Fixes #564. Fixes #669. Fixes #928. --- std/fmt/errol/index.zig | 3 + std/fmt/index.zig | 400 ++++++++++++++++++++-------------------- 2 files changed, 202 insertions(+), 201 deletions(-) diff --git a/std/fmt/errol/index.zig b/std/fmt/errol/index.zig index 8204a6f0f..00c69cd29 100644 --- a/std/fmt/errol/index.zig +++ b/std/fmt/errol/index.zig @@ -259,6 +259,9 @@ fn gethi(in: f64) f64 { /// Normalize the number by factoring in the error. /// @hp: The float pair. fn hpNormalize(hp: &HP) void { + // Required to avoid segfaults causing buffer overrun during errol3 digit output termination. + @setFloatMode(this, @import("builtin").FloatMode.Strict); + const val = hp.val; hp.val += hp.off; diff --git a/std/fmt/index.zig b/std/fmt/index.zig index 5d749bb4b..43e758038 100644 --- a/std/fmt/index.zig +++ b/std/fmt/index.zig @@ -779,212 +779,210 @@ test "fmt.format" { const result = try bufPrint(buf1[0..], "pointer: {}\n", &value); assert(mem.startsWith(u8, result, "pointer: Struct@")); } - - // TODO get these tests passing in release modes - // https://github.com/zig-lang/zig/issues/564 - if (builtin.mode == builtin.Mode.Debug) { - { - var buf1: [32]u8 = undefined; - const value: f32 = 1.34; - const result = try bufPrint(buf1[0..], "f32: {e}\n", value); - assert(mem.eql(u8, result, "f32: 1.34000003e+00\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 12.34; - const result = try bufPrint(buf1[0..], "f32: {e}\n", value); - assert(mem.eql(u8, result, "f32: 1.23400001e+01\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = -12.34e10; - const result = try bufPrint(buf1[0..], "f64: {e}\n", value); - assert(mem.eql(u8, result, "f64: -1.234e+11\n")); - } - { + { + var buf1: [32]u8 = undefined; + const value: f32 = 1.34; + const result = try bufPrint(buf1[0..], "f32: {e}\n", value); + assert(mem.eql(u8, result, "f32: 1.34000003e+00\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f32 = 12.34; + const result = try bufPrint(buf1[0..], "f32: {e}\n", value); + assert(mem.eql(u8, result, "f32: 1.23400001e+01\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = -12.34e10; + const result = try bufPrint(buf1[0..], "f64: {e}\n", value); + assert(mem.eql(u8, result, "f64: -1.234e+11\n")); + } + { + // This fails on release due to a minor rounding difference. + // --release-fast outputs 9.999960000000001e-40 vs. the expected. + if (builtin.mode == builtin.Mode.Debug) { var buf1: [32]u8 = undefined; const value: f64 = 9.999960e-40; const result = try bufPrint(buf1[0..], "f64: {e}\n", value); assert(mem.eql(u8, result, "f64: 9.99996e-40\n")); } - { - var buf1: [32]u8 = undefined; - const value: f64 = 1.409706e-42; - const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); - assert(mem.eql(u8, result, "f64: 1.40971e-42\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = @bitCast(f32, u32(814313563)); - const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); - assert(mem.eql(u8, result, "f64: 1.00000e-09\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = @bitCast(f32, u32(1006632960)); - const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); - assert(mem.eql(u8, result, "f64: 7.81250e-03\n")); - } - { - // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. - // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. - var buf1: [32]u8 = undefined; - const value: f64 = @bitCast(f32, u32(1203982400)); - const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); - assert(mem.eql(u8, result, "f64: 1.00001e+05\n")); - } - { - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64); - assert(mem.eql(u8, result, "f64: nan\n")); - } - { - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64); - assert(mem.eql(u8, result, "f64: -nan\n")); - } - { - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64); - assert(mem.eql(u8, result, "f64: inf\n")); - } - { - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64); - assert(mem.eql(u8, result, "f64: -inf\n")); - } - { - var buf1: [64]u8 = undefined; - const value: f64 = 1.52314e+29; - const result = try bufPrint(buf1[0..], "f64: {.}\n", value); - assert(mem.eql(u8, result, "f64: 152314000000000000000000000000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 1.1234; - const result = try bufPrint(buf1[0..], "f32: {.1}\n", value); - assert(mem.eql(u8, result, "f32: 1.1\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 1234.567; - const result = try bufPrint(buf1[0..], "f32: {.2}\n", value); - assert(mem.eql(u8, result, "f32: 1234.57\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = -11.1234; - const result = try bufPrint(buf1[0..], "f32: {.4}\n", value); - // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). - // -11.12339... is rounded back up to -11.1234 - assert(mem.eql(u8, result, "f32: -11.1234\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 91.12345; - const result = try bufPrint(buf1[0..], "f32: {.5}\n", value); - assert(mem.eql(u8, result, "f32: 91.12345\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 91.12345678901235; - const result = try bufPrint(buf1[0..], "f64: {.10}\n", value); - assert(mem.eql(u8, result, "f64: 91.1234567890\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 0.0; - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 0.00000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 5.700; - const result = try bufPrint(buf1[0..], "f64: {.0}\n", value); - assert(mem.eql(u8, result, "f64: 6\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 9.999; - const result = try bufPrint(buf1[0..], "f64: {.1}\n", value); - assert(mem.eql(u8, result, "f64: 10.0\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 1.0; - const result = try bufPrint(buf1[0..], "f64: {.3}\n", value); - assert(mem.eql(u8, result, "f64: 1.000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 0.0003; - const result = try bufPrint(buf1[0..], "f64: {.8}\n", value); - assert(mem.eql(u8, result, "f64: 0.00030000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 1.40130e-45; - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 0.00000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = 9.999960e-40; - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 0.00000\n")); - } - // libc checks - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(916964781))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 0.00001\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(925353389))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 0.00001\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(1036831278))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 0.10000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(1065353133))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 1.00000\n")); - } - { - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(1092616192))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 10.00000\n")); - } - // libc differences - { - var buf1: [32]u8 = undefined; - // This is 0.015625 exactly according to gdb. We thus round down, - // however glibc rounds up for some reason. This occurs for all - // floats of the form x.yyyy25 on a precision point. - const value: f64 = f64(@bitCast(f32, u32(1015021568))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 0.01563\n")); - } - - // std-windows-x86_64-Debug-bare test case fails - { - // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 - // also rounds to 630 so I'm inclined to believe libc is not - // optimal here. - var buf1: [32]u8 = undefined; - const value: f64 = f64(@bitCast(f32, u32(1518338049))); - const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); - assert(mem.eql(u8, result, "f64: 18014400656965630.00000\n")); - } + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 1.409706e-42; + const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); + assert(mem.eql(u8, result, "f64: 1.40971e-42\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = @bitCast(f32, u32(814313563)); + const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); + assert(mem.eql(u8, result, "f64: 1.00000e-09\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = @bitCast(f32, u32(1006632960)); + const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); + assert(mem.eql(u8, result, "f64: 7.81250e-03\n")); + } + { + // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. + // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. + var buf1: [32]u8 = undefined; + const value: f64 = @bitCast(f32, u32(1203982400)); + const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); + assert(mem.eql(u8, result, "f64: 1.00001e+05\n")); + } + { + var buf1: [32]u8 = undefined; + const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64); + assert(mem.eql(u8, result, "f64: nan\n")); + } + { + var buf1: [32]u8 = undefined; + const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64); + assert(mem.eql(u8, result, "f64: -nan\n")); + } + { + var buf1: [32]u8 = undefined; + const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64); + assert(mem.eql(u8, result, "f64: inf\n")); + } + { + var buf1: [32]u8 = undefined; + const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64); + assert(mem.eql(u8, result, "f64: -inf\n")); + } + { + var buf1: [64]u8 = undefined; + const value: f64 = 1.52314e+29; + const result = try bufPrint(buf1[0..], "f64: {.}\n", value); + assert(mem.eql(u8, result, "f64: 152314000000000000000000000000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f32 = 1.1234; + const result = try bufPrint(buf1[0..], "f32: {.1}\n", value); + assert(mem.eql(u8, result, "f32: 1.1\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f32 = 1234.567; + const result = try bufPrint(buf1[0..], "f32: {.2}\n", value); + assert(mem.eql(u8, result, "f32: 1234.57\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f32 = -11.1234; + const result = try bufPrint(buf1[0..], "f32: {.4}\n", value); + // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). + // -11.12339... is rounded back up to -11.1234 + assert(mem.eql(u8, result, "f32: -11.1234\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f32 = 91.12345; + const result = try bufPrint(buf1[0..], "f32: {.5}\n", value); + assert(mem.eql(u8, result, "f32: 91.12345\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 91.12345678901235; + const result = try bufPrint(buf1[0..], "f64: {.10}\n", value); + assert(mem.eql(u8, result, "f64: 91.1234567890\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 0.0; + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 5.700; + const result = try bufPrint(buf1[0..], "f64: {.0}\n", value); + assert(mem.eql(u8, result, "f64: 6\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 9.999; + const result = try bufPrint(buf1[0..], "f64: {.1}\n", value); + assert(mem.eql(u8, result, "f64: 10.0\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 1.0; + const result = try bufPrint(buf1[0..], "f64: {.3}\n", value); + assert(mem.eql(u8, result, "f64: 1.000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 0.0003; + const result = try bufPrint(buf1[0..], "f64: {.8}\n", value); + assert(mem.eql(u8, result, "f64: 0.00030000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 1.40130e-45; + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = 9.999960e-40; + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00000\n")); + } + // libc checks + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(916964781))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00001\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(925353389))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.00001\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(1036831278))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.10000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(1065353133))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 1.00000\n")); + } + { + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(1092616192))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 10.00000\n")); + } + // libc differences + { + var buf1: [32]u8 = undefined; + // This is 0.015625 exactly according to gdb. We thus round down, + // however glibc rounds up for some reason. This occurs for all + // floats of the form x.yyyy25 on a precision point. + const value: f64 = f64(@bitCast(f32, u32(1015021568))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 0.01563\n")); + } + // std-windows-x86_64-Debug-bare test case fails + { + // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 + // also rounds to 630 so I'm inclined to believe libc is not + // optimal here. + var buf1: [32]u8 = undefined; + const value: f64 = f64(@bitCast(f32, u32(1518338049))); + const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); + assert(mem.eql(u8, result, "f64: 18014400656965630.00000\n")); } } From 15bf0c1541479870dff1f8d64a3746c337f901ef Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 23 Apr 2018 18:06:33 -0400 Subject: [PATCH 03/58] fix interaction between defer and labeled break closes #830 --- src/ir.cpp | 3 +++ test/cases/defer.zig | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index cd00fc623..86c77758b 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -3147,6 +3147,9 @@ static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode if (block_node->data.block.name == nullptr || incoming_blocks.length == 0) { return noreturn_return_value; } + + ir_set_cursor_at_end_and_append_block(irb, scope_block->end_block); + return ir_build_phi(irb, parent_scope, block_node, incoming_blocks.length, incoming_blocks.items, incoming_values.items); } else { incoming_blocks.append(irb->current_basic_block); incoming_values.append(ir_mark_gen(ir_build_const_void(irb, parent_scope, block_node))); diff --git a/test/cases/defer.zig b/test/cases/defer.zig index a989af18c..5470b4bbd 100644 --- a/test/cases/defer.zig +++ b/test/cases/defer.zig @@ -41,3 +41,14 @@ fn testBreakContInDefer(x: usize) void { assert(i == 5); } } + +test "defer and labeled break" { + var i = usize(0); + + blk: { + defer i += 1; + break :blk; + } + + assert(i == 1); +} From d5e99cc05ea05d701adffce55defc512d75e10d1 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Tue, 24 Apr 2018 19:18:31 +1200 Subject: [PATCH 04/58] Add initial complex-number support - Library type instead of builtin - All C complex functions implemented Partial WIP: Needs more tests for edge cases. --- CMakeLists.txt | 22 +++++ std/math/complex/abs.zig | 18 ++++ std/math/complex/acos.zig | 24 ++++++ std/math/complex/acosh.zig | 24 ++++++ std/math/complex/arg.zig | 18 ++++ std/math/complex/asin.zig | 30 +++++++ std/math/complex/asinh.zig | 25 ++++++ std/math/complex/atan.zig | 136 +++++++++++++++++++++++++++++ std/math/complex/atanh.zig | 25 ++++++ std/math/complex/conj.zig | 17 ++++ std/math/complex/cos.zig | 24 ++++++ std/math/complex/cosh.zig | 171 ++++++++++++++++++++++++++++++++++++ std/math/complex/exp.zig | 146 +++++++++++++++++++++++++++++++ std/math/complex/index.zig | 172 +++++++++++++++++++++++++++++++++++++ std/math/complex/ldexp.zig | 75 ++++++++++++++++ std/math/complex/log.zig | 26 ++++++ std/math/complex/pow.zig | 25 ++++++ std/math/complex/proj.zig | 24 ++++++ std/math/complex/sin.zig | 25 ++++++ std/math/complex/sinh.zig | 170 ++++++++++++++++++++++++++++++++++++ std/math/complex/sqrt.zig | 140 ++++++++++++++++++++++++++++++ std/math/complex/tan.zig | 25 ++++++ std/math/complex/tanh.zig | 117 +++++++++++++++++++++++++ std/math/index.zig | 5 ++ 24 files changed, 1484 insertions(+) create mode 100644 std/math/complex/abs.zig create mode 100644 std/math/complex/acos.zig create mode 100644 std/math/complex/acosh.zig create mode 100644 std/math/complex/arg.zig create mode 100644 std/math/complex/asin.zig create mode 100644 std/math/complex/asinh.zig create mode 100644 std/math/complex/atan.zig create mode 100644 std/math/complex/atanh.zig create mode 100644 std/math/complex/conj.zig create mode 100644 std/math/complex/cos.zig create mode 100644 std/math/complex/cosh.zig create mode 100644 std/math/complex/exp.zig create mode 100644 std/math/complex/index.zig create mode 100644 std/math/complex/ldexp.zig create mode 100644 std/math/complex/log.zig create mode 100644 std/math/complex/pow.zig create mode 100644 std/math/complex/proj.zig create mode 100644 std/math/complex/sin.zig create mode 100644 std/math/complex/sinh.zig create mode 100644 std/math/complex/sqrt.zig create mode 100644 std/math/complex/tan.zig create mode 100644 std/math/complex/tanh.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index e69297471..9bf4bdd70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -498,6 +498,28 @@ set(ZIG_STD_FILES "math/tan.zig" "math/tanh.zig" "math/trunc.zig" + "math/complex/abs.zig" + "math/complex/acosh.zig" + "math/complex/acos.zig" + "math/complex/arg.zig" + "math/complex/asinh.zig" + "math/complex/asin.zig" + "math/complex/atanh.zig" + "math/complex/atan.zig" + "math/complex/conj.zig" + "math/complex/cosh.zig" + "math/complex/cos.zig" + "math/complex/exp.zig" + "math/complex/index.zig" + "math/complex/ldexp.zig" + "math/complex/log.zig" + "math/complex/pow.zig" + "math/complex/proj.zig" + "math/complex/sinh.zig" + "math/complex/sin.zig" + "math/complex/sqrt.zig" + "math/complex/tanh.zig" + "math/complex/tan.zig" "mem.zig" "net.zig" "os/child_process.zig" diff --git a/std/math/complex/abs.zig b/std/math/complex/abs.zig new file mode 100644 index 000000000..4cd095c46 --- /dev/null +++ b/std/math/complex/abs.zig @@ -0,0 +1,18 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn abs(z: var) @typeOf(z.re) { + const T = @typeOf(z.re); + return math.hypot(T, z.re, z.im); +} + +const epsilon = 0.0001; + +test "complex.cabs" { + const a = Complex(f32).new(5, 3); + const c = abs(a); + debug.assert(math.approxEq(f32, c, 5.83095, epsilon)); +} diff --git a/std/math/complex/acos.zig b/std/math/complex/acos.zig new file mode 100644 index 000000000..cd37ddd92 --- /dev/null +++ b/std/math/complex/acos.zig @@ -0,0 +1,24 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn acos(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = cmath.asin(z); + return Complex(T).new(T(math.pi) / 2 - q.re, -q.im); +} + +const epsilon = 0.0001; + +test "complex.cacos" { + const a = Complex(f32).new(5, 3); + const c = acos(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 0.546975, epsilon)); + debug.assert(math.approxEq(f32, im, -2.452914, epsilon)); +} diff --git a/std/math/complex/acosh.zig b/std/math/complex/acosh.zig new file mode 100644 index 000000000..830ff79e5 --- /dev/null +++ b/std/math/complex/acosh.zig @@ -0,0 +1,24 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn acosh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = cmath.acos(z); + return Complex(T).new(-q.im, q.re); +} + +const epsilon = 0.0001; + +test "complex.cacosh" { + const a = Complex(f32).new(5, 3); + const c = acosh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 2.452914, epsilon)); + debug.assert(math.approxEq(f32, im, 0.546975, epsilon)); +} diff --git a/std/math/complex/arg.zig b/std/math/complex/arg.zig new file mode 100644 index 000000000..f24512ac7 --- /dev/null +++ b/std/math/complex/arg.zig @@ -0,0 +1,18 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn arg(z: var) @typeOf(z.re) { + const T = @typeOf(z.re); + return math.atan2(T, z.im, z.re); +} + +const epsilon = 0.0001; + +test "complex.carg" { + const a = Complex(f32).new(5, 3); + const c = arg(a); + debug.assert(math.approxEq(f32, c, 0.540420, epsilon)); +} diff --git a/std/math/complex/asin.zig b/std/math/complex/asin.zig new file mode 100644 index 000000000..84cabc694 --- /dev/null +++ b/std/math/complex/asin.zig @@ -0,0 +1,30 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn asin(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const x = z.re; + const y = z.im; + + const p = Complex(T).new(1.0 - (x - y) * (x + y), -2.0 * x * y); + const q = Complex(T).new(-y, x); + const r = cmath.log(q.add(cmath.sqrt(p))); + + return Complex(T).new(r.im, -r.re); +} + +const epsilon = 0.0001; + +test "complex.casin" { + const a = Complex(f32).new(5, 3); + const c = asin(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 1.023822, epsilon)); + debug.assert(math.approxEq(f32, im, 2.452914, epsilon)); +} diff --git a/std/math/complex/asinh.zig b/std/math/complex/asinh.zig new file mode 100644 index 000000000..5cf086d52 --- /dev/null +++ b/std/math/complex/asinh.zig @@ -0,0 +1,25 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn asinh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = Complex(T).new(-z.im, z.re); + const r = cmath.asin(q); + return Complex(T).new(r.im, -r.re); +} + +const epsilon = 0.0001; + +test "complex.casinh" { + const a = Complex(f32).new(5, 3); + const c = asinh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 2.459831, epsilon)); + debug.assert(math.approxEq(f32, im, 0.533999, epsilon)); +} diff --git a/std/math/complex/atan.zig b/std/math/complex/atan.zig new file mode 100644 index 000000000..2aa021133 --- /dev/null +++ b/std/math/complex/atan.zig @@ -0,0 +1,136 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn atan(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + return switch (T) { + f32 => atan32(z), + f64 => atan64(z), + else => @compileError("atan not implemented for " ++ @typeName(z)), + }; +} + +fn redupif32(x: f32) f32 { + const DP1 = 3.140625; + const DP2 = 9.67502593994140625e-4; + const DP3 = 1.509957990978376432e-7; + + var t = x / math.pi; + if (t >= 0.0) { + t += 0.5; + } else { + t -= 0.5; + } + + const u = f32(i32(t)); + return ((x - u * DP1) - u * DP2) - t * DP3; +} + +fn atan32(z: &const Complex(f32)) Complex(f32) { + const maxnum = 1.0e38; + + const x = z.re; + const y = z.im; + + if ((x == 0.0) and (y > 1.0)) { + // overflow + return Complex(f32).new(maxnum, maxnum); + } + + const x2 = x * x; + var a = 1.0 - x2 - (y * y); + if (a == 0.0) { + // overflow + return Complex(f32).new(maxnum, maxnum); + } + + var t = 0.5 * math.atan2(f32, 2.0 * x, a); + var w = redupif32(t); + + t = y - 1.0; + a = x2 + t * t; + if (a == 0.0) { + // overflow + return Complex(f32).new(maxnum, maxnum); + } + + t = y + 1.0; + a = (x2 + (t * t)) / a; + return Complex(f32).new(w, 0.25 * math.ln(a)); +} + +fn redupif64(x: f64) f64 { + const DP1 = 3.14159265160560607910; + const DP2 = 1.98418714791870343106e-9; + const DP3 = 1.14423774522196636802e-17; + + var t = x / math.pi; + if (t >= 0.0) { + t += 0.5; + } else { + t -= 0.5; + } + + const u = f64(i64(t)); + return ((x - u * DP1) - u * DP2) - t * DP3; +} + +fn atan64(z: &const Complex(f64)) Complex(f64) { + const maxnum = 1.0e308; + + const x = z.re; + const y = z.im; + + if ((x == 0.0) and (y > 1.0)) { + // overflow + return Complex(f64).new(maxnum, maxnum); + } + + const x2 = x * x; + var a = 1.0 - x2 - (y * y); + if (a == 0.0) { + // overflow + return Complex(f64).new(maxnum, maxnum); + } + + var t = 0.5 * math.atan2(f64, 2.0 * x, a); + var w = redupif64(t); + + t = y - 1.0; + a = x2 + t * t; + if (a == 0.0) { + // overflow + return Complex(f64).new(maxnum, maxnum); + } + + t = y + 1.0; + a = (x2 + (t * t)) / a; + return Complex(f64).new(w, 0.25 * math.ln(a)); +} + +const epsilon = 0.0001; + +test "complex.ctan32" { + const a = Complex(f32).new(5, 3); + const c = atan(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 1.423679, epsilon)); + debug.assert(math.approxEq(f32, im, 0.086569, epsilon)); +} + +test "complex.ctan64" { + const a = Complex(f64).new(5, 3); + const c = atan(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f64, re, 1.423679, epsilon)); + debug.assert(math.approxEq(f64, im, 0.086569, epsilon)); +} diff --git a/std/math/complex/atanh.zig b/std/math/complex/atanh.zig new file mode 100644 index 000000000..5fbbe2eb4 --- /dev/null +++ b/std/math/complex/atanh.zig @@ -0,0 +1,25 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn atanh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = Complex(T).new(-z.im, z.re); + const r = cmath.atan(q); + return Complex(T).new(r.im, -r.re); +} + +const epsilon = 0.0001; + +test "complex.catanh" { + const a = Complex(f32).new(5, 3); + const c = atanh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 0.146947, epsilon)); + debug.assert(math.approxEq(f32, im, 1.480870, epsilon)); +} diff --git a/std/math/complex/conj.zig b/std/math/complex/conj.zig new file mode 100644 index 000000000..ad3e8b503 --- /dev/null +++ b/std/math/complex/conj.zig @@ -0,0 +1,17 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn conj(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + return Complex(T).new(z.re, -z.im); +} + +test "complex.conj" { + const a = Complex(f32).new(5, 3); + const c = a.conjugate(); + + debug.assert(c.re == 5 and c.im == -3); +} diff --git a/std/math/complex/cos.zig b/std/math/complex/cos.zig new file mode 100644 index 000000000..35908898a --- /dev/null +++ b/std/math/complex/cos.zig @@ -0,0 +1,24 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn cos(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const p = Complex(T).new(-z.im, z.re); + return cmath.cosh(p); +} + +const epsilon = 0.0001; + +test "complex.ccos" { + const a = Complex(f32).new(5, 3); + const c = cos(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 2.855815, epsilon)); + debug.assert(math.approxEq(f32, im, 9.606383, epsilon)); +} diff --git a/std/math/complex/cosh.zig b/std/math/complex/cosh.zig new file mode 100644 index 000000000..1387ecb05 --- /dev/null +++ b/std/math/complex/cosh.zig @@ -0,0 +1,171 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; + +pub fn cosh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + return switch (T) { + f32 => cosh32(z), + f64 => cosh64(z), + else => @compileError("cosh not implemented for " ++ @typeName(z)), + }; +} + +fn cosh32(z: &const Complex(f32)) Complex(f32) { + const x = z.re; + const y = z.im; + + const hx = @bitCast(u32, x); + const ix = hx & 0x7fffffff; + + const hy = @bitCast(u32, y); + const iy = hy & 0x7fffffff; + + if (ix < 0x7f800000 and iy < 0x7f800000) { + if (iy == 0) { + return Complex(f32).new(math.cosh(x), y); + } + // small x: normal case + if (ix < 0x41100000) { + return Complex(f32).new(math.cosh(x) * math.cos(y), math.sinh(x) * math.sin(y)); + } + + // |x|>= 9, so cosh(x) ~= exp(|x|) + if (ix < 0x42b17218) { + // x < 88.7: exp(|x|) won't overflow + const h = math.exp(math.fabs(x)) * 0.5; + return Complex(f32).new(math.copysign(f32, h, x) * math.cos(y), h * math.sin(y)); + } + // x < 192.7: scale to avoid overflow + else if (ix < 0x4340b1e7) { + const v = Complex(f32).new(math.fabs(x), y); + const r = ldexp_cexp(v, -1); + return Complex(f32).new(x, y * math.copysign(f32, 1, x)); + } + // x >= 192.7: result always overflows + else { + const h = 0x1p127 * x; + return Complex(f32).new(h * h * math.cos(y), h * math.sin(y)); + } + } + + if (ix == 0 and iy >= 0x7f800000) { + return Complex(f32).new(y - y, math.copysign(f32, 0, x * (y - y))); + } + + if (iy == 0 and ix >= 0x7f800000) { + if (hx & 0x7fffff == 0) { + return Complex(f32).new(x * x, math.copysign(f32, 0, x) * y); + } + return Complex(f32).new(x, math.copysign(f32, 0, (x + x) * y)); + } + + if (ix < 0x7f800000 and iy >= 0x7f800000) { + return Complex(f32).new(y - y, x * (y - y)); + } + + if (ix >= 0x7f800000 and (hx & 0x7fffff) == 0) { + if (iy >= 0x7f800000) { + return Complex(f32).new(x * x, x * (y - y)); + } + return Complex(f32).new((x * x) * math.cos(y), x * math.sin(y)); + } + + return Complex(f32).new((x * x) * (y - y), (x + x) * (y - y)); +} + +fn cosh64(z: &const Complex(f64)) Complex(f64) { + const x = z.re; + const y = z.im; + + const fx = @bitCast(u64, x); + const hx = u32(fx >> 32); + const lx = @truncate(u32, fx); + const ix = hx & 0x7fffffff; + + const fy = @bitCast(u64, y); + const hy = u32(fy >> 32); + const ly = @truncate(u32, fy); + const iy = hy & 0x7fffffff; + + // nearly non-exceptional case where x, y are finite + if (ix < 0x7ff00000 and iy < 0x7ff00000) { + if (iy | ly == 0) { + return Complex(f64).new(math.cosh(x), x * y); + } + // small x: normal case + if (ix < 0x40360000) { + return Complex(f64).new(math.cosh(x) * math.cos(y), math.sinh(x) * math.sin(y)); + } + + // |x|>= 22, so cosh(x) ~= exp(|x|) + if (ix < 0x40862e42) { + // x < 710: exp(|x|) won't overflow + const h = math.exp(math.fabs(x)) * 0.5; + return Complex(f64).new(h * math.cos(y), math.copysign(f64, h, x) * math.sin(y)); + } + // x < 1455: scale to avoid overflow + else if (ix < 0x4096bbaa) { + const v = Complex(f64).new(math.fabs(x), y); + const r = ldexp_cexp(v, -1); + return Complex(f64).new(x, y * math.copysign(f64, 1, x)); + } + // x >= 1455: result always overflows + else { + const h = 0x1p1023; + return Complex(f64).new(h * h * math.cos(y), h * math.sin(y)); + } + } + + if (ix | lx == 0 and iy >= 0x7ff00000) { + return Complex(f64).new(y - y, math.copysign(f64, 0, x * (y - y))); + } + + if (iy | ly == 0 and ix >= 0x7ff00000) { + if ((hx & 0xfffff) | lx == 0) { + return Complex(f64).new(x * x, math.copysign(f64, 0, x) * y); + } + return Complex(f64).new(x * x, math.copysign(f64, 0, (x + x) * y)); + } + + if (ix < 0x7ff00000 and iy >= 0x7ff00000) { + return Complex(f64).new(y - y, x * (y - y)); + } + + if (ix >= 0x7ff00000 and (hx & 0xfffff) | lx == 0) { + if (iy >= 0x7ff00000) { + return Complex(f64).new(x * x, x * (y - y)); + } + return Complex(f64).new(x * x * math.cos(y), x * math.sin(y)); + } + + return Complex(f64).new((x * x) * (y - y), (x + x) * (y - y)); +} + +const epsilon = 0.0001; + +test "complex.ccosh32" { + const a = Complex(f32).new(5, 3); + const c = cosh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, -73.467300, epsilon)); + debug.assert(math.approxEq(f32, im, 10.471557, epsilon)); +} + +test "complex.ccosh64" { + const a = Complex(f64).new(5, 3); + const c = cosh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f64, re, -73.467300, epsilon)); + debug.assert(math.approxEq(f64, im, 10.471557, epsilon)); +} diff --git a/std/math/complex/exp.zig b/std/math/complex/exp.zig new file mode 100644 index 000000000..ace596771 --- /dev/null +++ b/std/math/complex/exp.zig @@ -0,0 +1,146 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; + +pub fn exp(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + + return switch (T) { + f32 => exp32(z), + f64 => exp64(z), + else => @compileError("exp not implemented for " ++ @typeName(z)), + }; +} + +fn exp32(z: &const Complex(f32)) Complex(f32) { + @setFloatMode(this, @import("builtin").FloatMode.Strict); + + const exp_overflow = 0x42b17218; // max_exp * ln2 ~= 88.72283955 + const cexp_overflow = 0x43400074; // (max_exp - min_denom_exp) * ln2 + + const x = z.re; + const y = z.im; + + const hy = @bitCast(u32, y) & 0x7fffffff; + // cexp(x + i0) = exp(x) + i0 + if (hy == 0) { + return Complex(f32).new(math.exp(x), y); + } + + const hx = @bitCast(u32, x); + // cexp(0 + iy) = cos(y) + isin(y) + if ((hx & 0x7fffffff) == 0) { + return Complex(f32).new(math.cos(y), math.sin(y)); + } + + if (hy >= 0x7f800000) { + // cexp(finite|nan +- i inf|nan) = nan + i nan + if ((hx & 0x7fffffff) != 0x7f800000) { + return Complex(f32).new(y - y, y - y); + } + // cexp(-inf +- i inf|nan) = 0 + i0 + else if (hx & 0x80000000 != 0) { + return Complex(f32).new(0, 0); + } + // cexp(+inf +- i inf|nan) = inf + i nan + else { + return Complex(f32).new(x, y - y); + } + } + + // 88.7 <= x <= 192 so must scale + if (hx >= exp_overflow and hx <= cexp_overflow) { + return ldexp_cexp(z, 0); + } + // - x < exp_overflow => exp(x) won't overflow (common) + // - x > cexp_overflow, so exp(x) * s overflows for s > 0 + // - x = +-inf + // - x = nan + else { + const exp_x = math.exp(x); + return Complex(f32).new(exp_x * math.cos(y), exp_x * math.sin(y)); + } +} + +fn exp64(z: &const Complex(f64)) Complex(f64) { + const exp_overflow = 0x40862e42; // high bits of max_exp * ln2 ~= 710 + const cexp_overflow = 0x4096b8e4; // (max_exp - min_denorm_exp) * ln2 + + const x = z.re; + const y = z.im; + + const fy = @bitCast(u64, y); + const hy = u32(fy >> 32) & 0x7fffffff; + const ly = @truncate(u32, fy); + + // cexp(x + i0) = exp(x) + i0 + if (hy | ly == 0) { + return Complex(f64).new(math.exp(x), y); + } + + const fx = @bitCast(u64, x); + const hx = u32(fx >> 32); + const lx = @truncate(u32, fx); + + // cexp(0 + iy) = cos(y) + isin(y) + if ((hx & 0x7fffffff) | lx == 0) { + return Complex(f64).new(math.cos(y), math.sin(y)); + } + + if (hy >= 0x7ff00000) { + // cexp(finite|nan +- i inf|nan) = nan + i nan + if (lx != 0 or (hx & 0x7fffffff) != 0x7ff00000) { + return Complex(f64).new(y - y, y - y); + } + // cexp(-inf +- i inf|nan) = 0 + i0 + else if (hx & 0x80000000 != 0) { + return Complex(f64).new(0, 0); + } + // cexp(+inf +- i inf|nan) = inf + i nan + else { + return Complex(f64).new(x, y - y); + } + } + + // 709.7 <= x <= 1454.3 so must scale + if (hx >= exp_overflow and hx <= cexp_overflow) { + const r = ldexp_cexp(z, 0); + return *r; + } + // - x < exp_overflow => exp(x) won't overflow (common) + // - x > cexp_overflow, so exp(x) * s overflows for s > 0 + // - x = +-inf + // - x = nan + else { + const exp_x = math.exp(x); + return Complex(f64).new(exp_x * math.cos(y), exp_x * math.sin(y)); + } +} + +const epsilon = 0.0001; + +test "complex.cexp32" { + const a = Complex(f32).new(5, 3); + const c = exp(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, -146.927917, epsilon)); + debug.assert(math.approxEq(f32, im, 20.944065, epsilon)); +} + +test "complex.cexp64" { + const a = Complex(f32).new(5, 3); + const c = exp(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f64, re, -146.927917, epsilon)); + debug.assert(math.approxEq(f64, im, 20.944065, epsilon)); +} diff --git a/std/math/complex/index.zig b/std/math/complex/index.zig new file mode 100644 index 000000000..24f726453 --- /dev/null +++ b/std/math/complex/index.zig @@ -0,0 +1,172 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; + +pub const abs = @import("abs.zig").abs; +pub const acosh = @import("acosh.zig").acosh; +pub const acos = @import("acos.zig").acos; +pub const arg = @import("arg.zig").arg; +pub const asinh = @import("asinh.zig").asinh; +pub const asin = @import("asin.zig").asin; +pub const atanh = @import("atanh.zig").atanh; +pub const atan = @import("atan.zig").atan; +pub const conj = @import("conj.zig").conj; +pub const cosh = @import("cosh.zig").cosh; +pub const cos = @import("cos.zig").cos; +pub const exp = @import("exp.zig").exp; +pub const log = @import("log.zig").log; +pub const pow = @import("pow.zig").pow; +pub const proj = @import("proj.zig").proj; +pub const sinh = @import("sinh.zig").sinh; +pub const sin = @import("sin.zig").sin; +pub const sqrt = @import("sqrt.zig").sqrt; +pub const tanh = @import("tanh.zig").tanh; +pub const tan = @import("tan.zig").tan; + +// NOTE: Make this a builtin at some point? +pub fn Complex(comptime T: type) type { + return struct { + const Self = this; + + re: T, + im: T, + + pub fn new(re: T, im: T) Self { + return Self { + .re = re, + .im = im, + }; + } + + pub fn add(self: &const Self, other: &const Self) Self { + return Self { + .re = self.re + other.re, + .im = self.im + other.im, + }; + } + + pub fn sub(self: &const Self, other: &const Self) Self { + return Self { + .re = self.re - other.re, + .im = self.im - other.im, + }; + } + + pub fn mul(self: &const Self, other: &const Self) Self { + return Self { + .re = self.re * other.re - self.im * other.im, + .im = self.im * other.re + self.re * other.im, + }; + } + + pub fn div(self: &const Self, other: &const Self) Self { + const re_num = self.re * other.re + self.im * other.im; + const im_num = self.im * other.re - self.re * other.im; + const den = other.re * other.re + other.im * other.im; + + return Self { + .re = re_num / den, + .im = im_num / den, + }; + } + + pub fn conjugate(self: &const Self) Self { + return Self { + .re = self.re, + .im = -self.im, + }; + } + + pub fn reciprocal(self: &const Self) Self { + const m = self.re * self.re + self.im * self.im; + return Self { + .re = self.re / m, + .im = -self.im / m, + }; + } + + pub fn magnitude(self: &const Self) T { + return math.sqrt(self.re * self.re + self.im * self.im); + } + }; +} + +const epsilon = 0.0001; + +test "complex.add" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2, 7); + const c = a.add(b); + + debug.assert(c.re == 7 and c.im == 10); +} + +test "complex.sub" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2, 7); + const c = a.sub(b); + + debug.assert(c.re == 3 and c.im == -4); +} + +test "complex.mul" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2, 7); + const c = a.mul(b); + + debug.assert(c.re == -11 and c.im == 41); +} + +test "complex.div" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2, 7); + const c = a.div(b); + + debug.assert(math.approxEq(f32, c.re, f32(31)/53, epsilon) and + math.approxEq(f32, c.im, f32(-29)/53, epsilon)); +} + +test "complex.conjugate" { + const a = Complex(f32).new(5, 3); + const c = a.conjugate(); + + debug.assert(c.re == 5 and c.im == -3); +} + +test "complex.reciprocal" { + const a = Complex(f32).new(5, 3); + const c = a.reciprocal(); + + debug.assert(math.approxEq(f32, c.re, f32(5)/34, epsilon) and + math.approxEq(f32, c.im, f32(-3)/34, epsilon)); +} + +test "complex.magnitude" { + const a = Complex(f32).new(5, 3); + const c = a.magnitude(); + + debug.assert(math.approxEq(f32, c, 5.83095, epsilon)); +} + +test "complex.cmath" { + _ = @import("abs.zig"); + _ = @import("acosh.zig"); + _ = @import("acos.zig"); + _ = @import("arg.zig"); + _ = @import("asinh.zig"); + _ = @import("asin.zig"); + _ = @import("atanh.zig"); + _ = @import("atan.zig"); + _ = @import("conj.zig"); + _ = @import("cosh.zig"); + _ = @import("cos.zig"); + _ = @import("exp.zig"); + _ = @import("log.zig"); + _ = @import("pow.zig"); + _ = @import("proj.zig"); + _ = @import("sinh.zig"); + _ = @import("sin.zig"); + _ = @import("sqrt.zig"); + _ = @import("tanh.zig"); + _ = @import("tan.zig"); +} diff --git a/std/math/complex/ldexp.zig b/std/math/complex/ldexp.zig new file mode 100644 index 000000000..4fb5a6815 --- /dev/null +++ b/std/math/complex/ldexp.zig @@ -0,0 +1,75 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn ldexp_cexp(z: var, expt: i32) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + + return switch (T) { + f32 => ldexp_cexp32(z, expt), + f64 => ldexp_cexp64(z, expt), + else => unreachable, + }; +} + +fn frexp_exp32(x: f32, expt: &i32) f32 { + const k = 235; // reduction constant + const kln2 = 162.88958740; // k * ln2 + + const exp_x = math.exp(x - kln2); + const hx = @bitCast(u32, exp_x); + *expt = i32(hx >> 23) - (0x7f + 127) + k; + return @bitCast(f32, (hx & 0x7fffff) | ((0x7f + 127) << 23)); +} + +fn ldexp_cexp32(z: &const Complex(f32), expt: i32) Complex(f32) { + var ex_expt: i32 = undefined; + const exp_x = frexp_exp32(z.re, &ex_expt); + const exptf = expt + ex_expt; + + const half_expt1 = @divTrunc(exptf, 2); + const scale1 = @bitCast(f32, (0x7f + half_expt1) << 23); + + const half_expt2 = exptf - half_expt1; + const scale2 = @bitCast(f32, (0x7f + half_expt2) << 23); + + return Complex(f32).new( + math.cos(z.im) * exp_x * scale1 * scale2, + math.sin(z.im) * exp_x * scale1 * scale2, + ); +} + +fn frexp_exp64(x: f64, expt: &i32) f64 { + const k = 1799; // reduction constant + const kln2 = 1246.97177782734161156; // k * ln2 + + const exp_x = math.exp(x - kln2); + + const fx = @bitCast(u64, x); + const hx = u32(fx >> 32); + const lx = @truncate(u32, fx); + + *expt = i32(hx >> 20) - (0x3ff + 1023) + k; + + const high_word = (hx & 0xfffff) | ((0x3ff + 1023) << 20); + return @bitCast(f64, (u64(high_word) << 32) | lx); +} + +fn ldexp_cexp64(z: &const Complex(f64), expt: i32) Complex(f64) { + var ex_expt: i32 = undefined; + const exp_x = frexp_exp64(z.re, &ex_expt); + const exptf = i64(expt + ex_expt); + + const half_expt1 = @divTrunc(exptf, 2); + const scale1 = @bitCast(f64, (0x3ff + half_expt1) << 20); + + const half_expt2 = exptf - half_expt1; + const scale2 = @bitCast(f64, (0x3ff + half_expt2) << 20); + + return Complex(f64).new( + math.cos(z.im) * exp_x * scale1 * scale2, + math.sin(z.im) * exp_x * scale1 * scale2, + ); +} diff --git a/std/math/complex/log.zig b/std/math/complex/log.zig new file mode 100644 index 000000000..6fe4d6b50 --- /dev/null +++ b/std/math/complex/log.zig @@ -0,0 +1,26 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn log(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const r = cmath.abs(z); + const phi = cmath.arg(z); + + return Complex(T).new(math.ln(r), phi); +} + +const epsilon = 0.0001; + +test "complex.clog" { + const a = Complex(f32).new(5, 3); + const c = log(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 1.763180, epsilon)); + debug.assert(math.approxEq(f32, im, 0.540419, epsilon)); +} diff --git a/std/math/complex/pow.zig b/std/math/complex/pow.zig new file mode 100644 index 000000000..34414254c --- /dev/null +++ b/std/math/complex/pow.zig @@ -0,0 +1,25 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn pow(comptime T: type, z: &const T, c: &const T) T { + const p = cmath.log(z); + const q = c.mul(p); + return cmath.exp(q); +} + +const epsilon = 0.0001; + +test "complex.cpow" { + const a = Complex(f32).new(5, 3); + const b = Complex(f32).new(2.3, -1.3); + const c = pow(Complex(f32), a, b); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 58.049110, epsilon)); + debug.assert(math.approxEq(f32, im, -101.003433, epsilon)); +} diff --git a/std/math/complex/proj.zig b/std/math/complex/proj.zig new file mode 100644 index 000000000..b6c4cc046 --- /dev/null +++ b/std/math/complex/proj.zig @@ -0,0 +1,24 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn proj(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + + if (math.isInf(z.re) or math.isInf(z.im)) { + return Complex(T).new(math.inf(T), math.copysign(T, 0, z.re)); + } + + return Complex(T).new(z.re, z.im); +} + +const epsilon = 0.0001; + +test "complex.cproj" { + const a = Complex(f32).new(5, 3); + const c = proj(a); + + debug.assert(c.re == 5 and c.im == 3); +} diff --git a/std/math/complex/sin.zig b/std/math/complex/sin.zig new file mode 100644 index 000000000..a334d6625 --- /dev/null +++ b/std/math/complex/sin.zig @@ -0,0 +1,25 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn sin(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const p = Complex(T).new(-z.im, z.re); + const q = cmath.sinh(p); + return Complex(T).new(q.im, -q.re); +} + +const epsilon = 0.0001; + +test "complex.csin" { + const a = Complex(f32).new(5, 3); + const c = sin(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, -9.654126, epsilon)); + debug.assert(math.approxEq(f32, im, 2.841692, epsilon)); +} diff --git a/std/math/complex/sinh.zig b/std/math/complex/sinh.zig new file mode 100644 index 000000000..799ee9ad6 --- /dev/null +++ b/std/math/complex/sinh.zig @@ -0,0 +1,170 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; + +pub fn sinh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + return switch (T) { + f32 => sinh32(z), + f64 => sinh64(z), + else => @compileError("tan not implemented for " ++ @typeName(z)), + }; +} + +fn sinh32(z: &const Complex(f32)) Complex(f32) { + const x = z.re; + const y = z.im; + + const hx = @bitCast(u32, x); + const ix = hx & 0x7fffffff; + + const hy = @bitCast(u32, y); + const iy = hy & 0x7fffffff; + + if (ix < 0x7f800000 and iy < 0x7f800000) { + if (iy == 0) { + return Complex(f32).new(math.sinh(x), y); + } + // small x: normal case + if (ix < 0x41100000) { + return Complex(f32).new(math.sinh(x) * math.cos(y), math.cosh(x) * math.sin(y)); + } + + // |x|>= 9, so cosh(x) ~= exp(|x|) + if (ix < 0x42b17218) { + // x < 88.7: exp(|x|) won't overflow + const h = math.exp(math.fabs(x)) * 0.5; + return Complex(f32).new(math.copysign(f32, h, x) * math.cos(y), h * math.sin(y)); + } + // x < 192.7: scale to avoid overflow + else if (ix < 0x4340b1e7) { + const v = Complex(f32).new(math.fabs(x), y); + const r = ldexp_cexp(v, -1); + return Complex(f32).new(x * math.copysign(f32, 1, x), y); + } + // x >= 192.7: result always overflows + else { + const h = 0x1p127 * x; + return Complex(f32).new(h * math.cos(y), h * h * math.sin(y)); + } + } + + if (ix == 0 and iy >= 0x7f800000) { + return Complex(f32).new(math.copysign(f32, 0, x * (y - y)), y - y); + } + + if (iy == 0 and ix >= 0x7f800000) { + if (hx & 0x7fffff == 0) { + return Complex(f32).new(x, y); + } + return Complex(f32).new(x, math.copysign(f32, 0, y)); + } + + if (ix < 0x7f800000 and iy >= 0x7f800000) { + return Complex(f32).new(y - y, x * (y - y)); + } + + if (ix >= 0x7f800000 and (hx & 0x7fffff) == 0) { + if (iy >= 0x7f800000) { + return Complex(f32).new(x * x, x * (y - y)); + } + return Complex(f32).new(x * math.cos(y), math.inf_f32 * math.sin(y)); + } + + return Complex(f32).new((x * x) * (y - y), (x + x) * (y - y)); +} + +fn sinh64(z: &const Complex(f64)) Complex(f64) { + const x = z.re; + const y = z.im; + + const fx = @bitCast(u64, x); + const hx = u32(fx >> 32); + const lx = @truncate(u32, fx); + const ix = hx & 0x7fffffff; + + const fy = @bitCast(u64, y); + const hy = u32(fy >> 32); + const ly = @truncate(u32, fy); + const iy = hy & 0x7fffffff; + + if (ix < 0x7ff00000 and iy < 0x7ff00000) { + if (iy | ly == 0) { + return Complex(f64).new(math.sinh(x), y); + } + // small x: normal case + if (ix < 0x40360000) { + return Complex(f64).new(math.sinh(x) * math.cos(y), math.cosh(x) * math.sin(y)); + } + + // |x|>= 22, so cosh(x) ~= exp(|x|) + if (ix < 0x40862e42) { + // x < 710: exp(|x|) won't overflow + const h = math.exp(math.fabs(x)) * 0.5; + return Complex(f64).new(math.copysign(f64, h, x) * math.cos(y), h * math.sin(y)); + } + // x < 1455: scale to avoid overflow + else if (ix < 0x4096bbaa) { + const v = Complex(f64).new(math.fabs(x), y); + const r = ldexp_cexp(v, -1); + return Complex(f64).new(x * math.copysign(f64, 1, x), y); + } + // x >= 1455: result always overflows + else { + const h = 0x1p1023 * x; + return Complex(f64).new(h * math.cos(y), h * h * math.sin(y)); + } + } + + if (ix | lx == 0 and iy >= 0x7ff00000) { + return Complex(f64).new(math.copysign(f64, 0, x * (y - y)), y - y); + } + + if (iy | ly == 0 and ix >= 0x7ff00000) { + if ((hx & 0xfffff) | lx == 0) { + return Complex(f64).new(x, y); + } + return Complex(f64).new(x, math.copysign(f64, 0, y)); + } + + if (ix < 0x7ff00000 and iy >= 0x7ff00000) { + return Complex(f64).new(y - y, x * (y - y)); + } + + if (ix >= 0x7ff00000 and (hx & 0xfffff) | lx == 0) { + if (iy >= 0x7ff00000) { + return Complex(f64).new(x * x, x * (y - y)); + } + return Complex(f64).new(x * math.cos(y), math.inf_f64 * math.sin(y)); + } + + return Complex(f64).new((x * x) * (y - y), (x + x) * (y - y)); +} + +const epsilon = 0.0001; + +test "complex.csinh32" { + const a = Complex(f32).new(5, 3); + const c = sinh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, -73.460617, epsilon)); + debug.assert(math.approxEq(f32, im, 10.472508, epsilon)); +} + +test "complex.csinh64" { + const a = Complex(f64).new(5, 3); + const c = sinh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f64, re, -73.460617, epsilon)); + debug.assert(math.approxEq(f64, im, 10.472508, epsilon)); +} diff --git a/std/math/complex/sqrt.zig b/std/math/complex/sqrt.zig new file mode 100644 index 000000000..03935bd3b --- /dev/null +++ b/std/math/complex/sqrt.zig @@ -0,0 +1,140 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +// NOTE: Returning @typeOf(z) here causes issues when trying to access the value. This is +// why we currently assign re, im parts to a new value explicitly for all tests. +pub fn sqrt(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + + return switch (T) { + f32 => sqrt32(z), + f64 => sqrt64(z), + else => @compileError("sqrt not implemented for " ++ @typeName(z)), + }; +} + +fn sqrt32(z: &const Complex(f32)) Complex(f32) { + const x = z.re; + const y = z.im; + + if (x == 0 and y == 0) { + return Complex(f32).new(0, y); + } + if (math.isInf(y)) { + return Complex(f32).new(math.inf(f32), y); + } + if (math.isNan(x)) { + // raise invalid if y is not nan + const t = (y - y) / (y - y); + return Complex(f32).new(x, t); + } + if (math.isInf(x)) { + // sqrt(inf + i nan) = inf + nan i + // sqrt(inf + iy) = inf + i0 + // sqrt(-inf + i nan) = nan +- inf i + // sqrt(-inf + iy) = 0 + inf i + if (math.signbit(x)) { + return Complex(f32).new(math.fabs(x - y), math.copysign(f32, x, y)); + } else { + return Complex(f32).new(x, math.copysign(f32, y - y, y)); + } + } + + // y = nan special case is handled fine below + + // double-precision avoids overflow with correct rounding. + const dx = f64(x); + const dy = f64(y); + + if (dx >= 0) { + const t = math.sqrt((dx + math.hypot(f64, dx, dy)) * 0.5); + return Complex(f32).new(f32(t), f32(dy / (2.0 * t))); + } else { + const t = math.sqrt((-dx + math.hypot(f64, dx, dy)) * 0.5); + return Complex(f32).new(f32(math.fabs(y) / (2.0 * t)), f32(math.copysign(f64, t, y))); + } +} + +fn sqrt64(z: &const Complex(f64)) Complex(f64) { + // may encounter overflow for im,re >= DBL_MAX / (1 + sqrt(2)) + const threshold = 0x1.a827999fcef32p+1022; + + var x = z.re; + var y = z.im; + + if (x == 0 and y == 0) { + return Complex(f64).new(0, y); + } + if (math.isInf(y)) { + return Complex(f64).new(math.inf(f64), y); + } + if (math.isNan(x)) { + // raise invalid if y is not nan + const t = (y - y) / (y - y); + return Complex(f64).new(x, t); + } + if (math.isInf(x)) { + // sqrt(inf + i nan) = inf + nan i + // sqrt(inf + iy) = inf + i0 + // sqrt(-inf + i nan) = nan +- inf i + // sqrt(-inf + iy) = 0 + inf i + if (math.signbit(x)) { + return Complex(f64).new(math.fabs(x - y), math.copysign(f64, x, y)); + } else { + return Complex(f64).new(x, math.copysign(f64, y - y, y)); + } + } + + // y = nan special case is handled fine below + + // scale to avoid overflow + var scale = false; + if (math.fabs(x) >= threshold or math.fabs(y) >= threshold) { + x *= 0.25; + y *= 0.25; + scale = true; + } + + var result: Complex(f64) = undefined; + if (x >= 0) { + const t = math.sqrt((x + math.hypot(f64, x, y)) * 0.5); + result = Complex(f64).new(t, y / (2.0 * t)); + } else { + const t = math.sqrt((-x + math.hypot(f64, x, y)) * 0.5); + result = Complex(f64).new(math.fabs(y) / (2.0 * t), math.copysign(f64, t, y)); + } + + if (scale) { + result.re *= 2; + result.im *= 2; + } + + return result; +} + +const epsilon = 0.0001; + +test "complex.csqrt32" { + const a = Complex(f32).new(5, 3); + const c = sqrt(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 2.327117, epsilon)); + debug.assert(math.approxEq(f32, im, 0.644574, epsilon)); +} + +test "complex.csqrt64" { + const a = Complex(f64).new(5, 3); + const c = sqrt(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f64, re, 2.3271175190399496, epsilon)); + debug.assert(math.approxEq(f64, im, 0.6445742373246469, epsilon)); +} diff --git a/std/math/complex/tan.zig b/std/math/complex/tan.zig new file mode 100644 index 000000000..cb43ff36d --- /dev/null +++ b/std/math/complex/tan.zig @@ -0,0 +1,25 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn tan(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + const q = Complex(T).new(-z.im, z.re); + const r = cmath.tanh(q); + return Complex(T).new(r.im, -r.re); +} + +const epsilon = 0.0001; + +test "complex.ctan" { + const a = Complex(f32).new(5, 3); + const c = tan(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, -0.002708233, epsilon)); + debug.assert(math.approxEq(f32, im, 1.004165, epsilon)); +} diff --git a/std/math/complex/tanh.zig b/std/math/complex/tanh.zig new file mode 100644 index 000000000..bc68aafb0 --- /dev/null +++ b/std/math/complex/tanh.zig @@ -0,0 +1,117 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +pub fn tanh(z: var) Complex(@typeOf(z.re)) { + const T = @typeOf(z.re); + return switch (T) { + f32 => tanh32(z), + f64 => tanh64(z), + else => @compileError("tan not implemented for " ++ @typeName(z)), + }; +} + +fn tanh32(z: &const Complex(f32)) Complex(f32) { + const x = z.re; + const y = z.im; + + const hx = @bitCast(u32, x); + const ix = hx & 0x7fffffff; + + if (ix >= 0x7f800000) { + if (ix & 0x7fffff != 0) { + const r = if (y == 0) y else x * y; + return Complex(f32).new(x, r); + } + const xx = @bitCast(f32, hx - 0x40000000); + const r = if (math.isInf(y)) y else math.sin(y) * math.cos(y); + return Complex(f32).new(xx, math.copysign(f32, 0, r)); + } + + if (!math.isFinite(y)) { + const r = if (ix != 0) y - y else x; + return Complex(f32).new(r, y - y); + } + + // x >= 11 + if (ix >= 0x41300000) { + const exp_mx = math.exp(-math.fabs(x)); + return Complex(f32).new(math.copysign(f32, 1, x), 4 * math.sin(y) * math.cos(y) * exp_mx * exp_mx); + } + + // Kahan's algorithm + const t = math.tan(y); + const beta = 1.0 + t * t; + const s = math.sinh(x); + const rho = math.sqrt(1 + s * s); + const den = 1 + beta * s * s; + + return Complex(f32).new((beta * rho * s) / den, t / den); +} + +fn tanh64(z: &const Complex(f64)) Complex(f64) { + const x = z.re; + const y = z.im; + + const fx = @bitCast(u64, x); + const hx = u32(fx >> 32); + const lx = @truncate(u32, fx); + const ix = hx & 0x7fffffff; + + if (ix >= 0x7ff00000) { + if ((ix & 0x7fffff) | lx != 0) { + const r = if (y == 0) y else x * y; + return Complex(f64).new(x, r); + } + + const xx = @bitCast(f64, (u64(hx - 0x40000000) << 32) | lx); + const r = if (math.isInf(y)) y else math.sin(y) * math.cos(y); + return Complex(f64).new(xx, math.copysign(f64, 0, r)); + } + + if (!math.isFinite(y)) { + const r = if (ix != 0) y - y else x; + return Complex(f64).new(r, y - y); + } + + // x >= 22 + if (ix >= 0x40360000) { + const exp_mx = math.exp(-math.fabs(x)); + return Complex(f64).new(math.copysign(f64, 1, x), 4 * math.sin(y) * math.cos(y) * exp_mx * exp_mx); + } + + // Kahan's algorithm + const t = math.tan(y); + const beta = 1.0 + t * t; + const s = math.sinh(x); + const rho = math.sqrt(1 + s * s); + const den = 1 + beta * s * s; + + return Complex(f64).new((beta * rho * s) / den, t / den); +} + +const epsilon = 0.0001; + +test "complex.ctanh32" { + const a = Complex(f32).new(5, 3); + const c = tanh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f32, re, 0.999913, epsilon)); + debug.assert(math.approxEq(f32, im, -0.000025, epsilon)); +} + +test "complex.ctanh64" { + const a = Complex(f64).new(5, 3); + const c = tanh(a); + + const re = c.re; + const im = c.im; + + debug.assert(math.approxEq(f64, re, 0.999913, epsilon)); + debug.assert(math.approxEq(f64, im, -0.000025, epsilon)); +} diff --git a/std/math/index.zig b/std/math/index.zig index 477dafcbc..83ba05532 100644 --- a/std/math/index.zig +++ b/std/math/index.zig @@ -129,6 +129,9 @@ pub const cos = @import("cos.zig").cos; pub const sin = @import("sin.zig").sin; pub const tan = @import("tan.zig").tan; +pub const complex = @import("complex/index.zig"); +pub const Complex = complex.Complex; + test "math" { _ = @import("nan.zig"); _ = @import("isnan.zig"); @@ -172,6 +175,8 @@ test "math" { _ = @import("sin.zig"); _ = @import("cos.zig"); _ = @import("tan.zig"); + + _ = @import("complex/index.zig"); } From 0501e066b51b659371739008c20e80642532497a Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Tue, 24 Apr 2018 23:54:27 +1200 Subject: [PATCH 05/58] crypto throughput test now uses os.time module --- std/crypto/throughput_test.zig | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/std/crypto/throughput_test.zig b/std/crypto/throughput_test.zig index d086b555e..0756f9a4e 100644 --- a/std/crypto/throughput_test.zig +++ b/std/crypto/throughput_test.zig @@ -1,17 +1,13 @@ // Modify the HashFunction variable to the one wanted to test. // -// NOTE: The throughput measurement may be slightly lower than other measurements since we run -// through our block alignment functions as well. Be aware when comparing against other tests. -// // ``` -// zig build-exe --release-fast --library c throughput_test.zig +// zig build-exe --release-fast throughput_test.zig // ./throughput_test // ``` const std = @import("std"); -const c = @cImport({ - @cInclude("time.h"); -}); +const time = std.os.time; +const Timer = time.Timer; const HashFunction = @import("md5.zig").Md5; const MiB = 1024 * 1024; @@ -28,13 +24,14 @@ pub fn main() !void { var h = HashFunction.init(); var offset: usize = 0; - const start = c.clock(); + var timer = try Timer.start(); + const start = timer.lap(); while (offset < BytesToHash) : (offset += block.len) { h.update(block[0..]); } - const end = c.clock(); + const end = timer.read(); - const elapsed_s = f64(end - start) / f64(c.CLOCKS_PER_SEC); + const elapsed_s = f64(end - start) / time.ns_per_s; const throughput = u64(BytesToHash / elapsed_s); try stdout.print("{}: {} MiB/s\n", @typeName(HashFunction), throughput / (1 * MiB)); From 13076d5f225cf8ca2cddf1b67478baebabe8b13f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 24 Apr 2018 20:50:21 -0400 Subject: [PATCH 06/58] std.mem: add more slice manipulation functions * add std.mem.trimLeft * add std.mem.trimRight * add std.mem.trimRight * add std.mem.lastIndexOfScalar * add std.mem.lastIndexOfAny * add std.mem.lastIndexOf * add std.mem.endsWith closes #944 Thanks Braedon Wooding for the original PR --- std/mem.zig | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/std/mem.zig b/std/mem.zig index eb67ce83e..cc3161cdd 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -20,7 +20,7 @@ pub const Allocator = struct { /// * alignment >= alignment of old_mem.ptr /// /// If `new_byte_count <= old_mem.len`: - /// * this function must return successfully. + /// * this function must return successfully. /// * alignment <= alignment of old_mem.ptr /// /// The returned newly allocated memory is undefined. @@ -174,6 +174,20 @@ pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) ![]T { return new_buf; } +/// Remove values from the beginning of a slice. +pub fn trimLeft(comptime T: type, slice: []const T, values_to_strip: []const T) []const T { + var begin: usize = 0; + while (begin < slice.len and indexOfScalar(T, values_to_strip, slice[begin]) != null) : (begin += 1) {} + return slice[begin..]; +} + +/// Remove values from the end of a slice. +pub fn trimRight(comptime T: type, slice: []const T, values_to_strip: []const T) []const T { + var end: usize = slice.len; + while (end > 0 and indexOfScalar(T, values_to_strip, slice[end - 1]) != null) : (end -= 1) {} + return slice[0..end]; +} + /// Remove values from the beginning and end of a slice. pub fn trim(comptime T: type, slice: []const T, values_to_strip: []const T) []const T { var begin: usize = 0; @@ -184,6 +198,8 @@ pub fn trim(comptime T: type, slice: []const T, values_to_strip: []const T) []co } test "mem.trim" { + assert(eql(u8, trimLeft(u8, " foo\n ", " \n"), "foo\n ")); + assert(eql(u8, trimRight(u8, " foo\n ", " \n"), " foo")); assert(eql(u8, trim(u8, " foo\n ", " \n"), "foo")); assert(eql(u8, trim(u8, "foo", " \n"), "foo")); } @@ -193,6 +209,17 @@ pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) ?usize { return indexOfScalarPos(T, slice, 0, value); } +/// Linear search for the last index of a scalar value inside a slice. +pub fn lastIndexOfScalar(comptime T: type, slice: []const T, value: T) ?usize { + var i: usize = slice.len; + while (i != 0) { + i -= 1; + if (slice[i] == value) + return i; + } + return null; +} + pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize, value: T) ?usize { var i: usize = start_index; while (i < slice.len) : (i += 1) { @@ -206,6 +233,18 @@ pub fn indexOfAny(comptime T: type, slice: []const T, values: []const T) ?usize return indexOfAnyPos(T, slice, 0, values); } +pub fn lastIndexOfAny(comptime T: type, slice: []const T, values: []const T) ?usize { + var i: usize = slice.len; + while (i != 0) { + i -= 1; + for (values) |value| { + if (slice[i] == value) + return i; + } + } + return null; +} + pub fn indexOfAnyPos(comptime T: type, slice: []const T, start_index: usize, values: []const T) ?usize { var i: usize = start_index; while (i < slice.len) : (i += 1) { @@ -221,6 +260,22 @@ pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) ?usize return indexOfPos(T, haystack, 0, needle); } +/// Find the index in a slice of a sub-slice, searching from the end backwards. +/// To start looking at a different index, slice the haystack first. +/// TODO is there even a better algorithm for this? +pub fn lastIndexOf(comptime T: type, haystack: []const T, needle: []const T) ?usize { + if (needle.len > haystack.len) + return null; + + var i: usize = haystack.len - needle.len; + while (true) : (i -= 1) { + if (mem.eql(T, haystack[i..i+needle.len], needle)) + return i; + if (i == 0) + return null; + } +} + // TODO boyer-moore algorithm pub fn indexOfPos(comptime T: type, haystack: []const T, start_index: usize, needle: []const T) ?usize { if (needle.len > haystack.len) @@ -237,9 +292,19 @@ pub fn indexOfPos(comptime T: type, haystack: []const T, start_index: usize, nee test "mem.indexOf" { assert(??indexOf(u8, "one two three four", "four") == 14); + assert(??lastIndexOf(u8, "one two three two four", "two") == 14); assert(indexOf(u8, "one two three four", "gour") == null); + assert(lastIndexOf(u8, "one two three four", "gour") == null); assert(??indexOf(u8, "foo", "foo") == 0); + assert(??lastIndexOf(u8, "foo", "foo") == 0); assert(indexOf(u8, "foo", "fool") == null); + assert(lastIndexOf(u8, "foo", "lfoo") == null); + assert(lastIndexOf(u8, "foo", "fool") == null); + + assert(??indexOf(u8, "foo foo", "foo") == 0); + assert(??lastIndexOf(u8, "foo foo", "foo") == 4); + assert(??lastIndexOfAny(u8, "boo, cat", "abo") == 6); + assert(??lastIndexOfScalar(u8, "boo", 'o') == 2); } /// Reads an integer from memory with size equal to bytes.len. @@ -359,9 +424,24 @@ pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) bool return if (needle.len > haystack.len) false else eql(T, haystack[0 .. needle.len], needle); } +test "mem.startsWith" { + assert(startsWith(u8, "Bob", "Bo")); + assert(!startsWith(u8, "Needle in haystack", "haystack")); +} + +pub fn endsWith(comptime T: type, haystack: []const T, needle: []const T) bool { + return if (needle.len > haystack.len) false else eql(T, haystack[haystack.len - needle.len ..], needle); +} + + +test "mem.endsWith" { + assert(endsWith(u8, "Needle in haystack", "haystack")); + assert(!endsWith(u8, "Bob", "Bo")); +} + pub const SplitIterator = struct { buffer: []const u8, - split_bytes: []const u8, + split_bytes: []const u8, index: usize, pub fn next(self: &SplitIterator) ?[]const u8 { From 1d998d5dce73d8d500dee5e41a3dca92a7f9b03e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 24 Apr 2018 21:14:12 -0400 Subject: [PATCH 07/58] clean up complex math tests --- std/math/complex/acos.zig | 7 ++----- std/math/complex/acosh.zig | 7 ++----- std/math/complex/asin.zig | 7 ++----- std/math/complex/asinh.zig | 7 ++----- std/math/complex/atan.zig | 18 ++++++------------ std/math/complex/atanh.zig | 7 ++----- std/math/complex/cos.zig | 7 ++----- std/math/complex/cosh.zig | 14 ++++---------- std/math/complex/exp.zig | 14 ++++---------- std/math/complex/log.zig | 7 ++----- std/math/complex/pow.zig | 7 ++----- std/math/complex/sin.zig | 7 ++----- std/math/complex/sinh.zig | 14 ++++---------- std/math/complex/sqrt.zig | 14 ++++---------- std/math/complex/tan.zig | 7 ++----- std/math/complex/tanh.zig | 14 ++++---------- 16 files changed, 46 insertions(+), 112 deletions(-) diff --git a/std/math/complex/acos.zig b/std/math/complex/acos.zig index cd37ddd92..a5760b4ac 100644 --- a/std/math/complex/acos.zig +++ b/std/math/complex/acos.zig @@ -16,9 +16,6 @@ test "complex.cacos" { const a = Complex(f32).new(5, 3); const c = acos(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 0.546975, epsilon)); - debug.assert(math.approxEq(f32, im, -2.452914, epsilon)); + debug.assert(math.approxEq(f32, c.re, 0.546975, epsilon)); + debug.assert(math.approxEq(f32, c.im, -2.452914, epsilon)); } diff --git a/std/math/complex/acosh.zig b/std/math/complex/acosh.zig index 830ff79e5..8dd91b283 100644 --- a/std/math/complex/acosh.zig +++ b/std/math/complex/acosh.zig @@ -16,9 +16,6 @@ test "complex.cacosh" { const a = Complex(f32).new(5, 3); const c = acosh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 2.452914, epsilon)); - debug.assert(math.approxEq(f32, im, 0.546975, epsilon)); + debug.assert(math.approxEq(f32, c.re, 2.452914, epsilon)); + debug.assert(math.approxEq(f32, c.im, 0.546975, epsilon)); } diff --git a/std/math/complex/asin.zig b/std/math/complex/asin.zig index 84cabc694..584a3a1a9 100644 --- a/std/math/complex/asin.zig +++ b/std/math/complex/asin.zig @@ -22,9 +22,6 @@ test "complex.casin" { const a = Complex(f32).new(5, 3); const c = asin(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 1.023822, epsilon)); - debug.assert(math.approxEq(f32, im, 2.452914, epsilon)); + debug.assert(math.approxEq(f32, c.re, 1.023822, epsilon)); + debug.assert(math.approxEq(f32, c.im, 2.452914, epsilon)); } diff --git a/std/math/complex/asinh.zig b/std/math/complex/asinh.zig index 5cf086d52..0c4dc2b6e 100644 --- a/std/math/complex/asinh.zig +++ b/std/math/complex/asinh.zig @@ -17,9 +17,6 @@ test "complex.casinh" { const a = Complex(f32).new(5, 3); const c = asinh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 2.459831, epsilon)); - debug.assert(math.approxEq(f32, im, 0.533999, epsilon)); + debug.assert(math.approxEq(f32, c.re, 2.459831, epsilon)); + debug.assert(math.approxEq(f32, c.im, 0.533999, epsilon)); } diff --git a/std/math/complex/atan.zig b/std/math/complex/atan.zig index 2aa021133..b7bbf930e 100644 --- a/std/math/complex/atan.zig +++ b/std/math/complex/atan.zig @@ -113,24 +113,18 @@ fn atan64(z: &const Complex(f64)) Complex(f64) { const epsilon = 0.0001; -test "complex.ctan32" { +test "complex.catan32" { const a = Complex(f32).new(5, 3); const c = atan(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 1.423679, epsilon)); - debug.assert(math.approxEq(f32, im, 0.086569, epsilon)); + debug.assert(math.approxEq(f32, c.re, 1.423679, epsilon)); + debug.assert(math.approxEq(f32, c.im, 0.086569, epsilon)); } -test "complex.ctan64" { +test "complex.catan64" { const a = Complex(f64).new(5, 3); const c = atan(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f64, re, 1.423679, epsilon)); - debug.assert(math.approxEq(f64, im, 0.086569, epsilon)); + debug.assert(math.approxEq(f64, c.re, 1.423679, epsilon)); + debug.assert(math.approxEq(f64, c.im, 0.086569, epsilon)); } diff --git a/std/math/complex/atanh.zig b/std/math/complex/atanh.zig index 5fbbe2eb4..f70c74176 100644 --- a/std/math/complex/atanh.zig +++ b/std/math/complex/atanh.zig @@ -17,9 +17,6 @@ test "complex.catanh" { const a = Complex(f32).new(5, 3); const c = atanh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 0.146947, epsilon)); - debug.assert(math.approxEq(f32, im, 1.480870, epsilon)); + debug.assert(math.approxEq(f32, c.re, 0.146947, epsilon)); + debug.assert(math.approxEq(f32, c.im, 1.480870, epsilon)); } diff --git a/std/math/complex/cos.zig b/std/math/complex/cos.zig index 35908898a..96e4ffcdb 100644 --- a/std/math/complex/cos.zig +++ b/std/math/complex/cos.zig @@ -16,9 +16,6 @@ test "complex.ccos" { const a = Complex(f32).new(5, 3); const c = cos(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 2.855815, epsilon)); - debug.assert(math.approxEq(f32, im, 9.606383, epsilon)); + debug.assert(math.approxEq(f32, c.re, 2.855815, epsilon)); + debug.assert(math.approxEq(f32, c.im, 9.606383, epsilon)); } diff --git a/std/math/complex/cosh.zig b/std/math/complex/cosh.zig index 1387ecb05..96eac6855 100644 --- a/std/math/complex/cosh.zig +++ b/std/math/complex/cosh.zig @@ -152,20 +152,14 @@ test "complex.ccosh32" { const a = Complex(f32).new(5, 3); const c = cosh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, -73.467300, epsilon)); - debug.assert(math.approxEq(f32, im, 10.471557, epsilon)); + debug.assert(math.approxEq(f32, c.re, -73.467300, epsilon)); + debug.assert(math.approxEq(f32, c.im, 10.471557, epsilon)); } test "complex.ccosh64" { const a = Complex(f64).new(5, 3); const c = cosh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f64, re, -73.467300, epsilon)); - debug.assert(math.approxEq(f64, im, 10.471557, epsilon)); + debug.assert(math.approxEq(f64, c.re, -73.467300, epsilon)); + debug.assert(math.approxEq(f64, c.im, 10.471557, epsilon)); } diff --git a/std/math/complex/exp.zig b/std/math/complex/exp.zig index ace596771..03f7f9e41 100644 --- a/std/math/complex/exp.zig +++ b/std/math/complex/exp.zig @@ -127,20 +127,14 @@ test "complex.cexp32" { const a = Complex(f32).new(5, 3); const c = exp(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, -146.927917, epsilon)); - debug.assert(math.approxEq(f32, im, 20.944065, epsilon)); + debug.assert(math.approxEq(f32, c.re, -146.927917, epsilon)); + debug.assert(math.approxEq(f32, c.im, 20.944065, epsilon)); } test "complex.cexp64" { const a = Complex(f32).new(5, 3); const c = exp(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f64, re, -146.927917, epsilon)); - debug.assert(math.approxEq(f64, im, 20.944065, epsilon)); + debug.assert(math.approxEq(f64, c.re, -146.927917, epsilon)); + debug.assert(math.approxEq(f64, c.im, 20.944065, epsilon)); } diff --git a/std/math/complex/log.zig b/std/math/complex/log.zig index 6fe4d6b50..a4a1d1664 100644 --- a/std/math/complex/log.zig +++ b/std/math/complex/log.zig @@ -18,9 +18,6 @@ test "complex.clog" { const a = Complex(f32).new(5, 3); const c = log(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 1.763180, epsilon)); - debug.assert(math.approxEq(f32, im, 0.540419, epsilon)); + debug.assert(math.approxEq(f32, c.re, 1.763180, epsilon)); + debug.assert(math.approxEq(f32, c.im, 0.540419, epsilon)); } diff --git a/std/math/complex/pow.zig b/std/math/complex/pow.zig index 34414254c..bef9fde54 100644 --- a/std/math/complex/pow.zig +++ b/std/math/complex/pow.zig @@ -17,9 +17,6 @@ test "complex.cpow" { const b = Complex(f32).new(2.3, -1.3); const c = pow(Complex(f32), a, b); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 58.049110, epsilon)); - debug.assert(math.approxEq(f32, im, -101.003433, epsilon)); + debug.assert(math.approxEq(f32, c.re, 58.049110, epsilon)); + debug.assert(math.approxEq(f32, c.im, -101.003433, epsilon)); } diff --git a/std/math/complex/sin.zig b/std/math/complex/sin.zig index a334d6625..d32b771d3 100644 --- a/std/math/complex/sin.zig +++ b/std/math/complex/sin.zig @@ -17,9 +17,6 @@ test "complex.csin" { const a = Complex(f32).new(5, 3); const c = sin(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, -9.654126, epsilon)); - debug.assert(math.approxEq(f32, im, 2.841692, epsilon)); + debug.assert(math.approxEq(f32, c.re, -9.654126, epsilon)); + debug.assert(math.approxEq(f32, c.im, 2.841692, epsilon)); } diff --git a/std/math/complex/sinh.zig b/std/math/complex/sinh.zig index 799ee9ad6..09a62ca05 100644 --- a/std/math/complex/sinh.zig +++ b/std/math/complex/sinh.zig @@ -151,20 +151,14 @@ test "complex.csinh32" { const a = Complex(f32).new(5, 3); const c = sinh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, -73.460617, epsilon)); - debug.assert(math.approxEq(f32, im, 10.472508, epsilon)); + debug.assert(math.approxEq(f32, c.re, -73.460617, epsilon)); + debug.assert(math.approxEq(f32, c.im, 10.472508, epsilon)); } test "complex.csinh64" { const a = Complex(f64).new(5, 3); const c = sinh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f64, re, -73.460617, epsilon)); - debug.assert(math.approxEq(f64, im, 10.472508, epsilon)); + debug.assert(math.approxEq(f64, c.re, -73.460617, epsilon)); + debug.assert(math.approxEq(f64, c.im, 10.472508, epsilon)); } diff --git a/std/math/complex/sqrt.zig b/std/math/complex/sqrt.zig index 03935bd3b..02e639057 100644 --- a/std/math/complex/sqrt.zig +++ b/std/math/complex/sqrt.zig @@ -121,20 +121,14 @@ test "complex.csqrt32" { const a = Complex(f32).new(5, 3); const c = sqrt(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 2.327117, epsilon)); - debug.assert(math.approxEq(f32, im, 0.644574, epsilon)); + debug.assert(math.approxEq(f32, c.re, 2.327117, epsilon)); + debug.assert(math.approxEq(f32, c.im, 0.644574, epsilon)); } test "complex.csqrt64" { const a = Complex(f64).new(5, 3); const c = sqrt(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f64, re, 2.3271175190399496, epsilon)); - debug.assert(math.approxEq(f64, im, 0.6445742373246469, epsilon)); + debug.assert(math.approxEq(f64, c.re, 2.3271175190399496, epsilon)); + debug.assert(math.approxEq(f64, c.im, 0.6445742373246469, epsilon)); } diff --git a/std/math/complex/tan.zig b/std/math/complex/tan.zig index cb43ff36d..4ea5182fa 100644 --- a/std/math/complex/tan.zig +++ b/std/math/complex/tan.zig @@ -17,9 +17,6 @@ test "complex.ctan" { const a = Complex(f32).new(5, 3); const c = tan(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, -0.002708233, epsilon)); - debug.assert(math.approxEq(f32, im, 1.004165, epsilon)); + debug.assert(math.approxEq(f32, c.re, -0.002708233, epsilon)); + debug.assert(math.approxEq(f32, c.im, 1.004165, epsilon)); } diff --git a/std/math/complex/tanh.zig b/std/math/complex/tanh.zig index bc68aafb0..6af62f48a 100644 --- a/std/math/complex/tanh.zig +++ b/std/math/complex/tanh.zig @@ -98,20 +98,14 @@ test "complex.ctanh32" { const a = Complex(f32).new(5, 3); const c = tanh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f32, re, 0.999913, epsilon)); - debug.assert(math.approxEq(f32, im, -0.000025, epsilon)); + debug.assert(math.approxEq(f32, c.re, 0.999913, epsilon)); + debug.assert(math.approxEq(f32, c.im, -0.000025, epsilon)); } test "complex.ctanh64" { const a = Complex(f64).new(5, 3); const c = tanh(a); - const re = c.re; - const im = c.im; - - debug.assert(math.approxEq(f64, re, 0.999913, epsilon)); - debug.assert(math.approxEq(f64, im, -0.000025, epsilon)); + debug.assert(math.approxEq(f64, c.re, 0.999913, epsilon)); + debug.assert(math.approxEq(f64, c.im, -0.000025, epsilon)); } From 84391af7b8951735ec4719a6495ac37c032a1e36 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 24 Apr 2018 21:23:03 -0400 Subject: [PATCH 08/58] convert NOTE to TODO so we catch it later See #363 For Complex as a builtin type, see discussion in #949 --- std/math/complex/index.zig | 1 - std/math/complex/sqrt.zig | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/std/math/complex/index.zig b/std/math/complex/index.zig index 24f726453..a4d493307 100644 --- a/std/math/complex/index.zig +++ b/std/math/complex/index.zig @@ -23,7 +23,6 @@ pub const sqrt = @import("sqrt.zig").sqrt; pub const tanh = @import("tanh.zig").tanh; pub const tan = @import("tan.zig").tan; -// NOTE: Make this a builtin at some point? pub fn Complex(comptime T: type) type { return struct { const Self = this; diff --git a/std/math/complex/sqrt.zig b/std/math/complex/sqrt.zig index 02e639057..afda69f7c 100644 --- a/std/math/complex/sqrt.zig +++ b/std/math/complex/sqrt.zig @@ -4,8 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; -// NOTE: Returning @typeOf(z) here causes issues when trying to access the value. This is -// why we currently assign re, im parts to a new value explicitly for all tests. +// TODO when #733 is solved this can be @typeOf(z) instead of Complex(@typeOf(z.re)) pub fn sqrt(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); From f6cbe9a9cca3718501272cea177ab1ad48852ffe Mon Sep 17 00:00:00 2001 From: Braedon Date: Wed, 25 Apr 2018 14:59:03 +1000 Subject: [PATCH 09/58] Utf8 Encode --- std/unicode.zig | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/std/unicode.zig b/std/unicode.zig index 356df824f..e8a82e7f0 100644 --- a/std/unicode.zig +++ b/std/unicode.zig @@ -1,6 +1,17 @@ const std = @import("./index.zig"); const debug = std.debug; +// Given a Utf8-Codepoint returns how many (1-4) +// bytes there are if represented as an array of bytes. +pub fn utf8CodepointSequenceLength(c: u32) !u3 { + if (c < 0x80) return u3(1); + if (c < 0x800) return u3(2); + if (c -% 0xd800 < 0x800) return error.InvalidCodepoint; + if (c < 0x10000) return u3(3); + if (c < 0x110000) return u3(4); + return error.CodepointTooLarge; +} + /// Given the first byte of a UTF-8 codepoint, /// returns a number 1-4 indicating the total length of the codepoint in bytes. /// If this byte does not match the form of a UTF-8 start byte, returns Utf8InvalidStartByte. @@ -12,6 +23,47 @@ pub fn utf8ByteSequenceLength(first_byte: u8) !u3 { return error.Utf8InvalidStartByte; } +/// Encodes a code point back into utf8 +/// c: the code point +/// out: the out buffer to write to +/// Notes: out has to have a len big enough for the bytes +/// however this limit is dependent on the code point +/// but giving it a minimum of 4 will ensure it will work +/// for all code points. +/// Errors: Will return an error if the code point is invalid. +pub fn utf8Encode(c: u32, out: []u8) !u3 { + if (utf8CodepointSequenceLength(c)) |length| { + debug.assert(out.len >= length); + switch (length) { + 1 => out[0] = u8(c), // Can just do 0 + codepoint for initial range + 2 => { + // 64 to convert the codepoint into its segments + out[0] = u8(0b11000000 + c / 64); + out[1] = u8(0b10000000 + c % 64); + }, + 3 => { + // Again using 64 as a conversion into their segments + // But using C / 4096 (64 * 64) as the first, (C/64) % 64 as the second, and just C % 64 as the last + out[0] = u8(0b11100000 + c / 4096); + out[1] = u8(0b10000000 + (c / 64) % 64); + out[2] = u8(0b10000000 + c % 64); + }, + 4 => { + // Same as previously but now its C / 64^3 (262144), (C / 4096) % 64, (C / 64) % 64 and C % 64 + out[0] = u8(0b11110000 + c / 262144); + out[1] = u8(0b10000000 + (c / 4096) % 64); + out[2] = u8(0b10000000 + (c / 64) % 64); + out[3] = u8(0b10000000 + c % 64); + }, + else => unreachable, + } + + return length; + } else |err| { + return err; + } +} + /// Decodes the UTF-8 codepoint encoded in the given slice of bytes. /// bytes.len must be equal to utf8ByteSequenceLength(bytes[0]) catch unreachable. /// If you already know the length at comptime, you can call one of @@ -25,6 +77,7 @@ pub fn utf8Decode(bytes: []const u8) !u32 { else => unreachable, }; } + pub fn utf8Decode2(bytes: []const u8) !u32 { debug.assert(bytes.len == 2); debug.assert(bytes[0] & 0b11100000 == 0b11000000); @@ -38,6 +91,7 @@ pub fn utf8Decode2(bytes: []const u8) !u32 { return value; } + pub fn utf8Decode3(bytes: []const u8) !u32 { debug.assert(bytes.len == 3); debug.assert(bytes[0] & 0b11110000 == 0b11100000); @@ -56,6 +110,7 @@ pub fn utf8Decode3(bytes: []const u8) !u32 { return value; } + pub fn utf8Decode4(bytes: []const u8) !u32 { debug.assert(bytes.len == 4); debug.assert(bytes[0] & 0b11111000 == 0b11110000); @@ -170,6 +225,42 @@ const Utf8Iterator = struct { } }; +test "utf8 encode" { + // A few taken from wikipedia a few taken elsewhere + var array: [4]u8 = undefined; + debug.assert((try utf8Encode(try utf8Decode("€"), array[0..])) == 3); + debug.assert(array[0] == 0b11100010); + debug.assert(array[1] == 0b10000010); + debug.assert(array[2] == 0b10101100); + + debug.assert((try utf8Encode(try utf8Decode("$"), array[0..])) == 1); + debug.assert(array[0] == 0b00100100); + + debug.assert((try utf8Encode(try utf8Decode("¢"), array[0..])) == 2); + debug.assert(array[0] == 0b11000010); + debug.assert(array[1] == 0b10100010); + + debug.assert((try utf8Encode(try utf8Decode("𐍈"), array[0..])) == 4); + debug.assert(array[0] == 0b11110000); + debug.assert(array[1] == 0b10010000); + debug.assert(array[2] == 0b10001101); + debug.assert(array[3] == 0b10001000); +} + +test "utf8 encode error" { + var array: [4]u8 = undefined; + testErrorEncode(0xFFFFFF, array[0..], error.CodepointTooLarge); + testErrorEncode(0xd900, array[0..], error.InvalidCodepoint); +} + +fn testErrorEncode(codePoint: u32, array: []u8, expectedErr: error) void { + if (utf8Encode(codePoint, array)) |_| { + unreachable; + } else |err| { + assert(err == expectedErr); + } +} + test "utf8 iterator on ascii" { const s = Utf8View.initComptime("abc"); From 07af6559d8a883dcb21ff99cddb4a836d2bb66bd Mon Sep 17 00:00:00 2001 From: Braedon Date: Wed, 25 Apr 2018 16:26:57 +1000 Subject: [PATCH 10/58] Changed to use shifting and masking --- std/unicode.zig | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/std/unicode.zig b/std/unicode.zig index e8a82e7f0..7650f83c8 100644 --- a/std/unicode.zig +++ b/std/unicode.zig @@ -35,25 +35,25 @@ pub fn utf8Encode(c: u32, out: []u8) !u3 { if (utf8CodepointSequenceLength(c)) |length| { debug.assert(out.len >= length); switch (length) { + // The pattern for each is the same + // - Increasing the initial shift by 6 each time + // - Each time after the first shorten the shifted + // value to a max of 0b111111 (63) 1 => out[0] = u8(c), // Can just do 0 + codepoint for initial range 2 => { - // 64 to convert the codepoint into its segments - out[0] = u8(0b11000000 + c / 64); - out[1] = u8(0b10000000 + c % 64); + out[0] = u8(0b11000000 | (c >> 6)); + out[1] = u8(0b10000000 | (c & 0b111111)); }, 3 => { - // Again using 64 as a conversion into their segments - // But using C / 4096 (64 * 64) as the first, (C/64) % 64 as the second, and just C % 64 as the last - out[0] = u8(0b11100000 + c / 4096); - out[1] = u8(0b10000000 + (c / 64) % 64); - out[2] = u8(0b10000000 + c % 64); + out[0] = u8(0b11100000 | (c >> 12)); + out[1] = u8(0b10000000 | ((c >> 6) & 0b111111)); + out[2] = u8(0b10000000 | (c & 0b111111)); }, 4 => { - // Same as previously but now its C / 64^3 (262144), (C / 4096) % 64, (C / 64) % 64 and C % 64 - out[0] = u8(0b11110000 + c / 262144); - out[1] = u8(0b10000000 + (c / 4096) % 64); - out[2] = u8(0b10000000 + (c / 64) % 64); - out[3] = u8(0b10000000 + c % 64); + out[0] = u8(0b11110000 | (c >> 18)); + out[1] = u8(0b10000000 | ((c >> 12) & 0b111111)); + out[2] = u8(0b10000000 | ((c >> 6) & 0b111111)); + out[3] = u8(0b10000000 | (c & 0b111111)); }, else => unreachable, } @@ -257,7 +257,7 @@ fn testErrorEncode(codePoint: u32, array: []u8, expectedErr: error) void { if (utf8Encode(codePoint, array)) |_| { unreachable; } else |err| { - assert(err == expectedErr); + debug.assert(err == expectedErr); } } From 3178528335cb5efbf237cecb9ea9eb3bfa31b21f Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sat, 28 Apr 2018 14:05:08 +0200 Subject: [PATCH 11/58] Removed zero sized error set optimization fixes #762 fixes #818 --- src/ir.cpp | 14 ++++---------- test/cases/error.zig | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 86c77758b..ec7f41d74 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -6166,16 +6166,10 @@ static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, A buf_init_from_buf(&err_set_type->name, type_name); err_set_type->is_copyable = true; err_set_type->data.error_set.err_count = err_count; - - if (err_count == 0) { - err_set_type->zero_bits = true; - err_set_type->di_type = irb->codegen->builtin_types.entry_void->di_type; - } else { - err_set_type->type_ref = irb->codegen->builtin_types.entry_global_error_set->type_ref; - err_set_type->di_type = irb->codegen->builtin_types.entry_global_error_set->di_type; - irb->codegen->error_di_types.append(&err_set_type->di_type); - err_set_type->data.error_set.errors = allocate(err_count); - } + err_set_type->type_ref = irb->codegen->builtin_types.entry_global_error_set->type_ref; + err_set_type->di_type = irb->codegen->builtin_types.entry_global_error_set->di_type; + irb->codegen->error_di_types.append(&err_set_type->di_type); + err_set_type->data.error_set.errors = allocate(err_count); ErrorTableEntry **errors = allocate(irb->codegen->errors_by_index.length + err_count); diff --git a/test/cases/error.zig b/test/cases/error.zig index e64bf02c9..c64c835fc 100644 --- a/test/cases/error.zig +++ b/test/cases/error.zig @@ -175,3 +175,30 @@ fn baz_1() !i32 { fn quux_1() !i32 { return error.C; } + + +test "error: fn returning empty error set can be passed as fn returning any error" { + entry(); + comptime entry(); +} + +fn entry() void { + foo2(bar2); +} + +fn foo2(f: fn()error!void) void { + const x = f(); +} + +fn bar2() (error{}!void) { } + + +test "error: Zero sized error set returned with value payload crash" { + _ = foo3(0); + _ = comptime foo3(0); +} + +const Error = error{}; +fn foo3(b: usize) Error!usize { + return b; +} From 2fc34eaa581cc31827e978fbd973bf36d2c647e2 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sat, 28 Apr 2018 16:27:31 +0200 Subject: [PATCH 12/58] Functions with infered error set can now return literals fixes #852 --- src/analyze.cpp | 1 - src/ir.cpp | 36 +++++++++++++++++++----------------- test/cases/error.zig | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index a598d7676..11715220c 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -6131,4 +6131,3 @@ bool type_can_fail(TypeTableEntry *type_entry) { bool fn_type_can_fail(FnTypeId *fn_type_id) { return type_can_fail(fn_type_id->return_type) || fn_type_id->cc == CallingConventionAsync; } - diff --git a/src/ir.cpp b/src/ir.cpp index ec7f41d74..d8156b214 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -8111,7 +8111,7 @@ static void update_errors_helper(CodeGen *g, ErrorTableEntry ***errors, size_t * *errors = reallocate(*errors, old_errors_count, *errors_count); } -static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, IrInstruction **instructions, size_t instruction_count) { +static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, TypeTableEntry *expected_type, IrInstruction **instructions, size_t instruction_count) { assert(instruction_count >= 1); IrInstruction *prev_inst = instructions[0]; if (type_is_invalid(prev_inst->value.type)) { @@ -8158,16 +8158,6 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod continue; } - if (prev_type->id == TypeTableEntryIdNullLit) { - prev_inst = cur_inst; - continue; - } - - if (cur_type->id == TypeTableEntryIdNullLit) { - any_are_null = true; - continue; - } - if (prev_type->id == TypeTableEntryIdErrorSet) { assert(err_set_type != nullptr); if (cur_type->id == TypeTableEntryIdErrorSet) { @@ -8427,6 +8417,16 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod } } + if (prev_type->id == TypeTableEntryIdNullLit) { + prev_inst = cur_inst; + continue; + } + + if (cur_type->id == TypeTableEntryIdNullLit) { + any_are_null = true; + continue; + } + if (types_match_const_cast_only(ira, prev_type, cur_type, source_node).id == ConstCastResultIdOk) { continue; } @@ -8610,6 +8610,10 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod } else if (err_set_type != nullptr) { if (prev_inst->value.type->id == TypeTableEntryIdErrorSet) { return err_set_type; + } else if (prev_inst->value.type->id == TypeTableEntryIdErrorUnion) { + return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type->data.error_union.payload_type); + } else if (expected_type != nullptr && expected_type->id == TypeTableEntryIdErrorUnion) { + return get_error_union_type(ira->codegen, err_set_type, expected_type->data.error_union.payload_type); } else { if (prev_inst->value.type->id == TypeTableEntryIdNumLitInt || prev_inst->value.type->id == TypeTableEntryIdNumLitFloat) @@ -8621,8 +8625,6 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod ir_add_error_node(ira, source_node, buf_sprintf("unable to make error union out of null literal")); return ira->codegen->builtin_types.entry_invalid; - } else if (prev_inst->value.type->id == TypeTableEntryIdErrorUnion) { - return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type->data.error_union.payload_type); } else { return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type); } @@ -10645,7 +10647,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp } IrInstruction *instructions[] = {op1, op2}; - TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, source_node, instructions, 2); + TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, source_node, nullptr, instructions, 2); if (type_is_invalid(resolved_type)) return resolved_type; type_ensure_zero_bits_known(ira->codegen, resolved_type); @@ -11035,7 +11037,7 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp IrInstruction *op1 = bin_op_instruction->op1->other; IrInstruction *op2 = bin_op_instruction->op2->other; IrInstruction *instructions[] = {op1, op2}; - TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, bin_op_instruction->base.source_node, instructions, 2); + TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, bin_op_instruction->base.source_node, nullptr, instructions, 2); if (type_is_invalid(resolved_type)) return resolved_type; IrBinOp op_id = bin_op_instruction->op_id; @@ -13004,7 +13006,7 @@ static TypeTableEntry *ir_analyze_instruction_phi(IrAnalyze *ira, IrInstructionP return first_value->value.type; } - TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, phi_instruction->base.source_node, + TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, phi_instruction->base.source_node, nullptr, new_incoming_values.items, new_incoming_values.length); if (type_is_invalid(resolved_type)) return resolved_type; @@ -18696,7 +18698,7 @@ TypeTableEntry *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutabl } else if (ira->src_implicit_return_type_list.length == 0) { return codegen->builtin_types.entry_unreachable; } else { - return ir_resolve_peer_types(ira, expected_type_source_node, ira->src_implicit_return_type_list.items, + return ir_resolve_peer_types(ira, expected_type_source_node, expected_type, ira->src_implicit_return_type_list.items, ira->src_implicit_return_type_list.length); } } diff --git a/test/cases/error.zig b/test/cases/error.zig index c64c835fc..2a1433df5 100644 --- a/test/cases/error.zig +++ b/test/cases/error.zig @@ -202,3 +202,42 @@ const Error = error{}; fn foo3(b: usize) Error!usize { return b; } + + +test "error: Infer error set from literals" { + _ = nullLiteral("n") catch |err| handleErrors(err); + _ = floatLiteral("n") catch |err| handleErrors(err); + _ = intLiteral("n") catch |err| handleErrors(err); + _ = comptime nullLiteral("n") catch |err| handleErrors(err); + _ = comptime floatLiteral("n") catch |err| handleErrors(err); + _ = comptime intLiteral("n") catch |err| handleErrors(err); +} + +fn handleErrors(err: var) noreturn { + switch (err) { + error.T => {} + } + + unreachable; +} + +fn nullLiteral(str: []const u8) !?i64 { + if (str[0] == 'n') + return null; + + return error.T; +} + +fn floatLiteral(str: []const u8) !?f64 { + if (str[0] == 'n') + return 1.0; + + return error.T; +} + +fn intLiteral(str: []const u8) !?i64 { + if (str[0] == 'n') + return 1; + + return error.T; +} From fba0347ec43fb5c06b5ac9bec541b740d95194fe Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sat, 28 Apr 2018 17:17:48 +0200 Subject: [PATCH 13/58] .ReturnType and @ArgType now emits errors on unresolved types related: #846 --- src/ir.cpp | 19 +++++++++++++++++++ test/compile_errors.zig | 17 +++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index d8156b214..641b8fc30 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -13859,6 +13859,15 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru } } else if (child_type->id == TypeTableEntryIdFn) { if (buf_eql_str(field_name, "ReturnType")) { + if (child_type->data.fn.fn_type_id.return_type == nullptr) { + // Return type can only ever be null, if the function is generic + assert(child_type->data.fn.is_generic); + + ir_add_error(ira, &field_ptr_instruction->base, + buf_sprintf("ReturnType has not been resolved because '%s' is generic", buf_ptr(&child_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + bool ptr_is_const = true; bool ptr_is_volatile = false; return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, @@ -17860,6 +17869,16 @@ static TypeTableEntry *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstruc ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); out_val->data.x_type = fn_type_id->param_info[arg_index].type; + if (out_val->data.x_type == nullptr) { + // Args are only unresolved if our function is generic. + assert(fn_type->data.fn.is_generic); + + ir_add_error(ira, arg_index_inst, + buf_sprintf("@ArgType could not resolve the type of arg %" ZIG_PRI_usize " because '%s' is generic", + arg_index, buf_ptr(&fn_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + return ira->codegen->builtin_types.entry_type; } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index f8febc27b..52e063eb3 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -3209,4 +3209,21 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\} , ".tmp_source.zig:5:42: error: zero-bit field 'val' in struct 'Empty' has no offset"); + + cases.add("getting return type of generic function", + \\fn generic(a: var) void {} + \\comptime { + \\ _ = @typeOf(generic).ReturnType; + \\} + , + ".tmp_source.zig:3:25: error: ReturnType has not been resolved because 'fn(var)var' is generic"); + + cases.add("getting @ArgType of generic function", + \\fn generic(a: var) void {} + \\comptime { + \\ _ = @ArgType(@typeOf(generic), 0); + \\} + , + ".tmp_source.zig:3:36: error: @ArgType could not resolve the type of arg 0 because 'fn(var)var' is generic"); + } From 341f8c1e8680aa3cfbeba6833f85df00355a95ef Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sat, 28 Apr 2018 17:57:47 +0200 Subject: [PATCH 14/58] Fixed wrong formatting for arg_index when reporting @ArgType error --- src/ir.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 641b8fc30..4bf824047 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -17874,8 +17874,8 @@ static TypeTableEntry *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstruc assert(fn_type->data.fn.is_generic); ir_add_error(ira, arg_index_inst, - buf_sprintf("@ArgType could not resolve the type of arg %" ZIG_PRI_usize " because '%s' is generic", - arg_index, buf_ptr(&fn_type->name))); + buf_sprintf("@ArgType could not resolve the type of arg %" ZIG_PRI_u64 " because '%s' is generic", + arg_index, buf_ptr(&fn_type->name))); return ira->codegen->builtin_types.entry_invalid; } From 837166319dd1a5df14e5d4bebd62080bb6ebdaa1 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sat, 28 Apr 2018 19:02:46 +0200 Subject: [PATCH 15/58] Trying to fix osx build failing by setting param_info.type to nullptr --- src/analyze.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/analyze.cpp b/src/analyze.cpp index 11715220c..29a2fc256 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1261,6 +1261,10 @@ void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_cou fn_type_id->param_info = allocate_nonzero(param_count_alloc); fn_type_id->next_param_index = 0; fn_type_id->is_var_args = fn_proto->is_var_args; + + // We set param_info to 0, as param_info[i]->type is checked for null + // when checking if a parameters type has been resolved. + memset(fn_type_id->param_info, 0, sizeof(fn_type_id->param_info[i]) * fn_type_id->param_count); } static bool analyze_const_align(CodeGen *g, Scope *scope, AstNode *node, uint32_t *result) { From d6f033b42dcb49cfe45cb61821f2f451e4004686 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sat, 28 Apr 2018 19:09:25 +0200 Subject: [PATCH 16/58] Fixed build error --- src/analyze.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 29a2fc256..1003cf8ed 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1264,7 +1264,7 @@ void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_cou // We set param_info to 0, as param_info[i]->type is checked for null // when checking if a parameters type has been resolved. - memset(fn_type_id->param_info, 0, sizeof(fn_type_id->param_info[i]) * fn_type_id->param_count); + memset(fn_type_id->param_info, 0, sizeof(fn_type_id->param_info[0]) * fn_type_id->param_count); } static bool analyze_const_align(CodeGen *g, Scope *scope, AstNode *node, uint32_t *result) { From 73bf897b5cc25ee3f1ec9d0ba1483d779de4b7c3 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sat, 28 Apr 2018 19:21:23 +0200 Subject: [PATCH 17/58] Using allocate instead of allocate_nonzero so we don't have to memset --- src/analyze.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 1003cf8ed..1ecfe32f4 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1258,13 +1258,9 @@ void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_cou } fn_type_id->param_count = fn_proto->params.length; - fn_type_id->param_info = allocate_nonzero(param_count_alloc); + fn_type_id->param_info = allocate(param_count_alloc); fn_type_id->next_param_index = 0; fn_type_id->is_var_args = fn_proto->is_var_args; - - // We set param_info to 0, as param_info[i]->type is checked for null - // when checking if a parameters type has been resolved. - memset(fn_type_id->param_info, 0, sizeof(fn_type_id->param_info[0]) * fn_type_id->param_count); } static bool analyze_const_align(CodeGen *g, Scope *scope, AstNode *node, uint32_t *result) { From 4ac36d094c06b04c1c71d972d3f3e1187bccea95 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 27 Apr 2018 19:27:58 -0400 Subject: [PATCH 18/58] add std.atomic.Stack and std.atomic.Queue --- CMakeLists.txt | 3 +++ std/atomic/index.zig | 7 +++++++ std/atomic/queue.zig | 37 ++++++++++++++++++++++++++++++++++++ std/atomic/stack.zig | 45 ++++++++++++++++++++++++++++++++++++++++++++ std/index.zig | 2 ++ 5 files changed, 94 insertions(+) create mode 100644 std/atomic/index.zig create mode 100644 std/atomic/queue.zig create mode 100644 std/atomic/stack.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bf4bdd70..721690e9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -415,6 +415,9 @@ set(ZIG_CPP_SOURCES set(ZIG_STD_FILES "array_list.zig" + "atomic/index.zig" + "atomic/stack.zig" + "atomic/queue.zig" "base64.zig" "buf_map.zig" "buf_set.zig" diff --git a/std/atomic/index.zig b/std/atomic/index.zig new file mode 100644 index 000000000..9d556a641 --- /dev/null +++ b/std/atomic/index.zig @@ -0,0 +1,7 @@ +pub const Stack = @import("stack.zig").Stack; +pub const Queue = @import("queue.zig").Queue; + +test "std.atomic" { + _ = @import("stack.zig").Stack; + _ = @import("queue.zig").Queue; +} diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig new file mode 100644 index 000000000..54981d4a6 --- /dev/null +++ b/std/atomic/queue.zig @@ -0,0 +1,37 @@ +/// Many reader, many writer, non-allocating, thread-safe, lock-free +pub fn Queue(comptime T: type) type { + return struct { + head: &Node, + tail: &Node, + root: Node, + + pub const Self = this; + + pub const Node = struct { + next: ?&Node, + data: T, + }; + + // TODO: well defined copy elision + pub fn init(self: &Self) void { + self.root.next = null; + self.head = &self.root; + self.tail = &self.root; + } + + pub fn put(self: &Self, node: &Node) void { + node.next = null; + + const tail = @atomicRmw(&Node, &self.tail, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); + _ = @atomicRmw(?&Node, &tail.next, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst); + } + + pub fn get(self: &Self) ?&Node { + var head = @atomicLoad(&Node, &self.head, AtomicOrder.Acquire); + while (true) { + const node = head.next ?? return null; + head = @cmpxchgWeak(&Node, &self.head, head, node, AtomicOrder.Release, AtomicOrder.Acquire) ?? return node; + } + } + }; +} diff --git a/std/atomic/stack.zig b/std/atomic/stack.zig new file mode 100644 index 000000000..4ceecb7b1 --- /dev/null +++ b/std/atomic/stack.zig @@ -0,0 +1,45 @@ +/// Many reader, many writer, non-allocating, thread-safe, lock-free +pub fn Stack(comptime T: type) type { + return struct { + root: ?&Node, + + pub const Self = this; + + pub const Node = struct { + next: ?&Node, + data: T, + }; + + pub fn init() Self { + return Self { + .root = null, + }; + } + + /// push operation, but only if you are the first item in the stack. if you did not succeed in + /// being the first item in the stack, returns the other item that was there. + pub fn pushFirst(self: &Self, node: &Node) ?&Node { + node.next = null; + return @cmpxchgStrong(?&Node, &self.root, null, node, AtomicOrder.AcqRel, AtomicOrder.AcqRel); + } + + pub fn push(self: &Self, node: &Node) void { + var root = @atomicLoad(?&Node, &self.root, AtomicOrder.Acquire); + while (true) { + node.next = root; + root = @cmpxchgWeak(?&Node, &self.root, root, node, AtomicOrder.Release, AtomicOrder.Acquire) ?? break; + } + } + + pub fn pop(self: &Self) ?&Node { + var root = @atomicLoad(?&Node, &self.root, AtomicOrder.Acquire); + while (true) { + root = @cmpxchgWeak(?&Node, &self.root, root, (root ?? return null).next, AtomicOrder.Release, AtomicOrder.Acquire) ?? return root; + } + } + + pub fn isEmpty(self: &Self) bool { + return @atomicLoad(?&Node, &self.root, AtomicOrder.Relaxed) == null; + } + }; +} diff --git a/std/index.zig b/std/index.zig index 07c4360aa..d6a1e3c94 100644 --- a/std/index.zig +++ b/std/index.zig @@ -8,6 +8,7 @@ pub const HashMap = @import("hash_map.zig").HashMap; pub const LinkedList = @import("linked_list.zig").LinkedList; pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList; +pub const atomic = @import("atomic/index.zig"); pub const base64 = @import("base64.zig"); pub const build = @import("build.zig"); pub const c = @import("c/index.zig"); @@ -34,6 +35,7 @@ pub const zig = @import("zig/index.zig"); test "std" { // run tests from these + _ = @import("atomic/index.zig"); _ = @import("array_list.zig"); _ = @import("buf_map.zig"); _ = @import("buf_set.zig"); From 96ecb402590df7a02526009f1630f27e14a0e77c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 28 Apr 2018 17:53:06 -0400 Subject: [PATCH 19/58] add fuzz tests for std.atomic.Stack --- src/ir.cpp | 5 +++ std/atomic/stack.zig | 87 +++++++++++++++++++++++++++++++++++++++++--- std/heap.zig | 64 ++++++++++++++++++++++++++++---- 3 files changed, 143 insertions(+), 13 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 4bf824047..469900bf0 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -18184,6 +18184,11 @@ static TypeTableEntry *ir_analyze_instruction_atomic_rmw(IrAnalyze *ira, IrInstr } else { if (!ir_resolve_atomic_order(ira, instruction->ordering->other, &ordering)) return ira->codegen->builtin_types.entry_invalid; + if (ordering == AtomicOrderUnordered) { + ir_add_error(ira, instruction->ordering, + buf_sprintf("@atomicRmw atomic ordering must not be Unordered")); + return ira->codegen->builtin_types.entry_invalid; + } } if (instr_is_comptime(casted_operand) && instr_is_comptime(casted_ptr) && casted_ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar) diff --git a/std/atomic/stack.zig b/std/atomic/stack.zig index 4ceecb7b1..a1e686155 100644 --- a/std/atomic/stack.zig +++ b/std/atomic/stack.zig @@ -1,3 +1,6 @@ +const builtin = @import("builtin"); +const AtomicOrder = builtin.AtomicOrder; + /// Many reader, many writer, non-allocating, thread-safe, lock-free pub fn Stack(comptime T: type) type { return struct { @@ -20,26 +23,100 @@ pub fn Stack(comptime T: type) type { /// being the first item in the stack, returns the other item that was there. pub fn pushFirst(self: &Self, node: &Node) ?&Node { node.next = null; - return @cmpxchgStrong(?&Node, &self.root, null, node, AtomicOrder.AcqRel, AtomicOrder.AcqRel); + return @cmpxchgStrong(?&Node, &self.root, null, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst); } pub fn push(self: &Self, node: &Node) void { - var root = @atomicLoad(?&Node, &self.root, AtomicOrder.Acquire); + var root = @atomicLoad(?&Node, &self.root, AtomicOrder.SeqCst); while (true) { node.next = root; - root = @cmpxchgWeak(?&Node, &self.root, root, node, AtomicOrder.Release, AtomicOrder.Acquire) ?? break; + root = @cmpxchgWeak(?&Node, &self.root, root, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst) ?? break; } } pub fn pop(self: &Self) ?&Node { var root = @atomicLoad(?&Node, &self.root, AtomicOrder.Acquire); while (true) { - root = @cmpxchgWeak(?&Node, &self.root, root, (root ?? return null).next, AtomicOrder.Release, AtomicOrder.Acquire) ?? return root; + root = @cmpxchgWeak(?&Node, &self.root, root, (root ?? return null).next, AtomicOrder.SeqCst, AtomicOrder.SeqCst) ?? return root; } } pub fn isEmpty(self: &Self) bool { - return @atomicLoad(?&Node, &self.root, AtomicOrder.Relaxed) == null; + return @atomicLoad(?&Node, &self.root, AtomicOrder.SeqCst) == null; } }; } + +const std = @import("std"); +const Context = struct { + allocator: &std.mem.Allocator, + stack: &Stack(i32), + put_sum: isize, + get_sum: isize, + puts_done: u8, // TODO make this a bool +}; +const puts_per_thread = 1000; +const put_thread_count = 3; + +test "std.atomic.stack" { + var direct_allocator = std.heap.DirectAllocator.init(); + defer direct_allocator.deinit(); + + var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 64 * 1024 * 1024); + defer direct_allocator.allocator.free(plenty_of_memory); + + var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); + var a = &fixed_buffer_allocator.allocator; + + var stack = Stack(i32).init(); + var context = Context { + .allocator = a, + .stack = &stack, + .put_sum = 0, + .get_sum = 0, + .puts_done = 0, + }; + + var putters: [put_thread_count]&std.os.Thread = undefined; + for (putters) |*t| { + *t = try std.os.spawnThreadAllocator(a, &context, startPuts); + } + var getters: [put_thread_count]&std.os.Thread = undefined; + for (getters) |*t| { + *t = try std.os.spawnThreadAllocator(a, &context, startGets); + } + + for (putters) |t| t.wait(); + _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); + for (getters) |t| t.wait(); + + std.debug.assert(context.put_sum == context.get_sum); +} + +fn startPuts(ctx: &Context) u8 { + var put_count: usize = puts_per_thread; + var r = std.rand.DefaultPrng.init(0xdeadbeef); + while (put_count != 0) : (put_count -= 1) { + std.os.time.sleep(0, 1); // let the os scheduler be our fuzz + const x = @bitCast(i32, r.random.scalar(u32)); + const node = ctx.allocator.create(Stack(i32).Node) catch unreachable; + node.data = x; + ctx.stack.push(node); + _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); + } + return 0; +} + +fn startGets(ctx: &Context) u8 { + while (true) { + while (ctx.stack.pop()) |node| { + std.os.time.sleep(0, 1); // let the os scheduler be our fuzz + _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); + } + + if (@atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1) { + break; + } + } + return 0; +} diff --git a/std/heap.zig b/std/heap.zig index b3a1e6bf2..d632b44cd 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -47,13 +47,6 @@ pub const DirectAllocator = struct { const HeapHandle = if (builtin.os == Os.windows) os.windows.HANDLE else void; - //pub const canary_bytes = []u8 {48, 239, 128, 46, 18, 49, 147, 9, 195, 59, 203, 3, 245, 54, 9, 122}; - //pub const want_safety = switch (builtin.mode) { - // builtin.Mode.Debug => true, - // builtin.Mode.ReleaseSafe => true, - // else => false, - //}; - pub fn init() DirectAllocator { return DirectAllocator { .allocator = Allocator { @@ -298,7 +291,7 @@ pub const FixedBufferAllocator = struct { fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 { const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); - const addr = @ptrToInt(&self.buffer[self.end_index]); + const addr = @ptrToInt(self.buffer.ptr) + self.end_index; const rem = @rem(addr, alignment); const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); const adjusted_index = self.end_index + march_forward_bytes; @@ -325,6 +318,54 @@ pub const FixedBufferAllocator = struct { fn free(allocator: &Allocator, bytes: []u8) void { } }; +/// lock free +pub const ThreadSafeFixedBufferAllocator = struct { + allocator: Allocator, + end_index: usize, + buffer: []u8, + + pub fn init(buffer: []u8) ThreadSafeFixedBufferAllocator { + return ThreadSafeFixedBufferAllocator { + .allocator = Allocator { + .allocFn = alloc, + .reallocFn = realloc, + .freeFn = free, + }, + .buffer = buffer, + .end_index = 0, + }; + } + + fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 { + const self = @fieldParentPtr(ThreadSafeFixedBufferAllocator, "allocator", allocator); + var end_index = @atomicLoad(usize, &self.end_index, builtin.AtomicOrder.SeqCst); + while (true) { + const addr = @ptrToInt(self.buffer.ptr) + end_index; + const rem = @rem(addr, alignment); + const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); + const adjusted_index = end_index + march_forward_bytes; + const new_end_index = adjusted_index + n; + if (new_end_index > self.buffer.len) { + return error.OutOfMemory; + } + end_index = @cmpxchgWeak(usize, &self.end_index, end_index, new_end_index, + builtin.AtomicOrder.SeqCst, builtin.AtomicOrder.SeqCst) ?? return self.buffer[adjusted_index .. new_end_index]; + } + } + + fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 { + if (new_size <= old_mem.len) { + return old_mem[0..new_size]; + } else { + const result = try alloc(allocator, new_size, alignment); + mem.copy(u8, result, old_mem); + return result; + } + } + + fn free(allocator: &Allocator, bytes: []u8) void { } +}; + test "c_allocator" { @@ -363,6 +404,13 @@ test "FixedBufferAllocator" { try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); } +test "ThreadSafeFixedBufferAllocator" { + var fixed_buffer_allocator = ThreadSafeFixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..]); + + try testAllocator(&fixed_buffer_allocator.allocator); + try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); +} + fn testAllocator(allocator: &mem.Allocator) !void { var slice = try allocator.alloc(&i32, 100); From 5d6e44b3f2bf37deaf09ce4a473fa5b52a6fb760 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 28 Apr 2018 18:00:51 -0400 Subject: [PATCH 20/58] add tests for std.atomic Queue and Stack --- std/atomic/queue.zig | 85 +++++++++++++++++++++++++++++++++++++++++++- std/atomic/stack.zig | 4 +++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig index 54981d4a6..c7ce00d6c 100644 --- a/std/atomic/queue.zig +++ b/std/atomic/queue.zig @@ -1,3 +1,7 @@ +const builtin = @import("builtin"); +const AtomicOrder = builtin.AtomicOrder; +const AtomicRmwOp = builtin.AtomicRmwOp; + /// Many reader, many writer, non-allocating, thread-safe, lock-free pub fn Queue(comptime T: type) type { return struct { @@ -12,7 +16,7 @@ pub fn Queue(comptime T: type) type { data: T, }; - // TODO: well defined copy elision + // TODO: well defined copy elision: https://github.com/zig-lang/zig/issues/287 pub fn init(self: &Self) void { self.root.next = null; self.head = &self.root; @@ -35,3 +39,82 @@ pub fn Queue(comptime T: type) type { } }; } + +const std = @import("std"); +const Context = struct { + allocator: &std.mem.Allocator, + queue: &Queue(i32), + put_sum: isize, + get_sum: isize, + get_count: usize, + puts_done: u8, // TODO make this a bool +}; +const puts_per_thread = 10000; +const put_thread_count = 3; + +test "std.atomic.queue" { + var direct_allocator = std.heap.DirectAllocator.init(); + defer direct_allocator.deinit(); + + var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 64 * 1024 * 1024); + defer direct_allocator.allocator.free(plenty_of_memory); + + var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory); + var a = &fixed_buffer_allocator.allocator; + + var queue: Queue(i32) = undefined; + queue.init(); + var context = Context { + .allocator = a, + .queue = &queue, + .put_sum = 0, + .get_sum = 0, + .puts_done = 0, + .get_count = 0, + }; + + var putters: [put_thread_count]&std.os.Thread = undefined; + for (putters) |*t| { + *t = try std.os.spawnThreadAllocator(a, &context, startPuts); + } + var getters: [put_thread_count]&std.os.Thread = undefined; + for (getters) |*t| { + *t = try std.os.spawnThreadAllocator(a, &context, startGets); + } + + for (putters) |t| t.wait(); + _ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); + for (getters) |t| t.wait(); + + std.debug.assert(context.put_sum == context.get_sum); + std.debug.assert(context.get_count == puts_per_thread * put_thread_count); +} + +fn startPuts(ctx: &Context) u8 { + var put_count: usize = puts_per_thread; + var r = std.rand.DefaultPrng.init(0xdeadbeef); + while (put_count != 0) : (put_count -= 1) { + std.os.time.sleep(0, 1); // let the os scheduler be our fuzz + const x = @bitCast(i32, r.random.scalar(u32)); + const node = ctx.allocator.create(Queue(i32).Node) catch unreachable; + node.data = x; + ctx.queue.put(node); + _ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst); + } + return 0; +} + +fn startGets(ctx: &Context) u8 { + while (true) { + while (ctx.queue.get()) |node| { + std.os.time.sleep(0, 1); // let the os scheduler be our fuzz + _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); + _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); + } + + if (@atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1) { + break; + } + } + return 0; +} diff --git a/std/atomic/stack.zig b/std/atomic/stack.zig index a1e686155..a53f7682b 100644 --- a/std/atomic/stack.zig +++ b/std/atomic/stack.zig @@ -53,6 +53,7 @@ const Context = struct { stack: &Stack(i32), put_sum: isize, get_sum: isize, + get_count: usize, puts_done: u8, // TODO make this a bool }; const puts_per_thread = 1000; @@ -75,6 +76,7 @@ test "std.atomic.stack" { .put_sum = 0, .get_sum = 0, .puts_done = 0, + .get_count = 0, }; var putters: [put_thread_count]&std.os.Thread = undefined; @@ -91,6 +93,7 @@ test "std.atomic.stack" { for (getters) |t| t.wait(); std.debug.assert(context.put_sum == context.get_sum); + std.debug.assert(context.get_count == puts_per_thread * put_thread_count); } fn startPuts(ctx: &Context) u8 { @@ -112,6 +115,7 @@ fn startGets(ctx: &Context) u8 { while (ctx.stack.pop()) |node| { std.os.time.sleep(0, 1); // let the os scheduler be our fuzz _ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst); + _ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst); } if (@atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1) { From a10351b439769c57454e19915365b21e43f408bc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 28 Apr 2018 18:19:00 -0400 Subject: [PATCH 21/58] disable atomic stack and queue tests for non-linux --- std/atomic/queue.zig | 4 ++++ std/atomic/stack.zig | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig index c7ce00d6c..3866bad7c 100644 --- a/std/atomic/queue.zig +++ b/std/atomic/queue.zig @@ -53,6 +53,10 @@ const puts_per_thread = 10000; const put_thread_count = 3; test "std.atomic.queue" { + if (builtin.os != builtin.Os.linux) { + // TODO implement kernel threads for windows and macos + return; + } var direct_allocator = std.heap.DirectAllocator.init(); defer direct_allocator.deinit(); diff --git a/std/atomic/stack.zig b/std/atomic/stack.zig index a53f7682b..12de2edaa 100644 --- a/std/atomic/stack.zig +++ b/std/atomic/stack.zig @@ -60,6 +60,10 @@ const puts_per_thread = 1000; const put_thread_count = 3; test "std.atomic.stack" { + if (builtin.os != builtin.Os.linux) { + // TODO implement kernel threads for windows and macos + return; + } var direct_allocator = std.heap.DirectAllocator.init(); defer direct_allocator.deinit(); From ec2a81a081f6c6d77de1bbeebf1d87754a4fae67 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 28 Apr 2018 22:03:07 -0400 Subject: [PATCH 22/58] fix compiler-rt ABI for x86_64 windows --- std/os/time.zig | 2 +- std/special/compiler_rt/index.zig | 21 +++++++++++++++++---- std/special/compiler_rt/udivmodti4.zig | 6 ++++++ std/special/compiler_rt/udivti3.zig | 9 +++++++-- std/special/compiler_rt/umodti3.zig | 10 ++++++++-- test/cases/eval.zig | 7 +++++++ 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/std/os/time.zig b/std/os/time.zig index e9fbf9798..4fd2c4e92 100644 --- a/std/os/time.zig +++ b/std/os/time.zig @@ -281,7 +281,7 @@ test "os.time.Timer" { debug.assert(time_0 > 0 and time_0 < margin); const time_1 = timer.lap(); - debug.assert(time_1 > time_0); + debug.assert(time_1 >= time_0); timer.reset(); debug.assert(timer.read() < time_1); diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index 81fe1ffec..6ef43c4fe 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -32,10 +32,6 @@ comptime { @export("__fixunstfti", @import("fixunstfti.zig").__fixunstfti, linkage); @export("__udivmoddi4", @import("udivmoddi4.zig").__udivmoddi4, linkage); - @export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage); - - @export("__udivti3", @import("udivti3.zig").__udivti3, linkage); - @export("__umodti3", @import("umodti3.zig").__umodti3, linkage); @export("__udivsi3", __udivsi3, linkage); @export("__udivdi3", __udivdi3, linkage); @@ -62,9 +58,16 @@ comptime { @export("__chkstk", __chkstk, strong_linkage); @export("___chkstk_ms", ___chkstk_ms, linkage); } + @export("__udivti3", @import("udivti3.zig").__udivti3_windows_x86_64, linkage); + @export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4_windows_x86_64, linkage); + @export("__umodti3", @import("umodti3.zig").__umodti3_windows_x86_64, linkage); }, else => {}, } + } else { + @export("__udivti3", @import("udivti3.zig").__udivti3, linkage); + @export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage); + @export("__umodti3", @import("umodti3.zig").__umodti3, linkage); } } @@ -83,6 +86,16 @@ pub fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) noreturn } } +pub fn setXmm0(comptime T: type, value: T) void { + comptime assert(builtin.arch == builtin.Arch.x86_64); + const aligned_value: T align(16) = value; + asm volatile ( + \\movaps (%[ptr]), %%xmm0 + : + : [ptr] "r" (&aligned_value) + : "xmm0"); +} + extern fn __udivdi3(a: u64, b: u64) u64 { @setRuntimeSafety(is_test); return __udivmoddi4(a, b, null); diff --git a/std/special/compiler_rt/udivmodti4.zig b/std/special/compiler_rt/udivmodti4.zig index 196d067ae..f8fdebe4d 100644 --- a/std/special/compiler_rt/udivmodti4.zig +++ b/std/special/compiler_rt/udivmodti4.zig @@ -1,11 +1,17 @@ const udivmod = @import("udivmod.zig").udivmod; const builtin = @import("builtin"); +const compiler_rt = @import("index.zig"); pub extern fn __udivmodti4(a: u128, b: u128, maybe_rem: ?&u128) u128 { @setRuntimeSafety(builtin.is_test); return udivmod(u128, a, b, maybe_rem); } +pub extern fn __udivmodti4_windows_x86_64(a: &const u128, b: &const u128, maybe_rem: ?&u128) void { + @setRuntimeSafety(builtin.is_test); + compiler_rt.setXmm0(u128, udivmod(u128, *a, *b, maybe_rem)); +} + test "import udivmodti4" { _ = @import("udivmodti4_test.zig"); } diff --git a/std/special/compiler_rt/udivti3.zig b/std/special/compiler_rt/udivti3.zig index eaecbac4d..ad0f09e73 100644 --- a/std/special/compiler_rt/udivti3.zig +++ b/std/special/compiler_rt/udivti3.zig @@ -1,7 +1,12 @@ -const __udivmodti4 = @import("udivmodti4.zig").__udivmodti4; +const udivmodti4 = @import("udivmodti4.zig"); const builtin = @import("builtin"); pub extern fn __udivti3(a: u128, b: u128) u128 { @setRuntimeSafety(builtin.is_test); - return __udivmodti4(a, b, null); + return udivmodti4.__udivmodti4(a, b, null); +} + +pub extern fn __udivti3_windows_x86_64(a: &const u128, b: &const u128) void { + @setRuntimeSafety(builtin.is_test); + udivmodti4.__udivmodti4_windows_x86_64(a, b, null); } diff --git a/std/special/compiler_rt/umodti3.zig b/std/special/compiler_rt/umodti3.zig index 26b306efa..3e8b80058 100644 --- a/std/special/compiler_rt/umodti3.zig +++ b/std/special/compiler_rt/umodti3.zig @@ -1,9 +1,15 @@ -const __udivmodti4 = @import("udivmodti4.zig").__udivmodti4; +const udivmodti4 = @import("udivmodti4.zig"); const builtin = @import("builtin"); +const compiler_rt = @import("index.zig"); pub extern fn __umodti3(a: u128, b: u128) u128 { @setRuntimeSafety(builtin.is_test); var r: u128 = undefined; - _ = __udivmodti4(a, b, &r); + _ = udivmodti4.__udivmodti4(a, b, &r); return r; } + +pub extern fn __umodti3_windows_x86_64(a: &const u128, b: &const u128) void { + @setRuntimeSafety(builtin.is_test); + compiler_rt.setXmm0(u128, __umodti3(*a, *b)); +} diff --git a/test/cases/eval.zig b/test/cases/eval.zig index e13d4340e..364db5e15 100644 --- a/test/cases/eval.zig +++ b/test/cases/eval.zig @@ -529,3 +529,10 @@ test "comptime shlWithOverflow" { assert(ct_shifted == rt_shifted); } + +test "runtime 128 bit integer division" { + var a: u128 = 152313999999999991610955792383; + var b: u128 = 10000000000000000000; + var c = a / b; + assert(c == 15231399999); +} From a344cb03bc3c48f3c7fec32dc19c1bcad0910941 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 28 Apr 2018 23:30:13 -0400 Subject: [PATCH 23/58] *WIP* use pthreads when linking libc --- std/c/darwin.zig | 5 +++ std/c/index.zig | 10 +++++ std/c/linux.zig | 5 +++ std/os/index.zig | 112 +++++++++++++++++++++++++++++++++++------------ std/os/test.zig | 4 +- 5 files changed, 107 insertions(+), 29 deletions(-) diff --git a/std/c/darwin.zig b/std/c/darwin.zig index b958055ae..7ac57514c 100644 --- a/std/c/darwin.zig +++ b/std/c/darwin.zig @@ -81,3 +81,8 @@ pub const sockaddr = extern struct { }; pub const sa_family_t = u8; + +pub const pthread_attr_t = extern struct { + __sig: c_long, + __opaque: [56]u8, +}; diff --git a/std/c/index.zig b/std/c/index.zig index cff86f404..5ea7145cd 100644 --- a/std/c/index.zig +++ b/std/c/index.zig @@ -53,3 +53,13 @@ pub extern "c" fn malloc(usize) ?&c_void; pub extern "c" fn realloc(&c_void, usize) ?&c_void; pub extern "c" fn free(&c_void) void; pub extern "c" fn posix_memalign(memptr: &&c_void, alignment: usize, size: usize) c_int; + +pub extern "c" fn pthread_create(noalias newthread: &pthread_t, + noalias attr: ?&const pthread_attr_t, start_routine: extern fn(?&c_void) ?&c_void, + noalias arg: ?&c_void) c_int; +pub extern "c" fn pthread_attr_init(attr: &pthread_attr_t) c_int; +pub extern "c" fn pthread_attr_setstack(attr: &pthread_attr_t, stackaddr: &c_void, stacksize: usize) c_int; +pub extern "c" fn pthread_attr_destroy(attr: &pthread_attr_t) c_int; +pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?&?&c_void) c_int; + +pub const pthread_t = &@OpaqueType(); diff --git a/std/c/linux.zig b/std/c/linux.zig index b2ac05eba..7810fec13 100644 --- a/std/c/linux.zig +++ b/std/c/linux.zig @@ -3,3 +3,8 @@ pub use @import("../os/linux/errno.zig"); pub extern "c" fn getrandom(buf_ptr: &u8, buf_len: usize, flags: c_uint) c_int; extern "c" fn __errno_location() &c_int; pub const _errno = __errno_location; + +pub const pthread_attr_t = extern struct { + __size: [56]u8, + __align: c_long, +}; diff --git a/std/os/index.zig b/std/os/index.zig index 063949072..3669dca19 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2,6 +2,10 @@ const std = @import("../index.zig"); const builtin = @import("builtin"); const Os = builtin.Os; const is_windows = builtin.os == Os.windows; +const is_posix = switch (builtin.os) { + builtin.Os.linux, builtin.Os.macosx => true, + else => false, +}; const os = this; test "std.os" { @@ -2343,21 +2347,39 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void { } pub const Thread = struct { - pid: i32, + pid: pid_t, allocator: ?&mem.Allocator, stack: []u8, + pthread_handle: pthread_t, + + pub const use_pthreads = is_posix and builtin.link_libc; + const pthread_t = if (use_pthreads) c.pthread_t else void; + const pid_t = if (!use_pthreads) i32 else void; pub fn wait(self: &const Thread) void { - while (true) { - const pid_value = @atomicLoad(i32, &self.pid, builtin.AtomicOrder.SeqCst); - if (pid_value == 0) break; - const rc = linux.futex_wait(@ptrToInt(&self.pid), linux.FUTEX_WAIT, pid_value, null); - switch (linux.getErrno(rc)) { - 0 => continue, - posix.EINTR => continue, - posix.EAGAIN => continue, + if (use_pthreads) { + const err = c.pthread_join(self.pthread_handle, null); + switch (err) { + 0 => {}, + posix.EINVAL => unreachable, + posix.ESRCH => unreachable, + posix.EDEADLK => unreachable, else => unreachable, } + } else if (builtin.os == builtin.Os.linux) { + while (true) { + const pid_value = @atomicLoad(i32, &self.pid, builtin.AtomicOrder.SeqCst); + if (pid_value == 0) break; + const rc = linux.futex_wait(@ptrToInt(&self.pid), linux.FUTEX_WAIT, pid_value, null); + switch (linux.getErrno(rc)) { + 0 => continue, + posix.EINTR => continue, + posix.EAGAIN => continue, + else => unreachable, + } + } + } else { + @compileError("Unsupported OS"); } if (self.allocator) |a| { a.free(self.stack); @@ -2429,31 +2451,67 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread thread_ptr.stack = stack; thread_ptr.allocator = null; - const threadMain = struct { - extern fn threadMain(ctx_addr: usize) u8 { + const MainFuncs = struct { + extern fn linuxThreadMain(ctx_addr: usize) u8 { if (@sizeOf(Context) == 0) { return startFn({}); } else { return startFn(*@intToPtr(&const Context, ctx_addr)); } } - }.threadMain; + extern fn posixThreadMain(ctx: ?&c_void) ?&c_void { + if (@sizeOf(Context) == 0) { + _ = startFn({}); + return null; + } else { + _ = startFn(*@ptrCast(&const Context, @alignCast(@alignOf(Context), ctx))); + return null; + } + } + }; - const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND - | posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS - | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; - const newtls: usize = 0; - const rc = posix.clone(threadMain, stack_end, flags, arg, &thread_ptr.pid, newtls, &thread_ptr.pid); - const err = posix.getErrno(rc); - switch (err) { - 0 => return thread_ptr, - posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded, - posix.EINVAL => unreachable, - posix.ENOMEM => return SpawnThreadError.SystemResources, - posix.ENOSPC => unreachable, - posix.EPERM => unreachable, - posix.EUSERS => unreachable, - else => return unexpectedErrorPosix(err), + if (builtin.os == builtin.Os.windows) { + // use windows API directly + @compileError("TODO support spawnThread for Windows"); + } else if (Thread.use_pthreads) { + // use pthreads + var attr: c.pthread_attr_t = undefined; + if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources; + defer assert(c.pthread_attr_destroy(&attr) == 0); + + const stack_size = stack_end - @ptrToInt(stack.ptr); + if (c.pthread_attr_setstack(&attr, @ptrCast(&c_void, stack.ptr), stack_size) != 0) { + return SpawnThreadError.SystemResources; + } + + const err = c.pthread_create(&thread_ptr.pthread_handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg)); + switch (err) { + 0 => return thread_ptr, + posix.EAGAIN => return SpawnThreadError.SystemResources, + posix.EPERM => unreachable, + posix.EINVAL => unreachable, + else => return unexpectedErrorPosix(usize(err)), + } + } else if (builtin.os == builtin.Os.linux) { + // use linux API directly + const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND + | posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS + | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; + const newtls: usize = 0; + const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.pid, newtls, &thread_ptr.pid); + const err = posix.getErrno(rc); + switch (err) { + 0 => return thread_ptr, + posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded, + posix.EINVAL => unreachable, + posix.ENOMEM => return SpawnThreadError.SystemResources, + posix.ENOSPC => unreachable, + posix.EPERM => unreachable, + posix.EUSERS => unreachable, + else => return unexpectedErrorPosix(err), + } + } else { + @compileError("Unsupported OS"); } } diff --git a/std/os/test.zig b/std/os/test.zig index 41afee004..9a155c027 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -44,8 +44,8 @@ test "access file" { } test "spawn threads" { - if (builtin.os != builtin.Os.linux) { - // TODO implement threads on macos and windows + if (builtin.os == builtin.Os.windows) { + // TODO implement threads on windows return; } From 998e25a01e8b3ada235aee4a9f785a7454de4b3f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 28 Apr 2018 23:47:39 -0400 Subject: [PATCH 24/58] pthread support working --- src/all_types.hpp | 1 + src/analyze.cpp | 8 ++++++++ src/codegen.cpp | 2 ++ std/os/index.zig | 14 ++++++++------ std/os/test.zig | 4 ++-- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index d1b2ad61d..f08b870b3 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1486,6 +1486,7 @@ struct CodeGen { ZigList link_libs_list; LinkLib *libc_link_lib; + LinkLib *pthread_link_lib; // add -framework [name] args to linker ZigList darwin_frameworks; diff --git a/src/analyze.cpp b/src/analyze.cpp index 1ecfe32f4..8a9d23679 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -6049,10 +6049,15 @@ LinkLib *create_link_lib(Buf *name) { LinkLib *add_link_lib(CodeGen *g, Buf *name) { bool is_libc = buf_eql_str(name, "c"); + bool is_pthread = buf_eql_str(name, "pthread"); if (is_libc && g->libc_link_lib != nullptr) return g->libc_link_lib; + if (is_pthread && g->pthread_link_lib != nullptr) { + return g->pthread_link_lib; + } + for (size_t i = 0; i < g->link_libs_list.length; i += 1) { LinkLib *existing_lib = g->link_libs_list.at(i); if (buf_eql_buf(existing_lib->name, name)) { @@ -6066,6 +6071,9 @@ LinkLib *add_link_lib(CodeGen *g, Buf *name) { if (is_libc) g->libc_link_lib = link_lib; + if (is_pthread) + g->pthread_link_lib = link_lib; + return link_lib; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 2d8c385f4..9f064d5f1 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -145,6 +145,7 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out { g->libc_link_lib = create_link_lib(buf_create_from_str("c")); g->link_libs_list.append(g->libc_link_lib); + g->pthread_link_lib = create_link_lib(buf_create_from_str("pthread")); } return g; @@ -6373,6 +6374,7 @@ static void define_builtin_compile_vars(CodeGen *g) { buf_appendf(contents, "pub const object_format = ObjectFormat.%s;\n", cur_obj_fmt); buf_appendf(contents, "pub const mode = %s;\n", build_mode_to_str(g->build_mode)); buf_appendf(contents, "pub const link_libc = %s;\n", bool_to_str(g->libc_link_lib != nullptr)); + buf_appendf(contents, "pub const link_pthread = %s;\n", bool_to_str(g->pthread_link_lib != nullptr)); buf_appendf(contents, "pub const have_error_return_tracing = %s;\n", bool_to_str(g->have_err_ret_tracing)); buf_appendf(contents, "pub const __zig_test_fn_slice = {}; // overwritten later\n"); diff --git a/std/os/index.zig b/std/os/index.zig index 3669dca19..fa1cc418a 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2352,12 +2352,11 @@ pub const Thread = struct { stack: []u8, pthread_handle: pthread_t, - pub const use_pthreads = is_posix and builtin.link_libc; - const pthread_t = if (use_pthreads) c.pthread_t else void; - const pid_t = if (!use_pthreads) i32 else void; + const pthread_t = if (builtin.link_pthread) c.pthread_t else void; + const pid_t = if (!builtin.link_pthread) i32 else void; pub fn wait(self: &const Thread) void { - if (use_pthreads) { + if (builtin.link_pthread) { const err = c.pthread_join(self.pthread_handle, null); switch (err) { 0 => {}, @@ -2407,6 +2406,9 @@ pub const SpawnThreadError = error { /// be copied. SystemResources, + /// pthreads requires at least 16384 bytes of stack space + StackTooSmall, + Unexpected, }; @@ -2473,7 +2475,7 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread if (builtin.os == builtin.Os.windows) { // use windows API directly @compileError("TODO support spawnThread for Windows"); - } else if (Thread.use_pthreads) { + } else if (builtin.link_pthread) { // use pthreads var attr: c.pthread_attr_t = undefined; if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources; @@ -2481,7 +2483,7 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread const stack_size = stack_end - @ptrToInt(stack.ptr); if (c.pthread_attr_setstack(&attr, @ptrCast(&c_void, stack.ptr), stack_size) != 0) { - return SpawnThreadError.SystemResources; + return SpawnThreadError.StackTooSmall; // pthreads requires at least 16384 bytes } const err = c.pthread_create(&thread_ptr.pthread_handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg)); diff --git a/std/os/test.zig b/std/os/test.zig index 9a155c027..87486bde4 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -57,8 +57,8 @@ test "spawn threads" { const thread1 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, {}, start1); const thread4 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, &shared_ctx, start2); - var stack1: [1024]u8 = undefined; - var stack2: [1024]u8 = undefined; + var stack1: [20 * 1024]u8 = undefined; + var stack2: [20 * 1024]u8 = undefined; const thread2 = try std.os.spawnThread(stack1[0..], &shared_ctx, start2); const thread3 = try std.os.spawnThread(stack2[0..], &shared_ctx, start2); From a42542099392cf189b96bdd77ecd88feadfb6382 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 00:07:32 -0400 Subject: [PATCH 25/58] make pthreads threads work on darwin darwin pthreads adds a restriction that the stack start and end must be page aligned --- std/os/index.zig | 10 +++++++--- std/os/test.zig | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/std/os/index.zig b/std/os/index.zig index fa1cc418a..8681a018b 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2421,7 +2421,7 @@ pub fn spawnThreadAllocator(allocator: &mem.Allocator, context: var, comptime st // TODO compile-time call graph analysis to determine stack upper bound // https://github.com/zig-lang/zig/issues/157 const default_stack_size = 8 * 1024 * 1024; - const stack_bytes = try allocator.alloc(u8, default_stack_size); + const stack_bytes = try allocator.alignedAlloc(u8, os.page_size, default_stack_size); const thread = try spawnThread(stack_bytes, context, startFn); thread.allocator = allocator; return thread; @@ -2431,7 +2431,7 @@ pub fn spawnThreadAllocator(allocator: &mem.Allocator, context: var, comptime st /// fn startFn(@typeOf(context)) T /// where T is u8, noreturn, void, or !void /// caller must call wait on the returned thread -pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThreadError!&Thread { +pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime startFn: var) SpawnThreadError!&Thread { const Context = @typeOf(context); comptime assert(@ArgType(@typeOf(startFn), 0) == Context); @@ -2481,8 +2481,12 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources; defer assert(c.pthread_attr_destroy(&attr) == 0); + // align to page + stack_end -= stack_end % os.page_size; + const stack_size = stack_end - @ptrToInt(stack.ptr); - if (c.pthread_attr_setstack(&attr, @ptrCast(&c_void, stack.ptr), stack_size) != 0) { + const setstack_err = c.pthread_attr_setstack(&attr, @ptrCast(&c_void, stack.ptr), stack_size); + if (setstack_err != 0) { return SpawnThreadError.StackTooSmall; // pthreads requires at least 16384 bytes } diff --git a/std/os/test.zig b/std/os/test.zig index 87486bde4..37e5bf4bb 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -57,8 +57,8 @@ test "spawn threads" { const thread1 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, {}, start1); const thread4 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, &shared_ctx, start2); - var stack1: [20 * 1024]u8 = undefined; - var stack2: [20 * 1024]u8 = undefined; + var stack1: [20 * 1024]u8 align(os.page_size) = undefined; + var stack2: [20 * 1024]u8 align(os.page_size) = undefined; const thread2 = try std.os.spawnThread(stack1[0..], &shared_ctx, start2); const thread3 = try std.os.spawnThread(stack2[0..], &shared_ctx, start2); From abf90eaa674782e092e49bb23c4c7da0f581f604 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 00:09:18 -0400 Subject: [PATCH 26/58] enable atomic queue and stack tests for macos --- std/atomic/queue.zig | 4 ++-- std/atomic/stack.zig | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig index 3866bad7c..dd9b869f0 100644 --- a/std/atomic/queue.zig +++ b/std/atomic/queue.zig @@ -53,8 +53,8 @@ const puts_per_thread = 10000; const put_thread_count = 3; test "std.atomic.queue" { - if (builtin.os != builtin.Os.linux) { - // TODO implement kernel threads for windows and macos + if (builtin.os == builtin.Os.windows) { + // TODO implement kernel threads for windows return; } var direct_allocator = std.heap.DirectAllocator.init(); diff --git a/std/atomic/stack.zig b/std/atomic/stack.zig index 12de2edaa..9f2ceacfa 100644 --- a/std/atomic/stack.zig +++ b/std/atomic/stack.zig @@ -60,8 +60,8 @@ const puts_per_thread = 1000; const put_thread_count = 3; test "std.atomic.stack" { - if (builtin.os != builtin.Os.linux) { - // TODO implement kernel threads for windows and macos + if (builtin.os == builtin.Os.windows) { + // TODO implement kernel threads for windows return; } var direct_allocator = std.heap.DirectAllocator.init(); From bf8e419d2b7853f5cb5aba4dcba45ae28a3840aa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 00:40:04 -0400 Subject: [PATCH 27/58] linux uses pthreads when linking against libc --- src/all_types.hpp | 1 - src/analyze.cpp | 8 -------- src/codegen.cpp | 2 -- std/c/index.zig | 10 +++++----- std/os/index.zig | 9 +++++---- 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index f08b870b3..d1b2ad61d 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1486,7 +1486,6 @@ struct CodeGen { ZigList link_libs_list; LinkLib *libc_link_lib; - LinkLib *pthread_link_lib; // add -framework [name] args to linker ZigList darwin_frameworks; diff --git a/src/analyze.cpp b/src/analyze.cpp index 8a9d23679..1ecfe32f4 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -6049,15 +6049,10 @@ LinkLib *create_link_lib(Buf *name) { LinkLib *add_link_lib(CodeGen *g, Buf *name) { bool is_libc = buf_eql_str(name, "c"); - bool is_pthread = buf_eql_str(name, "pthread"); if (is_libc && g->libc_link_lib != nullptr) return g->libc_link_lib; - if (is_pthread && g->pthread_link_lib != nullptr) { - return g->pthread_link_lib; - } - for (size_t i = 0; i < g->link_libs_list.length; i += 1) { LinkLib *existing_lib = g->link_libs_list.at(i); if (buf_eql_buf(existing_lib->name, name)) { @@ -6071,9 +6066,6 @@ LinkLib *add_link_lib(CodeGen *g, Buf *name) { if (is_libc) g->libc_link_lib = link_lib; - if (is_pthread) - g->pthread_link_lib = link_lib; - return link_lib; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 9f064d5f1..2d8c385f4 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -145,7 +145,6 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out { g->libc_link_lib = create_link_lib(buf_create_from_str("c")); g->link_libs_list.append(g->libc_link_lib); - g->pthread_link_lib = create_link_lib(buf_create_from_str("pthread")); } return g; @@ -6374,7 +6373,6 @@ static void define_builtin_compile_vars(CodeGen *g) { buf_appendf(contents, "pub const object_format = ObjectFormat.%s;\n", cur_obj_fmt); buf_appendf(contents, "pub const mode = %s;\n", build_mode_to_str(g->build_mode)); buf_appendf(contents, "pub const link_libc = %s;\n", bool_to_str(g->libc_link_lib != nullptr)); - buf_appendf(contents, "pub const link_pthread = %s;\n", bool_to_str(g->pthread_link_lib != nullptr)); buf_appendf(contents, "pub const have_error_return_tracing = %s;\n", bool_to_str(g->have_err_ret_tracing)); buf_appendf(contents, "pub const __zig_test_fn_slice = {}; // overwritten later\n"); diff --git a/std/c/index.zig b/std/c/index.zig index 5ea7145cd..34269d2aa 100644 --- a/std/c/index.zig +++ b/std/c/index.zig @@ -54,12 +54,12 @@ pub extern "c" fn realloc(&c_void, usize) ?&c_void; pub extern "c" fn free(&c_void) void; pub extern "c" fn posix_memalign(memptr: &&c_void, alignment: usize, size: usize) c_int; -pub extern "c" fn pthread_create(noalias newthread: &pthread_t, +pub extern "pthread" fn pthread_create(noalias newthread: &pthread_t, noalias attr: ?&const pthread_attr_t, start_routine: extern fn(?&c_void) ?&c_void, noalias arg: ?&c_void) c_int; -pub extern "c" fn pthread_attr_init(attr: &pthread_attr_t) c_int; -pub extern "c" fn pthread_attr_setstack(attr: &pthread_attr_t, stackaddr: &c_void, stacksize: usize) c_int; -pub extern "c" fn pthread_attr_destroy(attr: &pthread_attr_t) c_int; -pub extern "c" fn pthread_join(thread: pthread_t, arg_return: ?&?&c_void) c_int; +pub extern "pthread" fn pthread_attr_init(attr: &pthread_attr_t) c_int; +pub extern "pthread" fn pthread_attr_setstack(attr: &pthread_attr_t, stackaddr: &c_void, stacksize: usize) c_int; +pub extern "pthread" fn pthread_attr_destroy(attr: &pthread_attr_t) c_int; +pub extern "pthread" fn pthread_join(thread: pthread_t, arg_return: ?&?&c_void) c_int; pub const pthread_t = &@OpaqueType(); diff --git a/std/os/index.zig b/std/os/index.zig index 8681a018b..85e46a1bf 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2352,11 +2352,12 @@ pub const Thread = struct { stack: []u8, pthread_handle: pthread_t, - const pthread_t = if (builtin.link_pthread) c.pthread_t else void; - const pid_t = if (!builtin.link_pthread) i32 else void; + pub const use_pthreads = is_posix and builtin.link_libc; + const pthread_t = if (use_pthreads) c.pthread_t else void; + const pid_t = if (!use_pthreads) i32 else void; pub fn wait(self: &const Thread) void { - if (builtin.link_pthread) { + if (use_pthreads) { const err = c.pthread_join(self.pthread_handle, null); switch (err) { 0 => {}, @@ -2475,7 +2476,7 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start if (builtin.os == builtin.Os.windows) { // use windows API directly @compileError("TODO support spawnThread for Windows"); - } else if (builtin.link_pthread) { + } else if (Thread.use_pthreads) { // use pthreads var attr: c.pthread_attr_t = undefined; if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources; From 6376d96824c5205ecc02b2c621bcef5dc78f1a81 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 02:40:22 -0400 Subject: [PATCH 28/58] support kernel threads for windows * remove std.os.spawnThreadAllocator - windows does not support an explicit stack, so using an allocator for a thread stack space does not work. * std.os.spawnThread - instead of accepting a stack argument, the implementation will directly allocate using OS-specific APIs. --- std/atomic/queue.zig | 8 +- std/atomic/stack.zig | 8 +- std/mem.zig | 1 + std/os/index.zig | 165 +++++++++++++++++++++++++-------------- std/os/test.zig | 20 +---- std/os/windows/index.zig | 6 ++ 6 files changed, 120 insertions(+), 88 deletions(-) diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig index dd9b869f0..1acecbab2 100644 --- a/std/atomic/queue.zig +++ b/std/atomic/queue.zig @@ -53,10 +53,6 @@ const puts_per_thread = 10000; const put_thread_count = 3; test "std.atomic.queue" { - if (builtin.os == builtin.Os.windows) { - // TODO implement kernel threads for windows - return; - } var direct_allocator = std.heap.DirectAllocator.init(); defer direct_allocator.deinit(); @@ -79,11 +75,11 @@ test "std.atomic.queue" { var putters: [put_thread_count]&std.os.Thread = undefined; for (putters) |*t| { - *t = try std.os.spawnThreadAllocator(a, &context, startPuts); + *t = try std.os.spawnThread(&context, startPuts); } var getters: [put_thread_count]&std.os.Thread = undefined; for (getters) |*t| { - *t = try std.os.spawnThreadAllocator(a, &context, startGets); + *t = try std.os.spawnThread(&context, startGets); } for (putters) |t| t.wait(); diff --git a/std/atomic/stack.zig b/std/atomic/stack.zig index 9f2ceacfa..accbcc942 100644 --- a/std/atomic/stack.zig +++ b/std/atomic/stack.zig @@ -60,10 +60,6 @@ const puts_per_thread = 1000; const put_thread_count = 3; test "std.atomic.stack" { - if (builtin.os == builtin.Os.windows) { - // TODO implement kernel threads for windows - return; - } var direct_allocator = std.heap.DirectAllocator.init(); defer direct_allocator.deinit(); @@ -85,11 +81,11 @@ test "std.atomic.stack" { var putters: [put_thread_count]&std.os.Thread = undefined; for (putters) |*t| { - *t = try std.os.spawnThreadAllocator(a, &context, startPuts); + *t = try std.os.spawnThread(&context, startPuts); } var getters: [put_thread_count]&std.os.Thread = undefined; for (getters) |*t| { - *t = try std.os.spawnThreadAllocator(a, &context, startGets); + *t = try std.os.spawnThread(&context, startGets); } for (putters) |t| t.wait(); diff --git a/std/mem.zig b/std/mem.zig index cc3161cdd..0f66f549c 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -32,6 +32,7 @@ pub const Allocator = struct { freeFn: fn (self: &Allocator, old_mem: []u8) void, fn create(self: &Allocator, comptime T: type) !&T { + if (@sizeOf(T) == 0) return &{}; const slice = try self.alloc(T, 1); return &slice[0]; } diff --git a/std/os/index.zig b/std/os/index.zig index 85e46a1bf..6842fd0fb 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2347,18 +2347,30 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void { } pub const Thread = struct { - pid: pid_t, - allocator: ?&mem.Allocator, - stack: []u8, - pthread_handle: pthread_t, + data: Data, pub const use_pthreads = is_posix and builtin.link_libc; - const pthread_t = if (use_pthreads) c.pthread_t else void; - const pid_t = if (!use_pthreads) i32 else void; + const Data = if (use_pthreads) struct { + handle: c.pthread_t, + stack_addr: usize, + stack_len: usize, + } else switch (builtin.os) { + builtin.Os.linux => struct { + pid: i32, + stack_addr: usize, + stack_len: usize, + }, + builtin.Os.windows => struct { + handle: windows.HANDLE, + alloc_start: &c_void, + heap_handle: windows.HANDLE, + }, + else => @compileError("Unsupported OS"), + }; pub fn wait(self: &const Thread) void { if (use_pthreads) { - const err = c.pthread_join(self.pthread_handle, null); + const err = c.pthread_join(self.data.handle, null); switch (err) { 0 => {}, posix.EINVAL => unreachable, @@ -2366,23 +2378,27 @@ pub const Thread = struct { posix.EDEADLK => unreachable, else => unreachable, } - } else if (builtin.os == builtin.Os.linux) { - while (true) { - const pid_value = @atomicLoad(i32, &self.pid, builtin.AtomicOrder.SeqCst); - if (pid_value == 0) break; - const rc = linux.futex_wait(@ptrToInt(&self.pid), linux.FUTEX_WAIT, pid_value, null); - switch (linux.getErrno(rc)) { - 0 => continue, - posix.EINTR => continue, - posix.EAGAIN => continue, - else => unreachable, + assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0); + } else switch (builtin.os) { + builtin.Os.linux => { + while (true) { + const pid_value = @atomicLoad(i32, &self.data.pid, builtin.AtomicOrder.SeqCst); + if (pid_value == 0) break; + const rc = linux.futex_wait(@ptrToInt(&self.data.pid), linux.FUTEX_WAIT, pid_value, null); + switch (linux.getErrno(rc)) { + 0 => continue, + posix.EINTR => continue, + posix.EAGAIN => continue, + else => unreachable, + } } - } - } else { - @compileError("Unsupported OS"); - } - if (self.allocator) |a| { - a.free(self.stack); + assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0); + }, + builtin.Os.windows => { + assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0); + assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0); + }, + else => @compileError("Unsupported OS"), } } }; @@ -2407,52 +2423,60 @@ pub const SpawnThreadError = error { /// be copied. SystemResources, - /// pthreads requires at least 16384 bytes of stack space - StackTooSmall, + /// Not enough userland memory to spawn the thread. + OutOfMemory, Unexpected, }; -pub const SpawnThreadAllocatorError = SpawnThreadError || error{OutOfMemory}; - /// caller must call wait on the returned thread /// fn startFn(@typeOf(context)) T /// where T is u8, noreturn, void, or !void -pub fn spawnThreadAllocator(allocator: &mem.Allocator, context: var, comptime startFn: var) SpawnThreadAllocatorError!&Thread { +/// caller must call wait on the returned thread +pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread { // TODO compile-time call graph analysis to determine stack upper bound // https://github.com/zig-lang/zig/issues/157 const default_stack_size = 8 * 1024 * 1024; - const stack_bytes = try allocator.alignedAlloc(u8, os.page_size, default_stack_size); - const thread = try spawnThread(stack_bytes, context, startFn); - thread.allocator = allocator; - return thread; -} -/// stack must be big enough to store one Thread and one @typeOf(context), each with default alignment, at the end -/// fn startFn(@typeOf(context)) T -/// where T is u8, noreturn, void, or !void -/// caller must call wait on the returned thread -pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime startFn: var) SpawnThreadError!&Thread { const Context = @typeOf(context); comptime assert(@ArgType(@typeOf(startFn), 0) == Context); - var stack_end: usize = @ptrToInt(stack.ptr) + stack.len; - var arg: usize = undefined; - if (@sizeOf(Context) != 0) { - stack_end -= @sizeOf(Context); - stack_end -= stack_end % @alignOf(Context); - assert(stack_end >= @ptrToInt(stack.ptr)); - const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end)); - *context_ptr = context; - arg = stack_end; - } + if (builtin.os == builtin.Os.windows) { + const WinThread = struct { + const OuterContext = struct { + thread: Thread, + inner: Context, + }; + extern fn threadMain(arg: windows.LPVOID) windows.DWORD { + if (@sizeOf(Context) == 0) { + return startFn({}); + } else { + return startFn(*@ptrCast(&Context, @alignCast(@alignOf(Context), arg))); + } + } + }; - stack_end -= @sizeOf(Thread); - stack_end -= stack_end % @alignOf(Thread); - assert(stack_end >= @ptrToInt(stack.ptr)); - const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end)); - thread_ptr.stack = stack; - thread_ptr.allocator = null; + const heap_handle = windows.GetProcessHeap() ?? return SpawnThreadError.OutOfMemory; + const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext); + const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) ?? return SpawnThreadError.OutOfMemory; + errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0); + const bytes = @ptrCast(&u8, bytes_ptr)[0..byte_count]; + const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable; + outer_context.inner = context; + outer_context.thread.data.heap_handle = heap_handle; + outer_context.thread.data.alloc_start = bytes_ptr; + + const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(&c_void, &outer_context.inner); + outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, + parameter, 0, null) ?? + { + const err = windows.GetLastError(); + return switch (err) { + else => os.unexpectedErrorWindows(err), + }; + }; + return &outer_context.thread; + } const MainFuncs = struct { extern fn linuxThreadMain(ctx_addr: usize) u8 { @@ -2473,6 +2497,29 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start } }; + const stack_len = default_stack_size; + const stack_addr = posix.mmap(null, stack_len, posix.PROT_READ|posix.PROT_WRITE, + posix.MAP_PRIVATE|posix.MAP_ANONYMOUS|posix.MAP_GROWSDOWN, -1, 0); + if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory; + errdefer _ = posix.munmap(stack_addr, stack_len); + + var stack_end: usize = stack_addr + stack_len; + var arg: usize = undefined; + if (@sizeOf(Context) != 0) { + stack_end -= @sizeOf(Context); + stack_end -= stack_end % @alignOf(Context); + assert(stack_end >= stack_addr); + const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end)); + *context_ptr = context; + arg = stack_end; + } + + stack_end -= @sizeOf(Thread); + stack_end -= stack_end % @alignOf(Thread); + assert(stack_end >= stack_addr); + const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end)); + + if (builtin.os == builtin.Os.windows) { // use windows API directly @compileError("TODO support spawnThread for Windows"); @@ -2484,14 +2531,12 @@ pub fn spawnThread(stack: []align(os.page_size) u8, context: var, comptime start // align to page stack_end -= stack_end % os.page_size; + assert(c.pthread_attr_setstack(&attr, @intToPtr(&c_void, stack_addr), stack_len) == 0); - const stack_size = stack_end - @ptrToInt(stack.ptr); - const setstack_err = c.pthread_attr_setstack(&attr, @ptrCast(&c_void, stack.ptr), stack_size); - if (setstack_err != 0) { - return SpawnThreadError.StackTooSmall; // pthreads requires at least 16384 bytes - } + thread_ptr.data.stack_addr = stack_addr; + thread_ptr.data.stack_len = stack_len; - const err = c.pthread_create(&thread_ptr.pthread_handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg)); + const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg)); switch (err) { 0 => return thread_ptr, posix.EAGAIN => return SpawnThreadError.SystemResources, diff --git a/std/os/test.zig b/std/os/test.zig index 37e5bf4bb..56d6e8b30 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -44,24 +44,12 @@ test "access file" { } test "spawn threads" { - if (builtin.os == builtin.Os.windows) { - // TODO implement threads on windows - return; - } - - var direct_allocator = std.heap.DirectAllocator.init(); - defer direct_allocator.deinit(); - var shared_ctx: i32 = 1; - const thread1 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, {}, start1); - const thread4 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, &shared_ctx, start2); - - var stack1: [20 * 1024]u8 align(os.page_size) = undefined; - var stack2: [20 * 1024]u8 align(os.page_size) = undefined; - - const thread2 = try std.os.spawnThread(stack1[0..], &shared_ctx, start2); - const thread3 = try std.os.spawnThread(stack2[0..], &shared_ctx, start2); + const thread1 = try std.os.spawnThread({}, start1); + const thread2 = try std.os.spawnThread(&shared_ctx, start2); + const thread3 = try std.os.spawnThread(&shared_ctx, start2); + const thread4 = try std.os.spawnThread(&shared_ctx, start2); thread1.wait(); thread2.wait(); diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index d6ef7205e..e13ed0f13 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -28,6 +28,9 @@ pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lp pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(lpSymlinkFileName: LPCSTR, lpTargetFileName: LPCSTR, dwFlags: DWORD) BOOLEAN; + +pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE; + pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL; pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn; @@ -318,6 +321,9 @@ pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000; pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004; pub const HEAP_NO_SERIALIZE = 0x00000001; +pub const PTHREAD_START_ROUTINE = extern fn(LPVOID) DWORD; +pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE; + test "import" { _ = @import("util.zig"); } From b21bcbd7755d236a313c06e6ff167f5395ab8ed4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 02:52:04 -0400 Subject: [PATCH 29/58] fix std threads for macos --- std/heap.zig | 6 +++--- std/os/darwin.zig | 8 ++++---- std/os/index.zig | 6 ++++-- std/os/linux/index.zig | 6 +++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/std/heap.zig b/std/heap.zig index d632b44cd..bfdf62f65 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -91,7 +91,7 @@ pub const DirectAllocator = struct { const unused_start = addr; const unused_len = aligned_addr - 1 - unused_start; - var err = p.munmap(@intToPtr(&u8, unused_start), unused_len); + var err = p.munmap(unused_start, unused_len); debug.assert(p.getErrno(err) == 0); //It is impossible that there is an unoccupied page at the top of our @@ -132,7 +132,7 @@ pub const DirectAllocator = struct { const rem = @rem(new_addr_end, os.page_size); const new_addr_end_rounded = new_addr_end + if (rem == 0) 0 else (os.page_size - rem); if (old_addr_end > new_addr_end_rounded) { - _ = os.posix.munmap(@intToPtr(&u8, new_addr_end_rounded), old_addr_end - new_addr_end_rounded); + _ = os.posix.munmap(new_addr_end_rounded, old_addr_end - new_addr_end_rounded); } return old_mem[0..new_size]; } @@ -170,7 +170,7 @@ pub const DirectAllocator = struct { switch (builtin.os) { Os.linux, Os.macosx, Os.ios => { - _ = os.posix.munmap(bytes.ptr, bytes.len); + _ = os.posix.munmap(@ptrToInt(bytes.ptr), bytes.len); }, Os.windows => { const record_addr = @ptrToInt(bytes.ptr) + bytes.len; diff --git a/std/os/darwin.zig b/std/os/darwin.zig index 44418649a..0a62b03ab 100644 --- a/std/os/darwin.zig +++ b/std/os/darwin.zig @@ -184,7 +184,7 @@ pub fn write(fd: i32, buf: &const u8, nbyte: usize) usize { return errnoWrap(c.write(fd, @ptrCast(&const c_void, buf), nbyte)); } -pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, +pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: u32, fd: i32, offset: isize) usize { const ptr_result = c.mmap(@ptrCast(&c_void, address), length, @@ -193,8 +193,8 @@ pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, return errnoWrap(isize_result); } -pub fn munmap(address: &u8, length: usize) usize { - return errnoWrap(c.munmap(@ptrCast(&c_void, address), length)); +pub fn munmap(address: usize, length: usize) usize { + return errnoWrap(c.munmap(@intToPtr(&c_void, address), length)); } pub fn unlink(path: &const u8) usize { @@ -341,4 +341,4 @@ pub const timeval = c.timeval; pub const mach_timebase_info_data = c.mach_timebase_info_data; pub const mach_absolute_time = c.mach_absolute_time; -pub const mach_timebase_info = c.mach_timebase_info; \ No newline at end of file +pub const mach_timebase_info = c.mach_timebase_info; diff --git a/std/os/index.zig b/std/os/index.zig index 6842fd0fb..5053a1b01 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2497,11 +2497,13 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread } }; + const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0; + const stack_len = default_stack_size; const stack_addr = posix.mmap(null, stack_len, posix.PROT_READ|posix.PROT_WRITE, - posix.MAP_PRIVATE|posix.MAP_ANONYMOUS|posix.MAP_GROWSDOWN, -1, 0); + posix.MAP_PRIVATE|posix.MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0); if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory; - errdefer _ = posix.munmap(stack_addr, stack_len); + errdefer assert(posix.munmap(stack_addr, stack_len) == 0); var stack_end: usize = stack_addr + stack_len; var arg: usize = undefined; diff --git a/std/os/linux/index.zig b/std/os/linux/index.zig index dcd9532d1..368f074b9 100644 --- a/std/os/linux/index.zig +++ b/std/os/linux/index.zig @@ -706,13 +706,13 @@ pub fn umount2(special: &const u8, flags: u32) usize { return syscall2(SYS_umount2, @ptrToInt(special), flags); } -pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: isize) usize { +pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: u32, fd: i32, offset: isize) usize { return syscall6(SYS_mmap, @ptrToInt(address), length, prot, flags, usize(fd), @bitCast(usize, offset)); } -pub fn munmap(address: &u8, length: usize) usize { - return syscall2(SYS_munmap, @ptrToInt(address), length); +pub fn munmap(address: usize, length: usize) usize { + return syscall2(SYS_munmap, address, length); } pub fn read(fd: i32, buf: &u8, count: usize) usize { From c76b0a845fb4176479c8bbf915e57dbdfdb7a594 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 02:56:59 -0400 Subject: [PATCH 30/58] fix std threads for linux --- std/os/index.zig | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/std/os/index.zig b/std/os/index.zig index 5053a1b01..ee9ff1516 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2499,13 +2499,13 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0; - const stack_len = default_stack_size; - const stack_addr = posix.mmap(null, stack_len, posix.PROT_READ|posix.PROT_WRITE, + const mmap_len = default_stack_size; + const stack_addr = posix.mmap(null, mmap_len, posix.PROT_READ|posix.PROT_WRITE, posix.MAP_PRIVATE|posix.MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0); if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory; - errdefer assert(posix.munmap(stack_addr, stack_len) == 0); + errdefer assert(posix.munmap(stack_addr, mmap_len) == 0); - var stack_end: usize = stack_addr + stack_len; + var stack_end: usize = stack_addr + mmap_len; var arg: usize = undefined; if (@sizeOf(Context) != 0) { stack_end -= @sizeOf(Context); @@ -2521,6 +2521,8 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread assert(stack_end >= stack_addr); const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end)); + thread_ptr.data.stack_addr = stack_addr; + thread_ptr.data.stack_len = mmap_len; if (builtin.os == builtin.Os.windows) { // use windows API directly @@ -2533,10 +2535,7 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread // align to page stack_end -= stack_end % os.page_size; - assert(c.pthread_attr_setstack(&attr, @intToPtr(&c_void, stack_addr), stack_len) == 0); - - thread_ptr.data.stack_addr = stack_addr; - thread_ptr.data.stack_len = stack_len; + assert(c.pthread_attr_setstack(&attr, @intToPtr(&c_void, stack_addr), stack_end - stack_addr) == 0); const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg)); switch (err) { @@ -2552,7 +2551,7 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread | posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; const newtls: usize = 0; - const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.pid, newtls, &thread_ptr.pid); + const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.pid, newtls, &thread_ptr.data.pid); const err = posix.getErrno(rc); switch (err) { 0 => return thread_ptr, From b7095912c77900eab6ad667a6eeb1add18ac8071 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 15:48:53 -0400 Subject: [PATCH 31/58] zig fmt: respect comments before statements --- std/mem.zig | 18 ++- std/zig/ast.zig | 4 +- std/zig/parser.zig | 291 +++++++++++++++++++++++++-------------------- 3 files changed, 180 insertions(+), 133 deletions(-) diff --git a/std/mem.zig b/std/mem.zig index 0f66f549c..d874f8a6c 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -37,6 +37,20 @@ pub const Allocator = struct { return &slice[0]; } + // TODO once #733 is solved, this will replace create + fn construct(self: &Allocator, init: var) t: { + // TODO this is a workaround for type getting parsed as Error!&const T + const T = @typeOf(init).Child; + break :t Error!&T; + } { + const T = @typeOf(init).Child; + if (@sizeOf(T) == 0) return &{}; + const slice = try self.alloc(T, 1); + const ptr = &slice[0]; + *ptr = *init; + return ptr; + } + fn destroy(self: &Allocator, ptr: var) void { self.free(ptr[0..1]); } @@ -54,7 +68,7 @@ pub const Allocator = struct { const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory; const byte_slice = try self.allocFn(self, byte_count, alignment); assert(byte_slice.len == byte_count); - // This loop should get optimized out in ReleaseFast mode + // This loop gets optimized out in ReleaseFast mode for (byte_slice) |*byte| { *byte = undefined; } @@ -81,7 +95,7 @@ pub const Allocator = struct { const byte_slice = try self.reallocFn(self, old_byte_slice, byte_count, alignment); assert(byte_slice.len == byte_count); if (n > old_mem.len) { - // This loop should get optimized out in ReleaseFast mode + // This loop gets optimized out in ReleaseFast mode for (byte_slice[old_byte_slice.len..]) |*byte| { *byte = undefined; } diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 76977a979..66c0455e8 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -6,6 +6,7 @@ const mem = std.mem; pub const Node = struct { id: Id, + comments: ?&LineComment, pub const Id = enum { // Top level @@ -139,7 +140,6 @@ pub const Node = struct { pub const VarDecl = struct { base: Node, - comments: ?&LineComment, visib_token: ?Token, name_token: Token, eq_token: Token, @@ -421,7 +421,6 @@ pub const Node = struct { pub const FnProto = struct { base: Node, - comments: ?&LineComment, visib_token: ?Token, fn_token: Token, name_token: ?Token, @@ -1732,7 +1731,6 @@ pub const Node = struct { pub const TestDecl = struct { base: Node, - comments: ?&LineComment, test_token: Token, name: &Node, body_node: &Node, diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 7f45cce28..b5fba6a9b 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -182,6 +182,11 @@ pub const Parser = struct { } }; + const AddCommentsCtx = struct { + node_ptr: &&ast.Node, + comments: ?&ast.Node.LineComment, + }; + const State = union(enum) { TopLevel, TopLevelExtern: TopLevelDeclCtx, @@ -221,6 +226,7 @@ pub const Parser = struct { Statement: &ast.Node.Block, ComptimeStatement: ComptimeStatementCtx, Semicolon: &&ast.Node, + AddComments: AddCommentsCtx, AsmOutputItems: &ArrayList(&ast.Node.AsmOutput), AsmOutputReturnOrType: &ast.Node.AsmOutput, @@ -345,24 +351,26 @@ pub const Parser = struct { Token.Id.Keyword_test => { stack.append(State.TopLevel) catch unreachable; - const block = try self.createNode(arena, ast.Node.Block, - ast.Node.Block { - .base = undefined, - .label = null, - .lbrace = undefined, - .statements = ArrayList(&ast.Node).init(arena), - .rbrace = undefined, - } - ); - const test_node = try self.createAttachNode(arena, &root_node.decls, ast.Node.TestDecl, - ast.Node.TestDecl { - .base = undefined, + const block = try arena.construct(ast.Node.Block { + .base = ast.Node { + .id = ast.Node.Id.Block, + .comments = null, + }, + .label = null, + .lbrace = undefined, + .statements = ArrayList(&ast.Node).init(arena), + .rbrace = undefined, + }); + const test_node = try arena.construct(ast.Node.TestDecl { + .base = ast.Node { + .id = ast.Node.Id.TestDecl, .comments = comments, - .test_token = token, - .name = undefined, - .body_node = &block.base, - } - ); + }, + .test_token = token, + .name = undefined, + .body_node = &block.base, + }); + try root_node.decls.append(&test_node.base); stack.append(State { .Block = block }) catch unreachable; try stack.append(State { .ExpectTokenSave = ExpectTokenSave { @@ -530,24 +538,25 @@ pub const Parser = struct { }, Token.Id.Keyword_fn, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc, Token.Id.Keyword_async => { - const fn_proto = try self.createAttachNode(arena, ctx.decls, ast.Node.FnProto, - ast.Node.FnProto { - .base = undefined, + const fn_proto = try arena.construct(ast.Node.FnProto { + .base = ast.Node { + .id = ast.Node.Id.FnProto, .comments = comments, - .visib_token = ctx.visib_token, - .name_token = null, - .fn_token = undefined, - .params = ArrayList(&ast.Node).init(arena), - .return_type = undefined, - .var_args_token = null, - .extern_export_inline_token = ctx.extern_export_inline_token, - .cc_token = null, - .async_attr = null, - .body_node = null, - .lib_name = ctx.lib_name, - .align_expr = null, - } - ); + }, + .visib_token = ctx.visib_token, + .name_token = null, + .fn_token = undefined, + .params = ArrayList(&ast.Node).init(arena), + .return_type = undefined, + .var_args_token = null, + .extern_export_inline_token = ctx.extern_export_inline_token, + .cc_token = null, + .async_attr = null, + .body_node = null, + .lib_name = ctx.lib_name, + .align_expr = null, + }); + try ctx.decls.append(&fn_proto.base); stack.append(State { .FnDef = fn_proto }) catch unreachable; try stack.append(State { .FnProto = fn_proto }); @@ -789,24 +798,25 @@ pub const Parser = struct { State.VarDecl => |ctx| { - const var_decl = try self.createAttachNode(arena, ctx.list, ast.Node.VarDecl, - ast.Node.VarDecl { - .base = undefined, + const var_decl = try arena.construct(ast.Node.VarDecl { + .base = ast.Node { + .id = ast.Node.Id.VarDecl, .comments = ctx.comments, - .visib_token = ctx.visib_token, - .mut_token = ctx.mut_token, - .comptime_token = ctx.comptime_token, - .extern_export_token = ctx.extern_export_token, - .type_node = null, - .align_node = null, - .init_node = null, - .lib_name = ctx.lib_name, - // initialized later - .name_token = undefined, - .eq_token = undefined, - .semicolon_token = undefined, - } - ); + }, + .visib_token = ctx.visib_token, + .mut_token = ctx.mut_token, + .comptime_token = ctx.comptime_token, + .extern_export_token = ctx.extern_export_token, + .type_node = null, + .align_node = null, + .init_node = null, + .lib_name = ctx.lib_name, + // initialized later + .name_token = undefined, + .eq_token = undefined, + .semicolon_token = undefined, + }); + try ctx.list.append(&var_decl.base); stack.append(State { .VarDeclAlign = var_decl }) catch unreachable; try stack.append(State { .TypeExprBegin = OptionalCtx { .RequiredNull = &var_decl.type_node} }); @@ -1218,19 +1228,23 @@ pub const Parser = struct { continue; }, Token.Id.Keyword_defer, Token.Id.Keyword_errdefer => { - const node = try self.createAttachNode(arena, &block.statements, ast.Node.Defer, - ast.Node.Defer { - .base = undefined, - .defer_token = token, - .kind = switch (token.id) { - Token.Id.Keyword_defer => ast.Node.Defer.Kind.Unconditional, - Token.Id.Keyword_errdefer => ast.Node.Defer.Kind.Error, - else => unreachable, - }, - .expr = undefined, - } - ); - stack.append(State { .Semicolon = &&node.base }) catch unreachable; + const node = try arena.construct(ast.Node.Defer { + .base = ast.Node { + .id = ast.Node.Id.Defer, + .comments = comments, + }, + .defer_token = token, + .kind = switch (token.id) { + Token.Id.Keyword_defer => ast.Node.Defer.Kind.Unconditional, + Token.Id.Keyword_errdefer => ast.Node.Defer.Kind.Error, + else => unreachable, + }, + .expr = undefined, + }); + const node_ptr = try block.statements.addOne(); + *node_ptr = &node.base; + + stack.append(State { .Semicolon = node_ptr }) catch unreachable; try stack.append(State { .AssignmentExpressionBegin = OptionalCtx{ .Required = &node.expr } }); continue; }, @@ -1249,9 +1263,13 @@ pub const Parser = struct { }, else => { self.putBackToken(token); - const statememt = try block.statements.addOne(); - stack.append(State { .Semicolon = statememt }) catch unreachable; - try stack.append(State { .AssignmentExpressionBegin = OptionalCtx{ .Required = statememt } }); + const statement = try block.statements.addOne(); + stack.append(State { .Semicolon = statement }) catch unreachable; + try stack.append(State { .AddComments = AddCommentsCtx { + .node_ptr = statement, + .comments = comments, + }}); + try stack.append(State { .AssignmentExpressionBegin = OptionalCtx{ .Required = statement } }); continue; } } @@ -1293,6 +1311,12 @@ pub const Parser = struct { continue; }, + State.AddComments => |add_comments_ctx| { + const node = *add_comments_ctx.node_ptr; + node.comments = add_comments_ctx.comments; + continue; + }, + State.AsmOutputItems => |items| { const lbracket = self.getNextToken(); @@ -1576,24 +1600,25 @@ pub const Parser = struct { State.ExternType => |ctx| { if (self.eatToken(Token.Id.Keyword_fn)) |fn_token| { - const fn_proto = try self.createToCtxNode(arena, ctx.opt_ctx, ast.Node.FnProto, - ast.Node.FnProto { - .base = undefined, + const fn_proto = try arena.construct(ast.Node.FnProto { + .base = ast.Node { + .id = ast.Node.Id.FnProto, .comments = ctx.comments, - .visib_token = null, - .name_token = null, - .fn_token = fn_token, - .params = ArrayList(&ast.Node).init(arena), - .return_type = undefined, - .var_args_token = null, - .extern_export_inline_token = ctx.extern_token, - .cc_token = null, - .async_attr = null, - .body_node = null, - .lib_name = null, - .align_expr = null, - } - ); + }, + .visib_token = null, + .name_token = null, + .fn_token = fn_token, + .params = ArrayList(&ast.Node).init(arena), + .return_type = undefined, + .var_args_token = null, + .extern_export_inline_token = ctx.extern_token, + .cc_token = null, + .async_attr = null, + .body_node = null, + .lib_name = null, + .align_expr = null, + }); + ctx.opt_ctx.store(&fn_proto.base); stack.append(State { .FnProto = fn_proto }) catch unreachable; continue; } @@ -2546,46 +2571,48 @@ pub const Parser = struct { continue; }, Token.Id.Keyword_fn => { - const fn_proto = try self.createToCtxNode(arena, opt_ctx, ast.Node.FnProto, - ast.Node.FnProto { - .base = undefined, + const fn_proto = try arena.construct(ast.Node.FnProto { + .base = ast.Node { + .id = ast.Node.Id.FnProto, .comments = null, - .visib_token = null, - .name_token = null, - .fn_token = token, - .params = ArrayList(&ast.Node).init(arena), - .return_type = undefined, - .var_args_token = null, - .extern_export_inline_token = null, - .cc_token = null, - .async_attr = null, - .body_node = null, - .lib_name = null, - .align_expr = null, - } - ); + }, + .visib_token = null, + .name_token = null, + .fn_token = token, + .params = ArrayList(&ast.Node).init(arena), + .return_type = undefined, + .var_args_token = null, + .extern_export_inline_token = null, + .cc_token = null, + .async_attr = null, + .body_node = null, + .lib_name = null, + .align_expr = null, + }); + opt_ctx.store(&fn_proto.base); stack.append(State { .FnProto = fn_proto }) catch unreachable; continue; }, Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => { - const fn_proto = try self.createToCtxNode(arena, opt_ctx, ast.Node.FnProto, - ast.Node.FnProto { - .base = undefined, + const fn_proto = try arena.construct(ast.Node.FnProto { + .base = ast.Node { + .id = ast.Node.Id.FnProto, .comments = null, - .visib_token = null, - .name_token = null, - .fn_token = undefined, - .params = ArrayList(&ast.Node).init(arena), - .return_type = undefined, - .var_args_token = null, - .extern_export_inline_token = null, - .cc_token = token, - .async_attr = null, - .body_node = null, - .lib_name = null, - .align_expr = null, - } - ); + }, + .visib_token = null, + .name_token = null, + .fn_token = undefined, + .params = ArrayList(&ast.Node).init(arena), + .return_type = undefined, + .var_args_token = null, + .extern_export_inline_token = null, + .cc_token = token, + .async_attr = null, + .body_node = null, + .lib_name = null, + .align_expr = null, + }); + opt_ctx.store(&fn_proto.base); stack.append(State { .FnProto = fn_proto }) catch unreachable; try stack.append(State { .ExpectTokenSave = ExpectTokenSave { @@ -2747,13 +2774,13 @@ pub const Parser = struct { if (result) |comment_node| { break :blk comment_node; } else { - const comment_node = try arena.create(ast.Node.LineComment); - *comment_node = ast.Node.LineComment { + const comment_node = try arena.construct(ast.Node.LineComment { .base = ast.Node { .id = ast.Node.Id.LineComment, + .comments = null, }, .lines = ArrayList(Token).init(arena), - }; + }); result = comment_node; break :blk comment_node; } @@ -3096,7 +3123,7 @@ pub const Parser = struct { *node = *init_to; node.base = blk: { const id = ast.Node.typeToId(T); - break :blk ast.Node {.id = id}; + break :blk ast.Node {.id = id, .comments = null}; }; return node; @@ -3269,7 +3296,7 @@ pub const Parser = struct { switch (decl.id) { ast.Node.Id.FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); - try self.renderComments(stream, fn_proto, indent); + try self.renderComments(stream, &fn_proto.base, indent); if (fn_proto.body_node) |body_node| { stack.append(RenderState { .Expression = body_node}) catch unreachable; @@ -3295,7 +3322,7 @@ pub const Parser = struct { }, ast.Node.Id.TestDecl => { const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl); - try self.renderComments(stream, test_decl, indent); + try self.renderComments(stream, &test_decl.base, indent); try stream.print("test "); try stack.append(RenderState { .Expression = test_decl.body_node }); try stack.append(RenderState { .Text = " " }); @@ -3338,7 +3365,6 @@ pub const Parser = struct { }, RenderState.FieldInitializer => |field_init| { - //TODO try self.renderComments(stream, field_init, indent); try stream.print(".{}", self.tokenizer.getTokenSlice(field_init.name_token)); try stream.print(" = "); try stack.append(RenderState { .Expression = field_init.expr }); @@ -3385,7 +3411,6 @@ pub const Parser = struct { RenderState.ParamDecl => |base| { const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", base); - // TODO try self.renderComments(stream, param_decl, indent); if (param_decl.comptime_token) |comptime_token| { try stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token)); } @@ -4328,10 +4353,10 @@ pub const Parser = struct { ast.Node.Id.ParamDecl => unreachable, }, RenderState.Statement => |base| { + try self.renderComments(stream, base, indent); switch (base.id) { ast.Node.Id.VarDecl => { const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base); - try self.renderComments(stream, var_decl, indent); try stack.append(RenderState { .VarDecl = var_decl}); }, else => { @@ -4348,7 +4373,7 @@ pub const Parser = struct { } } - fn renderComments(self: &Parser, stream: var, node: var, indent: usize) !void { + fn renderComments(self: &Parser, stream: var, node: &ast.Node, indent: usize) !void { const comment = node.comments ?? return; for (comment.lines.toSliceConst()) |line_token| { try stream.print("{}\n", self.tokenizer.getTokenSlice(line_token)); @@ -4430,6 +4455,16 @@ fn testCanonical(source: []const u8) !void { } } +test "zig fmt: preserve comments before statements" { + try testCanonical( + \\test "std" { + \\ // statement comment + \\ _ = @import("foo/bar.zig"); + \\} + \\ + ); +} + test "zig fmt: preserve top level comments" { try testCanonical( \\// top level comment From 5e5eceb0de4e0afb3f16abb453214aa171c630af Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 15:50:56 -0400 Subject: [PATCH 32/58] fix bootstrap_lib for windows --- std/special/bootstrap_lib.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/special/bootstrap_lib.zig b/std/special/bootstrap_lib.zig index 40b658883..a7aede28e 100644 --- a/std/special/bootstrap_lib.zig +++ b/std/special/bootstrap_lib.zig @@ -3,7 +3,7 @@ const std = @import("std"); comptime { - @export("_DllMainCRTStartup", _DllMainCRTStartup); + @export("_DllMainCRTStartup", _DllMainCRTStartup, builtin.GlobalLinkage.Strong); } stdcallcc fn _DllMainCRTStartup(hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD, From a0e9f1e0c3ba3d5e240492723ff1aec8f6b2ba50 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 15:51:23 -0400 Subject: [PATCH 33/58] fix bootstrap_lib for windows, take 2 --- std/special/bootstrap_lib.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/std/special/bootstrap_lib.zig b/std/special/bootstrap_lib.zig index a7aede28e..f55aaed96 100644 --- a/std/special/bootstrap_lib.zig +++ b/std/special/bootstrap_lib.zig @@ -1,6 +1,7 @@ // This file is included in the compilation unit when exporting a library on windows. const std = @import("std"); +const builtin = @import("builtin"); comptime { @export("_DllMainCRTStartup", _DllMainCRTStartup, builtin.GlobalLinkage.Strong); From ad4ee47d9fec15945d445f637987d487405e7b22 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 16:24:12 -0400 Subject: [PATCH 34/58] zig fmt: preserve comments before global variables --- std/zig/parser.zig | 93 +++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index b5fba6a9b..8b9dee59f 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -55,6 +55,7 @@ pub const Parser = struct { visib_token: ?Token, extern_export_inline_token: ?Token, lib_name: ?&ast.Node, + comments: ?&ast.Node.LineComment, }; const VarDeclCtx = struct { @@ -70,6 +71,7 @@ pub const Parser = struct { const TopLevelExternOrFieldCtx = struct { visib_token: Token, container_decl: &ast.Node.ContainerDecl, + comments: ?&ast.Node.LineComment, }; const ExternTypeCtx = struct { @@ -393,6 +395,7 @@ pub const Parser = struct { .visib_token = token, .extern_export_inline_token = null, .lib_name = null, + .comments = comments, } }); continue; @@ -433,6 +436,7 @@ pub const Parser = struct { .visib_token = null, .extern_export_inline_token = null, .lib_name = null, + .comments = comments, } }); continue; @@ -449,6 +453,7 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .extern_export_inline_token = token, .lib_name = null, + .comments = ctx.comments, }, }) catch unreachable; continue; @@ -460,6 +465,7 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .extern_export_inline_token = token, .lib_name = null, + .comments = ctx.comments, }, }) catch unreachable; continue; @@ -486,12 +492,12 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .extern_export_inline_token = ctx.extern_export_inline_token, .lib_name = lib_name, + .comments = ctx.comments, }, }) catch unreachable; continue; }, State.TopLevelDecl => |ctx| { - const comments = try self.eatComments(arena); const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_use => { @@ -525,7 +531,7 @@ pub const Parser = struct { stack.append(State { .VarDecl = VarDeclCtx { - .comments = comments, + .comments = ctx.comments, .visib_token = ctx.visib_token, .lib_name = ctx.lib_name, .comptime_token = null, @@ -541,7 +547,7 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .comments = comments, + .comments = ctx.comments, }, .visib_token = ctx.visib_token, .name_token = null, @@ -628,6 +634,7 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .extern_export_inline_token = null, .lib_name = null, + .comments = ctx.comments, } }); continue; @@ -746,6 +753,7 @@ pub const Parser = struct { .TopLevelExternOrField = TopLevelExternOrFieldCtx { .visib_token = token, .container_decl = container_decl, + .comments = null, } }); continue; @@ -758,6 +766,7 @@ pub const Parser = struct { .visib_token = token, .extern_export_inline_token = null, .lib_name = null, + .comments = null, } }); continue; @@ -772,6 +781,7 @@ pub const Parser = struct { .visib_token = token, .extern_export_inline_token = null, .lib_name = null, + .comments = null, } }); continue; @@ -789,6 +799,7 @@ pub const Parser = struct { .visib_token = null, .extern_export_inline_token = null, .lib_name = null, + .comments = null, } }); continue; @@ -3318,6 +3329,7 @@ pub const Parser = struct { }, ast.Node.Id.VarDecl => { const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl); + try self.renderComments(stream, &var_decl.base, indent); try stack.append(RenderState { .VarDecl = var_decl}); }, ast.Node.Id.TestDecl => { @@ -3827,41 +3839,45 @@ pub const Parser = struct { ast.Node.ContainerDecl.Kind.Union => try stream.print("union"), } - try stack.append(RenderState { .Text = "}"}); - try stack.append(RenderState.PrintIndent); - try stack.append(RenderState { .Indent = indent }); - try stack.append(RenderState { .Text = "\n"}); - const fields_and_decls = container_decl.fields_and_decls.toSliceConst(); - var i = fields_and_decls.len; - while (i != 0) { - i -= 1; - const node = fields_and_decls[i]; - switch (node.id) { - ast.Node.Id.StructField, - ast.Node.Id.UnionTag, - ast.Node.Id.EnumTag => { - try stack.append(RenderState { .Text = "," }); - }, - else => { } - } - try stack.append(RenderState { .TopLevelDecl = node}); + if (fields_and_decls.len == 0) { + try stack.append(RenderState { .Text = "{}"}); + } else { + try stack.append(RenderState { .Text = "}"}); try stack.append(RenderState.PrintIndent); - try stack.append(RenderState { - .Text = blk: { - if (i != 0) { - const prev_node = fields_and_decls[i - 1]; - const loc = self.tokenizer.getTokenLocation(prev_node.lastToken().end, node.firstToken()); - if (loc.line >= 2) { - break :blk "\n\n"; + try stack.append(RenderState { .Indent = indent }); + try stack.append(RenderState { .Text = "\n"}); + + var i = fields_and_decls.len; + while (i != 0) { + i -= 1; + const node = fields_and_decls[i]; + switch (node.id) { + ast.Node.Id.StructField, + ast.Node.Id.UnionTag, + ast.Node.Id.EnumTag => { + try stack.append(RenderState { .Text = "," }); + }, + else => { } + } + try stack.append(RenderState { .TopLevelDecl = node}); + try stack.append(RenderState.PrintIndent); + try stack.append(RenderState { + .Text = blk: { + if (i != 0) { + const prev_node = fields_and_decls[i - 1]; + const loc = self.tokenizer.getTokenLocation(prev_node.lastToken().end, node.firstToken()); + if (loc.line >= 2) { + break :blk "\n\n"; + } } - } - break :blk "\n"; - }, - }); + break :blk "\n"; + }, + }); + } + try stack.append(RenderState { .Indent = indent + indent_delta}); + try stack.append(RenderState { .Text = "{"}); } - try stack.append(RenderState { .Indent = indent + indent_delta}); - try stack.append(RenderState { .Text = "{"}); switch (container_decl.init_arg_expr) { ast.Node.ContainerDecl.InitArg.None => try stack.append(RenderState { .Text = " "}), @@ -4455,6 +4471,15 @@ fn testCanonical(source: []const u8) !void { } } +test "zig fmt: preserve comments before global variables" { + try testCanonical( + \\/// Foo copies keys and values before they go into the map, and + \\/// frees them when they get removed. + \\pub const Foo = struct {}; + \\ + ); +} + test "zig fmt: preserve comments before statements" { try testCanonical( \\test "std" { From 2387292f204c59259fc64d7c960d201e808af5a9 Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Sun, 29 Apr 2018 17:28:11 -0400 Subject: [PATCH 35/58] move some checks around in utf8Encode logic to be more zig idiomatic --- std/unicode.zig | 79 +++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/std/unicode.zig b/std/unicode.zig index 7650f83c8..954857678 100644 --- a/std/unicode.zig +++ b/std/unicode.zig @@ -1,12 +1,11 @@ const std = @import("./index.zig"); const debug = std.debug; -// Given a Utf8-Codepoint returns how many (1-4) -// bytes there are if represented as an array of bytes. +/// Returns how many bytes the UTF-8 representation would require +/// for the given codepoint. pub fn utf8CodepointSequenceLength(c: u32) !u3 { if (c < 0x80) return u3(1); if (c < 0x800) return u3(2); - if (c -% 0xd800 < 0x800) return error.InvalidCodepoint; if (c < 0x10000) return u3(3); if (c < 0x110000) return u3(4); return error.CodepointTooLarge; @@ -23,45 +22,39 @@ pub fn utf8ByteSequenceLength(first_byte: u8) !u3 { return error.Utf8InvalidStartByte; } -/// Encodes a code point back into utf8 -/// c: the code point -/// out: the out buffer to write to -/// Notes: out has to have a len big enough for the bytes -/// however this limit is dependent on the code point -/// but giving it a minimum of 4 will ensure it will work -/// for all code points. -/// Errors: Will return an error if the code point is invalid. +/// Encodes the given codepoint into a UTF-8 byte sequence. +/// c: the codepoint. +/// out: the out buffer to write to. Must have a len >= utf8CodepointSequenceLength(c). +/// Errors: if c cannot be encoded in UTF-8. +/// Returns: the number of bytes written to out. pub fn utf8Encode(c: u32, out: []u8) !u3 { - if (utf8CodepointSequenceLength(c)) |length| { - debug.assert(out.len >= length); - switch (length) { - // The pattern for each is the same - // - Increasing the initial shift by 6 each time - // - Each time after the first shorten the shifted - // value to a max of 0b111111 (63) - 1 => out[0] = u8(c), // Can just do 0 + codepoint for initial range - 2 => { - out[0] = u8(0b11000000 | (c >> 6)); - out[1] = u8(0b10000000 | (c & 0b111111)); - }, - 3 => { - out[0] = u8(0b11100000 | (c >> 12)); - out[1] = u8(0b10000000 | ((c >> 6) & 0b111111)); - out[2] = u8(0b10000000 | (c & 0b111111)); - }, - 4 => { - out[0] = u8(0b11110000 | (c >> 18)); - out[1] = u8(0b10000000 | ((c >> 12) & 0b111111)); - out[2] = u8(0b10000000 | ((c >> 6) & 0b111111)); - out[3] = u8(0b10000000 | (c & 0b111111)); - }, - else => unreachable, - } - - return length; - } else |err| { - return err; + const length = try utf8CodepointSequenceLength(c); + debug.assert(out.len >= length); + switch (length) { + // The pattern for each is the same + // - Increasing the initial shift by 6 each time + // - Each time after the first shorten the shifted + // value to a max of 0b111111 (63) + 1 => out[0] = u8(c), // Can just do 0 + codepoint for initial range + 2 => { + out[0] = u8(0b11000000 | (c >> 6)); + out[1] = u8(0b10000000 | (c & 0b111111)); + }, + 3 => { + if (0xd800 <= c and c <= 0xdfff) return error.Utf8CannotEncodeSurrogateHalf; + out[0] = u8(0b11100000 | (c >> 12)); + out[1] = u8(0b10000000 | ((c >> 6) & 0b111111)); + out[2] = u8(0b10000000 | (c & 0b111111)); + }, + 4 => { + out[0] = u8(0b11110000 | (c >> 18)); + out[1] = u8(0b10000000 | ((c >> 12) & 0b111111)); + out[2] = u8(0b10000000 | ((c >> 6) & 0b111111)); + out[3] = u8(0b10000000 | (c & 0b111111)); + }, + else => unreachable, } + return length; } /// Decodes the UTF-8 codepoint encoded in the given slice of bytes. @@ -249,8 +242,10 @@ test "utf8 encode" { test "utf8 encode error" { var array: [4]u8 = undefined; - testErrorEncode(0xFFFFFF, array[0..], error.CodepointTooLarge); - testErrorEncode(0xd900, array[0..], error.InvalidCodepoint); + testErrorEncode(0xd800, array[0..], error.Utf8CannotEncodeSurrogateHalf); + testErrorEncode(0xdfff, array[0..], error.Utf8CannotEncodeSurrogateHalf); + testErrorEncode(0x110000, array[0..], error.CodepointTooLarge); + testErrorEncode(0xffffffff, array[0..], error.CodepointTooLarge); } fn testErrorEncode(codePoint: u32, array: []u8, expectedErr: error) void { From c03b9010db55e52dfa227b17e35203b93b5ee1df Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 17:37:02 -0400 Subject: [PATCH 36/58] zig fmt: preserve same-line comment after statement --- std/zig/ast.zig | 3 +- std/zig/parser.zig | 79 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 66c0455e8..75c3696bf 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -6,7 +6,8 @@ const mem = std.mem; pub const Node = struct { id: Id, - comments: ?&LineComment, + before_comments: ?&LineComment, + same_line_comment: ?&Token, pub const Id = enum { // Top level diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 8b9dee59f..cfc880ff1 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -229,6 +229,7 @@ pub const Parser = struct { ComptimeStatement: ComptimeStatementCtx, Semicolon: &&ast.Node, AddComments: AddCommentsCtx, + LookForSameLineComment: &&ast.Node, AsmOutputItems: &ArrayList(&ast.Node.AsmOutput), AsmOutputReturnOrType: &ast.Node.AsmOutput, @@ -356,7 +357,8 @@ pub const Parser = struct { const block = try arena.construct(ast.Node.Block { .base = ast.Node { .id = ast.Node.Id.Block, - .comments = null, + .before_comments = null, + .same_line_comment = null, }, .label = null, .lbrace = undefined, @@ -366,7 +368,8 @@ pub const Parser = struct { const test_node = try arena.construct(ast.Node.TestDecl { .base = ast.Node { .id = ast.Node.Id.TestDecl, - .comments = comments, + .before_comments = comments, + .same_line_comment = null, }, .test_token = token, .name = undefined, @@ -547,7 +550,8 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .comments = ctx.comments, + .before_comments = ctx.comments, + .same_line_comment = null, }, .visib_token = ctx.visib_token, .name_token = null, @@ -812,7 +816,8 @@ pub const Parser = struct { const var_decl = try arena.construct(ast.Node.VarDecl { .base = ast.Node { .id = ast.Node.Id.VarDecl, - .comments = ctx.comments, + .before_comments = ctx.comments, + .same_line_comment = null, }, .visib_token = ctx.visib_token, .mut_token = ctx.mut_token, @@ -1242,7 +1247,8 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.Defer { .base = ast.Node { .id = ast.Node.Id.Defer, - .comments = comments, + .before_comments = comments, + .same_line_comment = null, }, .defer_token = token, .kind = switch (token.id) { @@ -1275,7 +1281,8 @@ pub const Parser = struct { else => { self.putBackToken(token); const statement = try block.statements.addOne(); - stack.append(State { .Semicolon = statement }) catch unreachable; + stack.append(State { .LookForSameLineComment = statement }) catch unreachable; + try stack.append(State { .Semicolon = statement }); try stack.append(State { .AddComments = AddCommentsCtx { .node_ptr = statement, .comments = comments, @@ -1324,7 +1331,28 @@ pub const Parser = struct { State.AddComments => |add_comments_ctx| { const node = *add_comments_ctx.node_ptr; - node.comments = add_comments_ctx.comments; + node.before_comments = add_comments_ctx.comments; + continue; + }, + + State.LookForSameLineComment => |node_ptr| { + const node = *node_ptr; + const node_last_token = node.lastToken(); + + const line_comment_token = self.getNextToken(); + if (line_comment_token.id != Token.Id.LineComment) { + self.putBackToken(line_comment_token); + continue; + } + + const offset_loc = self.tokenizer.getTokenLocation(node_last_token.end, line_comment_token); + const different_line = offset_loc.line != 0; + if (different_line) { + self.putBackToken(line_comment_token); + continue; + } + + node.same_line_comment = try arena.construct(line_comment_token); continue; }, @@ -1614,7 +1642,8 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .comments = ctx.comments, + .before_comments = ctx.comments, + .same_line_comment = null, }, .visib_token = null, .name_token = null, @@ -2585,7 +2614,8 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .comments = null, + .before_comments = null, + .same_line_comment = null, }, .visib_token = null, .name_token = null, @@ -2608,7 +2638,8 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .comments = null, + .before_comments = null, + .same_line_comment = null, }, .visib_token = null, .name_token = null, @@ -2788,7 +2819,8 @@ pub const Parser = struct { const comment_node = try arena.construct(ast.Node.LineComment { .base = ast.Node { .id = ast.Node.Id.LineComment, - .comments = null, + .before_comments = null, + .same_line_comment = null, }, .lines = ArrayList(Token).init(arena), }); @@ -3134,7 +3166,11 @@ pub const Parser = struct { *node = *init_to; node.base = blk: { const id = ast.Node.typeToId(T); - break :blk ast.Node {.id = id, .comments = null}; + break :blk ast.Node { + .id = id, + .before_comments = null, + .same_line_comment = null, + }; }; return node; @@ -3270,6 +3306,7 @@ pub const Parser = struct { FieldInitializer: &ast.Node.FieldInitializer, PrintIndent, Indent: usize, + PrintSameLineComment: ?&Token, }; pub fn renderSource(self: &Parser, stream: var, root_node: &ast.Node.Root) !void { @@ -4370,6 +4407,7 @@ pub const Parser = struct { }, RenderState.Statement => |base| { try self.renderComments(stream, base, indent); + try stack.append(RenderState { .PrintSameLineComment = base.same_line_comment } ); switch (base.id) { ast.Node.Id.VarDecl => { const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base); @@ -4385,12 +4423,16 @@ pub const Parser = struct { }, RenderState.Indent => |new_indent| indent = new_indent, RenderState.PrintIndent => try stream.writeByteNTimes(' ', indent), + RenderState.PrintSameLineComment => |maybe_comment| blk: { + const comment_token = maybe_comment ?? break :blk; + try stream.print(" {}", self.tokenizer.getTokenSlice(comment_token)); + }, } } } fn renderComments(self: &Parser, stream: var, node: &ast.Node, indent: usize) !void { - const comment = node.comments ?? return; + const comment = node.before_comments ?? return; for (comment.lines.toSliceConst()) |line_token| { try stream.print("{}\n", self.tokenizer.getTokenSlice(line_token)); try stream.writeByteNTimes(' ', indent); @@ -4471,6 +4513,17 @@ fn testCanonical(source: []const u8) !void { } } +test "zig fmt: preserve same-line comment after a statement" { + try testCanonical( + \\test "" { + \\ a = b; + \\ debug.assert(H.digest_size <= H.block_size); // HMAC makes this assumption + \\ a = b; + \\} + \\ + ); +} + test "zig fmt: preserve comments before global variables" { try testCanonical( \\/// Foo copies keys and values before they go into the map, and From 9543c0a7cc0bbdccc405370e224b546c56b76a0f Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Sun, 29 Apr 2018 17:38:41 -0400 Subject: [PATCH 37/58] use explicit error sets for utf8Decode functions and run unicode tests at comptime also --- std/unicode.zig | 77 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/std/unicode.zig b/std/unicode.zig index 954857678..300e12964 100644 --- a/std/unicode.zig +++ b/std/unicode.zig @@ -57,11 +57,12 @@ pub fn utf8Encode(c: u32, out: []u8) !u3 { return length; } +const Utf8DecodeError = Utf8Decode2Error || Utf8Decode3Error || Utf8Decode4Error; /// Decodes the UTF-8 codepoint encoded in the given slice of bytes. /// bytes.len must be equal to utf8ByteSequenceLength(bytes[0]) catch unreachable. /// If you already know the length at comptime, you can call one of /// utf8Decode2,utf8Decode3,utf8Decode4 directly instead of this function. -pub fn utf8Decode(bytes: []const u8) !u32 { +pub fn utf8Decode(bytes: []const u8) Utf8DecodeError!u32 { return switch (bytes.len) { 1 => u32(bytes[0]), 2 => utf8Decode2(bytes), @@ -71,7 +72,11 @@ pub fn utf8Decode(bytes: []const u8) !u32 { }; } -pub fn utf8Decode2(bytes: []const u8) !u32 { +const Utf8Decode2Error = error{ + Utf8ExpectedContinuation, + Utf8OverlongEncoding, +}; +pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u32 { debug.assert(bytes.len == 2); debug.assert(bytes[0] & 0b11100000 == 0b11000000); var value: u32 = bytes[0] & 0b00011111; @@ -85,7 +90,12 @@ pub fn utf8Decode2(bytes: []const u8) !u32 { return value; } -pub fn utf8Decode3(bytes: []const u8) !u32 { +const Utf8Decode3Error = error{ + Utf8ExpectedContinuation, + Utf8OverlongEncoding, + Utf8EncodesSurrogateHalf, +}; +pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u32 { debug.assert(bytes.len == 3); debug.assert(bytes[0] & 0b11110000 == 0b11100000); var value: u32 = bytes[0] & 0b00001111; @@ -104,7 +114,12 @@ pub fn utf8Decode3(bytes: []const u8) !u32 { return value; } -pub fn utf8Decode4(bytes: []const u8) !u32 { +const Utf8Decode4Error = error{ + Utf8ExpectedContinuation, + Utf8OverlongEncoding, + Utf8CodepointTooLarge, +}; +pub fn utf8Decode4(bytes: []const u8) Utf8Decode4Error!u32 { debug.assert(bytes.len == 4); debug.assert(bytes[0] & 0b11111000 == 0b11110000); var value: u32 = bytes[0] & 0b00000111; @@ -206,19 +221,21 @@ const Utf8Iterator = struct { pub fn nextCodepoint(it: &Utf8Iterator) ?u32 { const slice = it.nextCodepointSlice() ?? return null; - const r = switch (slice.len) { - 1 => u32(slice[0]), - 2 => utf8Decode2(slice), - 3 => utf8Decode3(slice), - 4 => utf8Decode4(slice), + switch (slice.len) { + 1 => return u32(slice[0]), + 2 => return utf8Decode2(slice) catch unreachable, + 3 => return utf8Decode3(slice) catch unreachable, + 4 => return utf8Decode4(slice) catch unreachable, else => unreachable, - }; - - return r catch unreachable; + } } }; test "utf8 encode" { + comptime testUtf8Encode() catch unreachable; + try testUtf8Encode(); +} +fn testUtf8Encode() !void { // A few taken from wikipedia a few taken elsewhere var array: [4]u8 = undefined; debug.assert((try utf8Encode(try utf8Decode("€"), array[0..])) == 3); @@ -241,6 +258,10 @@ test "utf8 encode" { } test "utf8 encode error" { + comptime testUtf8EncodeError(); + testUtf8EncodeError(); +} +fn testUtf8EncodeError() void { var array: [4]u8 = undefined; testErrorEncode(0xd800, array[0..], error.Utf8CannotEncodeSurrogateHalf); testErrorEncode(0xdfff, array[0..], error.Utf8CannotEncodeSurrogateHalf); @@ -257,6 +278,10 @@ fn testErrorEncode(codePoint: u32, array: []u8, expectedErr: error) void { } test "utf8 iterator on ascii" { + comptime testUtf8IteratorOnAscii(); + testUtf8IteratorOnAscii(); +} +fn testUtf8IteratorOnAscii() void { const s = Utf8View.initComptime("abc"); var it1 = s.iterator(); @@ -273,6 +298,10 @@ test "utf8 iterator on ascii" { } test "utf8 view bad" { + comptime testUtf8ViewBad(); + testUtf8ViewBad(); +} +fn testUtf8ViewBad() void { // Compile-time error. // const s3 = Utf8View.initComptime("\xfe\xf2"); @@ -281,6 +310,10 @@ test "utf8 view bad" { } test "utf8 view ok" { + comptime testUtf8ViewOk(); + testUtf8ViewOk(); +} +fn testUtf8ViewOk() void { const s = Utf8View.initComptime("東京市"); var it1 = s.iterator(); @@ -297,6 +330,10 @@ test "utf8 view ok" { } test "bad utf8 slice" { + comptime testBadUtf8Slice(); + testBadUtf8Slice(); +} +fn testBadUtf8Slice() void { debug.assert(utf8ValidateSlice("abc")); debug.assert(!utf8ValidateSlice("abc\xc0")); debug.assert(!utf8ValidateSlice("abc\xc0abc")); @@ -304,6 +341,10 @@ test "bad utf8 slice" { } test "valid utf8" { + comptime testValidUtf8(); + testValidUtf8(); +} +fn testValidUtf8() void { testValid("\x00", 0x0); testValid("\x20", 0x20); testValid("\x7f", 0x7f); @@ -319,6 +360,10 @@ test "valid utf8" { } test "invalid utf8 continuation bytes" { + comptime testInvalidUtf8ContinuationBytes(); + testInvalidUtf8ContinuationBytes(); +} +fn testInvalidUtf8ContinuationBytes() void { // unexpected continuation testError("\x80", error.Utf8InvalidStartByte); testError("\xbf", error.Utf8InvalidStartByte); @@ -347,6 +392,10 @@ test "invalid utf8 continuation bytes" { } test "overlong utf8 codepoint" { + comptime testOverlongUtf8Codepoint(); + testOverlongUtf8Codepoint(); +} +fn testOverlongUtf8Codepoint() void { testError("\xc0\x80", error.Utf8OverlongEncoding); testError("\xc1\xbf", error.Utf8OverlongEncoding); testError("\xe0\x80\x80", error.Utf8OverlongEncoding); @@ -356,6 +405,10 @@ test "overlong utf8 codepoint" { } test "misc invalid utf8" { + comptime testMiscInvalidUtf8(); + testMiscInvalidUtf8(); +} +fn testMiscInvalidUtf8() void { // codepoint out of bounds testError("\xf4\x90\x80\x80", error.Utf8CodepointTooLarge); testError("\xf7\xbf\xbf\xbf", error.Utf8CodepointTooLarge); From 3fa0bed985b8de950859d4c482efe9cb30fdaf27 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 18:22:24 -0400 Subject: [PATCH 38/58] zig fmt: array literal with 1 item on 1 line --- std/zig/parser.zig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index cfc880ff1..6a6bd49f6 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -3730,6 +3730,16 @@ pub const Parser = struct { try stack.append(RenderState { .Expression = suffix_op.lhs }); continue; } + if (exprs.len == 1) { + const expr = exprs.at(0); + + try stack.append(RenderState { .Text = "}" }); + try stack.append(RenderState { .Expression = expr }); + try stack.append(RenderState { .Text = " {" }); + try stack.append(RenderState { .Expression = suffix_op.lhs }); + continue; + } + try stack.append(RenderState { .Text = "}"}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Indent = indent }); @@ -4513,6 +4523,13 @@ fn testCanonical(source: []const u8) !void { } } +test "zig fmt: array literal with 1 item on 1 line" { + try testCanonical( + \\var s = []const u64 {0} ** 25; + \\ + ); +} + test "zig fmt: preserve same-line comment after a statement" { try testCanonical( \\test "" { From 3235eb03f979cbcc38cbe8b69d55761079e6d864 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 19:23:19 -0400 Subject: [PATCH 39/58] zig fmt: preserve same line comment after struct field --- std/zig/parser.zig | 1069 +++------------------------------------ std/zig/parser_test.zig | 975 +++++++++++++++++++++++++++++++++++ 2 files changed, 1039 insertions(+), 1005 deletions(-) create mode 100644 std/zig/parser_test.zig diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 6a6bd49f6..5f0928ee4 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -376,7 +376,7 @@ pub const Parser = struct { .body_node = &block.base, }); try root_node.decls.append(&test_node.base); - stack.append(State { .Block = block }) catch unreachable; + try stack.append(State { .Block = block }); try stack.append(State { .ExpectTokenSave = ExpectTokenSave { .id = Token.Id.LBrace, @@ -616,14 +616,18 @@ pub const Parser = struct { State.TopLevelExternOrField => |ctx| { if (self.eatToken(Token.Id.Identifier)) |identifier| { std.debug.assert(ctx.container_decl.kind == ast.Node.ContainerDecl.Kind.Struct); - const node = try self.createAttachNode(arena, &ctx.container_decl.fields_and_decls, ast.Node.StructField, - ast.Node.StructField { - .base = undefined, - .visib_token = ctx.visib_token, - .name_token = identifier, - .type_expr = undefined, - } - ); + const node = try arena.construct(ast.Node.StructField { + .base = ast.Node { + .id = ast.Node.Id.StructField, + .before_comments = null, + .same_line_comment = null, + }, + .visib_token = ctx.visib_token, + .name_token = identifier, + .type_expr = undefined, + }); + const node_ptr = try ctx.container_decl.fields_and_decls.addOne(); + *node_ptr = &node.base; stack.append(State { .FieldListCommaOrEnd = ctx.container_decl }) catch unreachable; try stack.append(State { .Expression = OptionalCtx { .Required = &node.type_expr } }); @@ -706,16 +710,20 @@ pub const Parser = struct { Token.Id.Identifier => { switch (container_decl.kind) { ast.Node.ContainerDecl.Kind.Struct => { - const node = try self.createAttachNode(arena, &container_decl.fields_and_decls, ast.Node.StructField, - ast.Node.StructField { - .base = undefined, - .visib_token = null, - .name_token = token, - .type_expr = undefined, - } - ); + const node = try arena.construct(ast.Node.StructField { + .base = ast.Node { + .id = ast.Node.Id.StructField, + .before_comments = null, + .same_line_comment = null, + }, + .visib_token = null, + .name_token = token, + .type_expr = undefined, + }); + const node_ptr = try container_decl.fields_and_decls.addOne(); + *node_ptr = &node.base; - stack.append(State { .FieldListCommaOrEnd = container_decl }) catch unreachable; + try stack.append(State { .FieldListCommaOrEnd = container_decl }); try stack.append(State { .TypeExprBegin = OptionalCtx { .Required = &node.type_expr } }); try stack.append(State { .ExpectToken = Token.Id.Colon }); continue; @@ -1336,23 +1344,7 @@ pub const Parser = struct { }, State.LookForSameLineComment => |node_ptr| { - const node = *node_ptr; - const node_last_token = node.lastToken(); - - const line_comment_token = self.getNextToken(); - if (line_comment_token.id != Token.Id.LineComment) { - self.putBackToken(line_comment_token); - continue; - } - - const offset_loc = self.tokenizer.getTokenLocation(node_last_token.end, line_comment_token); - const different_line = offset_loc.line != 0; - if (different_line) { - self.putBackToken(line_comment_token); - continue; - } - - node.same_line_comment = try arena.construct(line_comment_token); + try self.lookForSameLineComment(arena, *node_ptr); continue; }, @@ -1463,14 +1455,16 @@ pub const Parser = struct { continue; } - const node = try self.createNode(arena, ast.Node.FieldInitializer, - ast.Node.FieldInitializer { - .base = undefined, - .period_token = undefined, - .name_token = undefined, - .expr = undefined, - } - ); + const node = try arena.construct(ast.Node.FieldInitializer { + .base = ast.Node { + .id = ast.Node.Id.FieldInitializer, + .before_comments = null, + .same_line_comment = null, + }, + .period_token = undefined, + .name_token = undefined, + .expr = undefined, + }); try list_state.list.append(node); stack.append(State { .FieldInitListCommaOrEnd = list_state }) catch unreachable; @@ -1504,7 +1498,8 @@ pub const Parser = struct { container_decl.rbrace_token = end; continue; } else { - stack.append(State { .ContainerDecl = container_decl }) catch unreachable; + try self.lookForSameLineComment(arena, container_decl.fields_and_decls.toSlice()[container_decl.fields_and_decls.len - 1]); + try stack.append(State { .ContainerDecl = container_decl }); continue; } }, @@ -2908,6 +2903,25 @@ pub const Parser = struct { } } + fn lookForSameLineComment(self: &Parser, arena: &mem.Allocator, node: &ast.Node) !void { + const node_last_token = node.lastToken(); + + const line_comment_token = self.getNextToken(); + if (line_comment_token.id != Token.Id.LineComment) { + self.putBackToken(line_comment_token); + return; + } + + const offset_loc = self.tokenizer.getTokenLocation(node_last_token.end, line_comment_token); + const different_line = offset_loc.line != 0; + if (different_line) { + self.putBackToken(line_comment_token); + return; + } + + node.same_line_comment = try arena.construct(line_comment_token); + } + fn parseStringLiteral(self: &Parser, arena: &mem.Allocator, token: &const Token) !?&ast.Node { switch (token.id) { Token.Id.StringLiteral => { @@ -3341,6 +3355,7 @@ pub const Parser = struct { while (stack.popOrNull()) |state| { switch (state) { RenderState.TopLevelDecl => |decl| { + try stack.append(RenderState { .PrintSameLineComment = decl.same_line_comment } ); switch (decl.id) { ast.Node.Id.FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); @@ -3383,12 +3398,14 @@ pub const Parser = struct { try stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); } try stream.print("{}: ", self.tokenizer.getTokenSlice(field.name_token)); + try stack.append(RenderState { .Text = "," }); try stack.append(RenderState { .Expression = field.type_expr}); }, ast.Node.Id.UnionTag => { const tag = @fieldParentPtr(ast.Node.UnionTag, "base", decl); try stream.print("{}", self.tokenizer.getTokenSlice(tag.name_token)); + try stack.append(RenderState { .Text = "," }); if (tag.type_expr) |type_expr| { try stream.print(": "); try stack.append(RenderState { .Expression = type_expr}); @@ -3398,6 +3415,7 @@ pub const Parser = struct { const tag = @fieldParentPtr(ast.Node.EnumTag, "base", decl); try stream.print("{}", self.tokenizer.getTokenSlice(tag.name_token)); + try stack.append(RenderState { .Text = "," }); if (tag.value) |value| { try stream.print(" = "); try stack.append(RenderState { .Expression = value}); @@ -3899,14 +3917,6 @@ pub const Parser = struct { while (i != 0) { i -= 1; const node = fields_and_decls[i]; - switch (node.id) { - ast.Node.Id.StructField, - ast.Node.Id.UnionTag, - ast.Node.Id.EnumTag => { - try stack.append(RenderState { .Text = "," }); - }, - else => { } - } try stack.append(RenderState { .TopLevelDecl = node}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { @@ -4466,957 +4476,6 @@ pub const Parser = struct { }; -var fixed_buffer_mem: [100 * 1024]u8 = undefined; - -fn testParse(source: []const u8, allocator: &mem.Allocator) ![]u8 { - var tokenizer = Tokenizer.init(source); - var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); - defer parser.deinit(); - - var tree = try parser.parse(); - defer tree.deinit(); - - var buffer = try std.Buffer.initSize(allocator, 0); - errdefer buffer.deinit(); - - var buffer_out_stream = io.BufferOutStream.init(&buffer); - try parser.renderSource(&buffer_out_stream.stream, tree.root_node); - return buffer.toOwnedSlice(); -} - -fn testCanonical(source: []const u8) !void { - const needed_alloc_count = x: { - // Try it once with unlimited memory, make sure it works - var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, @maxValue(usize)); - const result_source = try testParse(source, &failing_allocator.allocator); - if (!mem.eql(u8, result_source, source)) { - warn("\n====== expected this output: =========\n"); - warn("{}", source); - warn("\n======== instead found this: =========\n"); - warn("{}", result_source); - warn("\n======================================\n"); - return error.TestFailed; - } - failing_allocator.allocator.free(result_source); - break :x failing_allocator.index; - }; - - var fail_index: usize = 0; - while (fail_index < needed_alloc_count) : (fail_index += 1) { - var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); - var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, fail_index); - if (testParse(source, &failing_allocator.allocator)) |_| { - return error.NondeterministicMemoryUsage; - } else |err| switch (err) { - error.OutOfMemory => { - if (failing_allocator.allocated_bytes != failing_allocator.freed_bytes) { - warn("\nfail_index: {}/{}\nallocated bytes: {}\nfreed bytes: {}\nallocations: {}\ndeallocations: {}\n", - fail_index, needed_alloc_count, - failing_allocator.allocated_bytes, failing_allocator.freed_bytes, - failing_allocator.index, failing_allocator.deallocations); - return error.MemoryLeakDetected; - } - }, - error.ParseError => @panic("test failed"), - } - } -} - -test "zig fmt: array literal with 1 item on 1 line" { - try testCanonical( - \\var s = []const u64 {0} ** 25; - \\ - ); -} - -test "zig fmt: preserve same-line comment after a statement" { - try testCanonical( - \\test "" { - \\ a = b; - \\ debug.assert(H.digest_size <= H.block_size); // HMAC makes this assumption - \\ a = b; - \\} - \\ - ); -} - -test "zig fmt: preserve comments before global variables" { - try testCanonical( - \\/// Foo copies keys and values before they go into the map, and - \\/// frees them when they get removed. - \\pub const Foo = struct {}; - \\ - ); -} - -test "zig fmt: preserve comments before statements" { - try testCanonical( - \\test "std" { - \\ // statement comment - \\ _ = @import("foo/bar.zig"); - \\} - \\ - ); -} - -test "zig fmt: preserve top level comments" { - try testCanonical( - \\// top level comment - \\test "hi" {} - \\ - ); -} - -test "zig fmt: get stdout or fail" { - try testCanonical( - \\const std = @import("std"); - \\ - \\pub fn main() !void { - \\ // If this program is run without stdout attached, exit with an error. - \\ // another comment - \\ var stdout_file = try std.io.getStdOut; - \\} - \\ - ); -} - -test "zig fmt: preserve spacing" { - try testCanonical( - \\const std = @import("std"); - \\ - \\pub fn main() !void { - \\ var stdout_file = try std.io.getStdOut; - \\ var stdout_file = try std.io.getStdOut; - \\ - \\ var stdout_file = try std.io.getStdOut; - \\ var stdout_file = try std.io.getStdOut; - \\} - \\ - ); -} - -test "zig fmt: return types" { - try testCanonical( - \\pub fn main() !void {} - \\pub fn main() var {} - \\pub fn main() i32 {} - \\ - ); -} - -test "zig fmt: imports" { - try testCanonical( - \\const std = @import("std"); - \\const std = @import(); - \\ - ); -} - -test "zig fmt: global declarations" { - try testCanonical( - \\const a = b; - \\pub const a = b; - \\var a = b; - \\pub var a = b; - \\const a: i32 = b; - \\pub const a: i32 = b; - \\var a: i32 = b; - \\pub var a: i32 = b; - \\extern const a: i32 = b; - \\pub extern const a: i32 = b; - \\extern var a: i32 = b; - \\pub extern var a: i32 = b; - \\extern "a" const a: i32 = b; - \\pub extern "a" const a: i32 = b; - \\extern "a" var a: i32 = b; - \\pub extern "a" var a: i32 = b; - \\ - ); -} - -test "zig fmt: extern declaration" { - try testCanonical( - \\extern var foo: c_int; - \\ - ); -} - -test "zig fmt: alignment" { - try testCanonical( - \\var foo: c_int align(1); - \\ - ); -} - -test "zig fmt: C main" { - try testCanonical( - \\fn main(argc: c_int, argv: &&u8) c_int { - \\ const a = b; - \\} - \\ - ); -} - -test "zig fmt: return" { - try testCanonical( - \\fn foo(argc: c_int, argv: &&u8) c_int { - \\ return 0; - \\} - \\ - \\fn bar() void { - \\ return; - \\} - \\ - ); -} - -test "zig fmt: pointer attributes" { - try testCanonical( - \\extern fn f1(s: &align(&u8) u8) c_int; - \\extern fn f2(s: &&align(1) &const &volatile u8) c_int; - \\extern fn f3(s: &align(1) const &align(1) volatile &const volatile u8) c_int; - \\extern fn f4(s: &align(1) const volatile u8) c_int; - \\ - ); -} - -test "zig fmt: slice attributes" { - try testCanonical( - \\extern fn f1(s: &align(&u8) u8) c_int; - \\extern fn f2(s: &&align(1) &const &volatile u8) c_int; - \\extern fn f3(s: &align(1) const &align(1) volatile &const volatile u8) c_int; - \\extern fn f4(s: &align(1) const volatile u8) c_int; - \\ - ); -} - -test "zig fmt: test declaration" { - try testCanonical( - \\test "test name" { - \\ const a = 1; - \\ var b = 1; - \\} - \\ - ); -} - -test "zig fmt: infix operators" { - try testCanonical( - \\test "infix operators" { - \\ var i = undefined; - \\ i = 2; - \\ i *= 2; - \\ i |= 2; - \\ i ^= 2; - \\ i <<= 2; - \\ i >>= 2; - \\ i &= 2; - \\ i *= 2; - \\ i *%= 2; - \\ i -= 2; - \\ i -%= 2; - \\ i += 2; - \\ i +%= 2; - \\ i /= 2; - \\ i %= 2; - \\ _ = i == i; - \\ _ = i != i; - \\ _ = i != i; - \\ _ = i.i; - \\ _ = i || i; - \\ _ = i!i; - \\ _ = i ** i; - \\ _ = i ++ i; - \\ _ = i ?? i; - \\ _ = i % i; - \\ _ = i / i; - \\ _ = i *% i; - \\ _ = i * i; - \\ _ = i -% i; - \\ _ = i - i; - \\ _ = i +% i; - \\ _ = i + i; - \\ _ = i << i; - \\ _ = i >> i; - \\ _ = i & i; - \\ _ = i ^ i; - \\ _ = i | i; - \\ _ = i >= i; - \\ _ = i <= i; - \\ _ = i > i; - \\ _ = i < i; - \\ _ = i and i; - \\ _ = i or i; - \\} - \\ - ); -} - -test "zig fmt: precedence" { - try testCanonical( - \\test "precedence" { - \\ a!b(); - \\ (a!b)(); - \\ !a!b; - \\ !(a!b); - \\ !a{}; - \\ !(a{}); - \\ a + b{}; - \\ (a + b){}; - \\ a << b + c; - \\ (a << b) + c; - \\ a & b << c; - \\ (a & b) << c; - \\ a ^ b & c; - \\ (a ^ b) & c; - \\ a | b ^ c; - \\ (a | b) ^ c; - \\ a == b | c; - \\ (a == b) | c; - \\ a and b == c; - \\ (a and b) == c; - \\ a or b and c; - \\ (a or b) and c; - \\ (a or b) and c; - \\} - \\ - ); -} - -test "zig fmt: prefix operators" { - try testCanonical( - \\test "prefix operators" { - \\ try return --%~??!*&0; - \\} - \\ - ); -} - -test "zig fmt: call expression" { - try testCanonical( - \\test "test calls" { - \\ a(); - \\ a(1); - \\ a(1, 2); - \\ a(1, 2) + a(1, 2); - \\} - \\ - ); -} - -test "zig fmt: var args" { - try testCanonical( - \\fn print(args: ...) void {} - \\ - ); -} - -test "zig fmt: var type" { - try testCanonical( - \\fn print(args: var) var {} - \\const Var = var; - \\const i: var = 0; - \\ - ); -} - -test "zig fmt: functions" { - try testCanonical( - \\extern fn puts(s: &const u8) c_int; - \\extern "c" fn puts(s: &const u8) c_int; - \\export fn puts(s: &const u8) c_int; - \\inline fn puts(s: &const u8) c_int; - \\pub extern fn puts(s: &const u8) c_int; - \\pub extern "c" fn puts(s: &const u8) c_int; - \\pub export fn puts(s: &const u8) c_int; - \\pub inline fn puts(s: &const u8) c_int; - \\pub extern fn puts(s: &const u8) align(2 + 2) c_int; - \\pub extern "c" fn puts(s: &const u8) align(2 + 2) c_int; - \\pub export fn puts(s: &const u8) align(2 + 2) c_int; - \\pub inline fn puts(s: &const u8) align(2 + 2) c_int; - \\ - ); -} - -test "zig fmt: multiline string" { - try testCanonical( - \\const s = - \\ \\ something - \\ \\ something else - \\ ; - \\ - ); -} - -test "zig fmt: values" { - try testCanonical( - \\test "values" { - \\ 1; - \\ 1.0; - \\ "string"; - \\ c"cstring"; - \\ 'c'; - \\ true; - \\ false; - \\ null; - \\ undefined; - \\ error; - \\ this; - \\ unreachable; - \\} - \\ - ); -} - -test "zig fmt: indexing" { - try testCanonical( - \\test "test index" { - \\ a[0]; - \\ a[0 + 5]; - \\ a[0..]; - \\ a[0..5]; - \\ a[a[0]]; - \\ a[a[0..]]; - \\ a[a[0..5]]; - \\ a[a[0]..]; - \\ a[a[0..5]..]; - \\ a[a[0]..a[0]]; - \\ a[a[0..5]..a[0]]; - \\ a[a[0..5]..a[0..5]]; - \\} - \\ - ); -} - -test "zig fmt: struct declaration" { - try testCanonical( - \\const S = struct { - \\ const Self = this; - \\ f1: u8, - \\ pub f3: u8, - \\ - \\ fn method(self: &Self) Self { - \\ return *self; - \\ } - \\ - \\ f2: u8, - \\}; - \\ - \\const Ps = packed struct { - \\ a: u8, - \\ pub b: u8, - \\ - \\ c: u8, - \\}; - \\ - \\const Es = extern struct { - \\ a: u8, - \\ pub b: u8, - \\ - \\ c: u8, - \\}; - \\ - ); -} - -test "zig fmt: enum declaration" { - try testCanonical( - \\const E = enum { - \\ Ok, - \\ SomethingElse = 0, - \\}; - \\ - \\const E2 = enum(u8) { - \\ Ok, - \\ SomethingElse = 255, - \\ SomethingThird, - \\}; - \\ - \\const Ee = extern enum { - \\ Ok, - \\ SomethingElse, - \\ SomethingThird, - \\}; - \\ - \\const Ep = packed enum { - \\ Ok, - \\ SomethingElse, - \\ SomethingThird, - \\}; - \\ - ); -} - -test "zig fmt: union declaration" { - try testCanonical( - \\const U = union { - \\ Int: u8, - \\ Float: f32, - \\ None, - \\ Bool: bool, - \\}; - \\ - \\const Ue = union(enum) { - \\ Int: u8, - \\ Float: f32, - \\ None, - \\ Bool: bool, - \\}; - \\ - \\const E = enum { - \\ Int, - \\ Float, - \\ None, - \\ Bool, - \\}; - \\ - \\const Ue2 = union(E) { - \\ Int: u8, - \\ Float: f32, - \\ None, - \\ Bool: bool, - \\}; - \\ - \\const Eu = extern union { - \\ Int: u8, - \\ Float: f32, - \\ None, - \\ Bool: bool, - \\}; - \\ - ); -} - -test "zig fmt: error set declaration" { - try testCanonical( - \\const E = error { - \\ A, - \\ B, - \\ - \\ C, - \\}; - \\ - ); -} - -test "zig fmt: arrays" { - try testCanonical( - \\test "test array" { - \\ const a: [2]u8 = [2]u8 { - \\ 1, - \\ 2, - \\ }; - \\ const a: [2]u8 = []u8 { - \\ 1, - \\ 2, - \\ }; - \\ const a: [0]u8 = []u8{}; - \\} - \\ - ); -} - -test "zig fmt: container initializers" { - try testCanonical( - \\const a1 = []u8{}; - \\const a2 = []u8 { - \\ 1, - \\ 2, - \\ 3, - \\ 4, - \\}; - \\const s1 = S{}; - \\const s2 = S { - \\ .a = 1, - \\ .b = 2, - \\}; - \\ - ); -} - -test "zig fmt: catch" { - try testCanonical( - \\test "catch" { - \\ const a: error!u8 = 0; - \\ _ = a catch return; - \\ _ = a catch |err| return; - \\} - \\ - ); -} - -test "zig fmt: blocks" { - try testCanonical( - \\test "blocks" { - \\ { - \\ const a = 0; - \\ const b = 0; - \\ } - \\ - \\ blk: { - \\ const a = 0; - \\ const b = 0; - \\ } - \\ - \\ const r = blk: { - \\ const a = 0; - \\ const b = 0; - \\ }; - \\} - \\ - ); -} - -test "zig fmt: switch" { - try testCanonical( - \\test "switch" { - \\ switch (0) { - \\ 0 => {}, - \\ 1 => unreachable, - \\ 2, - \\ 3 => {}, - \\ 4 ... 7 => {}, - \\ 1 + 4 * 3 + 22 => {}, - \\ else => { - \\ const a = 1; - \\ const b = a; - \\ }, - \\ } - \\ - \\ const res = switch (0) { - \\ 0 => 0, - \\ 1 => 2, - \\ 1 => a = 4, - \\ else => 4, - \\ }; - \\ - \\ const Union = union(enum) { - \\ Int: i64, - \\ Float: f64, - \\ }; - \\ - \\ const u = Union { - \\ .Int = 0, - \\ }; - \\ switch (u) { - \\ Union.Int => |int| {}, - \\ Union.Float => |*float| unreachable, - \\ } - \\} - \\ - ); -} - -test "zig fmt: while" { - try testCanonical( - \\test "while" { - \\ while (10 < 1) { - \\ unreachable; - \\ } - \\ - \\ while (10 < 1) - \\ unreachable; - \\ - \\ var i: usize = 0; - \\ while (i < 10) : (i += 1) { - \\ continue; - \\ } - \\ - \\ i = 0; - \\ while (i < 10) : (i += 1) - \\ continue; - \\ - \\ i = 0; - \\ var j: usize = 0; - \\ while (i < 10) : ({ - \\ i += 1; - \\ j += 1; - \\ }) { - \\ continue; - \\ } - \\ - \\ var a: ?u8 = 2; - \\ while (a) |v| : (a = null) { - \\ continue; - \\ } - \\ - \\ while (a) |v| : (a = null) - \\ unreachable; - \\ - \\ label: while (10 < 0) { - \\ unreachable; - \\ } - \\ - \\ const res = while (0 < 10) { - \\ break 7; - \\ } else { - \\ unreachable; - \\ }; - \\ - \\ const res = while (0 < 10) - \\ break 7 - \\ else - \\ unreachable; - \\ - \\ var a: error!u8 = 0; - \\ while (a) |v| { - \\ a = error.Err; - \\ } else |err| { - \\ i = 1; - \\ } - \\ - \\ comptime var k: usize = 0; - \\ inline while (i < 10) : (i += 1) - \\ j += 2; - \\} - \\ - ); -} - -test "zig fmt: for" { - try testCanonical( - \\test "for" { - \\ const a = []u8 { - \\ 1, - \\ 2, - \\ 3, - \\ }; - \\ for (a) |v| { - \\ continue; - \\ } - \\ - \\ for (a) |v| - \\ continue; - \\ - \\ for (a) |*v| - \\ continue; - \\ - \\ for (a) |v, i| { - \\ continue; - \\ } - \\ - \\ for (a) |v, i| - \\ continue; - \\ - \\ const res = for (a) |v, i| { - \\ break v; - \\ } else { - \\ unreachable; - \\ }; - \\ - \\ var num: usize = 0; - \\ inline for (a) |v, i| { - \\ num += v; - \\ num += i; - \\ } - \\} - \\ - ); -} - -test "zig fmt: if" { - try testCanonical( - \\test "if" { - \\ if (10 < 0) { - \\ unreachable; - \\ } - \\ - \\ if (10 < 0) unreachable; - \\ - \\ if (10 < 0) { - \\ unreachable; - \\ } else { - \\ const a = 20; - \\ } - \\ - \\ if (10 < 0) { - \\ unreachable; - \\ } else if (5 < 0) { - \\ unreachable; - \\ } else { - \\ const a = 20; - \\ } - \\ - \\ const is_world_broken = if (10 < 0) true else false; - \\ const some_number = 1 + if (10 < 0) 2 else 3; - \\ - \\ const a: ?u8 = 10; - \\ const b: ?u8 = null; - \\ if (a) |v| { - \\ const some = v; - \\ } else if (b) |*v| { - \\ unreachable; - \\ } else { - \\ const some = 10; - \\ } - \\ - \\ const non_null_a = if (a) |v| v else 0; - \\ - \\ const a_err: error!u8 = 0; - \\ if (a_err) |v| { - \\ const p = v; - \\ } else |err| { - \\ unreachable; - \\ } - \\} - \\ - ); -} - -test "zig fmt: defer" { - try testCanonical( - \\test "defer" { - \\ var i: usize = 0; - \\ defer i = 1; - \\ defer { - \\ i += 2; - \\ i *= i; - \\ } - \\ - \\ errdefer i += 3; - \\ errdefer { - \\ i += 2; - \\ i /= i; - \\ } - \\} - \\ - ); -} - -test "zig fmt: comptime" { - try testCanonical( - \\fn a() u8 { - \\ return 5; - \\} - \\ - \\fn b(comptime i: u8) u8 { - \\ return i; - \\} - \\ - \\const av = comptime a(); - \\const av2 = comptime blk: { - \\ var res = a(); - \\ res *= b(2); - \\ break :blk res; - \\}; - \\ - \\comptime { - \\ _ = a(); - \\} - \\ - \\test "comptime" { - \\ const av3 = comptime a(); - \\ const av4 = comptime blk: { - \\ var res = a(); - \\ res *= a(); - \\ break :blk res; - \\ }; - \\ - \\ comptime var i = 0; - \\ comptime { - \\ i = a(); - \\ i += b(i); - \\ } - \\} - \\ - ); -} - -test "zig fmt: fn type" { - try testCanonical( - \\fn a(i: u8) u8 { - \\ return i + 1; - \\} - \\ - \\const a: fn(u8) u8 = undefined; - \\const b: extern fn(u8) u8 = undefined; - \\const c: nakedcc fn(u8) u8 = undefined; - \\const ap: fn(u8) u8 = a; - \\ - ); -} - -test "zig fmt: inline asm" { - try testCanonical( - \\pub fn syscall1(number: usize, arg1: usize) usize { - \\ return asm volatile ("syscall" - \\ : [ret] "={rax}" (-> usize) - \\ : [number] "{rax}" (number), - \\ [arg1] "{rdi}" (arg1) - \\ : "rcx", "r11"); - \\} - \\ - ); -} - -test "zig fmt: coroutines" { - try testCanonical( - \\async fn simpleAsyncFn() void { - \\ const a = async a.b(); - \\ x += 1; - \\ suspend; - \\ x += 1; - \\ suspend |p| {} - \\ const p = async simpleAsyncFn() catch unreachable; - \\ await p; - \\} - \\ - \\test "coroutine suspend, resume, cancel" { - \\ const p = try async testAsyncSeq(); - \\ resume p; - \\ cancel p; - \\} - \\ - ); -} - -test "zig fmt: Block after if" { - try testCanonical( - \\test "Block after if" { - \\ if (true) { - \\ const a = 0; - \\ } - \\ - \\ { - \\ const a = 0; - \\ } - \\} - \\ - ); -} - -test "zig fmt: use" { - try testCanonical( - \\use @import("std"); - \\pub use @import("std"); - \\ - ); -} - -test "zig fmt: string identifier" { - try testCanonical( - \\const @"a b" = @"c d".@"e f"; - \\fn @"g h"() void {} - \\ - ); -} - -test "zig fmt: error return" { - try testCanonical( - \\fn err() error { - \\ call(); - \\ return error.InvalidArgs; - \\} - \\ - ); -} - -test "zig fmt: struct literals with fields on each line" { - try testCanonical( - \\var self = BufSet { - \\ .hash_map = BufSetHashMap.init(a), - \\}; - \\ - ); +test "std.zig.parser" { + _ = @import("parser_test.zig"); } diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig new file mode 100644 index 000000000..4edce9843 --- /dev/null +++ b/std/zig/parser_test.zig @@ -0,0 +1,975 @@ +test "zig fmt: line comment after field decl" { + try testCanonical( + \\pub const dirent = extern struct { + \\ d_name: u8, + \\ d_name: u8, // comment 1 + \\ d_name: u8, + \\ d_name: u8, // comment 2 + \\ d_name: u8, + \\}; + \\ + ); +} + +test "zig fmt: array literal with 1 item on 1 line" { + try testCanonical( + \\var s = []const u64 {0} ** 25; + \\ + ); +} + +test "zig fmt: preserve same-line comment after a statement" { + try testCanonical( + \\test "" { + \\ a = b; + \\ debug.assert(H.digest_size <= H.block_size); // HMAC makes this assumption + \\ a = b; + \\} + \\ + ); +} + +test "zig fmt: preserve comments before global variables" { + try testCanonical( + \\/// Foo copies keys and values before they go into the map, and + \\/// frees them when they get removed. + \\pub const Foo = struct {}; + \\ + ); +} + +test "zig fmt: preserve comments before statements" { + try testCanonical( + \\test "std" { + \\ // statement comment + \\ _ = @import("foo/bar.zig"); + \\} + \\ + ); +} + +test "zig fmt: preserve top level comments" { + try testCanonical( + \\// top level comment + \\test "hi" {} + \\ + ); +} + +test "zig fmt: get stdout or fail" { + try testCanonical( + \\const std = @import("std"); + \\ + \\pub fn main() !void { + \\ // If this program is run without stdout attached, exit with an error. + \\ // another comment + \\ var stdout_file = try std.io.getStdOut; + \\} + \\ + ); +} + +test "zig fmt: preserve spacing" { + try testCanonical( + \\const std = @import("std"); + \\ + \\pub fn main() !void { + \\ var stdout_file = try std.io.getStdOut; + \\ var stdout_file = try std.io.getStdOut; + \\ + \\ var stdout_file = try std.io.getStdOut; + \\ var stdout_file = try std.io.getStdOut; + \\} + \\ + ); +} + +test "zig fmt: return types" { + try testCanonical( + \\pub fn main() !void {} + \\pub fn main() var {} + \\pub fn main() i32 {} + \\ + ); +} + +test "zig fmt: imports" { + try testCanonical( + \\const std = @import("std"); + \\const std = @import(); + \\ + ); +} + +test "zig fmt: global declarations" { + try testCanonical( + \\const a = b; + \\pub const a = b; + \\var a = b; + \\pub var a = b; + \\const a: i32 = b; + \\pub const a: i32 = b; + \\var a: i32 = b; + \\pub var a: i32 = b; + \\extern const a: i32 = b; + \\pub extern const a: i32 = b; + \\extern var a: i32 = b; + \\pub extern var a: i32 = b; + \\extern "a" const a: i32 = b; + \\pub extern "a" const a: i32 = b; + \\extern "a" var a: i32 = b; + \\pub extern "a" var a: i32 = b; + \\ + ); +} + +test "zig fmt: extern declaration" { + try testCanonical( + \\extern var foo: c_int; + \\ + ); +} + +test "zig fmt: alignment" { + try testCanonical( + \\var foo: c_int align(1); + \\ + ); +} + +test "zig fmt: C main" { + try testCanonical( + \\fn main(argc: c_int, argv: &&u8) c_int { + \\ const a = b; + \\} + \\ + ); +} + +test "zig fmt: return" { + try testCanonical( + \\fn foo(argc: c_int, argv: &&u8) c_int { + \\ return 0; + \\} + \\ + \\fn bar() void { + \\ return; + \\} + \\ + ); +} + +test "zig fmt: pointer attributes" { + try testCanonical( + \\extern fn f1(s: &align(&u8) u8) c_int; + \\extern fn f2(s: &&align(1) &const &volatile u8) c_int; + \\extern fn f3(s: &align(1) const &align(1) volatile &const volatile u8) c_int; + \\extern fn f4(s: &align(1) const volatile u8) c_int; + \\ + ); +} + +test "zig fmt: slice attributes" { + try testCanonical( + \\extern fn f1(s: &align(&u8) u8) c_int; + \\extern fn f2(s: &&align(1) &const &volatile u8) c_int; + \\extern fn f3(s: &align(1) const &align(1) volatile &const volatile u8) c_int; + \\extern fn f4(s: &align(1) const volatile u8) c_int; + \\ + ); +} + +test "zig fmt: test declaration" { + try testCanonical( + \\test "test name" { + \\ const a = 1; + \\ var b = 1; + \\} + \\ + ); +} + +test "zig fmt: infix operators" { + try testCanonical( + \\test "infix operators" { + \\ var i = undefined; + \\ i = 2; + \\ i *= 2; + \\ i |= 2; + \\ i ^= 2; + \\ i <<= 2; + \\ i >>= 2; + \\ i &= 2; + \\ i *= 2; + \\ i *%= 2; + \\ i -= 2; + \\ i -%= 2; + \\ i += 2; + \\ i +%= 2; + \\ i /= 2; + \\ i %= 2; + \\ _ = i == i; + \\ _ = i != i; + \\ _ = i != i; + \\ _ = i.i; + \\ _ = i || i; + \\ _ = i!i; + \\ _ = i ** i; + \\ _ = i ++ i; + \\ _ = i ?? i; + \\ _ = i % i; + \\ _ = i / i; + \\ _ = i *% i; + \\ _ = i * i; + \\ _ = i -% i; + \\ _ = i - i; + \\ _ = i +% i; + \\ _ = i + i; + \\ _ = i << i; + \\ _ = i >> i; + \\ _ = i & i; + \\ _ = i ^ i; + \\ _ = i | i; + \\ _ = i >= i; + \\ _ = i <= i; + \\ _ = i > i; + \\ _ = i < i; + \\ _ = i and i; + \\ _ = i or i; + \\} + \\ + ); +} + +test "zig fmt: precedence" { + try testCanonical( + \\test "precedence" { + \\ a!b(); + \\ (a!b)(); + \\ !a!b; + \\ !(a!b); + \\ !a{}; + \\ !(a{}); + \\ a + b{}; + \\ (a + b){}; + \\ a << b + c; + \\ (a << b) + c; + \\ a & b << c; + \\ (a & b) << c; + \\ a ^ b & c; + \\ (a ^ b) & c; + \\ a | b ^ c; + \\ (a | b) ^ c; + \\ a == b | c; + \\ (a == b) | c; + \\ a and b == c; + \\ (a and b) == c; + \\ a or b and c; + \\ (a or b) and c; + \\ (a or b) and c; + \\} + \\ + ); +} + +test "zig fmt: prefix operators" { + try testCanonical( + \\test "prefix operators" { + \\ try return --%~??!*&0; + \\} + \\ + ); +} + +test "zig fmt: call expression" { + try testCanonical( + \\test "test calls" { + \\ a(); + \\ a(1); + \\ a(1, 2); + \\ a(1, 2) + a(1, 2); + \\} + \\ + ); +} + +test "zig fmt: var args" { + try testCanonical( + \\fn print(args: ...) void {} + \\ + ); +} + +test "zig fmt: var type" { + try testCanonical( + \\fn print(args: var) var {} + \\const Var = var; + \\const i: var = 0; + \\ + ); +} + +test "zig fmt: functions" { + try testCanonical( + \\extern fn puts(s: &const u8) c_int; + \\extern "c" fn puts(s: &const u8) c_int; + \\export fn puts(s: &const u8) c_int; + \\inline fn puts(s: &const u8) c_int; + \\pub extern fn puts(s: &const u8) c_int; + \\pub extern "c" fn puts(s: &const u8) c_int; + \\pub export fn puts(s: &const u8) c_int; + \\pub inline fn puts(s: &const u8) c_int; + \\pub extern fn puts(s: &const u8) align(2 + 2) c_int; + \\pub extern "c" fn puts(s: &const u8) align(2 + 2) c_int; + \\pub export fn puts(s: &const u8) align(2 + 2) c_int; + \\pub inline fn puts(s: &const u8) align(2 + 2) c_int; + \\ + ); +} + +test "zig fmt: multiline string" { + try testCanonical( + \\const s = + \\ \\ something + \\ \\ something else + \\ ; + \\ + ); +} + +test "zig fmt: values" { + try testCanonical( + \\test "values" { + \\ 1; + \\ 1.0; + \\ "string"; + \\ c"cstring"; + \\ 'c'; + \\ true; + \\ false; + \\ null; + \\ undefined; + \\ error; + \\ this; + \\ unreachable; + \\} + \\ + ); +} + +test "zig fmt: indexing" { + try testCanonical( + \\test "test index" { + \\ a[0]; + \\ a[0 + 5]; + \\ a[0..]; + \\ a[0..5]; + \\ a[a[0]]; + \\ a[a[0..]]; + \\ a[a[0..5]]; + \\ a[a[0]..]; + \\ a[a[0..5]..]; + \\ a[a[0]..a[0]]; + \\ a[a[0..5]..a[0]]; + \\ a[a[0..5]..a[0..5]]; + \\} + \\ + ); +} + +test "zig fmt: struct declaration" { + try testCanonical( + \\const S = struct { + \\ const Self = this; + \\ f1: u8, + \\ pub f3: u8, + \\ + \\ fn method(self: &Self) Self { + \\ return *self; + \\ } + \\ + \\ f2: u8, + \\}; + \\ + \\const Ps = packed struct { + \\ a: u8, + \\ pub b: u8, + \\ + \\ c: u8, + \\}; + \\ + \\const Es = extern struct { + \\ a: u8, + \\ pub b: u8, + \\ + \\ c: u8, + \\}; + \\ + ); +} + +test "zig fmt: enum declaration" { + try testCanonical( + \\const E = enum { + \\ Ok, + \\ SomethingElse = 0, + \\}; + \\ + \\const E2 = enum(u8) { + \\ Ok, + \\ SomethingElse = 255, + \\ SomethingThird, + \\}; + \\ + \\const Ee = extern enum { + \\ Ok, + \\ SomethingElse, + \\ SomethingThird, + \\}; + \\ + \\const Ep = packed enum { + \\ Ok, + \\ SomethingElse, + \\ SomethingThird, + \\}; + \\ + ); +} + +test "zig fmt: union declaration" { + try testCanonical( + \\const U = union { + \\ Int: u8, + \\ Float: f32, + \\ None, + \\ Bool: bool, + \\}; + \\ + \\const Ue = union(enum) { + \\ Int: u8, + \\ Float: f32, + \\ None, + \\ Bool: bool, + \\}; + \\ + \\const E = enum { + \\ Int, + \\ Float, + \\ None, + \\ Bool, + \\}; + \\ + \\const Ue2 = union(E) { + \\ Int: u8, + \\ Float: f32, + \\ None, + \\ Bool: bool, + \\}; + \\ + \\const Eu = extern union { + \\ Int: u8, + \\ Float: f32, + \\ None, + \\ Bool: bool, + \\}; + \\ + ); +} + +test "zig fmt: error set declaration" { + try testCanonical( + \\const E = error { + \\ A, + \\ B, + \\ + \\ C, + \\}; + \\ + ); +} + +test "zig fmt: arrays" { + try testCanonical( + \\test "test array" { + \\ const a: [2]u8 = [2]u8 { + \\ 1, + \\ 2, + \\ }; + \\ const a: [2]u8 = []u8 { + \\ 1, + \\ 2, + \\ }; + \\ const a: [0]u8 = []u8{}; + \\} + \\ + ); +} + +test "zig fmt: container initializers" { + try testCanonical( + \\const a1 = []u8{}; + \\const a2 = []u8 { + \\ 1, + \\ 2, + \\ 3, + \\ 4, + \\}; + \\const s1 = S{}; + \\const s2 = S { + \\ .a = 1, + \\ .b = 2, + \\}; + \\ + ); +} + +test "zig fmt: catch" { + try testCanonical( + \\test "catch" { + \\ const a: error!u8 = 0; + \\ _ = a catch return; + \\ _ = a catch |err| return; + \\} + \\ + ); +} + +test "zig fmt: blocks" { + try testCanonical( + \\test "blocks" { + \\ { + \\ const a = 0; + \\ const b = 0; + \\ } + \\ + \\ blk: { + \\ const a = 0; + \\ const b = 0; + \\ } + \\ + \\ const r = blk: { + \\ const a = 0; + \\ const b = 0; + \\ }; + \\} + \\ + ); +} + +test "zig fmt: switch" { + try testCanonical( + \\test "switch" { + \\ switch (0) { + \\ 0 => {}, + \\ 1 => unreachable, + \\ 2, + \\ 3 => {}, + \\ 4 ... 7 => {}, + \\ 1 + 4 * 3 + 22 => {}, + \\ else => { + \\ const a = 1; + \\ const b = a; + \\ }, + \\ } + \\ + \\ const res = switch (0) { + \\ 0 => 0, + \\ 1 => 2, + \\ 1 => a = 4, + \\ else => 4, + \\ }; + \\ + \\ const Union = union(enum) { + \\ Int: i64, + \\ Float: f64, + \\ }; + \\ + \\ const u = Union { + \\ .Int = 0, + \\ }; + \\ switch (u) { + \\ Union.Int => |int| {}, + \\ Union.Float => |*float| unreachable, + \\ } + \\} + \\ + ); +} + +test "zig fmt: while" { + try testCanonical( + \\test "while" { + \\ while (10 < 1) { + \\ unreachable; + \\ } + \\ + \\ while (10 < 1) + \\ unreachable; + \\ + \\ var i: usize = 0; + \\ while (i < 10) : (i += 1) { + \\ continue; + \\ } + \\ + \\ i = 0; + \\ while (i < 10) : (i += 1) + \\ continue; + \\ + \\ i = 0; + \\ var j: usize = 0; + \\ while (i < 10) : ({ + \\ i += 1; + \\ j += 1; + \\ }) { + \\ continue; + \\ } + \\ + \\ var a: ?u8 = 2; + \\ while (a) |v| : (a = null) { + \\ continue; + \\ } + \\ + \\ while (a) |v| : (a = null) + \\ unreachable; + \\ + \\ label: while (10 < 0) { + \\ unreachable; + \\ } + \\ + \\ const res = while (0 < 10) { + \\ break 7; + \\ } else { + \\ unreachable; + \\ }; + \\ + \\ const res = while (0 < 10) + \\ break 7 + \\ else + \\ unreachable; + \\ + \\ var a: error!u8 = 0; + \\ while (a) |v| { + \\ a = error.Err; + \\ } else |err| { + \\ i = 1; + \\ } + \\ + \\ comptime var k: usize = 0; + \\ inline while (i < 10) : (i += 1) + \\ j += 2; + \\} + \\ + ); +} + +test "zig fmt: for" { + try testCanonical( + \\test "for" { + \\ const a = []u8 { + \\ 1, + \\ 2, + \\ 3, + \\ }; + \\ for (a) |v| { + \\ continue; + \\ } + \\ + \\ for (a) |v| + \\ continue; + \\ + \\ for (a) |*v| + \\ continue; + \\ + \\ for (a) |v, i| { + \\ continue; + \\ } + \\ + \\ for (a) |v, i| + \\ continue; + \\ + \\ const res = for (a) |v, i| { + \\ break v; + \\ } else { + \\ unreachable; + \\ }; + \\ + \\ var num: usize = 0; + \\ inline for (a) |v, i| { + \\ num += v; + \\ num += i; + \\ } + \\} + \\ + ); +} + +test "zig fmt: if" { + try testCanonical( + \\test "if" { + \\ if (10 < 0) { + \\ unreachable; + \\ } + \\ + \\ if (10 < 0) unreachable; + \\ + \\ if (10 < 0) { + \\ unreachable; + \\ } else { + \\ const a = 20; + \\ } + \\ + \\ if (10 < 0) { + \\ unreachable; + \\ } else if (5 < 0) { + \\ unreachable; + \\ } else { + \\ const a = 20; + \\ } + \\ + \\ const is_world_broken = if (10 < 0) true else false; + \\ const some_number = 1 + if (10 < 0) 2 else 3; + \\ + \\ const a: ?u8 = 10; + \\ const b: ?u8 = null; + \\ if (a) |v| { + \\ const some = v; + \\ } else if (b) |*v| { + \\ unreachable; + \\ } else { + \\ const some = 10; + \\ } + \\ + \\ const non_null_a = if (a) |v| v else 0; + \\ + \\ const a_err: error!u8 = 0; + \\ if (a_err) |v| { + \\ const p = v; + \\ } else |err| { + \\ unreachable; + \\ } + \\} + \\ + ); +} + +test "zig fmt: defer" { + try testCanonical( + \\test "defer" { + \\ var i: usize = 0; + \\ defer i = 1; + \\ defer { + \\ i += 2; + \\ i *= i; + \\ } + \\ + \\ errdefer i += 3; + \\ errdefer { + \\ i += 2; + \\ i /= i; + \\ } + \\} + \\ + ); +} + +test "zig fmt: comptime" { + try testCanonical( + \\fn a() u8 { + \\ return 5; + \\} + \\ + \\fn b(comptime i: u8) u8 { + \\ return i; + \\} + \\ + \\const av = comptime a(); + \\const av2 = comptime blk: { + \\ var res = a(); + \\ res *= b(2); + \\ break :blk res; + \\}; + \\ + \\comptime { + \\ _ = a(); + \\} + \\ + \\test "comptime" { + \\ const av3 = comptime a(); + \\ const av4 = comptime blk: { + \\ var res = a(); + \\ res *= a(); + \\ break :blk res; + \\ }; + \\ + \\ comptime var i = 0; + \\ comptime { + \\ i = a(); + \\ i += b(i); + \\ } + \\} + \\ + ); +} + +test "zig fmt: fn type" { + try testCanonical( + \\fn a(i: u8) u8 { + \\ return i + 1; + \\} + \\ + \\const a: fn(u8) u8 = undefined; + \\const b: extern fn(u8) u8 = undefined; + \\const c: nakedcc fn(u8) u8 = undefined; + \\const ap: fn(u8) u8 = a; + \\ + ); +} + +test "zig fmt: inline asm" { + try testCanonical( + \\pub fn syscall1(number: usize, arg1: usize) usize { + \\ return asm volatile ("syscall" + \\ : [ret] "={rax}" (-> usize) + \\ : [number] "{rax}" (number), + \\ [arg1] "{rdi}" (arg1) + \\ : "rcx", "r11"); + \\} + \\ + ); +} + +test "zig fmt: coroutines" { + try testCanonical( + \\async fn simpleAsyncFn() void { + \\ const a = async a.b(); + \\ x += 1; + \\ suspend; + \\ x += 1; + \\ suspend |p| {} + \\ const p = async simpleAsyncFn() catch unreachable; + \\ await p; + \\} + \\ + \\test "coroutine suspend, resume, cancel" { + \\ const p = try async testAsyncSeq(); + \\ resume p; + \\ cancel p; + \\} + \\ + ); +} + +test "zig fmt: Block after if" { + try testCanonical( + \\test "Block after if" { + \\ if (true) { + \\ const a = 0; + \\ } + \\ + \\ { + \\ const a = 0; + \\ } + \\} + \\ + ); +} + +test "zig fmt: use" { + try testCanonical( + \\use @import("std"); + \\pub use @import("std"); + \\ + ); +} + +test "zig fmt: string identifier" { + try testCanonical( + \\const @"a b" = @"c d".@"e f"; + \\fn @"g h"() void {} + \\ + ); +} + +test "zig fmt: error return" { + try testCanonical( + \\fn err() error { + \\ call(); + \\ return error.InvalidArgs; + \\} + \\ + ); +} + +test "zig fmt: struct literals with fields on each line" { + try testCanonical( + \\var self = BufSet { + \\ .hash_map = BufSetHashMap.init(a), + \\}; + \\ + ); +} + +const std = @import("std"); +const mem = std.mem; +const warn = std.debug.warn; +const Tokenizer = std.zig.Tokenizer; +const Parser = std.zig.Parser; +const io = std.io; + +var fixed_buffer_mem: [100 * 1024]u8 = undefined; + +fn testParse(source: []const u8, allocator: &mem.Allocator) ![]u8 { + var tokenizer = Tokenizer.init(source); + var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); + defer parser.deinit(); + + var tree = try parser.parse(); + defer tree.deinit(); + + var buffer = try std.Buffer.initSize(allocator, 0); + errdefer buffer.deinit(); + + var buffer_out_stream = io.BufferOutStream.init(&buffer); + try parser.renderSource(&buffer_out_stream.stream, tree.root_node); + return buffer.toOwnedSlice(); +} + +fn testCanonical(source: []const u8) !void { + const needed_alloc_count = x: { + // Try it once with unlimited memory, make sure it works + var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, @maxValue(usize)); + const result_source = try testParse(source, &failing_allocator.allocator); + if (!mem.eql(u8, result_source, source)) { + warn("\n====== expected this output: =========\n"); + warn("{}", source); + warn("\n======== instead found this: =========\n"); + warn("{}", result_source); + warn("\n======================================\n"); + return error.TestFailed; + } + failing_allocator.allocator.free(result_source); + break :x failing_allocator.index; + }; + + var fail_index: usize = 0; + while (fail_index < needed_alloc_count) : (fail_index += 1) { + var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, fail_index); + if (testParse(source, &failing_allocator.allocator)) |_| { + return error.NondeterministicMemoryUsage; + } else |err| switch (err) { + error.OutOfMemory => { + if (failing_allocator.allocated_bytes != failing_allocator.freed_bytes) { + warn("\nfail_index: {}/{}\nallocated bytes: {}\nfreed bytes: {}\nallocations: {}\ndeallocations: {}\n", + fail_index, needed_alloc_count, + failing_allocator.allocated_bytes, failing_allocator.freed_bytes, + failing_allocator.index, failing_allocator.deallocations); + return error.MemoryLeakDetected; + } + }, + error.ParseError => @panic("test failed"), + } + } +} + From c53209a8a8cee014382a735b3baa7cd439106c6f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 19:55:57 -0400 Subject: [PATCH 40/58] zig fmt: comments before var decl in struct --- std/zig/parser.zig | 26 +++++++++++++++++--------- std/zig/parser_test.zig | 33 ++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 5f0928ee4..cd4b9c136 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -230,6 +230,7 @@ pub const Parser = struct { Semicolon: &&ast.Node, AddComments: AddCommentsCtx, LookForSameLineComment: &&ast.Node, + LookForSameLineCommentDirect: &ast.Node, AsmOutputItems: &ArrayList(&ast.Node.AsmOutput), AsmOutputReturnOrType: &ast.Node.AsmOutput, @@ -532,7 +533,7 @@ pub const Parser = struct { } } - stack.append(State { + try stack.append(State { .VarDecl = VarDeclCtx { .comments = ctx.comments, .visib_token = ctx.visib_token, @@ -542,7 +543,7 @@ pub const Parser = struct { .mut_token = token, .list = ctx.decls } - }) catch unreachable; + }); continue; }, Token.Id.Keyword_fn, Token.Id.Keyword_nakedcc, @@ -705,6 +706,7 @@ pub const Parser = struct { continue; }, State.ContainerDecl => |container_decl| { + const comments = try self.eatComments(arena); const token = self.getNextToken(); switch (token.id) { Token.Id.Identifier => { @@ -713,7 +715,7 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.StructField { .base = ast.Node { .id = ast.Node.Id.StructField, - .before_comments = null, + .before_comments = comments, .same_line_comment = null, }, .visib_token = null, @@ -765,7 +767,7 @@ pub const Parser = struct { .TopLevelExternOrField = TopLevelExternOrFieldCtx { .visib_token = token, .container_decl = container_decl, - .comments = null, + .comments = comments, } }); continue; @@ -778,7 +780,7 @@ pub const Parser = struct { .visib_token = token, .extern_export_inline_token = null, .lib_name = null, - .comments = null, + .comments = comments, } }); continue; @@ -793,7 +795,7 @@ pub const Parser = struct { .visib_token = token, .extern_export_inline_token = null, .lib_name = null, - .comments = null, + .comments = comments, } }); continue; @@ -811,7 +813,7 @@ pub const Parser = struct { .visib_token = null, .extern_export_inline_token = null, .lib_name = null, - .comments = null, + .comments = comments, } }); continue; @@ -842,7 +844,8 @@ pub const Parser = struct { }); try ctx.list.append(&var_decl.base); - stack.append(State { .VarDeclAlign = var_decl }) catch unreachable; + try stack.append(State { .LookForSameLineCommentDirect = &var_decl.base }); + try stack.append(State { .VarDeclAlign = var_decl }); try stack.append(State { .TypeExprBegin = OptionalCtx { .RequiredNull = &var_decl.type_node} }); try stack.append(State { .IfToken = Token.Id.Colon }); try stack.append(State { @@ -854,7 +857,7 @@ pub const Parser = struct { continue; }, State.VarDeclAlign => |var_decl| { - stack.append(State { .VarDeclEq = var_decl }) catch unreachable; + try stack.append(State { .VarDeclEq = var_decl }); const next_token = self.getNextToken(); if (next_token.id == Token.Id.Keyword_align) { @@ -1348,6 +1351,11 @@ pub const Parser = struct { continue; }, + State.LookForSameLineCommentDirect => |node| { + try self.lookForSameLineComment(arena, node); + continue; + }, + State.AsmOutputItems => |items| { const lbracket = self.getNextToken(); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 4edce9843..78b08d91c 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,4 +1,27 @@ -test "zig fmt: line comment after field decl" { +test "zig fmt: comments before var decl in struct" { + try testCanonical( + \\pub const vfs_cap_data = extern struct { + \\ // All of these are mandated as little endian + \\ // when on disk. + \\ const Data = struct { + \\ permitted: u32, + \\ inheritable: u32, + \\ }; + \\}; + \\ + ); +} + +test "zig fmt: same-line comment after var decl in struct" { + try testCanonical( + \\pub const vfs_cap_data = extern struct { + \\ const Data = struct {}; // when on disk. + \\}; + \\ + ); +} + +test "zig fmt: same-line comment after field decl" { try testCanonical( \\pub const dirent = extern struct { \\ d_name: u8, @@ -18,7 +41,7 @@ test "zig fmt: array literal with 1 item on 1 line" { ); } -test "zig fmt: preserve same-line comment after a statement" { +test "zig fmt: same-line comment after a statement" { try testCanonical( \\test "" { \\ a = b; @@ -29,7 +52,7 @@ test "zig fmt: preserve same-line comment after a statement" { ); } -test "zig fmt: preserve comments before global variables" { +test "zig fmt: comments before global variables" { try testCanonical( \\/// Foo copies keys and values before they go into the map, and \\/// frees them when they get removed. @@ -38,7 +61,7 @@ test "zig fmt: preserve comments before global variables" { ); } -test "zig fmt: preserve comments before statements" { +test "zig fmt: comments before statements" { try testCanonical( \\test "std" { \\ // statement comment @@ -48,7 +71,7 @@ test "zig fmt: preserve comments before statements" { ); } -test "zig fmt: preserve top level comments" { +test "zig fmt: comments before test decl" { try testCanonical( \\// top level comment \\test "hi" {} From a912c7d75f61fb127ff8552e7a10ffe4672ac1aa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 21:27:44 -0400 Subject: [PATCH 41/58] zig fmt: same-line comment after switch prong --- std/zig/parser.zig | 41 ++++++++++++++++++++++++----------------- std/zig/parser_test.zig | 12 ++++++++++++ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index cd4b9c136..aae34f39b 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -1505,11 +1505,11 @@ pub const Parser = struct { if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| { container_decl.rbrace_token = end; continue; - } else { - try self.lookForSameLineComment(arena, container_decl.fields_and_decls.toSlice()[container_decl.fields_and_decls.len - 1]); - try stack.append(State { .ContainerDecl = container_decl }); - continue; } + + try self.lookForSameLineComment(arena, container_decl.fields_and_decls.toSlice()[container_decl.fields_and_decls.len - 1]); + try stack.append(State { .ContainerDecl = container_decl }); + continue; }, State.IdentifierListItemOrEnd => |list_state| { if (self.eatToken(Token.Id.RBrace)) |rbrace| { @@ -1536,30 +1536,36 @@ pub const Parser = struct { continue; } - const node = try self.createNode(arena, ast.Node.SwitchCase, - ast.Node.SwitchCase { - .base = undefined, - .items = ArrayList(&ast.Node).init(arena), - .payload = null, - .expr = undefined, - } - ); + const node = try arena.construct(ast.Node.SwitchCase { + .base = ast.Node { + .id = ast.Node.Id.SwitchCase, + .before_comments = null, + .same_line_comment = null, + }, + .items = ArrayList(&ast.Node).init(arena), + .payload = null, + .expr = undefined, + }); try list_state.list.append(node); - stack.append(State { .SwitchCaseCommaOrEnd = list_state }) catch unreachable; + try stack.append(State { .SwitchCaseCommaOrEnd = list_state }); try stack.append(State { .AssignmentExpressionBegin = OptionalCtx { .Required = &node.expr } }); try stack.append(State { .PointerPayload = OptionalCtx { .Optional = &node.payload } }); try stack.append(State { .SwitchCaseFirstItem = &node.items }); continue; }, + State.SwitchCaseCommaOrEnd => |list_state| { if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| { *list_state.ptr = end; continue; - } else { - stack.append(State { .SwitchCaseOrEnd = list_state }) catch unreachable; - continue; } + + const switch_case = list_state.list.toSlice()[list_state.list.len - 1]; + try self.lookForSameLineComment(arena, &switch_case.base); + try stack.append(State { .SwitchCaseOrEnd = list_state }); + continue; }, + State.SwitchCaseFirstItem => |case_items| { const token = self.getNextToken(); if (token.id == Token.Id.Keyword_else) { @@ -4095,7 +4101,6 @@ pub const Parser = struct { while (i != 0) { i -= 1; const node = cases[i]; - try stack.append(RenderState { .Text = ","}); try stack.append(RenderState { .Expression = &node.base}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { @@ -4118,6 +4123,8 @@ pub const Parser = struct { ast.Node.Id.SwitchCase => { const switch_case = @fieldParentPtr(ast.Node.SwitchCase, "base", base); + try stack.append(RenderState { .PrintSameLineComment = switch_case.base.same_line_comment }); + try stack.append(RenderState { .Text = "," }); try stack.append(RenderState { .Expression = switch_case.expr }); if (switch_case.payload) |payload| { try stack.append(RenderState { .Text = " " }); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 78b08d91c..e37cc7239 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,3 +1,15 @@ +test "zig fmt: same-line comment after switch prong" { + try testCanonical( + \\test "" { + \\ switch (err) { + \\ error.PathAlreadyExists => {}, // comment 2 + \\ else => return err, // comment 1 + \\ } + \\} + \\ + ); +} + test "zig fmt: comments before var decl in struct" { try testCanonical( \\pub const vfs_cap_data = extern struct { From f04015c080b8e4e97166d6ac9356f818e766fc48 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 21:47:54 -0400 Subject: [PATCH 42/58] zig fmt: comments before switch prong --- std/zig/parser.zig | 8 ++++++-- std/zig/parser_test.zig | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index aae34f39b..454ab445c 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -1536,10 +1536,11 @@ pub const Parser = struct { continue; } + const comments = try self.eatComments(arena); const node = try arena.construct(ast.Node.SwitchCase { .base = ast.Node { .id = ast.Node.Id.SwitchCase, - .before_comments = null, + .before_comments = comments, .same_line_comment = null, }, .items = ArrayList(&ast.Node).init(arena), @@ -1551,6 +1552,7 @@ pub const Parser = struct { try stack.append(State { .AssignmentExpressionBegin = OptionalCtx { .Required = &node.expr } }); try stack.append(State { .PointerPayload = OptionalCtx { .Optional = &node.payload } }); try stack.append(State { .SwitchCaseFirstItem = &node.items }); + continue; }, @@ -4123,7 +4125,9 @@ pub const Parser = struct { ast.Node.Id.SwitchCase => { const switch_case = @fieldParentPtr(ast.Node.SwitchCase, "base", base); - try stack.append(RenderState { .PrintSameLineComment = switch_case.base.same_line_comment }); + try self.renderComments(stream, base, indent); + + try stack.append(RenderState { .PrintSameLineComment = base.same_line_comment }); try stack.append(RenderState { .Text = "," }); try stack.append(RenderState { .Expression = switch_case.expr }); if (switch_case.payload) |payload| { diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index e37cc7239..e70b6adb0 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,3 +1,18 @@ +test "zig fmt: comments before switch prong" { + try testCanonical( + \\test "" { + \\ switch (err) { + \\ error.PathAlreadyExists => continue, + \\ + \\ // comment 1 + \\ // comment 2 + \\ else => return err, + \\ } + \\} + \\ + ); +} + test "zig fmt: same-line comment after switch prong" { try testCanonical( \\test "" { From 4e23fb7f062322e604d74ebb7e62d707ecf7e7b6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 22:12:17 -0400 Subject: [PATCH 43/58] zig fmt: comments before error set decl --- std/zig/parser.zig | 36 ++++++++++++++++++++++++++---------- std/zig/parser_test.zig | 13 +++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 454ab445c..ffbc0a223 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -1517,8 +1517,15 @@ pub const Parser = struct { continue; } - stack.append(State { .IdentifierListCommaOrEnd = list_state }) catch unreachable; - try stack.append(State { .Identifier = OptionalCtx { .Required = try list_state.list.addOne() } }); + const comments = try self.eatComments(arena); + const node_ptr = try list_state.list.addOne(); + + try stack.append(State { .AddComments = AddCommentsCtx { + .node_ptr = node_ptr, + .comments = comments, + }}); + try stack.append(State { .IdentifierListCommaOrEnd = list_state }); + try stack.append(State { .Identifier = OptionalCtx { .Required = node_ptr } }); continue; }, State.IdentifierListCommaOrEnd => |list_state| { @@ -2739,14 +2746,17 @@ pub const Parser = struct { continue; } - const node = try self.createToCtxNode(arena, ctx.opt_ctx, ast.Node.ErrorSetDecl, - ast.Node.ErrorSetDecl { - .base = undefined, - .error_token = ctx.error_token, - .decls = ArrayList(&ast.Node).init(arena), - .rbrace_token = undefined, - } - ); + const node = try arena.construct(ast.Node.ErrorSetDecl { + .base = ast.Node { + .id = ast.Node.Id.ErrorSetDecl, + .before_comments = null, + .same_line_comment = null, + }, + .error_token = ctx.error_token, + .decls = ArrayList(&ast.Node).init(arena), + .rbrace_token = undefined, + }); + ctx.opt_ctx.store(&node.base); stack.append(State { .IdentifierListItemOrEnd = ListSave(&ast.Node) { @@ -3337,6 +3347,7 @@ pub const Parser = struct { PrintIndent, Indent: usize, PrintSameLineComment: ?&Token, + PrintComments: &ast.Node, }; pub fn renderSource(self: &Parser, stream: var, root_node: &ast.Node.Root) !void { @@ -3978,6 +3989,7 @@ pub const Parser = struct { const node = decls[i]; try stack.append(RenderState { .Text = "," }); try stack.append(RenderState { .Expression = node }); + try stack.append(RenderState { .PrintComments = node }); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Text = blk: { @@ -4466,6 +4478,10 @@ pub const Parser = struct { const comment_token = maybe_comment ?? break :blk; try stream.print(" {}", self.tokenizer.getTokenSlice(comment_token)); }, + + RenderState.PrintComments => |node| blk: { + try self.renderComments(stream, node, indent); + }, } } } diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index e70b6adb0..a914775dc 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,3 +1,16 @@ +test "zig fmt: comments before error set decl" { + try testCanonical( + \\const UnexpectedError = error { + \\ /// The Operating System returned an undocumented error code. + \\ Unexpected, + \\ + \\ // another + \\ Another, + \\}; + \\ + ); +} + test "zig fmt: comments before switch prong" { try testCanonical( \\test "" { From 39befc35a851f6cf206c7e3b19d66f7df00440b0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 29 Apr 2018 22:31:42 -0400 Subject: [PATCH 44/58] update comment in std/os/index.zig --- std/os/index.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/os/index.zig b/std/os/index.zig index ee9ff1516..99aa7891c 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2546,9 +2546,9 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread else => return unexpectedErrorPosix(usize(err)), } } else if (builtin.os == builtin.Os.linux) { - // use linux API directly + // use linux API directly. TODO use posix.CLONE_SETTLS and initialize thread local storage correctly const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND - | posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS + | posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; const newtls: usize = 0; const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.pid, newtls, &thread_ptr.data.pid); From fd2cd38bdb831ef78a0d4ab0973020dfbd348c1f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 00:19:55 -0400 Subject: [PATCH 45/58] zig fmt: support line comments and doc comments line comments can go anywhere a list of something is allowed --- std/zig/ast.zig | 29 ++++++-- std/zig/parser.zig | 142 +++++++++++++++++++++++++++------------- std/zig/parser_test.zig | 36 +++++++++- std/zig/tokenizer.zig | 17 ++++- 4 files changed, 168 insertions(+), 56 deletions(-) diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 75c3696bf..716ed8eb7 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -6,7 +6,7 @@ const mem = std.mem; pub const Node = struct { id: Id, - before_comments: ?&LineComment, + doc_comments: ?&DocComment, same_line_comment: ?&Token, pub const Id = enum { @@ -59,6 +59,7 @@ pub const Node = struct { // Misc LineComment, + DocComment, SwitchCase, SwitchElse, Else, @@ -718,7 +719,8 @@ pub const Node = struct { base: Node, switch_token: Token, expr: &Node, - cases: ArrayList(&SwitchCase), + /// these can be SwitchCase nodes or LineComment nodes + cases: ArrayList(&Node), rbrace: Token, pub fn iterate(self: &Switch, index: usize) ?&Node { @@ -727,7 +729,7 @@ pub const Node = struct { if (i < 1) return self.expr; i -= 1; - if (i < self.cases.len) return &self.cases.at(i).base; + if (i < self.cases.len) return self.cases.at(i); i -= self.cases.len; return null; @@ -1715,17 +1717,34 @@ pub const Node = struct { pub const LineComment = struct { base: Node, - lines: ArrayList(Token), + token: Token, pub fn iterate(self: &LineComment, index: usize) ?&Node { return null; } pub fn firstToken(self: &LineComment) Token { - return self.lines.at(0); + return self.token; } pub fn lastToken(self: &LineComment) Token { + return self.token; + } + }; + + pub const DocComment = struct { + base: Node, + lines: ArrayList(Token), + + pub fn iterate(self: &DocComment, index: usize) ?&Node { + return null; + } + + pub fn firstToken(self: &DocComment) Token { + return self.lines.at(0); + } + + pub fn lastToken(self: &DocComment) Token { return self.lines.at(self.lines.len - 1); } }; diff --git a/std/zig/parser.zig b/std/zig/parser.zig index ffbc0a223..b5849c3e9 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -55,7 +55,7 @@ pub const Parser = struct { visib_token: ?Token, extern_export_inline_token: ?Token, lib_name: ?&ast.Node, - comments: ?&ast.Node.LineComment, + comments: ?&ast.Node.DocComment, }; const VarDeclCtx = struct { @@ -65,19 +65,19 @@ pub const Parser = struct { extern_export_token: ?Token, lib_name: ?&ast.Node, list: &ArrayList(&ast.Node), - comments: ?&ast.Node.LineComment, + comments: ?&ast.Node.DocComment, }; const TopLevelExternOrFieldCtx = struct { visib_token: Token, container_decl: &ast.Node.ContainerDecl, - comments: ?&ast.Node.LineComment, + comments: ?&ast.Node.DocComment, }; const ExternTypeCtx = struct { opt_ctx: OptionalCtx, extern_token: Token, - comments: ?&ast.Node.LineComment, + comments: ?&ast.Node.DocComment, }; const ContainerKindCtx = struct { @@ -186,7 +186,7 @@ pub const Parser = struct { const AddCommentsCtx = struct { node_ptr: &&ast.Node, - comments: ?&ast.Node.LineComment, + comments: ?&ast.Node.DocComment, }; const State = union(enum) { @@ -244,8 +244,8 @@ pub const Parser = struct { FieldListCommaOrEnd: &ast.Node.ContainerDecl, IdentifierListItemOrEnd: ListSave(&ast.Node), IdentifierListCommaOrEnd: ListSave(&ast.Node), - SwitchCaseOrEnd: ListSave(&ast.Node.SwitchCase), - SwitchCaseCommaOrEnd: ListSave(&ast.Node.SwitchCase), + SwitchCaseOrEnd: ListSave(&ast.Node), + SwitchCaseCommaOrEnd: ListSave(&ast.Node), SwitchCaseFirstItem: &ArrayList(&ast.Node), SwitchCaseItem: &ArrayList(&ast.Node), SwitchCaseItemCommaOrEnd: &ArrayList(&ast.Node), @@ -349,6 +349,10 @@ pub const Parser = struct { switch (state) { State.TopLevel => { + while (try self.eatLineComment(arena)) |line_comment| { + try root_node.decls.append(&line_comment.base); + } + const comments = try self.eatComments(arena); const token = self.getNextToken(); switch (token.id) { @@ -358,7 +362,7 @@ pub const Parser = struct { const block = try arena.construct(ast.Node.Block { .base = ast.Node { .id = ast.Node.Id.Block, - .before_comments = null, + .doc_comments = null, .same_line_comment = null, }, .label = null, @@ -369,7 +373,7 @@ pub const Parser = struct { const test_node = try arena.construct(ast.Node.TestDecl { .base = ast.Node { .id = ast.Node.Id.TestDecl, - .before_comments = comments, + .doc_comments = comments, .same_line_comment = null, }, .test_token = token, @@ -551,7 +555,7 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .before_comments = ctx.comments, + .doc_comments = ctx.comments, .same_line_comment = null, }, .visib_token = ctx.visib_token, @@ -620,7 +624,7 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.StructField { .base = ast.Node { .id = ast.Node.Id.StructField, - .before_comments = null, + .doc_comments = null, .same_line_comment = null, }, .visib_token = ctx.visib_token, @@ -706,6 +710,10 @@ pub const Parser = struct { continue; }, State.ContainerDecl => |container_decl| { + while (try self.eatLineComment(arena)) |line_comment| { + try container_decl.fields_and_decls.append(&line_comment.base); + } + const comments = try self.eatComments(arena); const token = self.getNextToken(); switch (token.id) { @@ -715,7 +723,7 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.StructField { .base = ast.Node { .id = ast.Node.Id.StructField, - .before_comments = comments, + .doc_comments = comments, .same_line_comment = null, }, .visib_token = null, @@ -826,7 +834,7 @@ pub const Parser = struct { const var_decl = try arena.construct(ast.Node.VarDecl { .base = ast.Node { .id = ast.Node.Id.VarDecl, - .before_comments = ctx.comments, + .doc_comments = ctx.comments, .same_line_comment = null, }, .visib_token = ctx.visib_token, @@ -1222,6 +1230,14 @@ pub const Parser = struct { else => { self.putBackToken(token); stack.append(State { .Block = block }) catch unreachable; + + var any_comments = false; + while (try self.eatLineComment(arena)) |line_comment| { + try block.statements.append(&line_comment.base); + any_comments = true; + } + if (any_comments) continue; + try stack.append(State { .Statement = block }); continue; }, @@ -1258,7 +1274,7 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.Defer { .base = ast.Node { .id = ast.Node.Id.Defer, - .before_comments = comments, + .doc_comments = comments, .same_line_comment = null, }, .defer_token = token, @@ -1342,7 +1358,7 @@ pub const Parser = struct { State.AddComments => |add_comments_ctx| { const node = *add_comments_ctx.node_ptr; - node.before_comments = add_comments_ctx.comments; + node.doc_comments = add_comments_ctx.comments; continue; }, @@ -1466,7 +1482,7 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.FieldInitializer { .base = ast.Node { .id = ast.Node.Id.FieldInitializer, - .before_comments = null, + .doc_comments = null, .same_line_comment = null, }, .period_token = undefined, @@ -1512,6 +1528,10 @@ pub const Parser = struct { continue; }, State.IdentifierListItemOrEnd => |list_state| { + while (try self.eatLineComment(arena)) |line_comment| { + try list_state.list.append(&line_comment.base); + } + if (self.eatToken(Token.Id.RBrace)) |rbrace| { *list_state.ptr = rbrace; continue; @@ -1538,6 +1558,10 @@ pub const Parser = struct { } }, State.SwitchCaseOrEnd => |list_state| { + while (try self.eatLineComment(arena)) |line_comment| { + try list_state.list.append(&line_comment.base); + } + if (self.eatToken(Token.Id.RBrace)) |rbrace| { *list_state.ptr = rbrace; continue; @@ -1547,14 +1571,14 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.SwitchCase { .base = ast.Node { .id = ast.Node.Id.SwitchCase, - .before_comments = comments, + .doc_comments = comments, .same_line_comment = null, }, .items = ArrayList(&ast.Node).init(arena), .payload = null, .expr = undefined, }); - try list_state.list.append(node); + try list_state.list.append(&node.base); try stack.append(State { .SwitchCaseCommaOrEnd = list_state }); try stack.append(State { .AssignmentExpressionBegin = OptionalCtx { .Required = &node.expr } }); try stack.append(State { .PointerPayload = OptionalCtx { .Optional = &node.payload } }); @@ -1569,8 +1593,8 @@ pub const Parser = struct { continue; } - const switch_case = list_state.list.toSlice()[list_state.list.len - 1]; - try self.lookForSameLineComment(arena, &switch_case.base); + const node = list_state.list.toSlice()[list_state.list.len - 1]; + try self.lookForSameLineComment(arena, node); try stack.append(State { .SwitchCaseOrEnd = list_state }); continue; }, @@ -1660,7 +1684,7 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .before_comments = ctx.comments, + .doc_comments = ctx.comments, .same_line_comment = null, }, .visib_token = null, @@ -2632,7 +2656,7 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .before_comments = null, + .doc_comments = null, .same_line_comment = null, }, .visib_token = null, @@ -2656,7 +2680,7 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .before_comments = null, + .doc_comments = null, .same_line_comment = null, }, .visib_token = null, @@ -2749,7 +2773,7 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.ErrorSetDecl { .base = ast.Node { .id = ast.Node.Id.ErrorSetDecl, - .before_comments = null, + .doc_comments = null, .same_line_comment = null, }, .error_token = ctx.error_token, @@ -2829,18 +2853,18 @@ pub const Parser = struct { } } - fn eatComments(self: &Parser, arena: &mem.Allocator) !?&ast.Node.LineComment { - var result: ?&ast.Node.LineComment = null; + fn eatComments(self: &Parser, arena: &mem.Allocator) !?&ast.Node.DocComment { + var result: ?&ast.Node.DocComment = null; while (true) { - if (self.eatToken(Token.Id.LineComment)) |line_comment| { + if (self.eatToken(Token.Id.DocComment)) |line_comment| { const node = blk: { if (result) |comment_node| { break :blk comment_node; } else { - const comment_node = try arena.construct(ast.Node.LineComment { + const comment_node = try arena.construct(ast.Node.DocComment { .base = ast.Node { - .id = ast.Node.Id.LineComment, - .before_comments = null, + .id = ast.Node.Id.DocComment, + .doc_comments = null, .same_line_comment = null, }, .lines = ArrayList(Token).init(arena), @@ -2857,6 +2881,18 @@ pub const Parser = struct { return result; } + fn eatLineComment(self: &Parser, arena: &mem.Allocator) !?&ast.Node.LineComment { + const token = self.eatToken(Token.Id.LineComment) ?? return null; + return try arena.construct(ast.Node.LineComment { + .base = ast.Node { + .id = ast.Node.Id.LineComment, + .doc_comments = null, + .same_line_comment = null, + }, + .token = token, + }); + } + fn requireSemiColon(node: &const ast.Node) bool { var n = node; while (true) { @@ -2874,6 +2910,7 @@ pub const Parser = struct { ast.Node.Id.SwitchCase, ast.Node.Id.SwitchElse, ast.Node.Id.FieldInitializer, + ast.Node.Id.DocComment, ast.Node.Id.LineComment, ast.Node.Id.TestDecl => return false, ast.Node.Id.While => { @@ -2933,7 +2970,7 @@ pub const Parser = struct { const node_last_token = node.lastToken(); const line_comment_token = self.getNextToken(); - if (line_comment_token.id != Token.Id.LineComment) { + if (line_comment_token.id != Token.Id.DocComment and line_comment_token.id != Token.Id.LineComment) { self.putBackToken(line_comment_token); return; } @@ -3038,18 +3075,21 @@ pub const Parser = struct { return true; }, Token.Id.Keyword_switch => { - const node = try self.createToCtxNode(arena, ctx, ast.Node.Switch, - ast.Node.Switch { - .base = undefined, - .switch_token = *token, - .expr = undefined, - .cases = ArrayList(&ast.Node.SwitchCase).init(arena), - .rbrace = undefined, - } - ); + const node = try arena.construct(ast.Node.Switch { + .base = ast.Node { + .id = ast.Node.Id.Switch, + .doc_comments = null, + .same_line_comment = null, + }, + .switch_token = *token, + .expr = undefined, + .cases = ArrayList(&ast.Node).init(arena), + .rbrace = undefined, + }); + ctx.store(&node.base); stack.append(State { - .SwitchCaseOrEnd = ListSave(&ast.Node.SwitchCase) { + .SwitchCaseOrEnd = ListSave(&ast.Node) { .list = &node.cases, .ptr = &node.rbrace, }, @@ -3208,7 +3248,7 @@ pub const Parser = struct { const id = ast.Node.typeToId(T); break :blk ast.Node { .id = id, - .before_comments = null, + .doc_comments = null, .same_line_comment = null, }; }; @@ -3454,6 +3494,10 @@ pub const Parser = struct { } try stack.append(RenderState { .Expression = decl }); }, + ast.Node.Id.LineComment => { + const line_comment_node = @fieldParentPtr(ast.Node.LineComment, "base", decl); + try stream.write(self.tokenizer.getTokenSlice(line_comment_node.token)); + }, else => unreachable, } }, @@ -3987,7 +4031,9 @@ pub const Parser = struct { while (i != 0) { i -= 1; const node = decls[i]; - try stack.append(RenderState { .Text = "," }); + if (node.id != ast.Node.Id.LineComment) { + try stack.append(RenderState { .Text = "," }); + } try stack.append(RenderState { .Expression = node }); try stack.append(RenderState { .PrintComments = node }); try stack.append(RenderState.PrintIndent); @@ -4100,7 +4146,11 @@ pub const Parser = struct { try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(visib_token) }); } }, - ast.Node.Id.LineComment => @panic("TODO render line comment in an expression"), + ast.Node.Id.LineComment => { + const line_comment_node = @fieldParentPtr(ast.Node.LineComment, "base", base); + try stream.write(self.tokenizer.getTokenSlice(line_comment_node.token)); + }, + ast.Node.Id.DocComment => unreachable, // doc comments are attached to nodes ast.Node.Id.Switch => { const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base); try stream.print("{} (", self.tokenizer.getTokenSlice(switch_node.switch_token)); @@ -4115,7 +4165,7 @@ pub const Parser = struct { while (i != 0) { i -= 1; const node = cases[i]; - try stack.append(RenderState { .Expression = &node.base}); + try stack.append(RenderState { .Expression = node}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Text = blk: { @@ -4487,7 +4537,7 @@ pub const Parser = struct { } fn renderComments(self: &Parser, stream: var, node: &ast.Node, indent: usize) !void { - const comment = node.before_comments ?? return; + const comment = node.doc_comments ?? return; for (comment.lines.toSliceConst()) |line_token| { try stream.print("{}\n", self.tokenizer.getTokenSlice(line_token)); try stream.writeByteNTimes(' ', indent); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index a914775dc..b4cbef33b 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -3,9 +3,12 @@ test "zig fmt: comments before error set decl" { \\const UnexpectedError = error { \\ /// The Operating System returned an undocumented error code. \\ Unexpected, - \\ \\ // another \\ Another, + \\ + \\ // in between + \\ + \\ // at end \\}; \\ ); @@ -18,8 +21,10 @@ test "zig fmt: comments before switch prong" { \\ error.PathAlreadyExists => continue, \\ \\ // comment 1 + \\ \\ // comment 2 \\ else => return err, + \\ // at end \\ } \\} \\ @@ -47,6 +52,17 @@ test "zig fmt: comments before var decl in struct" { \\ permitted: u32, \\ inheritable: u32, \\ }; + \\ + \\ // in between + \\ + \\ /// All of these are mandated as little endian + \\ /// when on disk. + \\ const Data = struct { + \\ permitted: u32, + \\ inheritable: u32, + \\ }; + \\ + \\ // at end \\}; \\ ); @@ -106,6 +122,10 @@ test "zig fmt: comments before statements" { \\test "std" { \\ // statement comment \\ _ = @import("foo/bar.zig"); + \\ + \\ // middle + \\ + \\ // end \\} \\ ); @@ -113,17 +133,27 @@ test "zig fmt: comments before statements" { test "zig fmt: comments before test decl" { try testCanonical( - \\// top level comment + \\/// top level doc comment \\test "hi" {} \\ + \\// top level normal comment + \\test "hi" {} + \\ + \\// middle + \\ + \\// end + \\ ); } -test "zig fmt: get stdout or fail" { +test "zig fmt: comments before variable declarations" { try testCanonical( \\const std = @import("std"); \\ \\pub fn main() !void { + \\ /// If this program is run without stdout attached, exit with an error. + \\ /// another comment + \\ var stdout_file = try std.io.getStdOut; \\ // If this program is run without stdout attached, exit with an error. \\ // another comment \\ var stdout_file = try std.io.getStdOut; diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig index a2c4def9e..1d24a88d2 100644 --- a/std/zig/tokenizer.zig +++ b/std/zig/tokenizer.zig @@ -137,6 +137,7 @@ pub const Token = struct { IntegerLiteral, FloatLiteral, LineComment, + DocComment, Keyword_align, Keyword_and, Keyword_asm, @@ -257,6 +258,7 @@ pub const Tokenizer = struct { Asterisk, AsteriskPercent, Slash, + LineCommentStart, LineComment, Zero, IntegerLiteral, @@ -822,8 +824,7 @@ pub const Tokenizer = struct { State.Slash => switch (c) { '/' => { - result.id = Token.Id.LineComment; - state = State.LineComment; + state = State.LineCommentStart; }, '=' => { result.id = Token.Id.SlashEqual; @@ -835,6 +836,17 @@ pub const Tokenizer = struct { break; }, }, + State.LineCommentStart => switch (c) { + '/' => { + result.id = Token.Id.DocComment; + state = State.LineComment; + }, + '\n' => { + result.id = Token.Id.LineComment; + break; + }, + else => self.checkLiteralCharacter(), + }, State.LineComment => switch (c) { '\n' => break, else => self.checkLiteralCharacter(), @@ -920,6 +932,7 @@ pub const Tokenizer = struct { result.id = id; } }, + State.LineCommentStart, State.LineComment => { result.id = Token.Id.Eof; }, From 0bf7ebcfea7934cb972aef84b25494c92f0dcf6f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 00:52:09 -0400 Subject: [PATCH 46/58] std.zig.tokenizer: fix handling of line comment / doc comment --- std/zig/parser_test.zig | 1 + std/zig/tokenizer.zig | 115 +++++++++++++++++++++++++++++++--------- 2 files changed, 91 insertions(+), 25 deletions(-) diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index b4cbef33b..74a49a70e 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -124,6 +124,7 @@ test "zig fmt: comments before statements" { \\ _ = @import("foo/bar.zig"); \\ \\ // middle + \\ // middle2 \\ \\ // end \\} diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig index 1d24a88d2..9f5712fa9 100644 --- a/std/zig/tokenizer.zig +++ b/std/zig/tokenizer.zig @@ -260,6 +260,7 @@ pub const Tokenizer = struct { Slash, LineCommentStart, LineComment, + DocComment, Zero, IntegerLiteral, IntegerLiteralWithRadix, @@ -825,6 +826,7 @@ pub const Tokenizer = struct { State.Slash => switch (c) { '/' => { state = State.LineCommentStart; + result.id = Token.Id.LineComment; }, '=' => { result.id = Token.Id.SlashEqual; @@ -839,15 +841,15 @@ pub const Tokenizer = struct { State.LineCommentStart => switch (c) { '/' => { result.id = Token.Id.DocComment; + state = State.DocComment; + }, + '\n' => break, + else => { state = State.LineComment; + self.checkLiteralCharacter(); }, - '\n' => { - result.id = Token.Id.LineComment; - break; - }, - else => self.checkLiteralCharacter(), }, - State.LineComment => switch (c) { + State.LineComment, State.DocComment => switch (c) { '\n' => break, else => self.checkLiteralCharacter(), }, @@ -934,7 +936,10 @@ pub const Tokenizer = struct { }, State.LineCommentStart, State.LineComment => { - result.id = Token.Id.Eof; + result.id = Token.Id.LineComment; + }, + State.DocComment => { + result.id = Token.Id.DocComment; }, State.NumberDot, @@ -1105,41 +1110,77 @@ test "tokenizer - invalid literal/comment characters" { Token.Id.Invalid, }); testTokenize("//\x00", []Token.Id { + Token.Id.LineComment, Token.Id.Invalid, }); testTokenize("//\x1f", []Token.Id { + Token.Id.LineComment, Token.Id.Invalid, }); testTokenize("//\x7f", []Token.Id { + Token.Id.LineComment, Token.Id.Invalid, }); } test "tokenizer - utf8" { - testTokenize("//\xc2\x80", []Token.Id{}); - testTokenize("//\xf4\x8f\xbf\xbf", []Token.Id{}); + testTokenize("//\xc2\x80", []Token.Id{Token.Id.LineComment}); + testTokenize("//\xf4\x8f\xbf\xbf", []Token.Id{Token.Id.LineComment}); } test "tokenizer - invalid utf8" { - testTokenize("//\x80", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xbf", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xf8", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xff", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xc2\xc0", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xe0", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xf0", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xf0\x90\x80\xc0", []Token.Id{Token.Id.Invalid}); + testTokenize("//\x80", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xbf", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xf8", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xff", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xc2\xc0", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xe0", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xf0", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xf0\x90\x80\xc0", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); } test "tokenizer - illegal unicode codepoints" { // unicode newline characters.U+0085, U+2028, U+2029 - testTokenize("//\xc2\x84", []Token.Id{}); - testTokenize("//\xc2\x85", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xc2\x86", []Token.Id{}); - testTokenize("//\xe2\x80\xa7", []Token.Id{}); - testTokenize("//\xe2\x80\xa8", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xe2\x80\xa9", []Token.Id{Token.Id.Invalid}); - testTokenize("//\xe2\x80\xaa", []Token.Id{}); + testTokenize("//\xc2\x84", []Token.Id{Token.Id.LineComment}); + testTokenize("//\xc2\x85", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xc2\x86", []Token.Id{Token.Id.LineComment}); + testTokenize("//\xe2\x80\xa7", []Token.Id{Token.Id.LineComment}); + testTokenize("//\xe2\x80\xa8", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xe2\x80\xa9", []Token.Id{ + Token.Id.LineComment, + Token.Id.Invalid, + }); + testTokenize("//\xe2\x80\xaa", []Token.Id{Token.Id.LineComment}); } test "tokenizer - string identifier and builtin fns" { @@ -1166,11 +1207,35 @@ test "tokenizer - pipe and then invalid" { }); } +test "tokenizer - line comment and doc comment" { + testTokenize("//", []Token.Id{Token.Id.LineComment}); + testTokenize("// a / b", []Token.Id{Token.Id.LineComment}); + testTokenize("// /", []Token.Id{Token.Id.LineComment}); + testTokenize("/// a", []Token.Id{Token.Id.DocComment}); + testTokenize("///", []Token.Id{Token.Id.DocComment}); +} + +test "tokenizer - line comment followed by identifier" { + testTokenize( + \\ Unexpected, + \\ // another + \\ Another, + , []Token.Id{ + Token.Id.Identifier, + Token.Id.Comma, + Token.Id.LineComment, + Token.Id.Identifier, + Token.Id.Comma, + }); +} + fn testTokenize(source: []const u8, expected_tokens: []const Token.Id) void { var tokenizer = Tokenizer.init(source); for (expected_tokens) |expected_token_id| { const token = tokenizer.next(); - std.debug.assert(@TagType(Token.Id)(token.id) == @TagType(Token.Id)(expected_token_id)); + if (@TagType(Token.Id)(token.id) != @TagType(Token.Id)(expected_token_id)) { + std.debug.panic("expected {}, found {}\n", @tagName(@TagType(Token.Id)(expected_token_id)), @tagName(@TagType(Token.Id)(token.id))); + } switch (expected_token_id) { Token.Id.StringLiteral => |expected_kind| { std.debug.assert(expected_kind == switch (token.id) { Token.Id.StringLiteral => |kind| kind, else => unreachable }); From 54987c3d8f358e4ded9113a9ee5fa04dc1372a56 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 00:56:59 -0400 Subject: [PATCH 47/58] std.zig.tokenizer: 3 slashes is doc comment, 4 is line comment --- std/zig/tokenizer.zig | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig index 9f5712fa9..92a0fbc5d 100644 --- a/std/zig/tokenizer.zig +++ b/std/zig/tokenizer.zig @@ -260,6 +260,7 @@ pub const Tokenizer = struct { Slash, LineCommentStart, LineComment, + DocCommentStart, DocComment, Zero, IntegerLiteral, @@ -840,8 +841,7 @@ pub const Tokenizer = struct { }, State.LineCommentStart => switch (c) { '/' => { - result.id = Token.Id.DocComment; - state = State.DocComment; + state = State.DocCommentStart; }, '\n' => break, else => { @@ -849,6 +849,20 @@ pub const Tokenizer = struct { self.checkLiteralCharacter(); }, }, + State.DocCommentStart => switch (c) { + '/' => { + state = State.LineComment; + }, + '\n' => { + result.id = Token.Id.DocComment; + break; + }, + else => { + state = State.DocComment; + result.id = Token.Id.DocComment; + self.checkLiteralCharacter(); + }, + }, State.LineComment, State.DocComment => switch (c) { '\n' => break, else => self.checkLiteralCharacter(), @@ -938,7 +952,7 @@ pub const Tokenizer = struct { State.LineComment => { result.id = Token.Id.LineComment; }, - State.DocComment => { + State.DocComment, State.DocCommentStart => { result.id = Token.Id.DocComment; }, @@ -1213,6 +1227,7 @@ test "tokenizer - line comment and doc comment" { testTokenize("// /", []Token.Id{Token.Id.LineComment}); testTokenize("/// a", []Token.Id{Token.Id.DocComment}); testTokenize("///", []Token.Id{Token.Id.DocComment}); + testTokenize("////", []Token.Id{Token.Id.LineComment}); } test "tokenizer - line comment followed by identifier" { From e14db2366160840e0c25f3a467ff984304831e4c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 01:03:38 -0400 Subject: [PATCH 48/58] run zig fmt on std/os/index.zig --- std/os/index.zig | 326 +++++++++++++++++++++++++++++------------------ 1 file changed, 201 insertions(+), 125 deletions(-) diff --git a/std/os/index.zig b/std/os/index.zig index 99aa7891c..4f1826021 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -3,7 +3,8 @@ const builtin = @import("builtin"); const Os = builtin.Os; const is_windows = builtin.os == Os.windows; const is_posix = switch (builtin.os) { - builtin.Os.linux, builtin.Os.macosx => true, + builtin.Os.linux, + builtin.Os.macosx => true, else => false, }; const os = this; @@ -24,9 +25,10 @@ pub const windows = @import("windows/index.zig"); pub const darwin = @import("darwin.zig"); pub const linux = @import("linux/index.zig"); pub const zen = @import("zen.zig"); -pub const posix = switch(builtin.os) { +pub const posix = switch (builtin.os) { Os.linux => linux, - Os.macosx, Os.ios => darwin, + Os.macosx, + Os.ios => darwin, Os.zen => zen, else => @compileError("Unsupported OS"), }; @@ -58,7 +60,7 @@ pub const windowsWrite = windows_util.windowsWrite; pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty; pub const windowsOpen = windows_util.windowsOpen; pub const windowsLoadDll = windows_util.windowsLoadDll; -pub const windowsUnloadDll = windows_util.windowsUnloadDll; +pub const windowsUnloadDll = windows_util.windowsUnloadDll; pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; pub const WindowsWaitError = windows_util.WaitError; @@ -97,9 +99,9 @@ pub fn getRandomBytes(buf: []u8) !void { switch (err) { posix.EINVAL => unreachable, posix.EFAULT => unreachable, - posix.EINTR => continue, + posix.EINTR => continue, posix.ENOSYS => { - const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY|posix.O_CLOEXEC, 0); + const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0); defer close(fd); try posixRead(fd, buf); @@ -110,8 +112,9 @@ pub fn getRandomBytes(buf: []u8) !void { } return; }, - Os.macosx, Os.ios => { - const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY|posix.O_CLOEXEC, 0); + Os.macosx, + Os.ios => { + const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0); defer close(fd); try posixRead(fd, buf); @@ -134,7 +137,20 @@ pub fn getRandomBytes(buf: []u8) !void { } }, Os.zen => { - const randomness = []u8 {42, 1, 7, 12, 22, 17, 99, 16, 26, 87, 41, 45}; + const randomness = []u8 { + 42, + 1, + 7, + 12, + 22, + 17, + 99, + 16, + 26, + 87, + 41, + 45, + }; var i: usize = 0; while (i < buf.len) : (i += 1) { if (i > randomness.len) return error.Unknown; @@ -159,7 +175,9 @@ pub fn abort() noreturn { c.abort(); } switch (builtin.os) { - Os.linux, Os.macosx, Os.ios => { + Os.linux, + Os.macosx, + Os.ios => { _ = posix.raise(posix.SIGABRT); _ = posix.raise(posix.SIGKILL); while (true) {} @@ -181,7 +199,9 @@ pub fn exit(status: u8) noreturn { c.exit(status); } switch (builtin.os) { - Os.linux, Os.macosx, Os.ios => { + Os.linux, + Os.macosx, + Os.ios => { posix.exit(status); }, Os.windows => { @@ -230,12 +250,14 @@ pub fn posixRead(fd: i32, buf: []u8) !void { if (err > 0) { return switch (err) { posix.EINTR => continue, - posix.EINVAL, posix.EFAULT => unreachable, + posix.EINVAL, + posix.EFAULT => unreachable, posix.EAGAIN => error.WouldBlock, posix.EBADF => error.FileClosed, posix.EIO => error.InputOutput, posix.EISDIR => error.IsDir, - posix.ENOBUFS, posix.ENOMEM => error.SystemResources, + posix.ENOBUFS, + posix.ENOMEM => error.SystemResources, else => unexpectedErrorPosix(err), }; } @@ -269,18 +291,19 @@ pub fn posixWrite(fd: i32, bytes: []const u8) !void { const write_err = posix.getErrno(rc); if (write_err > 0) { return switch (write_err) { - posix.EINTR => continue, - posix.EINVAL, posix.EFAULT => unreachable, + posix.EINTR => continue, + posix.EINVAL, + posix.EFAULT => unreachable, posix.EAGAIN => PosixWriteError.WouldBlock, posix.EBADF => PosixWriteError.FileClosed, posix.EDESTADDRREQ => PosixWriteError.DestinationAddressRequired, posix.EDQUOT => PosixWriteError.DiskQuota, - posix.EFBIG => PosixWriteError.FileTooBig, - posix.EIO => PosixWriteError.InputOutput, + posix.EFBIG => PosixWriteError.FileTooBig, + posix.EIO => PosixWriteError.InputOutput, posix.ENOSPC => PosixWriteError.NoSpaceLeft, - posix.EPERM => PosixWriteError.AccessDenied, - posix.EPIPE => PosixWriteError.BrokenPipe, - else => unexpectedErrorPosix(write_err), + posix.EPERM => PosixWriteError.AccessDenied, + posix.EPIPE => PosixWriteError.BrokenPipe, + else => unexpectedErrorPosix(write_err), }; } index += rc; @@ -326,7 +349,8 @@ pub fn posixOpenC(file_path: &const u8, flags: u32, perm: usize) !i32 { posix.EFAULT => unreachable, posix.EINVAL => unreachable, posix.EACCES => return PosixOpenError.AccessDenied, - posix.EFBIG, posix.EOVERFLOW => return PosixOpenError.FileTooBig, + posix.EFBIG, + posix.EOVERFLOW => return PosixOpenError.FileTooBig, posix.EISDIR => return PosixOpenError.IsDir, posix.ELOOP => return PosixOpenError.SymLinkLoop, posix.EMFILE => return PosixOpenError.ProcessFdQuotaExceeded, @@ -351,7 +375,8 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) !void { const err = posix.getErrno(posix.dup2(old_fd, new_fd)); if (err > 0) { return switch (err) { - posix.EBUSY, posix.EINTR => continue, + posix.EBUSY, + posix.EINTR => continue, posix.EMFILE => error.ProcessFdQuotaExceeded, posix.EINVAL => unreachable, else => unexpectedErrorPosix(err), @@ -386,7 +411,7 @@ pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) void { for (envp_buf) |env| { - const env_buf = if (env) |ptr| ptr[0 .. cstr.len(ptr) + 1] else break; + const env_buf = if (env) |ptr| ptr[0..cstr.len(ptr) + 1] else break; allocator.free(env_buf); } allocator.free(envp_buf); @@ -397,9 +422,7 @@ pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) void { /// pointers after the args and after the environment variables. /// `argv[0]` is the executable path. /// This function also uses the PATH environment variable to get the full path to the executable. -pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap, - allocator: &Allocator) !void -{ +pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap, allocator: &Allocator) !void { const argv_buf = try allocator.alloc(?&u8, argv.len + 1); mem.set(?&u8, argv_buf, null); defer { @@ -438,7 +461,7 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap, while (it.next()) |search_path| { mem.copy(u8, path_buf, search_path); path_buf[search_path.len] = '/'; - mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path); + mem.copy(u8, path_buf[search_path.len + 1..], exe_path); path_buf[search_path.len + exe_path.len + 1] = 0; err = posix.getErrno(posix.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr)); assert(err > 0); @@ -470,10 +493,17 @@ fn posixExecveErrnoToErr(err: usize) PosixExecveError { assert(err > 0); return switch (err) { posix.EFAULT => unreachable, - posix.E2BIG, posix.EMFILE, posix.ENAMETOOLONG, posix.ENFILE, posix.ENOMEM => error.SystemResources, - posix.EACCES, posix.EPERM => error.AccessDenied, - posix.EINVAL, posix.ENOEXEC => error.InvalidExe, - posix.EIO, posix.ELOOP => error.FileSystem, + posix.E2BIG, + posix.EMFILE, + posix.ENAMETOOLONG, + posix.ENFILE, + posix.ENOMEM => error.SystemResources, + posix.EACCES, + posix.EPERM => error.AccessDenied, + posix.EINVAL, + posix.ENOEXEC => error.InvalidExe, + posix.EIO, + posix.ELOOP => error.FileSystem, posix.EISDIR => error.IsDir, posix.ENOENT => error.FileNotFound, posix.ENOTDIR => error.NotDir, @@ -482,7 +512,7 @@ fn posixExecveErrnoToErr(err: usize) PosixExecveError { }; } -pub var linux_aux_raw = []usize{0} ** 38; +pub var linux_aux_raw = []usize {0} ** 38; pub var posix_environ_raw: []&u8 = undefined; /// Caller must free result when done. @@ -496,8 +526,7 @@ pub fn getEnvMap(allocator: &Allocator) !BufMap { var i: usize = 0; while (true) { - if (ptr[i] == 0) - return result; + if (ptr[i] == 0) return result; const key_start = i; @@ -535,8 +564,7 @@ pub fn getEnvPosix(key: []const u8) ?[]const u8 { var line_i: usize = 0; while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {} const this_key = ptr[0..line_i]; - if (!mem.eql(u8, key, this_key)) - continue; + if (!mem.eql(u8, key, this_key)) continue; var end_i: usize = line_i; while (ptr[end_i] != 0) : (end_i += 1) {} @@ -689,8 +717,10 @@ pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path: const err = posix.getErrno(posix.symlink(existing_buf.ptr, new_buf.ptr)); if (err > 0) { return switch (err) { - posix.EFAULT, posix.EINVAL => unreachable, - posix.EACCES, posix.EPERM => error.AccessDenied, + posix.EFAULT, + posix.EINVAL => unreachable, + posix.EACCES, + posix.EPERM => error.AccessDenied, posix.EDQUOT => error.DiskQuota, posix.EEXIST => error.PathAlreadyExists, posix.EIO => error.FileSystem, @@ -707,9 +737,7 @@ pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path: } // here we replace the standard +/ with -_ so that it can be used in a file name -const b64_fs_encoder = base64.Base64Encoder.init( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", - base64.standard_pad_char); +const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) !void { if (symLink(allocator, existing_path, new_path)) { @@ -728,7 +756,7 @@ pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: tmp_path[dirname.len] = os.path.sep; while (true) { try getRandomBytes(rand_buf[0..]); - b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); + b64_fs_encoder.encode(tmp_path[dirname.len + 1..], rand_buf); if (symLink(allocator, existing_path, tmp_path)) { return rename(allocator, tmp_path, new_path); @@ -737,7 +765,6 @@ pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: else => return err, // TODO zig should know this set does not include PathAlreadyExists } } - } pub fn deleteFile(allocator: &Allocator, file_path: []const u8) !void { @@ -760,7 +787,8 @@ pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) !void { return switch (err) { windows.ERROR.FILE_NOT_FOUND => error.FileNotFound, windows.ERROR.ACCESS_DENIED => error.AccessDenied, - windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong, + windows.ERROR.FILENAME_EXCED_RANGE, + windows.ERROR.INVALID_PARAMETER => error.NameTooLong, else => unexpectedErrorWindows(err), }; } @@ -776,9 +804,11 @@ pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) !void { const err = posix.getErrno(posix.unlink(buf.ptr)); if (err > 0) { return switch (err) { - posix.EACCES, posix.EPERM => error.AccessDenied, + posix.EACCES, + posix.EPERM => error.AccessDenied, posix.EBUSY => error.FileBusy, - posix.EFAULT, posix.EINVAL => unreachable, + posix.EFAULT, + posix.EINVAL => unreachable, posix.EIO => error.FileSystem, posix.EISDIR => error.IsDir, posix.ELOOP => error.SymLinkLoop, @@ -856,7 +886,7 @@ pub const AtomicFile = struct { while (true) { try getRandomBytes(rand_buf[0..]); - b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); + b64_fs_encoder.encode(tmp_path[dirname.len + 1..], rand_buf); const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) { error.PathAlreadyExists => continue, @@ -907,7 +937,7 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) new_buf[new_path.len] = 0; if (is_windows) { - const flags = windows.MOVEFILE_REPLACE_EXISTING|windows.MOVEFILE_WRITE_THROUGH; + const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; if (windows.MoveFileExA(old_buf.ptr, new_buf.ptr, flags) == 0) { const err = windows.GetLastError(); return switch (err) { @@ -918,10 +948,12 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr)); if (err > 0) { return switch (err) { - posix.EACCES, posix.EPERM => error.AccessDenied, + posix.EACCES, + posix.EPERM => error.AccessDenied, posix.EBUSY => error.FileBusy, posix.EDQUOT => error.DiskQuota, - posix.EFAULT, posix.EINVAL => unreachable, + posix.EFAULT, + posix.EINVAL => unreachable, posix.EISDIR => error.IsDir, posix.ELOOP => error.SymLinkLoop, posix.EMLINK => error.LinkQuotaExceeded, @@ -930,7 +962,8 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) posix.ENOTDIR => error.NotDir, posix.ENOMEM => error.SystemResources, posix.ENOSPC => error.NoSpaceLeft, - posix.EEXIST, posix.ENOTEMPTY => error.PathAlreadyExists, + posix.EEXIST, + posix.ENOTEMPTY => error.PathAlreadyExists, posix.EROFS => error.ReadOnlyFileSystem, posix.EXDEV => error.RenameAcrossMountPoints, else => unexpectedErrorPosix(err), @@ -968,7 +1001,8 @@ pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) !void { const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755)); if (err > 0) { return switch (err) { - posix.EACCES, posix.EPERM => error.AccessDenied, + posix.EACCES, + posix.EPERM => error.AccessDenied, posix.EDQUOT => error.DiskQuota, posix.EEXIST => error.PathAlreadyExists, posix.EFAULT => unreachable, @@ -998,27 +1032,23 @@ pub fn makePath(allocator: &Allocator, full_path: []const u8) !void { // TODO stat the file and return an error if it's not a directory // this is important because otherwise a dangling symlink // could cause an infinite loop - if (end_index == resolved_path.len) - return; + if (end_index == resolved_path.len) return; } else if (err == error.FileNotFound) { // march end_index backward until next path component while (true) { end_index -= 1; - if (os.path.isSep(resolved_path[end_index])) - break; + if (os.path.isSep(resolved_path[end_index])) break; } continue; } else { return err; } }; - if (end_index == resolved_path.len) - return; + if (end_index == resolved_path.len) return; // march end_index forward until next path component while (true) { end_index += 1; - if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index])) - break; + if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index])) break; } } } @@ -1035,15 +1065,18 @@ pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) !void { const err = posix.getErrno(posix.rmdir(path_buf.ptr)); if (err > 0) { return switch (err) { - posix.EACCES, posix.EPERM => error.AccessDenied, + posix.EACCES, + posix.EPERM => error.AccessDenied, posix.EBUSY => error.FileBusy, - posix.EFAULT, posix.EINVAL => unreachable, + posix.EFAULT, + posix.EINVAL => unreachable, posix.ELOOP => error.SymLinkLoop, posix.ENAMETOOLONG => error.NameTooLong, posix.ENOENT => error.FileNotFound, posix.ENOMEM => error.SystemResources, posix.ENOTDIR => error.NotDir, - posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty, + posix.EEXIST, + posix.ENOTEMPTY => error.DirNotEmpty, posix.EROFS => error.ReadOnlyFileSystem, else => unexpectedErrorPosix(err), }; @@ -1053,7 +1086,7 @@ pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) !void { /// Whether ::full_path describes a symlink, file, or directory, this function /// removes it. If it cannot be removed because it is a non-empty directory, /// this function recursively removes its entries and then tries again. -// TODO non-recursive implementation +/// TODO non-recursive implementation const DeleteTreeError = error { OutOfMemory, AccessDenied, @@ -1095,8 +1128,7 @@ pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError! error.NotDir, error.FileSystem, error.FileBusy, - error.Unexpected - => return err, + error.Unexpected => return err, } { var dir = Dir.open(allocator, full_path) catch |err| switch (err) { @@ -1120,8 +1152,7 @@ pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError! error.SystemResources, error.NoSpaceLeft, error.PathAlreadyExists, - error.Unexpected - => return err, + error.Unexpected => return err, }; defer dir.close(); @@ -1151,7 +1182,8 @@ pub const Dir = struct { end_index: usize, const darwin_seek_t = switch (builtin.os) { - Os.macosx, Os.ios => i64, + Os.macosx, + Os.ios => i64, else => void, }; @@ -1175,12 +1207,14 @@ pub const Dir = struct { pub fn open(allocator: &Allocator, dir_path: []const u8) !Dir { const fd = switch (builtin.os) { Os.windows => @compileError("TODO support Dir.open for windows"), - Os.linux => try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0), - Os.macosx, Os.ios => try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_NONBLOCK|posix.O_DIRECTORY|posix.O_CLOEXEC, 0), + Os.linux => try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0), + Os.macosx, + Os.ios => try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, 0), else => @compileError("Dir.open is not supported for this platform"), }; const darwin_seek_init = switch (builtin.os) { - Os.macosx, Os.ios => 0, + Os.macosx, + Os.ios => 0, else => {}, }; return Dir { @@ -1203,7 +1237,8 @@ pub const Dir = struct { pub fn next(self: &Dir) !?Entry { switch (builtin.os) { Os.linux => return self.nextLinux(), - Os.macosx, Os.ios => return self.nextDarwin(), + Os.macosx, + Os.ios => return self.nextDarwin(), Os.windows => return self.nextWindows(), else => @compileError("Dir.next not supported on " ++ @tagName(builtin.os)), } @@ -1217,12 +1252,13 @@ pub const Dir = struct { } while (true) { - const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len, - &self.darwin_seek); + const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len, &self.darwin_seek); const err = posix.getErrno(result); if (err > 0) { switch (err) { - posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, + posix.EBADF, + posix.EFAULT, + posix.ENOTDIR => unreachable, posix.EINVAL => { self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2); continue; @@ -1230,14 +1266,13 @@ pub const Dir = struct { else => return unexpectedErrorPosix(err), } } - if (result == 0) - return null; + if (result == 0) return null; self.index = 0; self.end_index = result; break; } } - const darwin_entry = @ptrCast(& align(1) posix.dirent, &self.buf[self.index]); + const darwin_entry = @ptrCast(&align(1) posix.dirent, &self.buf[self.index]); const next_index = self.index + darwin_entry.d_reclen; self.index = next_index; @@ -1282,7 +1317,9 @@ pub const Dir = struct { const err = posix.getErrno(result); if (err > 0) { switch (err) { - posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, + posix.EBADF, + posix.EFAULT, + posix.ENOTDIR => unreachable, posix.EINVAL => { self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2); continue; @@ -1290,14 +1327,13 @@ pub const Dir = struct { else => return unexpectedErrorPosix(err), } } - if (result == 0) - return null; + if (result == 0) return null; self.index = 0; self.end_index = result; break; } } - const linux_entry = @ptrCast(& align(1) posix.dirent, &self.buf[self.index]); + const linux_entry = @ptrCast(&align(1) posix.dirent, &self.buf[self.index]); const next_index = self.index + linux_entry.d_reclen; self.index = next_index; @@ -1366,7 +1402,8 @@ pub fn readLink(allocator: &Allocator, pathname: []const u8) ![]u8 { if (err > 0) { return switch (err) { posix.EACCES => error.AccessDenied, - posix.EFAULT, posix.EINVAL => unreachable, + posix.EFAULT, + posix.EINVAL => unreachable, posix.EIO => error.FileSystem, posix.ELOOP => error.SymLinkLoop, posix.ENAMETOOLONG => error.NameTooLong, @@ -1459,8 +1496,7 @@ pub const ArgIteratorPosix = struct { } pub fn next(self: &ArgIteratorPosix) ?[]const u8 { - if (self.index == self.count) - return null; + if (self.index == self.count) return null; const s = raw[self.index]; self.index += 1; @@ -1468,8 +1504,7 @@ pub const ArgIteratorPosix = struct { } pub fn skip(self: &ArgIteratorPosix) bool { - if (self.index == self.count) - return false; + if (self.index == self.count) return false; self.index += 1; return true; @@ -1487,7 +1522,9 @@ pub const ArgIteratorWindows = struct { quote_count: usize, seen_quote_count: usize, - pub const NextError = error{OutOfMemory}; + pub const NextError = error { + OutOfMemory, + }; pub fn init() ArgIteratorWindows { return initWithCmdLine(windows.GetCommandLineA()); @@ -1510,7 +1547,8 @@ pub const ArgIteratorWindows = struct { const byte = self.cmd_line[self.index]; switch (byte) { 0 => return null, - ' ', '\t' => continue, + ' ', + '\t' => continue, else => break, } } @@ -1524,7 +1562,8 @@ pub const ArgIteratorWindows = struct { const byte = self.cmd_line[self.index]; switch (byte) { 0 => return false, - ' ', '\t' => continue, + ' ', + '\t' => continue, else => break, } } @@ -1543,7 +1582,8 @@ pub const ArgIteratorWindows = struct { '\\' => { backslash_count += 1; }, - ' ', '\t' => { + ' ', + '\t' => { if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) { return true; } @@ -1583,7 +1623,8 @@ pub const ArgIteratorWindows = struct { '\\' => { backslash_count += 1; }, - ' ', '\t' => { + ' ', + '\t' => { try self.emitBackslashes(&buf, backslash_count); backslash_count = 0; if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) { @@ -1627,7 +1668,6 @@ pub const ArgIteratorWindows = struct { } } } - }; pub const ArgIterator = struct { @@ -1642,7 +1682,7 @@ pub const ArgIterator = struct { } pub const NextError = ArgIteratorWindows.NextError; - + /// You must free the returned memory when done. pub fn next(self: &ArgIterator, allocator: &Allocator) ?(NextError![]u8) { if (builtin.os == Os.windows) { @@ -1717,15 +1757,47 @@ pub fn argsFree(allocator: &mem.Allocator, args_alloc: []const []u8) void { } test "windows arg parsing" { - testWindowsCmdLine(c"a b\tc d", [][]const u8{"a", "b", "c", "d"}); - testWindowsCmdLine(c"\"abc\" d e", [][]const u8{"abc", "d", "e"}); - testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{"a\\\\\\b", "de fg", "h"}); - testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{"a\\\"b", "c", "d"}); - testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{"a\\\\b c", "d", "e"}); - testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{"a", "b", "c", "\"d", "f"}); + testWindowsCmdLine(c"a b\tc d", [][]const u8 { + "a", + "b", + "c", + "d", + }); + testWindowsCmdLine(c"\"abc\" d e", [][]const u8 { + "abc", + "d", + "e", + }); + testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8 { + "a\\\\\\b", + "de fg", + "h", + }); + testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8 { + "a\\\"b", + "c", + "d", + }); + testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8 { + "a\\\\b c", + "d", + "e", + }); + testWindowsCmdLine(c"a b\tc \"d f", [][]const u8 { + "a", + "b", + "c", + "\"d", + "f", + }); - testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", - [][]const u8{".\\..\\zig-cache\\build", "bin\\zig.exe", ".\\..", ".\\..\\zig-cache", "--help"}); + testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", [][]const u8 { + ".\\..\\zig-cache\\build", + "bin\\zig.exe", + ".\\..", + ".\\..\\zig-cache", + "--help", + }); } fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const u8) void { @@ -1772,7 +1844,8 @@ pub fn openSelfExe() !os.File { var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); return os.File.openRead(&fixed_allocator.allocator, proc_file_path); }, - Os.macosx, Os.ios => { + Os.macosx, + Os.ios => { var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined; var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); const self_exe_path = try selfExePath(&fixed_allocator.allocator); @@ -1784,8 +1857,10 @@ pub fn openSelfExe() !os.File { test "openSelfExe" { switch (builtin.os) { - Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(), - else => return, // Unsupported OS. + Os.linux, + Os.macosx, + Os.ios => (try openSelfExe()).close(), + else => return, // Unsupported OS. } } @@ -1822,7 +1897,8 @@ pub fn selfExePath(allocator: &mem.Allocator) ![]u8 { try out_path.resize(new_len); } }, - Os.macosx, Os.ios => { + Os.macosx, + Os.ios => { var u32_len: u32 = 0; const ret1 = c._NSGetExecutablePath(undefined, &u32_len); assert(ret1 != 0); @@ -1850,7 +1926,9 @@ pub fn selfExeDirPath(allocator: &mem.Allocator) ![]u8 { const dir = path.dirname(full_exe_path); return allocator.shrink(u8, full_exe_path, dir.len); }, - Os.windows, Os.macosx, Os.ios => { + Os.windows, + Os.macosx, + Os.ios => { const self_exe_path = try selfExePath(allocator); errdefer allocator.free(self_exe_path); const dirname = os.path.dirname(self_exe_path); @@ -1907,7 +1985,8 @@ pub fn posixSocket(domain: u32, socket_type: u32, protocol: u32) !i32 { posix.EINVAL => return PosixSocketError.ProtocolFamilyNotAvailable, posix.EMFILE => return PosixSocketError.ProcessFdQuotaExceeded, posix.ENFILE => return PosixSocketError.SystemFdQuotaExceeded, - posix.ENOBUFS, posix.ENOMEM => return PosixSocketError.SystemResources, + posix.ENOBUFS, + posix.ENOMEM => return PosixSocketError.SystemResources, posix.EPROTONOSUPPORT => return PosixSocketError.ProtocolNotSupported, else => return unexpectedErrorPosix(err), } @@ -1938,7 +2017,7 @@ pub const PosixBindError = error { /// A nonexistent interface was requested or the requested address was not local. AddressNotAvailable, - + /// addr points outside the user's accessible address space. PageFault, @@ -2027,7 +2106,7 @@ pub const PosixAcceptError = error { FileDescriptorClosed, ConnectionAborted, - + /// The addr argument is not in a writable part of the user address space. PageFault, @@ -2040,7 +2119,7 @@ pub const PosixAcceptError = error { /// The system-wide limit on the total number of open files has been reached. SystemFdQuotaExceeded, - + /// Not enough free memory. This often means that the memory allocation is limited /// by the socket buffer limits, not by the system memory. SystemResources, @@ -2076,7 +2155,8 @@ pub fn posixAccept(fd: i32, addr: &posix.sockaddr, flags: u32) PosixAcceptError! posix.EINVAL => return PosixAcceptError.InvalidSyscall, posix.EMFILE => return PosixAcceptError.ProcessFdQuotaExceeded, posix.ENFILE => return PosixAcceptError.SystemFdQuotaExceeded, - posix.ENOBUFS, posix.ENOMEM => return PosixAcceptError.SystemResources, + posix.ENOBUFS, + posix.ENOMEM => return PosixAcceptError.SystemResources, posix.ENOTSOCK => return PosixAcceptError.FileDescriptorNotASocket, posix.EOPNOTSUPP => return PosixAcceptError.OperationNotSupported, posix.EPROTO => return PosixAcceptError.ProtocolFailure, @@ -2287,7 +2367,8 @@ pub fn posixConnectAsync(sockfd: i32, sockaddr: &const posix.sockaddr) PosixConn const rc = posix.connect(sockfd, sockaddr, @sizeOf(posix.sockaddr)); const err = posix.getErrno(rc); switch (err) { - 0, posix.EINPROGRESS => return, + 0, + posix.EINPROGRESS => return, else => return unexpectedErrorPosix(err), posix.EACCES => return PosixConnectError.PermissionDenied, @@ -2351,9 +2432,9 @@ pub const Thread = struct { pub const use_pthreads = is_posix and builtin.link_libc; const Data = if (use_pthreads) struct { - handle: c.pthread_t, - stack_addr: usize, - stack_len: usize, + handle: c.pthread_t, + stack_addr: usize, + stack_len: usize, } else switch (builtin.os) { builtin.Os.linux => struct { pid: i32, @@ -2467,9 +2548,7 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread outer_context.thread.data.alloc_start = bytes_ptr; const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(&c_void, &outer_context.inner); - outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, - parameter, 0, null) ?? - { + outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) ?? { const err = windows.GetLastError(); return switch (err) { else => os.unexpectedErrorWindows(err), @@ -2500,8 +2579,7 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0; const mmap_len = default_stack_size; - const stack_addr = posix.mmap(null, mmap_len, posix.PROT_READ|posix.PROT_WRITE, - posix.MAP_PRIVATE|posix.MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0); + const stack_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory; errdefer assert(posix.munmap(stack_addr, mmap_len) == 0); @@ -2547,9 +2625,7 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread } } else if (builtin.os == builtin.Os.linux) { // use linux API directly. TODO use posix.CLONE_SETTLS and initialize thread local storage correctly - const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND - | posix.CLONE_THREAD | posix.CLONE_SYSVSEM - | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; + const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; const newtls: usize = 0; const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.pid, newtls, &thread_ptr.data.pid); const err = posix.getErrno(rc); From 47680cc0d806775dd9576faaff6303e88b14fb5a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 15:10:55 -0400 Subject: [PATCH 49/58] zig fmt: better multiline string handling --- std/zig/parser.zig | 5 +++-- std/zig/parser_test.zig | 14 +++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index b5849c3e9..025bd3171 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -3512,7 +3512,8 @@ pub const Parser = struct { try stack.append(RenderState { .Text = ";" }); if (var_decl.init_node) |init_node| { try stack.append(RenderState { .Expression = init_node }); - try stack.append(RenderState { .Text = " = " }); + const text = if (init_node.id == ast.Node.Id.MultilineStringLiteral) " =" else " = "; + try stack.append(RenderState { .Text = text }); } if (var_decl.align_node) |align_node| { try stack.append(RenderState { .Text = ")" }); @@ -4063,7 +4064,7 @@ pub const Parser = struct { try stream.writeByteNTimes(' ', indent + indent_delta); try stream.print("{}", self.tokenizer.getTokenSlice(t)); } - try stream.writeByteNTimes(' ', indent + indent_delta); + try stream.writeByteNTimes(' ', indent); }, ast.Node.Id.UndefinedLiteral => { const undefined_literal = @fieldParentPtr(ast.Node.UndefinedLiteral, "base", base); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 74a49a70e..4c238254f 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -423,10 +423,18 @@ test "zig fmt: functions" { test "zig fmt: multiline string" { try testCanonical( - \\const s = - \\ \\ something - \\ \\ something else + \\test "" { + \\ const s1 = + \\ \\one + \\ \\two) + \\ \\three \\ ; + \\ const s2 = + \\ c\\one + \\ c\\two) + \\ c\\three + \\ ; + \\} \\ ); } From 37d3ef28351027a83249080d3238d61d9346f6db Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 16:16:58 -0400 Subject: [PATCH 50/58] zig fmt: support promise->T --- std/zig/ast.zig | 32 ++++++++++++++++++++++++++++++++ std/zig/parser.zig | 32 ++++++++++++++++++++++++++++++++ std/zig/parser_test.zig | 4 ++-- std/zig/tokenizer.zig | 2 ++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 716ed8eb7..b8b399afc 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -36,6 +36,7 @@ pub const Node = struct { VarType, ErrorType, FnProto, + PromiseType, // Primary expressions IntegerLiteral, @@ -495,6 +496,37 @@ pub const Node = struct { } }; + pub const PromiseType = struct { + base: Node, + promise_token: Token, + result: ?Result, + + pub const Result = struct { + arrow_token: Token, + return_type: &Node, + }; + + pub fn iterate(self: &PromiseType, index: usize) ?&Node { + var i = index; + + if (self.result) |result| { + if (i < 1) return result.return_type; + i -= 1; + } + + return null; + } + + pub fn firstToken(self: &PromiseType) Token { + return self.promise_token; + } + + pub fn lastToken(self: &PromiseType) Token { + if (self.result) |result| return result.return_type.lastToken(); + return self.promise_token; + } + }; + pub const ParamDecl = struct { base: Node, comptime_token: ?Token, diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 025bd3171..afef6704c 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -2550,6 +2550,30 @@ pub const Parser = struct { _ = try self.createToCtxLiteral(arena, opt_ctx, ast.Node.Unreachable, token); continue; }, + Token.Id.Keyword_promise => { + const node = try arena.construct(ast.Node.PromiseType { + .base = ast.Node { + .id = ast.Node.Id.PromiseType, + .doc_comments = null, + .same_line_comment = null, + }, + .promise_token = token, + .result = null, + }); + opt_ctx.store(&node.base); + const next_token = self.getNextToken(); + if (next_token.id != Token.Id.Arrow) { + self.putBackToken(next_token); + continue; + } + node.result = ast.Node.PromiseType.Result { + .arrow_token = next_token, + .return_type = undefined, + }; + const return_type_ptr = &((??node.result).return_type); + try stack.append(State { .Expression = OptionalCtx { .Required = return_type_ptr, } }); + continue; + }, Token.Id.StringLiteral, Token.Id.MultilineStringLiteralLine => { opt_ctx.store((try self.parseStringLiteral(arena, token)) ?? unreachable); continue; @@ -4147,6 +4171,14 @@ pub const Parser = struct { try stack.append(RenderState { .Text = self.tokenizer.getTokenSlice(visib_token) }); } }, + ast.Node.Id.PromiseType => { + const promise_type = @fieldParentPtr(ast.Node.PromiseType, "base", base); + try stream.write(self.tokenizer.getTokenSlice(promise_type.promise_token)); + if (promise_type.result) |result| { + try stream.write(self.tokenizer.getTokenSlice(result.arrow_token)); + try stack.append(RenderState { .Expression = result.return_type}); + } + }, ast.Node.Id.LineComment => { const line_comment_node = @fieldParentPtr(ast.Node.LineComment, "base", base); try stream.write(self.tokenizer.getTokenSlice(line_comment_node.token)); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 4c238254f..85fd6c807 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -948,12 +948,12 @@ test "zig fmt: coroutines" { \\ suspend; \\ x += 1; \\ suspend |p| {} - \\ const p = async simpleAsyncFn() catch unreachable; + \\ const p: promise->void = async simpleAsyncFn() catch unreachable; \\ await p; \\} \\ \\test "coroutine suspend, resume, cancel" { - \\ const p = try async testAsyncSeq(); + \\ const p: promise = try async testAsyncSeq(); \\ resume p; \\ cancel p; \\} diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig index 92a0fbc5d..6fc26bc5f 100644 --- a/std/zig/tokenizer.zig +++ b/std/zig/tokenizer.zig @@ -40,6 +40,7 @@ pub const Token = struct { KeywordId{.bytes="null", .id = Id.Keyword_null}, KeywordId{.bytes="or", .id = Id.Keyword_or}, KeywordId{.bytes="packed", .id = Id.Keyword_packed}, + KeywordId{.bytes="promise", .id = Id.Keyword_promise}, KeywordId{.bytes="pub", .id = Id.Keyword_pub}, KeywordId{.bytes="resume", .id = Id.Keyword_resume}, KeywordId{.bytes="return", .id = Id.Keyword_return}, @@ -166,6 +167,7 @@ pub const Token = struct { Keyword_null, Keyword_or, Keyword_packed, + Keyword_promise, Keyword_pub, Keyword_resume, Keyword_return, From 7dc8d433abbf697f05eb1ad2003b6335f750557b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 17:30:57 -0400 Subject: [PATCH 51/58] zig fmt: support labeled suspend --- std/zig/ast.zig | 2 ++ std/zig/parser.zig | 21 +++++++++++++++++++++ std/zig/parser_test.zig | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/std/zig/ast.zig b/std/zig/ast.zig index b8b399afc..70758ece5 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -1371,6 +1371,7 @@ pub const Node = struct { pub const Suspend = struct { base: Node, + label: ?Token, suspend_token: Token, payload: ?&Node, body: ?&Node, @@ -1392,6 +1393,7 @@ pub const Node = struct { } pub fn firstToken(self: &Suspend) Token { + if (self.label) |label| return label; return self.suspend_token; } diff --git a/std/zig/parser.zig b/std/zig/parser.zig index afef6704c..ebc0d5b06 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -1093,6 +1093,23 @@ pub const Parser = struct { }) catch unreachable; continue; }, + Token.Id.Keyword_suspend => { + const node = try arena.construct(ast.Node.Suspend { + .base = ast.Node { + .id = ast.Node.Id.Suspend, + .doc_comments = null, + .same_line_comment = null, + }, + .label = ctx.label, + .suspend_token = token, + .payload = null, + .body = null, + }); + ctx.opt_ctx.store(&node.base); + stack.append(State { .SuspendBody = node }) catch unreachable; + try stack.append(State { .Payload = OptionalCtx { .Optional = &node.payload } }); + continue; + }, Token.Id.Keyword_inline => { stack.append(State { .Inline = InlineCtx { @@ -3046,6 +3063,7 @@ pub const Parser = struct { const node = try self.createToCtxNode(arena, ctx, ast.Node.Suspend, ast.Node.Suspend { .base = undefined, + .label = null, .suspend_token = *token, .payload = null, .body = null, @@ -3655,6 +3673,9 @@ pub const Parser = struct { }, ast.Node.Id.Suspend => { const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base); + if (suspend_node.label) |label| { + try stream.print("{}: ", self.tokenizer.getTokenSlice(label)); + } try stream.print("{}", self.tokenizer.getTokenSlice(suspend_node.suspend_token)); if (suspend_node.body) |body| { diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 85fd6c807..c82030d22 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,3 +1,14 @@ +test "zig fmt: labeled suspend" { + try testCanonical( + \\fn foo() void { + \\ s: suspend |p| { + \\ break :s; + \\ } + \\} + \\ + ); +} + test "zig fmt: comments before error set decl" { try testCanonical( \\const UnexpectedError = error { From 1d06915f275ac59918c32206ad944fd10cc997d4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 18:20:27 -0400 Subject: [PATCH 52/58] zig fmt: support union(enum(tag)) and enum init values --- std/zig/ast.zig | 11 ++++++++++- std/zig/parser.zig | 40 ++++++++++++++++++++++++++++++++++++++-- std/zig/parser_test.zig | 12 ++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 70758ece5..f0d2f92e2 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -261,7 +261,7 @@ pub const Node = struct { const InitArg = union(enum) { None, - Enum, + Enum: ?&Node, Type: &Node, }; @@ -321,6 +321,7 @@ pub const Node = struct { base: Node, name_token: Token, type_expr: ?&Node, + value_expr: ?&Node, pub fn iterate(self: &UnionTag, index: usize) ?&Node { var i = index; @@ -330,6 +331,11 @@ pub const Node = struct { i -= 1; } + if (self.value_expr) |value_expr| { + if (i < 1) return value_expr; + i -= 1; + } + return null; } @@ -338,6 +344,9 @@ pub const Node = struct { } pub fn lastToken(self: &UnionTag) Token { + if (self.value_expr) |value_expr| { + return value_expr.lastToken(); + } if (self.type_expr) |type_expr| { return type_expr.lastToken(); } diff --git a/std/zig/parser.zig b/std/zig/parser.zig index ebc0d5b06..12b1c315e 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -242,6 +242,7 @@ pub const Parser = struct { FieldInitListItemOrEnd: ListSave(&ast.Node.FieldInitializer), FieldInitListCommaOrEnd: ListSave(&ast.Node.FieldInitializer), FieldListCommaOrEnd: &ast.Node.ContainerDecl, + FieldInitValue: OptionalCtx, IdentifierListItemOrEnd: ListSave(&ast.Node), IdentifierListCommaOrEnd: ListSave(&ast.Node), SwitchCaseOrEnd: ListSave(&ast.Node), @@ -653,6 +654,15 @@ pub const Parser = struct { continue; }, + State.FieldInitValue => |ctx| { + const eq_tok = self.getNextToken(); + if (eq_tok.id != Token.Id.Equal) { + self.putBackToken(eq_tok); + continue; + } + stack.append(State { .Expression = ctx }) catch unreachable; + continue; + }, State.ContainerKind => |ctx| { const token = self.getNextToken(); @@ -699,7 +709,16 @@ pub const Parser = struct { const init_arg_token = self.getNextToken(); switch (init_arg_token.id) { Token.Id.Keyword_enum => { - container_decl.init_arg_expr = ast.Node.ContainerDecl.InitArg.Enum; + container_decl.init_arg_expr = ast.Node.ContainerDecl.InitArg {.Enum = null}; + const lparen_tok = self.getNextToken(); + if (lparen_tok.id == Token.Id.LParen) { + try stack.append(State { .ExpectToken = Token.Id.RParen } ); + try stack.append(State { .Expression = OptionalCtx { + .RequiredNull = &container_decl.init_arg_expr.Enum, + } }); + } else { + self.putBackToken(lparen_tok); + } }, else => { self.putBackToken(init_arg_token); @@ -709,6 +728,7 @@ pub const Parser = struct { } continue; }, + State.ContainerDecl => |container_decl| { while (try self.eatLineComment(arena)) |line_comment| { try container_decl.fields_and_decls.append(&line_comment.base); @@ -744,10 +764,12 @@ pub const Parser = struct { .base = undefined, .name_token = token, .type_expr = null, + .value_expr = null, } ); stack.append(State { .FieldListCommaOrEnd = container_decl }) catch unreachable; + try stack.append(State { .FieldInitValue = OptionalCtx { .RequiredNull = &node.value_expr } }); try stack.append(State { .TypeExprBegin = OptionalCtx { .RequiredNull = &node.type_expr } }); try stack.append(State { .IfToken = Token.Id.Colon }); continue; @@ -3515,6 +3537,12 @@ pub const Parser = struct { try stream.print("{}", self.tokenizer.getTokenSlice(tag.name_token)); try stack.append(RenderState { .Text = "," }); + + if (tag.value_expr) |value_expr| { + try stack.append(RenderState { .Expression = value_expr }); + try stack.append(RenderState { .Text = " = " }); + } + if (tag.type_expr) |type_expr| { try stream.print(": "); try stack.append(RenderState { .Expression = type_expr}); @@ -4055,7 +4083,15 @@ pub const Parser = struct { switch (container_decl.init_arg_expr) { ast.Node.ContainerDecl.InitArg.None => try stack.append(RenderState { .Text = " "}), - ast.Node.ContainerDecl.InitArg.Enum => try stack.append(RenderState { .Text = "(enum) "}), + ast.Node.ContainerDecl.InitArg.Enum => |enum_tag_type| { + if (enum_tag_type) |expr| { + try stack.append(RenderState { .Text = ")) "}); + try stack.append(RenderState { .Expression = expr}); + try stack.append(RenderState { .Text = "(enum("}); + } else { + try stack.append(RenderState { .Text = "(enum) "}); + } + }, ast.Node.ContainerDecl.InitArg.Type => |type_expr| { try stack.append(RenderState { .Text = ") "}); try stack.append(RenderState { .Expression = type_expr}); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index c82030d22..f8b89ebe4 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,3 +1,15 @@ +test "zig fmt: union(enum(u32)) with assigned enum values" { + try testCanonical( + \\const MultipleChoice = union(enum(u32)) { + \\ A = 20, + \\ B = 40, + \\ C = 60, + \\ D = 1000, + \\}; + \\ + ); +} + test "zig fmt: labeled suspend" { try testCanonical( \\fn foo() void { From eed49a210474af98f64961ed603feb6dfa60acde Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 18:30:47 -0400 Subject: [PATCH 53/58] zig fmt: aggregate type init with only 1 field --- std/zig/parser.zig | 9 +++++++++ std/zig/parser_test.zig | 14 +++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 12b1c315e..5e3f3bff5 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -3872,6 +3872,15 @@ pub const Parser = struct { try stack.append(RenderState { .Expression = suffix_op.lhs }); continue; } + if (field_inits.len == 1) { + const field_init = field_inits.at(0); + + try stack.append(RenderState { .Text = "}" }); + try stack.append(RenderState { .FieldInitializer = field_init }); + try stack.append(RenderState { .Text = " {" }); + try stack.append(RenderState { .Expression = suffix_op.lhs }); + continue; + } try stack.append(RenderState { .Text = "}"}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Indent = indent }); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index f8b89ebe4..8b0002e6b 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,3 +1,12 @@ +test "zig fmt: aggregate type init with only 1 field" { + try testCanonical( + \\comptime { + \\ assert(bar(Payload {.A = 1234}) == -10); + \\} + \\ + ); +} + test "zig fmt: union(enum(u32)) with assigned enum values" { try testCanonical( \\const MultipleChoice = union(enum(u32)) { @@ -709,9 +718,7 @@ test "zig fmt: switch" { \\ Float: f64, \\ }; \\ - \\ const u = Union { - \\ .Int = 0, - \\ }; + \\ const u = Union {.Int = 0}; \\ switch (u) { \\ Union.Int => |int| {}, \\ Union.Float => |*float| unreachable, @@ -1029,6 +1036,7 @@ test "zig fmt: struct literals with fields on each line" { try testCanonical( \\var self = BufSet { \\ .hash_map = BufSetHashMap.init(a), + \\ .hash_map2 = xyz, \\}; \\ ); From 3e61c45f8936f6211ae9b61b2b0358c560344f7b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 18:49:05 -0400 Subject: [PATCH 54/58] zig fmt: consistent spacing for container inits --- std/zig/parser.zig | 10 +++++----- std/zig/parser_test.zig | 41 +++++++++-------------------------------- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 5e3f3bff5..b50150580 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -3875,9 +3875,9 @@ pub const Parser = struct { if (field_inits.len == 1) { const field_init = field_inits.at(0); - try stack.append(RenderState { .Text = "}" }); + try stack.append(RenderState { .Text = " }" }); try stack.append(RenderState { .FieldInitializer = field_init }); - try stack.append(RenderState { .Text = " {" }); + try stack.append(RenderState { .Text = "{ " }); try stack.append(RenderState { .Expression = suffix_op.lhs }); continue; } @@ -3893,7 +3893,7 @@ pub const Parser = struct { try stack.append(RenderState.PrintIndent); } try stack.append(RenderState { .Indent = indent + indent_delta }); - try stack.append(RenderState { .Text = " {\n"}); + try stack.append(RenderState { .Text = "{\n"}); try stack.append(RenderState { .Expression = suffix_op.lhs }); }, ast.Node.SuffixOp.Op.ArrayInitializer => |exprs| { @@ -3907,7 +3907,7 @@ pub const Parser = struct { try stack.append(RenderState { .Text = "}" }); try stack.append(RenderState { .Expression = expr }); - try stack.append(RenderState { .Text = " {" }); + try stack.append(RenderState { .Text = "{" }); try stack.append(RenderState { .Expression = suffix_op.lhs }); continue; } @@ -3924,7 +3924,7 @@ pub const Parser = struct { try stack.append(RenderState.PrintIndent); } try stack.append(RenderState { .Indent = indent + indent_delta }); - try stack.append(RenderState { .Text = " {\n"}); + try stack.append(RenderState { .Text = "{\n"}); try stack.append(RenderState { .Expression = suffix_op.lhs }); }, } diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 8b0002e6b..81f9e5622 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,12 +1,3 @@ -test "zig fmt: aggregate type init with only 1 field" { - try testCanonical( - \\comptime { - \\ assert(bar(Payload {.A = 1234}) == -10); - \\} - \\ - ); -} - test "zig fmt: union(enum(u32)) with assigned enum values" { try testCanonical( \\const MultipleChoice = union(enum(u32)) { @@ -124,7 +115,7 @@ test "zig fmt: same-line comment after field decl" { test "zig fmt: array literal with 1 item on 1 line" { try testCanonical( - \\var s = []const u64 {0} ** 25; + \\var s = []const u64{0} ** 25; \\ ); } @@ -625,11 +616,11 @@ test "zig fmt: error set declaration" { test "zig fmt: arrays" { try testCanonical( \\test "test array" { - \\ const a: [2]u8 = [2]u8 { + \\ const a: [2]u8 = [2]u8{ \\ 1, \\ 2, \\ }; - \\ const a: [2]u8 = []u8 { + \\ const a: [2]u8 = []u8{ \\ 1, \\ 2, \\ }; @@ -641,15 +632,17 @@ test "zig fmt: arrays" { test "zig fmt: container initializers" { try testCanonical( - \\const a1 = []u8{}; - \\const a2 = []u8 { + \\const a0 = []u8{}; + \\const a1 = []u8{1}; + \\const a2 = []u8{ \\ 1, \\ 2, \\ 3, \\ 4, \\}; - \\const s1 = S{}; - \\const s2 = S { + \\const s0 = S{}; + \\const s1 = S{ .a = 1 }; + \\const s2 = S{ \\ .a = 1, \\ .b = 2, \\}; @@ -718,7 +711,6 @@ test "zig fmt: switch" { \\ Float: f64, \\ }; \\ - \\ const u = Union {.Int = 0}; \\ switch (u) { \\ Union.Int => |int| {}, \\ Union.Float => |*float| unreachable, @@ -797,11 +789,6 @@ test "zig fmt: while" { test "zig fmt: for" { try testCanonical( \\test "for" { - \\ const a = []u8 { - \\ 1, - \\ 2, - \\ 3, - \\ }; \\ for (a) |v| { \\ continue; \\ } @@ -1032,16 +1019,6 @@ test "zig fmt: error return" { ); } -test "zig fmt: struct literals with fields on each line" { - try testCanonical( - \\var self = BufSet { - \\ .hash_map = BufSetHashMap.init(a), - \\ .hash_map2 = xyz, - \\}; - \\ - ); -} - const std = @import("std"); const mem = std.mem; const warn = std.debug.warn; From 4cc1008c2d2b438b9847944898ad2d7cfbbdab8b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 19:16:46 -0400 Subject: [PATCH 55/58] zig fmt: error set decls --- std/zig/parser.zig | 21 ++++++++++++++++--- std/zig/parser_test.zig | 46 ++++++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index b50150580..c1bae7f86 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -4110,14 +4110,30 @@ pub const Parser = struct { }, ast.Node.Id.ErrorSetDecl => { const err_set_decl = @fieldParentPtr(ast.Node.ErrorSetDecl, "base", base); - try stream.print("error "); + + const decls = err_set_decl.decls.toSliceConst(); + if (decls.len == 0) { + try stream.write("error{}"); + continue; + } + + if (decls.len == 1) blk: { + const node = decls[0]; + if (node.same_line_comment != null or node.doc_comments != null) break :blk; + + try stream.write("error{"); + try stack.append(RenderState { .Text = "}" }); + try stack.append(RenderState { .Expression = node }); + continue; + } + + try stream.write("error{"); try stack.append(RenderState { .Text = "}"}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Indent = indent }); try stack.append(RenderState { .Text = "\n"}); - const decls = err_set_decl.decls.toSliceConst(); var i = decls.len; while (i != 0) { i -= 1; @@ -4142,7 +4158,6 @@ pub const Parser = struct { }); } try stack.append(RenderState { .Indent = indent + indent_delta}); - try stack.append(RenderState { .Text = "{"}); }, ast.Node.Id.MultilineStringLiteral => { const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 81f9e5622..e5e3c9752 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,5 +1,35 @@ -test "zig fmt: union(enum(u32)) with assigned enum values" { +test "zig fmt: error set declaration" { try testCanonical( + \\const E = error{ + \\ A, + \\ B, + \\ + \\ C, + \\}; + \\ + \\const Error = error{ + \\ /// no more memory + \\ OutOfMemory, + \\}; + \\ + \\const Error = error{ + \\ /// no more memory + \\ OutOfMemory, + \\ + \\ /// another + \\ Another, + \\ + \\ // end + \\}; + \\ + \\const Error = error{OutOfMemory}; + \\const Error = error{}; + \\ + ); +} + +test "zig fmt: union(enum(u32)) with assigned enum values" { + try testCanonical( \\const MultipleChoice = union(enum(u32)) { \\ A = 20, \\ B = 40, @@ -23,7 +53,7 @@ test "zig fmt: labeled suspend" { test "zig fmt: comments before error set decl" { try testCanonical( - \\const UnexpectedError = error { + \\const UnexpectedError = error{ \\ /// The Operating System returned an undocumented error code. \\ Unexpected, \\ // another @@ -601,18 +631,6 @@ test "zig fmt: union declaration" { ); } -test "zig fmt: error set declaration" { - try testCanonical( - \\const E = error { - \\ A, - \\ B, - \\ - \\ C, - \\}; - \\ - ); -} - test "zig fmt: arrays" { try testCanonical( \\test "test array" { From 61a726c290a4e569ae28da59c94ba6a40df59a20 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 19:27:14 -0400 Subject: [PATCH 56/58] zig fmt: comments in field decls --- std/zig/parser.zig | 3 +++ std/zig/parser_test.zig | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/std/zig/parser.zig b/std/zig/parser.zig index c1bae7f86..1ea302135 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -3525,6 +3525,7 @@ pub const Parser = struct { }, ast.Node.Id.StructField => { const field = @fieldParentPtr(ast.Node.StructField, "base", decl); + try self.renderComments(stream, &field.base, indent); if (field.visib_token) |visib_token| { try stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); } @@ -3534,6 +3535,7 @@ pub const Parser = struct { }, ast.Node.Id.UnionTag => { const tag = @fieldParentPtr(ast.Node.UnionTag, "base", decl); + try self.renderComments(stream, &tag.base, indent); try stream.print("{}", self.tokenizer.getTokenSlice(tag.name_token)); try stack.append(RenderState { .Text = "," }); @@ -3550,6 +3552,7 @@ pub const Parser = struct { }, ast.Node.Id.EnumTag => { const tag = @fieldParentPtr(ast.Node.EnumTag, "base", decl); + try self.renderComments(stream, &tag.base, indent); try stream.print("{}", self.tokenizer.getTokenSlice(tag.name_token)); try stack.append(RenderState { .Text = "," }); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index e5e3c9752..5d4383d6a 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,5 +1,16 @@ +test "zig fmt: doc comments before struct field" { + try testCanonical( + \\pub const Allocator = struct { + \\ /// Allocate byte_count bytes and return them in a slice, with the + \\ /// slice's pointer aligned at least to alignment bytes. + \\ allocFn: fn() void, + \\}; + \\ + ); +} + test "zig fmt: error set declaration" { - try testCanonical( + try testCanonical( \\const E = error{ \\ A, \\ B, From 7c822869feac7713063da320c12a960f3ae58298 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 30 Apr 2018 20:25:54 -0400 Subject: [PATCH 57/58] zig fmt: only some docs have doc comments --- std/zig/ast.zig | 43 ++++++++++- std/zig/parser.zig | 153 +++++++++++++++++++++------------------- std/zig/parser_test.zig | 18 +---- 3 files changed, 125 insertions(+), 89 deletions(-) diff --git a/std/zig/ast.zig b/std/zig/ast.zig index f0d2f92e2..a311aa1d7 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -6,7 +6,6 @@ const mem = std.mem; pub const Node = struct { id: Id, - doc_comments: ?&DocComment, same_line_comment: ?&Token, pub const Id = enum { @@ -70,6 +69,7 @@ pub const Node = struct { StructField, UnionTag, EnumTag, + ErrorTag, AsmInput, AsmOutput, AsyncAttribute, @@ -77,6 +77,13 @@ pub const Node = struct { FieldInitializer, }; + pub fn cast(base: &Node, comptime T: type) ?&T { + if (base.id == comptime typeToId(T)) { + return @fieldParentPtr(T, "base", base); + } + return null; + } + pub fn iterate(base: &Node, index: usize) ?&Node { comptime var i = 0; inline while (i < @memberCount(Id)) : (i += 1) { @@ -122,6 +129,7 @@ pub const Node = struct { pub const Root = struct { base: Node, + doc_comments: ?&DocComment, decls: ArrayList(&Node), eof_token: Token, @@ -143,6 +151,7 @@ pub const Node = struct { pub const VarDecl = struct { base: Node, + doc_comments: ?&DocComment, visib_token: ?Token, name_token: Token, eq_token: Token, @@ -191,6 +200,7 @@ pub const Node = struct { pub const Use = struct { base: Node, + doc_comments: ?&DocComment, visib_token: ?Token, expr: &Node, semicolon_token: Token, @@ -294,6 +304,7 @@ pub const Node = struct { pub const StructField = struct { base: Node, + doc_comments: ?&DocComment, visib_token: ?Token, name_token: Token, type_expr: &Node, @@ -319,6 +330,7 @@ pub const Node = struct { pub const UnionTag = struct { base: Node, + doc_comments: ?&DocComment, name_token: Token, type_expr: ?&Node, value_expr: ?&Node, @@ -357,6 +369,7 @@ pub const Node = struct { pub const EnumTag = struct { base: Node, + doc_comments: ?&DocComment, name_token: Token, value: ?&Node, @@ -384,6 +397,31 @@ pub const Node = struct { } }; + pub const ErrorTag = struct { + base: Node, + doc_comments: ?&DocComment, + name_token: Token, + + pub fn iterate(self: &ErrorTag, index: usize) ?&Node { + var i = index; + + if (self.doc_comments) |comments| { + if (i < 1) return &comments.base; + i -= 1; + } + + return null; + } + + pub fn firstToken(self: &ErrorTag) Token { + return self.name_token; + } + + pub fn lastToken(self: &ErrorTag) Token { + return self.name_token; + } + }; + pub const Identifier = struct { base: Node, token: Token, @@ -433,6 +471,7 @@ pub const Node = struct { pub const FnProto = struct { base: Node, + doc_comments: ?&DocComment, visib_token: ?Token, fn_token: Token, name_token: ?Token, @@ -626,6 +665,7 @@ pub const Node = struct { pub const Comptime = struct { base: Node, + doc_comments: ?&DocComment, comptime_token: Token, expr: &Node, @@ -1794,6 +1834,7 @@ pub const Node = struct { pub const TestDecl = struct { base: Node, + doc_comments: ?&DocComment, test_token: Token, name: &Node, body_node: &Node, diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 1ea302135..ed6a1a425 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -228,7 +228,6 @@ pub const Parser = struct { Statement: &ast.Node.Block, ComptimeStatement: ComptimeStatementCtx, Semicolon: &&ast.Node, - AddComments: AddCommentsCtx, LookForSameLineComment: &&ast.Node, LookForSameLineCommentDirect: &ast.Node, @@ -243,8 +242,8 @@ pub const Parser = struct { FieldInitListCommaOrEnd: ListSave(&ast.Node.FieldInitializer), FieldListCommaOrEnd: &ast.Node.ContainerDecl, FieldInitValue: OptionalCtx, - IdentifierListItemOrEnd: ListSave(&ast.Node), - IdentifierListCommaOrEnd: ListSave(&ast.Node), + ErrorTagListItemOrEnd: ListSave(&ast.Node), + ErrorTagListCommaOrEnd: ListSave(&ast.Node), SwitchCaseOrEnd: ListSave(&ast.Node), SwitchCaseCommaOrEnd: ListSave(&ast.Node), SwitchCaseFirstItem: &ArrayList(&ast.Node), @@ -301,6 +300,7 @@ pub const Parser = struct { ErrorTypeOrSetDecl: ErrorTypeOrSetDeclCtx, StringLiteral: OptionalCtx, Identifier: OptionalCtx, + ErrorTag: &&ast.Node, IfToken: @TagType(Token.Id), @@ -325,6 +325,7 @@ pub const Parser = struct { ast.Node.Root { .base = undefined, .decls = ArrayList(&ast.Node).init(arena), + .doc_comments = null, // initialized when we get the eof token .eof_token = undefined, } @@ -354,7 +355,7 @@ pub const Parser = struct { try root_node.decls.append(&line_comment.base); } - const comments = try self.eatComments(arena); + const comments = try self.eatDocComments(arena); const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_test => { @@ -363,7 +364,6 @@ pub const Parser = struct { const block = try arena.construct(ast.Node.Block { .base = ast.Node { .id = ast.Node.Id.Block, - .doc_comments = null, .same_line_comment = null, }, .label = null, @@ -374,9 +374,9 @@ pub const Parser = struct { const test_node = try arena.construct(ast.Node.TestDecl { .base = ast.Node { .id = ast.Node.Id.TestDecl, - .doc_comments = comments, .same_line_comment = null, }, + .doc_comments = comments, .test_token = token, .name = undefined, .body_node = &block.base, @@ -394,7 +394,11 @@ pub const Parser = struct { }, Token.Id.Eof => { root_node.eof_token = token; - return Tree {.root_node = root_node, .arena_allocator = arena_allocator}; + root_node.doc_comments = comments; + return Tree { + .root_node = root_node, + .arena_allocator = arena_allocator, + }; }, Token.Id.Keyword_pub => { stack.append(State.TopLevel) catch unreachable; @@ -424,6 +428,7 @@ pub const Parser = struct { .base = undefined, .comptime_token = token, .expr = &block.base, + .doc_comments = comments, } ); stack.append(State.TopLevel) catch unreachable; @@ -520,6 +525,7 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .expr = undefined, .semicolon_token = undefined, + .doc_comments = ctx.comments, } ); stack.append(State { @@ -556,9 +562,9 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .doc_comments = ctx.comments, .same_line_comment = null, }, + .doc_comments = ctx.comments, .visib_token = ctx.visib_token, .name_token = null, .fn_token = undefined, @@ -625,9 +631,9 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.StructField { .base = ast.Node { .id = ast.Node.Id.StructField, - .doc_comments = null, .same_line_comment = null, }, + .doc_comments = ctx.comments, .visib_token = ctx.visib_token, .name_token = identifier, .type_expr = undefined, @@ -734,7 +740,7 @@ pub const Parser = struct { try container_decl.fields_and_decls.append(&line_comment.base); } - const comments = try self.eatComments(arena); + const comments = try self.eatDocComments(arena); const token = self.getNextToken(); switch (token.id) { Token.Id.Identifier => { @@ -743,9 +749,9 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.StructField { .base = ast.Node { .id = ast.Node.Id.StructField, - .doc_comments = comments, .same_line_comment = null, }, + .doc_comments = comments, .visib_token = null, .name_token = token, .type_expr = undefined, @@ -765,6 +771,7 @@ pub const Parser = struct { .name_token = token, .type_expr = null, .value_expr = null, + .doc_comments = comments, } ); @@ -780,6 +787,7 @@ pub const Parser = struct { .base = undefined, .name_token = token, .value = null, + .doc_comments = comments, } ); @@ -831,6 +839,9 @@ pub const Parser = struct { continue; }, Token.Id.RBrace => { + if (comments != null) { + return self.parseError(token, "doc comments must be attached to a node"); + } container_decl.rbrace_token = token; continue; }, @@ -856,9 +867,9 @@ pub const Parser = struct { const var_decl = try arena.construct(ast.Node.VarDecl { .base = ast.Node { .id = ast.Node.Id.VarDecl, - .doc_comments = ctx.comments, .same_line_comment = null, }, + .doc_comments = ctx.comments, .visib_token = ctx.visib_token, .mut_token = ctx.mut_token, .comptime_token = ctx.comptime_token, @@ -1119,7 +1130,6 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.Suspend { .base = ast.Node { .id = ast.Node.Id.Suspend, - .doc_comments = null, .same_line_comment = null, }, .label = ctx.label, @@ -1283,7 +1293,6 @@ pub const Parser = struct { } }, State.Statement => |block| { - const comments = try self.eatComments(arena); const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_comptime => { @@ -1298,7 +1307,7 @@ pub const Parser = struct { Token.Id.Keyword_var, Token.Id.Keyword_const => { stack.append(State { .VarDecl = VarDeclCtx { - .comments = comments, + .comments = null, .visib_token = null, .comptime_token = null, .extern_export_token = null, @@ -1313,7 +1322,6 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.Defer { .base = ast.Node { .id = ast.Node.Id.Defer, - .doc_comments = comments, .same_line_comment = null, }, .defer_token = token, @@ -1349,23 +1357,18 @@ pub const Parser = struct { const statement = try block.statements.addOne(); stack.append(State { .LookForSameLineComment = statement }) catch unreachable; try stack.append(State { .Semicolon = statement }); - try stack.append(State { .AddComments = AddCommentsCtx { - .node_ptr = statement, - .comments = comments, - }}); try stack.append(State { .AssignmentExpressionBegin = OptionalCtx{ .Required = statement } }); continue; } } }, State.ComptimeStatement => |ctx| { - const comments = try self.eatComments(arena); const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_var, Token.Id.Keyword_const => { stack.append(State { .VarDecl = VarDeclCtx { - .comments = comments, + .comments = null, .visib_token = null, .comptime_token = ctx.comptime_token, .extern_export_token = null, @@ -1395,12 +1398,6 @@ pub const Parser = struct { continue; }, - State.AddComments => |add_comments_ctx| { - const node = *add_comments_ctx.node_ptr; - node.doc_comments = add_comments_ctx.comments; - continue; - }, - State.LookForSameLineComment => |node_ptr| { try self.lookForSameLineComment(arena, *node_ptr); continue; @@ -1521,7 +1518,6 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.FieldInitializer { .base = ast.Node { .id = ast.Node.Id.FieldInitializer, - .doc_comments = null, .same_line_comment = null, }, .period_token = undefined, @@ -1566,7 +1562,7 @@ pub const Parser = struct { try stack.append(State { .ContainerDecl = container_decl }); continue; }, - State.IdentifierListItemOrEnd => |list_state| { + State.ErrorTagListItemOrEnd => |list_state| { while (try self.eatLineComment(arena)) |line_comment| { try list_state.list.append(&line_comment.base); } @@ -1576,23 +1572,18 @@ pub const Parser = struct { continue; } - const comments = try self.eatComments(arena); const node_ptr = try list_state.list.addOne(); - try stack.append(State { .AddComments = AddCommentsCtx { - .node_ptr = node_ptr, - .comments = comments, - }}); - try stack.append(State { .IdentifierListCommaOrEnd = list_state }); - try stack.append(State { .Identifier = OptionalCtx { .Required = node_ptr } }); + try stack.append(State { .ErrorTagListCommaOrEnd = list_state }); + try stack.append(State { .ErrorTag = node_ptr }); continue; }, - State.IdentifierListCommaOrEnd => |list_state| { + State.ErrorTagListCommaOrEnd => |list_state| { if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| { *list_state.ptr = end; continue; } else { - stack.append(State { .IdentifierListItemOrEnd = list_state }) catch unreachable; + stack.append(State { .ErrorTagListItemOrEnd = list_state }) catch unreachable; continue; } }, @@ -1606,11 +1597,10 @@ pub const Parser = struct { continue; } - const comments = try self.eatComments(arena); + const comments = try self.eatDocComments(arena); const node = try arena.construct(ast.Node.SwitchCase { .base = ast.Node { .id = ast.Node.Id.SwitchCase, - .doc_comments = comments, .same_line_comment = null, }, .items = ArrayList(&ast.Node).init(arena), @@ -1723,9 +1713,9 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .doc_comments = ctx.comments, .same_line_comment = null, }, + .doc_comments = ctx.comments, .visib_token = null, .name_token = null, .fn_token = fn_token, @@ -2593,7 +2583,6 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.PromiseType { .base = ast.Node { .id = ast.Node.Id.PromiseType, - .doc_comments = null, .same_line_comment = null, }, .promise_token = token, @@ -2719,9 +2708,9 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .doc_comments = null, .same_line_comment = null, }, + .doc_comments = null, .visib_token = null, .name_token = null, .fn_token = token, @@ -2743,9 +2732,9 @@ pub const Parser = struct { const fn_proto = try arena.construct(ast.Node.FnProto { .base = ast.Node { .id = ast.Node.Id.FnProto, - .doc_comments = null, .same_line_comment = null, }, + .doc_comments = null, .visib_token = null, .name_token = null, .fn_token = undefined, @@ -2836,7 +2825,6 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.ErrorSetDecl { .base = ast.Node { .id = ast.Node.Id.ErrorSetDecl, - .doc_comments = null, .same_line_comment = null, }, .error_token = ctx.error_token, @@ -2846,7 +2834,7 @@ pub const Parser = struct { ctx.opt_ctx.store(&node.base); stack.append(State { - .IdentifierListItemOrEnd = ListSave(&ast.Node) { + .ErrorTagListItemOrEnd = ListSave(&ast.Node) { .list = &node.decls, .ptr = &node.rbrace_token, } @@ -2866,6 +2854,7 @@ pub const Parser = struct { } ); }, + State.Identifier => |opt_ctx| { if (self.eatToken(Token.Id.Identifier)) |ident_token| { _ = try self.createToCtxLiteral(arena, opt_ctx, ast.Node.Identifier, ident_token); @@ -2878,6 +2867,25 @@ pub const Parser = struct { } }, + State.ErrorTag => |node_ptr| { + const comments = try self.eatDocComments(arena); + const ident_token = self.getNextToken(); + if (ident_token.id != Token.Id.Identifier) { + return self.parseError(ident_token, "expected {}, found {}", + @tagName(Token.Id.Identifier), @tagName(ident_token.id)); + } + + const node = try arena.construct(ast.Node.ErrorTag { + .base = ast.Node { + .id = ast.Node.Id.ErrorTag, + .same_line_comment = null, + }, + .doc_comments = comments, + .name_token = ident_token, + }); + *node_ptr = &node.base; + continue; + }, State.ExpectToken => |token_id| { _ = try self.expectToken(token_id); @@ -2916,7 +2924,7 @@ pub const Parser = struct { } } - fn eatComments(self: &Parser, arena: &mem.Allocator) !?&ast.Node.DocComment { + fn eatDocComments(self: &Parser, arena: &mem.Allocator) !?&ast.Node.DocComment { var result: ?&ast.Node.DocComment = null; while (true) { if (self.eatToken(Token.Id.DocComment)) |line_comment| { @@ -2927,7 +2935,6 @@ pub const Parser = struct { const comment_node = try arena.construct(ast.Node.DocComment { .base = ast.Node { .id = ast.Node.Id.DocComment, - .doc_comments = null, .same_line_comment = null, }, .lines = ArrayList(Token).init(arena), @@ -2949,7 +2956,6 @@ pub const Parser = struct { return try arena.construct(ast.Node.LineComment { .base = ast.Node { .id = ast.Node.Id.LineComment, - .doc_comments = null, .same_line_comment = null, }, .token = token, @@ -3142,7 +3148,6 @@ pub const Parser = struct { const node = try arena.construct(ast.Node.Switch { .base = ast.Node { .id = ast.Node.Id.Switch, - .doc_comments = null, .same_line_comment = null, }, .switch_token = *token, @@ -3170,6 +3175,7 @@ pub const Parser = struct { .base = undefined, .comptime_token = *token, .expr = undefined, + .doc_comments = null, } ); try stack.append(State { .Expression = OptionalCtx { .Required = &node.expr } }); @@ -3312,7 +3318,6 @@ pub const Parser = struct { const id = ast.Node.typeToId(T); break :blk ast.Node { .id = id, - .doc_comments = null, .same_line_comment = null, }; }; @@ -3451,7 +3456,6 @@ pub const Parser = struct { PrintIndent, Indent: usize, PrintSameLineComment: ?&Token, - PrintComments: &ast.Node, }; pub fn renderSource(self: &Parser, stream: var, root_node: &ast.Node.Root) !void { @@ -3490,7 +3494,7 @@ pub const Parser = struct { switch (decl.id) { ast.Node.Id.FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); - try self.renderComments(stream, &fn_proto.base, indent); + try self.renderComments(stream, fn_proto, indent); if (fn_proto.body_node) |body_node| { stack.append(RenderState { .Expression = body_node}) catch unreachable; @@ -3512,12 +3516,12 @@ pub const Parser = struct { }, ast.Node.Id.VarDecl => { const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl); - try self.renderComments(stream, &var_decl.base, indent); + try self.renderComments(stream, var_decl, indent); try stack.append(RenderState { .VarDecl = var_decl}); }, ast.Node.Id.TestDecl => { const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl); - try self.renderComments(stream, &test_decl.base, indent); + try self.renderComments(stream, test_decl, indent); try stream.print("test "); try stack.append(RenderState { .Expression = test_decl.body_node }); try stack.append(RenderState { .Text = " " }); @@ -3525,7 +3529,7 @@ pub const Parser = struct { }, ast.Node.Id.StructField => { const field = @fieldParentPtr(ast.Node.StructField, "base", decl); - try self.renderComments(stream, &field.base, indent); + try self.renderComments(stream, field, indent); if (field.visib_token) |visib_token| { try stream.print("{} ", self.tokenizer.getTokenSlice(visib_token)); } @@ -3535,7 +3539,7 @@ pub const Parser = struct { }, ast.Node.Id.UnionTag => { const tag = @fieldParentPtr(ast.Node.UnionTag, "base", decl); - try self.renderComments(stream, &tag.base, indent); + try self.renderComments(stream, tag, indent); try stream.print("{}", self.tokenizer.getTokenSlice(tag.name_token)); try stack.append(RenderState { .Text = "," }); @@ -3552,7 +3556,7 @@ pub const Parser = struct { }, ast.Node.Id.EnumTag => { const tag = @fieldParentPtr(ast.Node.EnumTag, "base", decl); - try self.renderComments(stream, &tag.base, indent); + try self.renderComments(stream, tag, indent); try stream.print("{}", self.tokenizer.getTokenSlice(tag.name_token)); try stack.append(RenderState { .Text = "," }); @@ -3561,6 +3565,11 @@ pub const Parser = struct { try stack.append(RenderState { .Expression = value}); } }, + ast.Node.Id.ErrorTag => { + const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", decl); + try self.renderComments(stream, tag, indent); + try stream.print("{}", self.tokenizer.getTokenSlice(tag.name_token)); + }, ast.Node.Id.Comptime => { if (requireSemiColon(decl)) { try stack.append(RenderState { .Text = ";" }); @@ -4122,11 +4131,20 @@ pub const Parser = struct { if (decls.len == 1) blk: { const node = decls[0]; - if (node.same_line_comment != null or node.doc_comments != null) break :blk; + + // if there are any doc comments or same line comments + // don't try to put it all on one line + if (node.same_line_comment != null) break :blk; + if (node.cast(ast.Node.ErrorTag)) |tag| { + if (tag.doc_comments != null) break :blk; + } else { + break :blk; + } + try stream.write("error{"); try stack.append(RenderState { .Text = "}" }); - try stack.append(RenderState { .Expression = node }); + try stack.append(RenderState { .TopLevelDecl = node }); continue; } @@ -4144,8 +4162,7 @@ pub const Parser = struct { if (node.id != ast.Node.Id.LineComment) { try stack.append(RenderState { .Text = "," }); } - try stack.append(RenderState { .Expression = node }); - try stack.append(RenderState { .PrintComments = node }); + try stack.append(RenderState { .TopLevelDecl = node }); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Text = blk: { @@ -4304,8 +4321,6 @@ pub const Parser = struct { ast.Node.Id.SwitchCase => { const switch_case = @fieldParentPtr(ast.Node.SwitchCase, "base", base); - try self.renderComments(stream, base, indent); - try stack.append(RenderState { .PrintSameLineComment = base.same_line_comment }); try stack.append(RenderState { .Text = "," }); try stack.append(RenderState { .Expression = switch_case.expr }); @@ -4617,6 +4632,7 @@ pub const Parser = struct { ast.Node.Id.StructField, ast.Node.Id.UnionTag, ast.Node.Id.EnumTag, + ast.Node.Id.ErrorTag, ast.Node.Id.Root, ast.Node.Id.VarDecl, ast.Node.Id.Use, @@ -4624,7 +4640,6 @@ pub const Parser = struct { ast.Node.Id.ParamDecl => unreachable, }, RenderState.Statement => |base| { - try self.renderComments(stream, base, indent); try stack.append(RenderState { .PrintSameLineComment = base.same_line_comment } ); switch (base.id) { ast.Node.Id.VarDecl => { @@ -4645,15 +4660,11 @@ pub const Parser = struct { const comment_token = maybe_comment ?? break :blk; try stream.print(" {}", self.tokenizer.getTokenSlice(comment_token)); }, - - RenderState.PrintComments => |node| blk: { - try self.renderComments(stream, node, indent); - }, } } } - fn renderComments(self: &Parser, stream: var, node: &ast.Node, indent: usize) !void { + fn renderComments(self: &Parser, stream: var, node: var, indent: usize) !void { const comment = node.doc_comments ?? return; for (comment.lines.toSliceConst()) |line_token| { try stream.print("{}\n", self.tokenizer.getTokenSlice(line_token)); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 5d4383d6a..07ae0a796 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -181,7 +181,7 @@ test "zig fmt: comments before global variables" { ); } -test "zig fmt: comments before statements" { +test "zig fmt: comments in statements" { try testCanonical( \\test "std" { \\ // statement comment @@ -211,22 +211,6 @@ test "zig fmt: comments before test decl" { ); } -test "zig fmt: comments before variable declarations" { - try testCanonical( - \\const std = @import("std"); - \\ - \\pub fn main() !void { - \\ /// If this program is run without stdout attached, exit with an error. - \\ /// another comment - \\ var stdout_file = try std.io.getStdOut; - \\ // If this program is run without stdout attached, exit with an error. - \\ // another comment - \\ var stdout_file = try std.io.getStdOut; - \\} - \\ - ); -} - test "zig fmt: preserve spacing" { try testCanonical( \\const std = @import("std"); From 3a8dc4e90ddf6b3dc2bdf640c89061c00eee7d45 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 1 May 2018 01:30:53 -0400 Subject: [PATCH 58/58] zig fmt: line comments in struct initializer --- std/zig/ast.zig | 4 ++-- std/zig/parser.zig | 40 +++++++++++++++++++++++++--------------- std/zig/parser_test.zig | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/std/zig/ast.zig b/std/zig/ast.zig index a311aa1d7..d1d7fe791 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -1269,7 +1269,7 @@ pub const Node = struct { ArrayAccess: &Node, Slice: SliceRange, ArrayInitializer: ArrayList(&Node), - StructInitializer: ArrayList(&FieldInitializer), + StructInitializer: ArrayList(&Node), }; const CallInfo = struct { @@ -1311,7 +1311,7 @@ pub const Node = struct { i -= exprs.len; }, Op.StructInitializer => |fields| { - if (i < fields.len) return &fields.at(i).base; + if (i < fields.len) return fields.at(i); i -= fields.len; }, } diff --git a/std/zig/parser.zig b/std/zig/parser.zig index ed6a1a425..fa4280790 100644 --- a/std/zig/parser.zig +++ b/std/zig/parser.zig @@ -238,8 +238,8 @@ pub const Parser = struct { ExprListItemOrEnd: ExprListCtx, ExprListCommaOrEnd: ExprListCtx, - FieldInitListItemOrEnd: ListSave(&ast.Node.FieldInitializer), - FieldInitListCommaOrEnd: ListSave(&ast.Node.FieldInitializer), + FieldInitListItemOrEnd: ListSave(&ast.Node), + FieldInitListCommaOrEnd: ListSave(&ast.Node), FieldListCommaOrEnd: &ast.Node.ContainerDecl, FieldInitValue: OptionalCtx, ErrorTagListItemOrEnd: ListSave(&ast.Node), @@ -1510,6 +1510,10 @@ pub const Parser = struct { } }, State.FieldInitListItemOrEnd => |list_state| { + while (try self.eatLineComment(arena)) |line_comment| { + try list_state.list.append(&line_comment.base); + } + if (self.eatToken(Token.Id.RBrace)) |rbrace| { *list_state.ptr = rbrace; continue; @@ -1524,7 +1528,7 @@ pub const Parser = struct { .name_token = undefined, .expr = undefined, }); - try list_state.list.append(node); + try list_state.list.append(&node.base); stack.append(State { .FieldInitListCommaOrEnd = list_state }) catch unreachable; try stack.append(State { .Expression = OptionalCtx{ .Required = &node.expr } }); @@ -2346,7 +2350,7 @@ pub const Parser = struct { .base = undefined, .lhs = lhs, .op = ast.Node.SuffixOp.Op { - .StructInitializer = ArrayList(&ast.Node.FieldInitializer).init(arena), + .StructInitializer = ArrayList(&ast.Node).init(arena), }, .rtoken = undefined, } @@ -2354,7 +2358,7 @@ pub const Parser = struct { stack.append(State { .CurlySuffixExpressionEnd = opt_ctx.toRequired() }) catch unreachable; try stack.append(State { .IfToken = Token.Id.LBrace }); try stack.append(State { - .FieldInitListItemOrEnd = ListSave(&ast.Node.FieldInitializer) { + .FieldInitListItemOrEnd = ListSave(&ast.Node) { .list = &node.op.StructInitializer, .ptr = &node.rtoken, } @@ -3452,7 +3456,6 @@ pub const Parser = struct { Expression: &ast.Node, VarDecl: &ast.Node.VarDecl, Statement: &ast.Node, - FieldInitializer: &ast.Node.FieldInitializer, PrintIndent, Indent: usize, PrintSameLineComment: ?&Token, @@ -3584,12 +3587,6 @@ pub const Parser = struct { } }, - RenderState.FieldInitializer => |field_init| { - try stream.print(".{}", self.tokenizer.getTokenSlice(field_init.name_token)); - try stream.print(" = "); - try stack.append(RenderState { .Expression = field_init.expr }); - }, - RenderState.VarDecl => |var_decl| { try stack.append(RenderState { .Text = ";" }); if (var_decl.init_node) |init_node| { @@ -3888,7 +3885,7 @@ pub const Parser = struct { const field_init = field_inits.at(0); try stack.append(RenderState { .Text = " }" }); - try stack.append(RenderState { .FieldInitializer = field_init }); + try stack.append(RenderState { .Expression = field_init }); try stack.append(RenderState { .Text = "{ " }); try stack.append(RenderState { .Expression = suffix_op.lhs }); continue; @@ -3896,13 +3893,26 @@ pub const Parser = struct { try stack.append(RenderState { .Text = "}"}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Indent = indent }); + try stack.append(RenderState { .Text = "\n" }); var i = field_inits.len; while (i != 0) { i -= 1; const field_init = field_inits.at(i); - try stack.append(RenderState { .Text = ",\n" }); - try stack.append(RenderState { .FieldInitializer = field_init }); + if (field_init.id != ast.Node.Id.LineComment) { + try stack.append(RenderState { .Text = "," }); + } + try stack.append(RenderState { .Expression = field_init }); try stack.append(RenderState.PrintIndent); + if (i != 0) { + try stack.append(RenderState { .Text = blk: { + const prev_node = field_inits.at(i - 1); + const loc = self.tokenizer.getTokenLocation(prev_node.lastToken().end, field_init.firstToken()); + if (loc.line >= 2) { + break :blk "\n\n"; + } + break :blk "\n"; + }}); + } } try stack.append(RenderState { .Indent = indent + indent_delta }); try stack.append(RenderState { .Text = "{\n"}); diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 07ae0a796..468893948 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,3 +1,44 @@ +test "zig fmt: line comments in struct initializer" { + try testCanonical( + \\fn foo() void { + \\ return Self{ + \\ .a = b, + \\ + \\ // Initialize these two fields to buffer_size so that + \\ // in `readFn` we treat the state as being able to read + \\ .start_index = buffer_size, + \\ .end_index = buffer_size, + \\ + \\ // middle + \\ + \\ .a = b, + \\ + \\ // end + \\ }; + \\} + \\ + ); +} + +//TODO +//test "zig fmt: same-line comptime" { +// try testCanonical( +// \\test "" { +// \\ comptime assert(@typeId(T) == builtin.TypeId.Int); // must pass an integer to absInt +// \\} +// \\ +// ); +//} + + +//TODO +//test "zig fmt: number literals" { +// try testCanonical( +// \\pub const f64_true_min = 4.94065645841246544177e-324; +// \\ +// ); +//} + test "zig fmt: doc comments before struct field" { try testCanonical( \\pub const Allocator = struct {