diff --git a/CMakeLists.txt b/CMakeLists.txt index e69297471..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" @@ -498,6 +501,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/src/analyze.cpp b/src/analyze.cpp index a598d7676..1ecfe32f4 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1258,7 +1258,7 @@ 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; } @@ -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 dfffb4187..ff81ebfd8 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -3161,6 +3161,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))); @@ -6187,16 +6190,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); @@ -8138,7 +8135,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)) { @@ -8185,16 +8182,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) { @@ -8454,6 +8441,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; } @@ -8637,6 +8634,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) @@ -8648,8 +8649,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); } @@ -10672,7 +10671,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); @@ -11062,7 +11061,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; @@ -13031,7 +13030,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; @@ -13928,6 +13927,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, @@ -18816,6 +18824,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_u64 " 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; } @@ -19121,6 +19139,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) @@ -19656,7 +19679,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/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..1acecbab2 --- /dev/null +++ b/std/atomic/queue.zig @@ -0,0 +1,120 @@ +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 { + head: &Node, + tail: &Node, + root: Node, + + pub const Self = this; + + pub const Node = struct { + next: ?&Node, + data: T, + }; + + // 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; + 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; + } + } + }; +} + +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.spawnThread(&context, startPuts); + } + var getters: [put_thread_count]&std.os.Thread = undefined; + for (getters) |*t| { + *t = try std.os.spawnThread(&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 new file mode 100644 index 000000000..accbcc942 --- /dev/null +++ b/std/atomic/stack.zig @@ -0,0 +1,126 @@ +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 { + 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.SeqCst, AtomicOrder.SeqCst); + } + + pub fn push(self: &Self, node: &Node) void { + var root = @atomicLoad(?&Node, &self.root, AtomicOrder.SeqCst); + while (true) { + node.next = root; + 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.SeqCst, AtomicOrder.SeqCst) ?? return root; + } + } + + pub fn isEmpty(self: &Self) bool { + 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, + get_count: usize, + 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, + .get_count = 0, + }; + + var putters: [put_thread_count]&std.os.Thread = undefined; + for (putters) |*t| { + *t = try std.os.spawnThread(&context, startPuts); + } + var getters: [put_thread_count]&std.os.Thread = undefined; + for (getters) |*t| { + *t = try std.os.spawnThread(&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(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); + _ = @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/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..34269d2aa 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 "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 "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/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/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)); diff --git a/std/fmt/errol/index.zig b/std/fmt/errol/index.zig index 42287bd25..00c69cd29 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, }; } @@ -189,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 7bb982911..43e758038 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 ..]); } } @@ -594,70 +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")); + } + { + // 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: f32 = 12.34; - const result = try bufPrint(buf1[0..], "f32: {}\n", value); - assert(mem.eql(u8, result, "f32: 1.23400001e1\n")); + 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 = -12.34e10; - const result = try bufPrint(buf1[0..], "f64: {}\n", value); - assert(mem.eql(u8, result, "f64: -1.234e11\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")); - } - { - var buf1: [32]u8 = undefined; - const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64); - assert(mem.eql(u8, result, "f64: -Infinity\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.56\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")); - } - { - var buf1: [32]u8 = undefined; - const value: f32 = 91.12345; - const result = try bufPrint(buf1[0..], "f32: {.}\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 = 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")); } } diff --git a/std/heap.zig b/std/heap.zig index b3a1e6bf2..bfdf62f65 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 { @@ -98,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 @@ -139,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]; } @@ -177,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; @@ -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); 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"); 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..a5760b4ac --- /dev/null +++ b/std/math/complex/acos.zig @@ -0,0 +1,21 @@ +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); + + 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 new file mode 100644 index 000000000..8dd91b283 --- /dev/null +++ b/std/math/complex/acosh.zig @@ -0,0 +1,21 @@ +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); + + 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/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..584a3a1a9 --- /dev/null +++ b/std/math/complex/asin.zig @@ -0,0 +1,27 @@ +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); + + 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 new file mode 100644 index 000000000..0c4dc2b6e --- /dev/null +++ b/std/math/complex/asinh.zig @@ -0,0 +1,22 @@ +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); + + 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 new file mode 100644 index 000000000..b7bbf930e --- /dev/null +++ b/std/math/complex/atan.zig @@ -0,0 +1,130 @@ +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.catan32" { + const a = Complex(f32).new(5, 3); + const c = atan(a); + + debug.assert(math.approxEq(f32, c.re, 1.423679, epsilon)); + debug.assert(math.approxEq(f32, c.im, 0.086569, epsilon)); +} + +test "complex.catan64" { + const a = Complex(f64).new(5, 3); + const c = atan(a); + + 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 new file mode 100644 index 000000000..f70c74176 --- /dev/null +++ b/std/math/complex/atanh.zig @@ -0,0 +1,22 @@ +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); + + 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/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..96e4ffcdb --- /dev/null +++ b/std/math/complex/cos.zig @@ -0,0 +1,21 @@ +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); + + 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 new file mode 100644 index 000000000..96eac6855 --- /dev/null +++ b/std/math/complex/cosh.zig @@ -0,0 +1,165 @@ +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); + + 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); + + 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 new file mode 100644 index 000000000..03f7f9e41 --- /dev/null +++ b/std/math/complex/exp.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; + +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); + + 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); + + 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/index.zig b/std/math/complex/index.zig new file mode 100644 index 000000000..a4d493307 --- /dev/null +++ b/std/math/complex/index.zig @@ -0,0 +1,171 @@ +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; + +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..a4a1d1664 --- /dev/null +++ b/std/math/complex/log.zig @@ -0,0 +1,23 @@ +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); + + 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 new file mode 100644 index 000000000..bef9fde54 --- /dev/null +++ b/std/math/complex/pow.zig @@ -0,0 +1,22 @@ +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); + + 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/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..d32b771d3 --- /dev/null +++ b/std/math/complex/sin.zig @@ -0,0 +1,22 @@ +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); + + 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 new file mode 100644 index 000000000..09a62ca05 --- /dev/null +++ b/std/math/complex/sinh.zig @@ -0,0 +1,164 @@ +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); + + 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); + + 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 new file mode 100644 index 000000000..afda69f7c --- /dev/null +++ b/std/math/complex/sqrt.zig @@ -0,0 +1,133 @@ +const std = @import("../../index.zig"); +const debug = std.debug; +const math = std.math; +const cmath = math.complex; +const Complex = cmath.Complex; + +// 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); + + 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); + + 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); + + 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 new file mode 100644 index 000000000..4ea5182fa --- /dev/null +++ b/std/math/complex/tan.zig @@ -0,0 +1,22 @@ +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); + + 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 new file mode 100644 index 000000000..6af62f48a --- /dev/null +++ b/std/math/complex/tanh.zig @@ -0,0 +1,111 @@ +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); + + 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); + + debug.assert(math.approxEq(f64, c.re, 0.999913, epsilon)); + debug.assert(math.approxEq(f64, c.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"); } diff --git a/std/mem.zig b/std/mem.zig index eb67ce83e..d874f8a6c 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. @@ -32,10 +32,25 @@ 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]; } + // 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]); } @@ -53,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; } @@ -80,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; } @@ -174,6 +189,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 +213,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 +224,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 +248,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 +275,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 +307,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 +439,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 { 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 063949072..4f1826021 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -2,6 +2,11 @@ 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" { @@ -20,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"), }; @@ -54,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; @@ -93,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); @@ -106,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); @@ -130,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; @@ -155,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) {} @@ -177,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 => { @@ -226,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), }; } @@ -265,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; @@ -322,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, @@ -347,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), @@ -382,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); @@ -393,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 { @@ -434,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); @@ -466,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, @@ -478,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. @@ -492,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; @@ -531,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) {} @@ -685,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, @@ -703,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)) { @@ -724,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); @@ -733,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 { @@ -756,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), }; } @@ -772,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, @@ -852,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, @@ -903,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) { @@ -914,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, @@ -926,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), @@ -964,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, @@ -994,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; } } } @@ -1031,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), }; @@ -1049,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, @@ -1091,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) { @@ -1116,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(); @@ -1147,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, }; @@ -1171,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 { @@ -1199,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)), } @@ -1213,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; @@ -1226,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; @@ -1278,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; @@ -1286,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; @@ -1362,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, @@ -1455,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; @@ -1464,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; @@ -1483,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()); @@ -1506,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, } } @@ -1520,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, } } @@ -1539,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; } @@ -1579,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) { @@ -1623,7 +1668,6 @@ pub const ArgIteratorWindows = struct { } } } - }; pub const ArgIterator = struct { @@ -1638,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) { @@ -1713,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 { @@ -1768,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); @@ -1780,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. } } @@ -1818,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); @@ -1846,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); @@ -1903,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), } @@ -1934,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, @@ -2023,7 +2106,7 @@ pub const PosixAcceptError = error { FileDescriptorClosed, ConnectionAborted, - + /// The addr argument is not in a writable part of the user address space. PageFault, @@ -2036,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, @@ -2072,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, @@ -2283,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, @@ -2343,24 +2428,58 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void { } pub const Thread = struct { - pid: i32, - allocator: ?&mem.Allocator, - stack: []u8, + data: Data, + + 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, + } 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 { - 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.data.handle, null); + switch (err) { + 0 => {}, + posix.EINVAL => unreachable, + posix.ESRCH => unreachable, + posix.EDEADLK => unreachable, else => unreachable, } - } - if (self.allocator) |a| { - a.free(self.stack); + 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, + } + } + 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"), } } }; @@ -2385,38 +2504,91 @@ pub const SpawnThreadError = error { /// be copied. SystemResources, + /// 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.alloc(u8, 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: []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; + 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))); + } + } + }; + + 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 { + if (@sizeOf(Context) == 0) { + return startFn({}); + } else { + return startFn(*@intToPtr(&const Context, ctx_addr)); + } + } + 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 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); + if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory; + errdefer assert(posix.munmap(stack_addr, mmap_len) == 0); + + var stack_end: usize = stack_addr + mmap_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)); + assert(stack_end >= stack_addr); const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end)); *context_ptr = context; arg = stack_end; @@ -2424,36 +2596,51 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread stack_end -= @sizeOf(Thread); stack_end -= stack_end % @alignOf(Thread); - assert(stack_end >= @ptrToInt(stack.ptr)); + assert(stack_end >= stack_addr); const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end)); - thread_ptr.stack = stack; - thread_ptr.allocator = null; - const threadMain = struct { - extern fn threadMain(ctx_addr: usize) u8 { - if (@sizeOf(Context) == 0) { - return startFn({}); - } else { - return startFn(*@intToPtr(&const Context, ctx_addr)); - } + thread_ptr.data.stack_addr = stack_addr; + thread_ptr.data.stack_len = mmap_len; + + 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); + + // align to page + stack_end -= stack_end % os.page_size; + 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) { + 0 => return thread_ptr, + posix.EAGAIN => return SpawnThreadError.SystemResources, + posix.EPERM => unreachable, + posix.EINVAL => unreachable, + else => return unexpectedErrorPosix(usize(err)), } - }.threadMain; - - 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), + } 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 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); + 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/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 { diff --git a/std/os/test.zig b/std/os/test.zig index 41afee004..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.linux) { - // TODO implement threads on macos and 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: [1024]u8 = undefined; - var stack2: [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); + 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/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/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"); } diff --git a/std/special/bootstrap_lib.zig b/std/special/bootstrap_lib.zig index 40b658883..f55aaed96 100644 --- a/std/special/bootstrap_lib.zig +++ b/std/special/bootstrap_lib.zig @@ -1,9 +1,10 @@ // 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); + @export("_DllMainCRTStartup", _DllMainCRTStartup, builtin.GlobalLinkage.Strong); } stdcallcc fn _DllMainCRTStartup(hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD, 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/std/unicode.zig b/std/unicode.zig index 356df824f..300e12964 100644 --- a/std/unicode.zig +++ b/std/unicode.zig @@ -1,6 +1,16 @@ const std = @import("./index.zig"); const debug = std.debug; +/// 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 < 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,11 +22,47 @@ pub fn utf8ByteSequenceLength(first_byte: u8) !u3 { return error.Utf8InvalidStartByte; } +/// 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 { + 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; +} + +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), @@ -25,7 +71,12 @@ pub fn utf8Decode(bytes: []const u8) !u32 { else => unreachable, }; } -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; @@ -38,7 +89,13 @@ 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; @@ -56,7 +113,13 @@ 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; @@ -158,19 +221,67 @@ 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); + 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" { + comptime testUtf8EncodeError(); + testUtf8EncodeError(); +} +fn testUtf8EncodeError() void { + var array: [4]u8 = undefined; + 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 { + if (utf8Encode(codePoint, array)) |_| { + unreachable; + } else |err| { + debug.assert(err == expectedErr); + } +} + test "utf8 iterator on ascii" { + comptime testUtf8IteratorOnAscii(); + testUtf8IteratorOnAscii(); +} +fn testUtf8IteratorOnAscii() void { const s = Utf8View.initComptime("abc"); var it1 = s.iterator(); @@ -187,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"); @@ -195,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(); @@ -211,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")); @@ -218,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); @@ -233,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); @@ -261,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); @@ -270,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); diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 76977a979..d1d7fe791 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, + same_line_comment: ?&Token, pub const Id = enum { // Top level @@ -34,6 +35,7 @@ pub const Node = struct { VarType, ErrorType, FnProto, + PromiseType, // Primary expressions IntegerLiteral, @@ -57,6 +59,7 @@ pub const Node = struct { // Misc LineComment, + DocComment, SwitchCase, SwitchElse, Else, @@ -66,6 +69,7 @@ pub const Node = struct { StructField, UnionTag, EnumTag, + ErrorTag, AsmInput, AsmOutput, AsyncAttribute, @@ -73,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) { @@ -118,6 +129,7 @@ pub const Node = struct { pub const Root = struct { base: Node, + doc_comments: ?&DocComment, decls: ArrayList(&Node), eof_token: Token, @@ -139,7 +151,7 @@ pub const Node = struct { pub const VarDecl = struct { base: Node, - comments: ?&LineComment, + doc_comments: ?&DocComment, visib_token: ?Token, name_token: Token, eq_token: Token, @@ -188,6 +200,7 @@ pub const Node = struct { pub const Use = struct { base: Node, + doc_comments: ?&DocComment, visib_token: ?Token, expr: &Node, semicolon_token: Token, @@ -258,7 +271,7 @@ pub const Node = struct { const InitArg = union(enum) { None, - Enum, + Enum: ?&Node, Type: &Node, }; @@ -291,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, @@ -316,8 +330,10 @@ pub const Node = struct { pub const UnionTag = struct { base: Node, + doc_comments: ?&DocComment, name_token: Token, type_expr: ?&Node, + value_expr: ?&Node, pub fn iterate(self: &UnionTag, index: usize) ?&Node { var i = index; @@ -327,6 +343,11 @@ pub const Node = struct { i -= 1; } + if (self.value_expr) |value_expr| { + if (i < 1) return value_expr; + i -= 1; + } + return null; } @@ -335,6 +356,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(); } @@ -345,6 +369,7 @@ pub const Node = struct { pub const EnumTag = struct { base: Node, + doc_comments: ?&DocComment, name_token: Token, value: ?&Node, @@ -372,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, @@ -421,7 +471,7 @@ pub const Node = struct { pub const FnProto = struct { base: Node, - comments: ?&LineComment, + doc_comments: ?&DocComment, visib_token: ?Token, fn_token: Token, name_token: ?Token, @@ -494,6 +544,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, @@ -584,6 +665,7 @@ pub const Node = struct { pub const Comptime = struct { base: Node, + doc_comments: ?&DocComment, comptime_token: Token, expr: &Node, @@ -718,7 +800,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 +810,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; @@ -1186,7 +1269,7 @@ pub const Node = struct { ArrayAccess: &Node, Slice: SliceRange, ArrayInitializer: ArrayList(&Node), - StructInitializer: ArrayList(&FieldInitializer), + StructInitializer: ArrayList(&Node), }; const CallInfo = struct { @@ -1228,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; }, } @@ -1337,6 +1420,7 @@ pub const Node = struct { pub const Suspend = struct { base: Node, + label: ?Token, suspend_token: Token, payload: ?&Node, body: ?&Node, @@ -1358,6 +1442,7 @@ pub const Node = struct { } pub fn firstToken(self: &Suspend) Token { + if (self.label) |label| return label; return self.suspend_token; } @@ -1715,24 +1800,41 @@ 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); } }; pub const TestDecl = struct { base: Node, - comments: ?&LineComment, + doc_comments: ?&DocComment, test_token: Token, name: &Node, body_node: &Node, diff --git a/std/zig/parser.zig b/std/zig/parser.zig index 7f45cce28..fa4280790 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.DocComment, }; const VarDeclCtx = struct { @@ -64,18 +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.DocComment, }; const ExternTypeCtx = struct { opt_ctx: OptionalCtx, extern_token: Token, - comments: ?&ast.Node.LineComment, + comments: ?&ast.Node.DocComment, }; const ContainerKindCtx = struct { @@ -182,6 +184,11 @@ pub const Parser = struct { } }; + const AddCommentsCtx = struct { + node_ptr: &&ast.Node, + comments: ?&ast.Node.DocComment, + }; + const State = union(enum) { TopLevel, TopLevelExtern: TopLevelDeclCtx, @@ -221,6 +228,8 @@ pub const Parser = struct { Statement: &ast.Node.Block, ComptimeStatement: ComptimeStatementCtx, Semicolon: &&ast.Node, + LookForSameLineComment: &&ast.Node, + LookForSameLineCommentDirect: &ast.Node, AsmOutputItems: &ArrayList(&ast.Node.AsmOutput), AsmOutputReturnOrType: &ast.Node.AsmOutput, @@ -229,13 +238,14 @@ 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, - IdentifierListItemOrEnd: ListSave(&ast.Node), - IdentifierListCommaOrEnd: ListSave(&ast.Node), - SwitchCaseOrEnd: ListSave(&ast.Node.SwitchCase), - SwitchCaseCommaOrEnd: ListSave(&ast.Node.SwitchCase), + FieldInitValue: OptionalCtx, + ErrorTagListItemOrEnd: ListSave(&ast.Node), + ErrorTagListCommaOrEnd: ListSave(&ast.Node), + SwitchCaseOrEnd: ListSave(&ast.Node), + SwitchCaseCommaOrEnd: ListSave(&ast.Node), SwitchCaseFirstItem: &ArrayList(&ast.Node), SwitchCaseItem: &ArrayList(&ast.Node), SwitchCaseItemCommaOrEnd: &ArrayList(&ast.Node), @@ -290,6 +300,7 @@ pub const Parser = struct { ErrorTypeOrSetDecl: ErrorTypeOrSetDeclCtx, StringLiteral: OptionalCtx, Identifier: OptionalCtx, + ErrorTag: &&ast.Node, IfToken: @TagType(Token.Id), @@ -314,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, } @@ -339,31 +351,38 @@ pub const Parser = struct { switch (state) { State.TopLevel => { - const comments = try self.eatComments(arena); + while (try self.eatLineComment(arena)) |line_comment| { + try root_node.decls.append(&line_comment.base); + } + + const comments = try self.eatDocComments(arena); const token = self.getNextToken(); switch (token.id) { 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, - .comments = comments, - .test_token = token, - .name = undefined, - .body_node = &block.base, - } - ); - stack.append(State { .Block = block }) catch unreachable; + const block = try arena.construct(ast.Node.Block { + .base = ast.Node { + .id = ast.Node.Id.Block, + .same_line_comment = 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, + .same_line_comment = null, + }, + .doc_comments = comments, + .test_token = token, + .name = undefined, + .body_node = &block.base, + }); + try root_node.decls.append(&test_node.base); + try stack.append(State { .Block = block }); try stack.append(State { .ExpectTokenSave = ExpectTokenSave { .id = Token.Id.LBrace, @@ -375,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; @@ -385,6 +408,7 @@ pub const Parser = struct { .visib_token = token, .extern_export_inline_token = null, .lib_name = null, + .comments = comments, } }); continue; @@ -404,6 +428,7 @@ pub const Parser = struct { .base = undefined, .comptime_token = token, .expr = &block.base, + .doc_comments = comments, } ); stack.append(State.TopLevel) catch unreachable; @@ -425,6 +450,7 @@ pub const Parser = struct { .visib_token = null, .extern_export_inline_token = null, .lib_name = null, + .comments = comments, } }); continue; @@ -441,6 +467,7 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .extern_export_inline_token = token, .lib_name = null, + .comments = ctx.comments, }, }) catch unreachable; continue; @@ -452,6 +479,7 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .extern_export_inline_token = token, .lib_name = null, + .comments = ctx.comments, }, }) catch unreachable; continue; @@ -478,12 +506,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 => { @@ -497,6 +525,7 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .expr = undefined, .semicolon_token = undefined, + .doc_comments = ctx.comments, } ); stack.append(State { @@ -515,9 +544,9 @@ pub const Parser = struct { } } - stack.append(State { + try stack.append(State { .VarDecl = VarDeclCtx { - .comments = comments, + .comments = ctx.comments, .visib_token = ctx.visib_token, .lib_name = ctx.lib_name, .comptime_token = null, @@ -525,29 +554,31 @@ pub const Parser = struct { .mut_token = token, .list = ctx.decls } - }) catch unreachable; + }); continue; }, 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, - .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, - } - ); + const fn_proto = try arena.construct(ast.Node.FnProto { + .base = ast.Node { + .id = ast.Node.Id.FnProto, + .same_line_comment = null, + }, + .doc_comments = ctx.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, + }); + try ctx.decls.append(&fn_proto.base); stack.append(State { .FnDef = fn_proto }) catch unreachable; try stack.append(State { .FnProto = fn_proto }); @@ -597,14 +628,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, + .same_line_comment = null, + }, + .doc_comments = ctx.comments, + .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 } }); @@ -619,11 +654,21 @@ pub const Parser = struct { .visib_token = ctx.visib_token, .extern_export_inline_token = null, .lib_name = null, + .comments = ctx.comments, } }); 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(); @@ -670,7 +715,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); @@ -680,22 +734,32 @@ 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.eatDocComments(arena); const token = self.getNextToken(); switch (token.id) { 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, + .same_line_comment = null, + }, + .doc_comments = comments, + .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; @@ -706,10 +770,13 @@ pub const Parser = struct { .base = undefined, .name_token = token, .type_expr = null, + .value_expr = null, + .doc_comments = comments, } ); 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; @@ -720,6 +787,7 @@ pub const Parser = struct { .base = undefined, .name_token = token, .value = null, + .doc_comments = comments, } ); @@ -737,6 +805,7 @@ pub const Parser = struct { .TopLevelExternOrField = TopLevelExternOrFieldCtx { .visib_token = token, .container_decl = container_decl, + .comments = comments, } }); continue; @@ -749,6 +818,7 @@ pub const Parser = struct { .visib_token = token, .extern_export_inline_token = null, .lib_name = null, + .comments = comments, } }); continue; @@ -763,11 +833,15 @@ pub const Parser = struct { .visib_token = token, .extern_export_inline_token = null, .lib_name = null, + .comments = comments, } }); 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; }, @@ -780,6 +854,7 @@ pub const Parser = struct { .visib_token = null, .extern_export_inline_token = null, .lib_name = null, + .comments = comments, } }); continue; @@ -789,26 +864,29 @@ pub const Parser = struct { State.VarDecl => |ctx| { - const var_decl = try self.createAttachNode(arena, ctx.list, ast.Node.VarDecl, - ast.Node.VarDecl { - .base = undefined, - .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, - } - ); + const var_decl = try arena.construct(ast.Node.VarDecl { + .base = ast.Node { + .id = ast.Node.Id.VarDecl, + .same_line_comment = null, + }, + .doc_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, + }); + 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 { @@ -820,7 +898,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) { @@ -1048,6 +1126,22 @@ 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, + .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 { @@ -1185,13 +1279,20 @@ 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; }, } }, State.Statement => |block| { - const comments = try self.eatComments(arena); const token = self.getNextToken(); switch (token.id) { Token.Id.Keyword_comptime => { @@ -1206,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, @@ -1218,19 +1319,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, + .same_line_comment = null, + }, + .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,21 +1354,21 @@ 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 { .LookForSameLineComment = statement }) catch unreachable; + try stack.append(State { .Semicolon = statement }); + 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, @@ -1293,6 +1398,16 @@ pub const Parser = struct { continue; }, + State.LookForSameLineComment => |node_ptr| { + try self.lookForSameLineComment(arena, *node_ptr); + continue; + }, + + State.LookForSameLineCommentDirect => |node| { + try self.lookForSameLineComment(arena, node); + continue; + }, + State.AsmOutputItems => |items| { const lbracket = self.getNextToken(); @@ -1395,20 +1510,25 @@ 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; } - const node = try self.createNode(arena, ast.Node.FieldInitializer, - ast.Node.FieldInitializer { - .base = undefined, - .period_token = undefined, - .name_token = undefined, - .expr = undefined, - } - ); - try list_state.list.append(node); + const node = try arena.construct(ast.Node.FieldInitializer { + .base = ast.Node { + .id = ast.Node.Id.FieldInitializer, + .same_line_comment = null, + }, + .period_token = undefined, + .name_token = undefined, + .expr = undefined, + }); + try list_state.list.append(&node.base); stack.append(State { .FieldInitListCommaOrEnd = list_state }) catch unreachable; try stack.append(State { .Expression = OptionalCtx{ .Required = &node.expr } }); @@ -1440,60 +1560,78 @@ pub const Parser = struct { if (try self.expectCommaOrEnd(Token.Id.RBrace)) |end| { container_decl.rbrace_token = end; continue; - } else { - stack.append(State { .ContainerDecl = container_decl }) catch unreachable; - 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| { + State.ErrorTagListItemOrEnd => |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; } - stack.append(State { .IdentifierListCommaOrEnd = list_state }) catch unreachable; - try stack.append(State { .Identifier = OptionalCtx { .Required = try list_state.list.addOne() } }); + const node_ptr = try list_state.list.addOne(); + + 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; } }, 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; } - const node = try self.createNode(arena, ast.Node.SwitchCase, - ast.Node.SwitchCase { - .base = undefined, - .items = ArrayList(&ast.Node).init(arena), - .payload = null, - .expr = undefined, - } - ); - try list_state.list.append(node); - stack.append(State { .SwitchCaseCommaOrEnd = list_state }) catch unreachable; + const comments = try self.eatDocComments(arena); + const node = try arena.construct(ast.Node.SwitchCase { + .base = ast.Node { + .id = ast.Node.Id.SwitchCase, + .same_line_comment = null, + }, + .items = ArrayList(&ast.Node).init(arena), + .payload = null, + .expr = undefined, + }); + 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 } }); 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 node = list_state.list.toSlice()[list_state.list.len - 1]; + try self.lookForSameLineComment(arena, node); + try stack.append(State { .SwitchCaseOrEnd = list_state }); + continue; }, + State.SwitchCaseFirstItem => |case_items| { const token = self.getNextToken(); if (token.id == Token.Id.Keyword_else) { @@ -1576,24 +1714,26 @@ 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, - .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, - } - ); + const fn_proto = try arena.construct(ast.Node.FnProto { + .base = ast.Node { + .id = ast.Node.Id.FnProto, + .same_line_comment = null, + }, + .doc_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, + }); + ctx.opt_ctx.store(&fn_proto.base); stack.append(State { .FnProto = fn_proto }) catch unreachable; continue; } @@ -2210,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, } @@ -2218,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, } @@ -2443,6 +2583,29 @@ 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, + .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; @@ -2546,46 +2709,50 @@ 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, - .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, - } - ); + const fn_proto = try arena.construct(ast.Node.FnProto { + .base = ast.Node { + .id = ast.Node.Id.FnProto, + .same_line_comment = null, + }, + .doc_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, + }); + 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, - .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, - } - ); + const fn_proto = try arena.construct(ast.Node.FnProto { + .base = ast.Node { + .id = ast.Node.Id.FnProto, + .same_line_comment = null, + }, + .doc_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, + }); + opt_ctx.store(&fn_proto.base); stack.append(State { .FnProto = fn_proto }) catch unreachable; try stack.append(State { .ExpectTokenSave = ExpectTokenSave { @@ -2659,17 +2826,19 @@ 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, + .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) { + .ErrorTagListItemOrEnd = ListSave(&ast.Node) { .list = &node.decls, .ptr = &node.rbrace_token, } @@ -2689,6 +2858,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); @@ -2701,6 +2871,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); @@ -2739,21 +2928,21 @@ pub const Parser = struct { } } - fn eatComments(self: &Parser, arena: &mem.Allocator) !?&ast.Node.LineComment { - var result: ?&ast.Node.LineComment = null; + fn eatDocComments(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.create(ast.Node.LineComment); - *comment_node = ast.Node.LineComment { + const comment_node = try arena.construct(ast.Node.DocComment { .base = ast.Node { - .id = ast.Node.Id.LineComment, + .id = ast.Node.Id.DocComment, + .same_line_comment = null, }, .lines = ArrayList(Token).init(arena), - }; + }); result = comment_node; break :blk comment_node; } @@ -2766,6 +2955,17 @@ 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, + .same_line_comment = null, + }, + .token = token, + }); + } + fn requireSemiColon(node: &const ast.Node) bool { var n = node; while (true) { @@ -2783,6 +2983,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 => { @@ -2838,6 +3039,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.DocComment and 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 => { @@ -2875,6 +3095,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, @@ -2928,18 +3149,20 @@ 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, + .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, }, @@ -2956,6 +3179,7 @@ pub const Parser = struct { .base = undefined, .comptime_token = *token, .expr = undefined, + .doc_comments = null, } ); try stack.append(State { .Expression = OptionalCtx { .Required = &node.expr } }); @@ -3096,7 +3320,10 @@ 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, + .same_line_comment = null, + }; }; return node; @@ -3229,9 +3456,9 @@ pub const Parser = struct { Expression: &ast.Node, VarDecl: &ast.Node.VarDecl, Statement: &ast.Node, - FieldInitializer: &ast.Node.FieldInitializer, PrintIndent, Indent: usize, + PrintSameLineComment: ?&Token, }; pub fn renderSource(self: &Parser, stream: var, root_node: &ast.Node.Root) !void { @@ -3266,6 +3493,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); @@ -3291,6 +3519,7 @@ pub const Parser = struct { }, ast.Node.Id.VarDecl => { const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl); + try self.renderComments(stream, var_decl, indent); try stack.append(RenderState { .VarDecl = var_decl}); }, ast.Node.Id.TestDecl => { @@ -3303,16 +3532,26 @@ pub const Parser = struct { }, ast.Node.Id.StructField => { const field = @fieldParentPtr(ast.Node.StructField, "base", decl); + try self.renderComments(stream, field, indent); if (field.visib_token) |visib_token| { 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 self.renderComments(stream, tag, indent); 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}); @@ -3320,35 +3559,40 @@ pub const Parser = struct { }, ast.Node.Id.EnumTag => { const tag = @fieldParentPtr(ast.Node.EnumTag, "base", decl); + try self.renderComments(stream, tag, indent); 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}); } }, + 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 = ";" }); } 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, } }, - 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 }); - }, - RenderState.VarDecl => |var_decl| { 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 = ")" }); @@ -3385,7 +3629,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)); } @@ -3467,6 +3710,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| { @@ -3635,19 +3881,41 @@ 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 { .Expression = 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 }); + 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"}); + try stack.append(RenderState { .Text = "{\n"}); try stack.append(RenderState { .Expression = suffix_op.lhs }); }, ast.Node.SuffixOp.Op.ArrayInitializer => |exprs| { @@ -3656,6 +3924,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 }); @@ -3668,7 +3946,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 }); }, } @@ -3802,45 +4080,49 @@ 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]; + 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 = " "}), - 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}); @@ -3850,20 +4132,47 @@ 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 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 { .TopLevelDecl = 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; const node = decls[i]; - try stack.append(RenderState { .Text = "," }); - try stack.append(RenderState { .Expression = node }); + if (node.id != ast.Node.Id.LineComment) { + try stack.append(RenderState { .Text = "," }); + } + try stack.append(RenderState { .TopLevelDecl = node }); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Text = blk: { @@ -3879,7 +4188,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); @@ -3891,7 +4199,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); @@ -3974,7 +4282,19 @@ 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.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)); + }, + 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)); @@ -3989,8 +4309,7 @@ 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 { .Expression = node}); try stack.append(RenderState.PrintIndent); try stack.append(RenderState { .Text = blk: { @@ -4012,6 +4331,8 @@ pub const Parser = struct { ast.Node.Id.SwitchCase => { const switch_case = @fieldParentPtr(ast.Node.SwitchCase, "base", base); + 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| { try stack.append(RenderState { .Text = " " }); @@ -4321,6 +4642,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, @@ -4328,10 +4650,10 @@ pub const Parser = struct { ast.Node.Id.ParamDecl => unreachable, }, RenderState.Statement => |base| { + 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); - try self.renderComments(stream, var_decl, indent); try stack.append(RenderState { .VarDecl = var_decl}); }, else => { @@ -4344,12 +4666,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: var, indent: usize) !void { - const comment = node.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); @@ -4373,920 +4699,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: 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..468893948 --- /dev/null +++ b/std/zig/parser_test.zig @@ -0,0 +1,1139 @@ +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 { + \\ /// 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( + \\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, + \\ C = 60, + \\ D = 1000, + \\}; + \\ + ); +} + +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{ + \\ /// The Operating System returned an undocumented error code. + \\ Unexpected, + \\ // another + \\ Another, + \\ + \\ // in between + \\ + \\ // at end + \\}; + \\ + ); +} + +test "zig fmt: comments before switch prong" { + try testCanonical( + \\test "" { + \\ switch (err) { + \\ error.PathAlreadyExists => continue, + \\ + \\ // comment 1 + \\ + \\ // comment 2 + \\ else => return err, + \\ // at end + \\ } + \\} + \\ + ); +} + +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 { + \\ // All of these are mandated as little endian + \\ // when on disk. + \\ const Data = 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 + \\}; + \\ + ); +} + +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, + \\ 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: 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: 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: comments in statements" { + try testCanonical( + \\test "std" { + \\ // statement comment + \\ _ = @import("foo/bar.zig"); + \\ + \\ // middle + \\ // middle2 + \\ + \\ // end + \\} + \\ + ); +} + +test "zig fmt: comments before test decl" { + try testCanonical( + \\/// top level doc comment + \\test "hi" {} + \\ + \\// top level normal comment + \\test "hi" {} + \\ + \\// middle + \\ + \\// end + \\ + ); +} + +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( + \\test "" { + \\ const s1 = + \\ \\one + \\ \\two) + \\ \\three + \\ ; + \\ const s2 = + \\ c\\one + \\ c\\two) + \\ c\\three + \\ ; + \\} + \\ + ); +} + +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: 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 a0 = []u8{}; + \\const a1 = []u8{1}; + \\const a2 = []u8{ + \\ 1, + \\ 2, + \\ 3, + \\ 4, + \\}; + \\const s0 = S{}; + \\const s1 = S{ .a = 1 }; + \\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, + \\ }; + \\ + \\ 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" { + \\ 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: promise->void = async simpleAsyncFn() catch unreachable; + \\ await p; + \\} + \\ + \\test "coroutine suspend, resume, cancel" { + \\ const p: promise = 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; + \\} + \\ + ); +} + +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"), + } + } +} + diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig index a2c4def9e..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}, @@ -137,6 +138,7 @@ pub const Token = struct { IntegerLiteral, FloatLiteral, LineComment, + DocComment, Keyword_align, Keyword_and, Keyword_asm, @@ -165,6 +167,7 @@ pub const Token = struct { Keyword_null, Keyword_or, Keyword_packed, + Keyword_promise, Keyword_pub, Keyword_resume, Keyword_return, @@ -257,7 +260,10 @@ pub const Tokenizer = struct { Asterisk, AsteriskPercent, Slash, + LineCommentStart, LineComment, + DocCommentStart, + DocComment, Zero, IntegerLiteral, IntegerLiteralWithRadix, @@ -822,8 +828,8 @@ pub const Tokenizer = struct { State.Slash => switch (c) { '/' => { + state = State.LineCommentStart; result.id = Token.Id.LineComment; - state = State.LineComment; }, '=' => { result.id = Token.Id.SlashEqual; @@ -835,7 +841,31 @@ pub const Tokenizer = struct { break; }, }, - State.LineComment => switch (c) { + State.LineCommentStart => switch (c) { + '/' => { + state = State.DocCommentStart; + }, + '\n' => break, + else => { + state = State.LineComment; + 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(), }, @@ -920,8 +950,12 @@ pub const Tokenizer = struct { result.id = id; } }, + State.LineCommentStart, State.LineComment => { - result.id = Token.Id.Eof; + result.id = Token.Id.LineComment; + }, + State.DocComment, State.DocCommentStart => { + result.id = Token.Id.DocComment; }, State.NumberDot, @@ -1092,41 +1126,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" { @@ -1153,11 +1223,36 @@ 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}); + testTokenize("////", []Token.Id{Token.Id.LineComment}); +} + +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 }); 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); +} diff --git a/test/cases/error.zig b/test/cases/error.zig index e64bf02c9..2a1433df5 100644 --- a/test/cases/error.zig +++ b/test/cases/error.zig @@ -175,3 +175,69 @@ 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; +} + + +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; +} 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); +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index d9454adf2..300f27cb6 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -3221,4 +3221,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) void { \\} , ".tmp_source.zig:7:24: error: accessing union field 'Bar' while field 'Baz' is set"); + + 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"); }