aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexandros Naskos <alex_naskos@hotmail.com>2018-05-01 13:09:34 +0300
committerAlexandros Naskos <alex_naskos@hotmail.com>2018-05-01 13:09:34 +0300
commit255c0ef40688342aa07f2b51be06791715fc8ae0 (patch)
tree489339ad9f5e4eeb9c5fffab8b1a16b19234981e
parente1535ee0a9f9d01d16a79c380b493f676ae57121 (diff)
parent3a8dc4e90ddf6b3dc2bdf640c89061c00eee7d45 (diff)
downloadzig-255c0ef40688342aa07f2b51be06791715fc8ae0.tar.gz
zig-255c0ef40688342aa07f2b51be06791715fc8ae0.zip
Resolved merge conflict.
-rw-r--r--CMakeLists.txt25
-rw-r--r--src/analyze.cpp3
-rw-r--r--src/ir.cpp77
-rw-r--r--std/atomic/index.zig7
-rw-r--r--std/atomic/queue.zig120
-rw-r--r--std/atomic/stack.zig126
-rw-r--r--std/c/darwin.zig5
-rw-r--r--std/c/index.zig10
-rw-r--r--std/c/linux.zig5
-rw-r--r--std/crypto/throughput_test.zig17
-rw-r--r--std/fmt/errol/index.zig79
-rw-r--r--std/fmt/index.zig539
-rw-r--r--std/heap.zig70
-rw-r--r--std/index.zig2
-rw-r--r--std/math/complex/abs.zig18
-rw-r--r--std/math/complex/acos.zig21
-rw-r--r--std/math/complex/acosh.zig21
-rw-r--r--std/math/complex/arg.zig18
-rw-r--r--std/math/complex/asin.zig27
-rw-r--r--std/math/complex/asinh.zig22
-rw-r--r--std/math/complex/atan.zig130
-rw-r--r--std/math/complex/atanh.zig22
-rw-r--r--std/math/complex/conj.zig17
-rw-r--r--std/math/complex/cos.zig21
-rw-r--r--std/math/complex/cosh.zig165
-rw-r--r--std/math/complex/exp.zig140
-rw-r--r--std/math/complex/index.zig171
-rw-r--r--std/math/complex/ldexp.zig75
-rw-r--r--std/math/complex/log.zig23
-rw-r--r--std/math/complex/pow.zig22
-rw-r--r--std/math/complex/proj.zig24
-rw-r--r--std/math/complex/sin.zig22
-rw-r--r--std/math/complex/sinh.zig164
-rw-r--r--std/math/complex/sqrt.zig133
-rw-r--r--std/math/complex/tan.zig22
-rw-r--r--std/math/complex/tanh.zig111
-rw-r--r--std/math/index.zig5
-rw-r--r--std/mem.zig103
-rw-r--r--std/os/darwin.zig8
-rw-r--r--std/os/index.zig527
-rw-r--r--std/os/linux/index.zig6
-rw-r--r--std/os/test.zig20
-rw-r--r--std/os/time.zig2
-rw-r--r--std/os/windows/index.zig6
-rw-r--r--std/special/bootstrap_lib.zig3
-rw-r--r--std/special/compiler_rt/index.zig21
-rw-r--r--std/special/compiler_rt/udivmodti4.zig6
-rw-r--r--std/special/compiler_rt/udivti3.zig9
-rw-r--r--std/special/compiler_rt/umodti3.zig10
-rw-r--r--std/unicode.zig163
-rw-r--r--std/zig/ast.zig122
-rw-r--r--std/zig/parser.zig1802
-rw-r--r--std/zig/parser_test.zig1139
-rw-r--r--std/zig/tokenizer.zig137
-rw-r--r--test/cases/defer.zig11
-rw-r--r--test/cases/error.zig66
-rw-r--r--test/cases/eval.zig7
-rw-r--r--test/compile_errors.zig16
58 files changed, 5058 insertions, 1605 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e692974719..721690e9dc 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 a598d7676e..1ecfe32f4c 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<FnTypeParamInfo>(param_count_alloc);
+ fn_type_id->param_info = allocate<FnTypeParamInfo>(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 dfffb41873..ff81ebfd86 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<ErrorTableEntry *>(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<ErrorTableEntry *>(err_count);
ErrorTableEntry **errors = allocate<ErrorTableEntry *>(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 0000000000..9d556a6415
--- /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 0000000000..1acecbab2c
--- /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 0000000000..accbcc942a
--- /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 b958055ae8..7ac57514c9 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 cff86f4041..34269d2aa2 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 b2ac05eba5..7810fec130 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 d086b555e9..0756f9a4eb 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 42287bd25b..00c69cd294 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 7bb9829117..43e758038f 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..]);
-
- const num_left_digits = if (float_decimal.exp > 0) usize(float_decimal.exp) else 1;
-
- 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;
-
- 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)]);
+ var float_decimal = errol.errol3(x, buffer[0..]);
+
+ if (maybe_precision) |precision| {
+ errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal);
+
+ // exp < 0 means the leading is always 0 as errol result is normalized.
+ var num_digits_whole = if (float_decimal.exp > 0) 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");
+ }
+
+ // {.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 = 12.34;
- const result = try bufPrint(buf1[0..], "f32: {}\n", value);
- assert(mem.eql(u8, result, "f32: 1.23400001e1\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: f32 = 1.34;
+ const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
+ assert(mem.eql(u8, result, "f32: 1.34000003e+00\n"));
+ }
+ {
+ var buf1: [32]u8 = undefined;
+ const value: f32 = 12.34;
+ const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
+ assert(mem.eql(u8, result, "f32: 1.23400001e+01\n"));
+ }
+ {
+ var buf1: [32]u8 = undefined;
+ const value: f64 = -12.34e10;
+ const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
+ assert(mem.eql(u8, result, "f64: -1.234e+11\n"));
+ }
+ {
+ // This fails on release due to a minor rounding difference.
+ // --release-fast outputs 9.999960000000001e-40 vs. the expected.
+ if (builtin.mode == builtin.Mode.Debug) {
var buf1: [32]u8 = undefined;
- const value: f64 = 91.12345678901235;
- const result = try bufPrint(buf1[0..], "f64: {.10}\n", value);
- assert(mem.eql(u8, result, "f64: 91.1234567890\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 = 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 b3a1e6bf27..bfdf62f658 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 07c4360aab..d6a1e3c94d 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 0000000000..4cd095c46b
--- /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 0000000000..a5760b4ace
--- /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 0000000000..8dd91b2836
--- /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 0000000000..f24512ac73
--- /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 0000000000..584a3a1a9b
--- /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 0000000000..0c4dc2b6e4
--- /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 0000000000..b7bbf930eb
--- /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 0000000000..f70c741765
--- /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 0000000000..ad3e8b5036
--- /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 0000000000..96e4ffcdb0
--- /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 0000000000..96eac68556
--- /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 0000000000..03f7f9e41b
--- /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 0000000000..a4d493307e
--- /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 0000000000..4fb5a6815f
--- /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 0000000000..a4a1d1664f
--- /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 0000000000..bef9fde542
--- /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 0000000000..b6c4cc046e
--- /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 0000000000..d32b771d3b
--- /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 0000000000..09a62ca058
--- /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 0000000000..afda69f7c9
--- /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 0000000000..4ea5182fa7
--- /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 0000000000..6af62f48ae
--- /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 477dafcbcc..83ba055329 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 eb67ce83ef..d874f8a6c9 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 44418649ab..0a62b03ab2 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 0639490725..4f1826021f 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"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"",
- [][]const u8{".\\..\\zig-cache\\build", "bin\\zig.exe", ".\\..", ".\\..\\zig-cache", "--help"});
+ 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",
+ });
}
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));
- }
- }
- }.threadMain;
+ thread_ptr.data.stack_addr = stack_addr;
+ thread_ptr.data.stack_len = mmap_len;
- const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND
- | posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS
- | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED;
- const newtls: usize = 0;
- const rc = posix.clone(threadMain, stack_end, flags, arg, &thread_ptr.pid, newtls, &thread_ptr.pid);
- const err = posix.getErrno(rc);
- switch (err) {
- 0 => return thread_ptr,
- posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded,
- posix.EINVAL => unreachable,
- posix.ENOMEM => return SpawnThreadError.SystemResources,
- posix.ENOSPC => unreachable,
- posix.EPERM => unreachable,
- posix.EUSERS => unreachable,
- else => return unexpectedErrorPosix(err),
+ if (builtin.os == builtin.Os.windows) {
+ // use windows API directly
+ @compileError("TODO support spawnThread for Windows");
+ } else if (Thread.use_pthreads) {
+ // use pthreads
+ var attr: c.pthread_attr_t = undefined;
+ if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources;
+ defer assert(c.pthread_attr_destroy(&attr) == 0);
+
+ // 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)),
+ }
+ } 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 dcd9532d1d..368f074b9b 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 41afee004a..56d6e8b309 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 e9fbf9798c..4fd2c4e924 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 d6ef7205e8..e13ed0f131 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 40b6588838..f55aaed96a 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 81fe1ffec1..6ef43c4fed 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 196d067aef..f8fdebe4db 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 eaecbac4d2..ad0f09e733 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 26b306efa9..3e8b80058e 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 356df824f0..300e129647 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 76977a979a..d1d7fe7914 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 7f45cce28b..fa42807907 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<std.debug.global_allocator> 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 0000000000..4688939485
--- /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<std.debug.global_allocator> 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 a2c4def9e0..6fc26bc5fd 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 a989af18c2..5470b4bbd0 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 e64bf02c91..2a1433df5b 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 e13d4340e7..364db5e152 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 d9454adf2c..300f27cb6a 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");
}