From 778bb4bc9c9ceb62426c0ed48c079142b713b910 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 14 Sep 2020 11:05:51 -0700 Subject: move std.cache_hash from std to stage2 The API is pretty specific to the implementationt details of the self-hosted compiler. I don't want to have to independently support and maintain this as part of the standard library, and be obligated to not make breaking changes to it with changes to the implementation of stage2. --- lib/std/std.zig | 1 - 1 file changed, 1 deletion(-) (limited to 'lib/std/std.zig') diff --git a/lib/std/std.zig b/lib/std/std.zig index 4236b29298..56b75e5656 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -48,7 +48,6 @@ pub const base64 = @import("base64.zig"); pub const build = @import("build.zig"); pub const builtin = @import("builtin.zig"); pub const c = @import("c.zig"); -pub const cache_hash = @import("cache_hash.zig"); pub const coff = @import("coff.zig"); pub const compress = @import("compress.zig"); pub const crypto = @import("crypto.zig"); -- cgit v1.2.3 From 63685190da9415ff7d27080e0f33cbeab6a80156 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 29 Sep 2020 16:01:20 -0700 Subject: move std.rb to the standard library orphanage https://github.com/ziglang/std-lib-orphanage/ This code is not used by anything else in the standard library or by the compiler or any of its tools and therefore it's a great candidate to be maintained by a third party. --- lib/std/rb.zig | 633 -------------------------------------------------------- lib/std/std.zig | 1 - 2 files changed, 634 deletions(-) delete mode 100644 lib/std/rb.zig (limited to 'lib/std/std.zig') diff --git a/lib/std/rb.zig b/lib/std/rb.zig deleted file mode 100644 index 8cf90a1eea..0000000000 --- a/lib/std/rb.zig +++ /dev/null @@ -1,633 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std"); -const assert = std.debug.assert; -const testing = std.testing; -const Order = std.math.Order; - -const Color = enum(u1) { - Black, - Red, -}; -const Red = Color.Red; -const Black = Color.Black; - -const ReplaceError = error{NotEqual}; -const SortError = error{NotUnique}; // The new comparison function results in duplicates. - -/// Insert this into your struct that you want to add to a red-black tree. -/// Do not use a pointer. Turn the *rb.Node results of the functions in rb -/// (after resolving optionals) to your structure using @fieldParentPtr(). Example: -/// -/// const Number = struct { -/// node: rb.Node, -/// value: i32, -/// }; -/// fn number(node: *rb.Node) Number { -/// return @fieldParentPtr(Number, "node", node); -/// } -pub const Node = struct { - left: ?*Node, - right: ?*Node, - - /// parent | color - parent_and_color: usize, - - pub fn next(constnode: *Node) ?*Node { - var node = constnode; - - if (node.right) |right| { - var n = right; - while (n.left) |left| - n = left; - return n; - } - - while (true) { - var parent = node.getParent(); - if (parent) |p| { - if (node != p.right) - return p; - node = p; - } else - return null; - } - } - - pub fn prev(constnode: *Node) ?*Node { - var node = constnode; - - if (node.left) |left| { - var n = left; - while (n.right) |right| - n = right; - return n; - } - - while (true) { - var parent = node.getParent(); - if (parent) |p| { - if (node != p.left) - return p; - node = p; - } else - return null; - } - } - - pub fn isRoot(node: *Node) bool { - return node.getParent() == null; - } - - fn isRed(node: *Node) bool { - return node.getColor() == Red; - } - - fn isBlack(node: *Node) bool { - return node.getColor() == Black; - } - - fn setParent(node: *Node, parent: ?*Node) void { - node.parent_and_color = @ptrToInt(parent) | (node.parent_and_color & 1); - } - - fn getParent(node: *Node) ?*Node { - const mask: usize = 1; - comptime { - assert(@alignOf(*Node) >= 2); - } - const maybe_ptr = node.parent_and_color & ~mask; - return if (maybe_ptr == 0) null else @intToPtr(*Node, maybe_ptr); - } - - fn setColor(node: *Node, color: Color) void { - const mask: usize = 1; - node.parent_and_color = (node.parent_and_color & ~mask) | @enumToInt(color); - } - - fn getColor(node: *Node) Color { - return @intToEnum(Color, @intCast(u1, node.parent_and_color & 1)); - } - - fn setChild(node: *Node, child: ?*Node, is_left: bool) void { - if (is_left) { - node.left = child; - } else { - node.right = child; - } - } - - fn getFirst(nodeconst: *Node) *Node { - var node = nodeconst; - while (node.left) |left| { - node = left; - } - return node; - } - - fn getLast(nodeconst: *Node) *Node { - var node = nodeconst; - while (node.right) |right| { - node = right; - } - return node; - } -}; - -pub const Tree = struct { - root: ?*Node, - compareFn: fn (*Node, *Node, *Tree) Order, - - /// Re-sorts a tree with a new compare function - pub fn sort(tree: *Tree, newCompareFn: fn (*Node, *Node, *Tree) Order) SortError!void { - var newTree = Tree.init(newCompareFn); - var node: *Node = undefined; - while (true) { - node = tree.first() orelse break; - tree.remove(node); - if (newTree.insert(node) != null) { - return error.NotUnique; // EEXISTS - } - } - tree.* = newTree; - } - - /// If you have a need for a version that caches this, please file a bug. - pub fn first(tree: *Tree) ?*Node { - var node: *Node = tree.root orelse return null; - - while (node.left) |left| { - node = left; - } - - return node; - } - - pub fn last(tree: *Tree) ?*Node { - var node: *Node = tree.root orelse return null; - - while (node.right) |right| { - node = right; - } - - return node; - } - - /// Duplicate keys are not allowed. The item with the same key already in the - /// tree will be returned, and the item will not be inserted. - pub fn insert(tree: *Tree, node_const: *Node) ?*Node { - var node = node_const; - var maybe_key: ?*Node = undefined; - var maybe_parent: ?*Node = undefined; - var is_left: bool = undefined; - - maybe_key = doLookup(node, tree, &maybe_parent, &is_left); - if (maybe_key) |key| { - return key; - } - - node.left = null; - node.right = null; - node.setColor(Red); - node.setParent(maybe_parent); - - if (maybe_parent) |parent| { - parent.setChild(node, is_left); - } else { - tree.root = node; - } - - while (node.getParent()) |*parent| { - if (parent.*.isBlack()) - break; - // the root is always black - var grandpa = parent.*.getParent() orelse unreachable; - - if (parent.* == grandpa.left) { - var maybe_uncle = grandpa.right; - - if (maybe_uncle) |uncle| { - if (uncle.isBlack()) - break; - - parent.*.setColor(Black); - uncle.setColor(Black); - grandpa.setColor(Red); - node = grandpa; - } else { - if (node == parent.*.right) { - rotateLeft(parent.*, tree); - node = parent.*; - parent.* = node.getParent().?; // Just rotated - } - parent.*.setColor(Black); - grandpa.setColor(Red); - rotateRight(grandpa, tree); - } - } else { - var maybe_uncle = grandpa.left; - - if (maybe_uncle) |uncle| { - if (uncle.isBlack()) - break; - - parent.*.setColor(Black); - uncle.setColor(Black); - grandpa.setColor(Red); - node = grandpa; - } else { - if (node == parent.*.left) { - rotateRight(parent.*, tree); - node = parent.*; - parent.* = node.getParent().?; // Just rotated - } - parent.*.setColor(Black); - grandpa.setColor(Red); - rotateLeft(grandpa, tree); - } - } - } - // This was an insert, there is at least one node. - tree.root.?.setColor(Black); - return null; - } - - /// lookup searches for the value of key, using binary search. It will - /// return a pointer to the node if it is there, otherwise it will return null. - /// Complexity guaranteed O(log n), where n is the number of nodes book-kept - /// by tree. - pub fn lookup(tree: *Tree, key: *Node) ?*Node { - var parent: ?*Node = undefined; - var is_left: bool = undefined; - return doLookup(key, tree, &parent, &is_left); - } - - /// If node is not part of tree, behavior is undefined. - pub fn remove(tree: *Tree, nodeconst: *Node) void { - var node = nodeconst; - // as this has the same value as node, it is unsafe to access node after newnode - var newnode: ?*Node = nodeconst; - var maybe_parent: ?*Node = node.getParent(); - var color: Color = undefined; - var next: *Node = undefined; - - // This clause is to avoid optionals - if (node.left == null and node.right == null) { - if (maybe_parent) |parent| { - parent.setChild(null, parent.left == node); - } else - tree.root = null; - color = node.getColor(); - newnode = null; - } else { - if (node.left == null) { - next = node.right.?; // Not both null as per above - } else if (node.right == null) { - next = node.left.?; // Not both null as per above - } else - next = node.right.?.getFirst(); // Just checked for null above - - if (maybe_parent) |parent| { - parent.setChild(next, parent.left == node); - } else - tree.root = next; - - if (node.left != null and node.right != null) { - const left = node.left.?; - const right = node.right.?; - - color = next.getColor(); - next.setColor(node.getColor()); - - next.left = left; - left.setParent(next); - - if (next != right) { - var parent = next.getParent().?; // Was traversed via child node (right/left) - next.setParent(node.getParent()); - - newnode = next.right; - parent.left = node; - - next.right = right; - right.setParent(next); - } else { - next.setParent(maybe_parent); - maybe_parent = next; - newnode = next.right; - } - } else { - color = node.getColor(); - newnode = next; - } - } - - if (newnode) |n| - n.setParent(maybe_parent); - - if (color == Red) - return; - if (newnode) |n| { - n.setColor(Black); - return; - } - - while (node == tree.root) { - // If not root, there must be parent - var parent = maybe_parent.?; - if (node == parent.left) { - var sibling = parent.right.?; // Same number of black nodes. - - if (sibling.isRed()) { - sibling.setColor(Black); - parent.setColor(Red); - rotateLeft(parent, tree); - sibling = parent.right.?; // Just rotated - } - if ((if (sibling.left) |n| n.isBlack() else true) and - (if (sibling.right) |n| n.isBlack() else true)) - { - sibling.setColor(Red); - node = parent; - maybe_parent = parent.getParent(); - continue; - } - if (if (sibling.right) |n| n.isBlack() else true) { - sibling.left.?.setColor(Black); // Same number of black nodes. - sibling.setColor(Red); - rotateRight(sibling, tree); - sibling = parent.right.?; // Just rotated - } - sibling.setColor(parent.getColor()); - parent.setColor(Black); - sibling.right.?.setColor(Black); // Same number of black nodes. - rotateLeft(parent, tree); - newnode = tree.root; - break; - } else { - var sibling = parent.left.?; // Same number of black nodes. - - if (sibling.isRed()) { - sibling.setColor(Black); - parent.setColor(Red); - rotateRight(parent, tree); - sibling = parent.left.?; // Just rotated - } - if ((if (sibling.left) |n| n.isBlack() else true) and - (if (sibling.right) |n| n.isBlack() else true)) - { - sibling.setColor(Red); - node = parent; - maybe_parent = parent.getParent(); - continue; - } - if (if (sibling.left) |n| n.isBlack() else true) { - sibling.right.?.setColor(Black); // Same number of black nodes - sibling.setColor(Red); - rotateLeft(sibling, tree); - sibling = parent.left.?; // Just rotated - } - sibling.setColor(parent.getColor()); - parent.setColor(Black); - sibling.left.?.setColor(Black); // Same number of black nodes - rotateRight(parent, tree); - newnode = tree.root; - break; - } - - if (node.isRed()) - break; - } - - if (newnode) |n| - n.setColor(Black); - } - - /// This is a shortcut to avoid removing and re-inserting an item with the same key. - pub fn replace(tree: *Tree, old: *Node, newconst: *Node) !void { - var new = newconst; - - // I assume this can get optimized out if the caller already knows. - if (tree.compareFn(old, new, tree) != .eq) return ReplaceError.NotEqual; - - if (old.getParent()) |parent| { - parent.setChild(new, parent.left == old); - } else - tree.root = new; - - if (old.left) |left| - left.setParent(new); - if (old.right) |right| - right.setParent(new); - - new.* = old.*; - } - - pub fn init(f: fn (*Node, *Node, *Tree) Order) Tree { - return Tree{ - .root = null, - .compareFn = f, - }; - } -}; - -fn rotateLeft(node: *Node, tree: *Tree) void { - var p: *Node = node; - var q: *Node = node.right orelse unreachable; - var parent: *Node = undefined; - - if (!p.isRoot()) { - parent = p.getParent().?; - if (parent.left == p) { - parent.left = q; - } else { - parent.right = q; - } - q.setParent(parent); - } else { - tree.root = q; - q.setParent(null); - } - p.setParent(q); - - p.right = q.left; - if (p.right) |right| { - right.setParent(p); - } - q.left = p; -} - -fn rotateRight(node: *Node, tree: *Tree) void { - var p: *Node = node; - var q: *Node = node.left orelse unreachable; - var parent: *Node = undefined; - - if (!p.isRoot()) { - parent = p.getParent().?; - if (parent.left == p) { - parent.left = q; - } else { - parent.right = q; - } - q.setParent(parent); - } else { - tree.root = q; - q.setParent(null); - } - p.setParent(q); - - p.left = q.right; - if (p.left) |left| { - left.setParent(p); - } - q.right = p; -} - -fn doLookup(key: *Node, tree: *Tree, pparent: *?*Node, is_left: *bool) ?*Node { - var maybe_node: ?*Node = tree.root; - - pparent.* = null; - is_left.* = false; - - while (maybe_node) |node| { - const res = tree.compareFn(node, key, tree); - if (res == .eq) { - return node; - } - pparent.* = node; - switch (res) { - .gt => { - is_left.* = true; - maybe_node = node.left; - }, - .lt => { - is_left.* = false; - maybe_node = node.right; - }, - .eq => unreachable, // handled above - } - } - return null; -} - -const testNumber = struct { - node: Node, - value: usize, -}; - -fn testGetNumber(node: *Node) *testNumber { - return @fieldParentPtr(testNumber, "node", node); -} - -fn testCompare(l: *Node, r: *Node, contextIgnored: *Tree) Order { - var left = testGetNumber(l); - var right = testGetNumber(r); - - if (left.value < right.value) { - return .lt; - } else if (left.value == right.value) { - return .eq; - } else if (left.value > right.value) { - return .gt; - } - unreachable; -} - -fn testCompareReverse(l: *Node, r: *Node, contextIgnored: *Tree) Order { - return testCompare(r, l, contextIgnored); -} - -test "rb" { - if (@import("builtin").arch == .aarch64) { - // TODO https://github.com/ziglang/zig/issues/3288 - return error.SkipZigTest; - } - - var tree = Tree.init(testCompare); - var ns: [10]testNumber = undefined; - ns[0].value = 42; - ns[1].value = 41; - ns[2].value = 40; - ns[3].value = 39; - ns[4].value = 38; - ns[5].value = 39; - ns[6].value = 3453; - ns[7].value = 32345; - ns[8].value = 392345; - ns[9].value = 4; - - var dup: testNumber = undefined; - dup.value = 32345; - - _ = tree.insert(&ns[1].node); - _ = tree.insert(&ns[2].node); - _ = tree.insert(&ns[3].node); - _ = tree.insert(&ns[4].node); - _ = tree.insert(&ns[5].node); - _ = tree.insert(&ns[6].node); - _ = tree.insert(&ns[7].node); - _ = tree.insert(&ns[8].node); - _ = tree.insert(&ns[9].node); - tree.remove(&ns[3].node); - testing.expect(tree.insert(&dup.node) == &ns[7].node); - try tree.replace(&ns[7].node, &dup.node); - - var num: *testNumber = undefined; - num = testGetNumber(tree.first().?); - while (num.node.next() != null) { - testing.expect(testGetNumber(num.node.next().?).value > num.value); - num = testGetNumber(num.node.next().?); - } -} - -test "inserting and looking up" { - var tree = Tree.init(testCompare); - var number: testNumber = undefined; - number.value = 1000; - _ = tree.insert(&number.node); - var dup: testNumber = undefined; - //Assert that tuples with identical value fields finds the same pointer - dup.value = 1000; - assert(tree.lookup(&dup.node) == &number.node); - //Assert that tuples with identical values do not clobber when inserted. - _ = tree.insert(&dup.node); - assert(tree.lookup(&dup.node) == &number.node); - assert(tree.lookup(&number.node) != &dup.node); - assert(testGetNumber(tree.lookup(&dup.node).?).value == testGetNumber(&dup.node).value); - //Assert that if looking for a non-existing value, return null. - var non_existing_value: testNumber = undefined; - non_existing_value.value = 1234; - assert(tree.lookup(&non_existing_value.node) == null); -} - -test "multiple inserts, followed by calling first and last" { - if (@import("builtin").arch == .aarch64) { - // TODO https://github.com/ziglang/zig/issues/3288 - return error.SkipZigTest; - } - var tree = Tree.init(testCompare); - var zeroth: testNumber = undefined; - zeroth.value = 0; - var first: testNumber = undefined; - first.value = 1; - var second: testNumber = undefined; - second.value = 2; - var third: testNumber = undefined; - third.value = 3; - _ = tree.insert(&zeroth.node); - _ = tree.insert(&first.node); - _ = tree.insert(&second.node); - _ = tree.insert(&third.node); - assert(testGetNumber(tree.first().?).value == 0); - assert(testGetNumber(tree.last().?).value == 3); - var lookupNode: testNumber = undefined; - lookupNode.value = 3; - assert(tree.lookup(&lookupNode.node) == &third.node); - tree.sort(testCompareReverse) catch unreachable; - assert(testGetNumber(tree.first().?).value == 3); - assert(testGetNumber(tree.last().?).value == 0); - assert(tree.lookup(&lookupNode.node) == &third.node); -} diff --git a/lib/std/std.zig b/lib/std/std.zig index 4236b29298..1ee3944d3b 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -78,7 +78,6 @@ pub const packed_int_array = @import("packed_int_array.zig"); pub const pdb = @import("pdb.zig"); pub const process = @import("process.zig"); pub const rand = @import("rand.zig"); -pub const rb = @import("rb.zig"); pub const sort = @import("sort.zig"); pub const ascii = @import("ascii.zig"); pub const testing = @import("testing.zig"); -- cgit v1.2.3 From d1cea16f5cd29eb143ff9b3302e7ec56731647ea Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 29 Sep 2020 16:39:05 -0700 Subject: move std.BloomFilter to the standard library orphanage --- lib/std/bloom_filter.zig | 265 ----------------------------------------------- lib/std/std.zig | 1 - 2 files changed, 266 deletions(-) delete mode 100644 lib/std/bloom_filter.zig (limited to 'lib/std/std.zig') diff --git a/lib/std/bloom_filter.zig b/lib/std/bloom_filter.zig deleted file mode 100644 index 0e251f548f..0000000000 --- a/lib/std/bloom_filter.zig +++ /dev/null @@ -1,265 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const builtin = @import("builtin"); -const std = @import("std.zig"); -const math = std.math; -const debug = std.debug; -const assert = std.debug.assert; -const testing = std.testing; - -/// There is a trade off of how quickly to fill a bloom filter; -/// the number of items is: -/// n_items / K * ln(2) -/// the rate of false positives is: -/// (1-e^(-K*N/n_items))^K -/// where N is the number of items -pub fn BloomFilter( - /// Size of bloom filter in cells, must be a power of two. - comptime n_items: usize, - /// Number of cells to set per item - comptime K: usize, - /// Cell type, should be: - /// - `bool` for a standard bloom filter - /// - an unsigned integer type for a counting bloom filter - comptime Cell: type, - /// endianess of the Cell - comptime endian: builtin.Endian, - /// Hash function to use - comptime hash: fn (out: []u8, Ki: usize, in: []const u8) void, -) type { - assert(n_items > 0); - assert(math.isPowerOfTwo(n_items)); - assert(K > 0); - const cellEmpty = if (Cell == bool) false else @as(Cell, 0); - const cellMax = if (Cell == bool) true else math.maxInt(Cell); - const n_bytes = (n_items * comptime std.meta.bitCount(Cell)) / 8; - assert(n_bytes > 0); - const Io = std.packed_int_array.PackedIntIo(Cell, endian); - - return struct { - const Self = @This(); - pub const items = n_items; - pub const Index = math.IntFittingRange(0, n_items - 1); - - data: [n_bytes]u8 = [_]u8{0} ** n_bytes, - - pub fn reset(self: *Self) void { - std.mem.set(u8, self.data[0..], 0); - } - - pub fn @"union"(x: Self, y: Self) Self { - var r = Self{ .data = undefined }; - inline for (x.data) |v, i| { - r.data[i] = v | y.data[i]; - } - return r; - } - - pub fn intersection(x: Self, y: Self) Self { - var r = Self{ .data = undefined }; - inline for (x.data) |v, i| { - r.data[i] = v & y.data[i]; - } - return r; - } - - pub fn getCell(self: Self, cell: Index) Cell { - return Io.get(&self.data, cell, 0); - } - - pub fn incrementCell(self: *Self, cell: Index) void { - if (Cell == bool or Cell == u1) { - // skip the 'get' operation - Io.set(&self.data, cell, 0, cellMax); - } else { - const old = Io.get(&self.data, cell, 0); - if (old != cellMax) { - Io.set(&self.data, cell, 0, old + 1); - } - } - } - - pub fn clearCell(self: *Self, cell: Index) void { - Io.set(&self.data, cell, 0, cellEmpty); - } - - pub fn add(self: *Self, item: []const u8) void { - comptime var i = 0; - inline while (i < K) : (i += 1) { - var K_th_bit: packed struct { - x: Index, - } = undefined; - hash(std.mem.asBytes(&K_th_bit), i, item); - incrementCell(self, K_th_bit.x); - } - } - - pub fn contains(self: Self, item: []const u8) bool { - comptime var i = 0; - inline while (i < K) : (i += 1) { - var K_th_bit: packed struct { - x: Index, - } = undefined; - hash(std.mem.asBytes(&K_th_bit), i, item); - if (getCell(self, K_th_bit.x) == cellEmpty) - return false; - } - return true; - } - - pub fn resize(self: Self, comptime newsize: usize) BloomFilter(newsize, K, Cell, endian, hash) { - var r: BloomFilter(newsize, K, Cell, endian, hash) = undefined; - if (newsize < n_items) { - std.mem.copy(u8, r.data[0..], self.data[0..r.data.len]); - var copied: usize = r.data.len; - while (copied < self.data.len) : (copied += r.data.len) { - for (self.data[copied .. copied + r.data.len]) |s, i| { - r.data[i] |= s; - } - } - } else if (newsize == n_items) { - r = self; - } else if (newsize > n_items) { - var copied: usize = 0; - while (copied < r.data.len) : (copied += self.data.len) { - std.mem.copy(u8, r.data[copied .. copied + self.data.len], &self.data); - } - } - return r; - } - - /// Returns number of non-zero cells - pub fn popCount(self: Self) Index { - var n: Index = 0; - if (Cell == bool or Cell == u1) { - for (self.data) |b, i| { - n += @popCount(u8, b); - } - } else { - var i: usize = 0; - while (i < n_items) : (i += 1) { - const cell = self.getCell(@intCast(Index, i)); - n += if (if (Cell == bool) cell else cell > 0) @as(Index, 1) else @as(Index, 0); - } - } - return n; - } - - pub fn estimateItems(self: Self) f64 { - const m = comptime @intToFloat(f64, n_items); - const k = comptime @intToFloat(f64, K); - const X = @intToFloat(f64, self.popCount()); - return (comptime (-m / k)) * math.log1p(X * comptime (-1 / m)); - } - }; -} - -fn hashFunc(out: []u8, Ki: usize, in: []const u8) void { - var st = std.crypto.hash.Gimli.init(.{}); - st.update(std.mem.asBytes(&Ki)); - st.update(in); - st.final(out); -} - -test "std.BloomFilter" { - // https://github.com/ziglang/zig/issues/5127 - if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest; - - inline for ([_]type{ bool, u1, u2, u3, u4 }) |Cell| { - const emptyCell = if (Cell == bool) false else @as(Cell, 0); - const BF = BloomFilter(128 * 8, 8, Cell, builtin.endian, hashFunc); - var bf = BF{}; - var i: usize = undefined; - // confirm that it is initialised to the empty filter - i = 0; - while (i < BF.items) : (i += 1) { - testing.expectEqual(emptyCell, bf.getCell(@intCast(BF.Index, i))); - } - testing.expectEqual(@as(BF.Index, 0), bf.popCount()); - testing.expectEqual(@as(f64, 0), bf.estimateItems()); - // fill in a few items - bf.incrementCell(42); - bf.incrementCell(255); - bf.incrementCell(256); - bf.incrementCell(257); - // check that they were set - testing.expectEqual(true, bf.getCell(42) != emptyCell); - testing.expectEqual(true, bf.getCell(255) != emptyCell); - testing.expectEqual(true, bf.getCell(256) != emptyCell); - testing.expectEqual(true, bf.getCell(257) != emptyCell); - // clear just one of them; make sure the rest are still set - bf.clearCell(256); - testing.expectEqual(true, bf.getCell(42) != emptyCell); - testing.expectEqual(true, bf.getCell(255) != emptyCell); - testing.expectEqual(false, bf.getCell(256) != emptyCell); - testing.expectEqual(true, bf.getCell(257) != emptyCell); - // reset any of the ones we've set and confirm we're back to the empty filter - bf.clearCell(42); - bf.clearCell(255); - bf.clearCell(257); - i = 0; - while (i < BF.items) : (i += 1) { - testing.expectEqual(emptyCell, bf.getCell(@intCast(BF.Index, i))); - } - testing.expectEqual(@as(BF.Index, 0), bf.popCount()); - testing.expectEqual(@as(f64, 0), bf.estimateItems()); - - // Lets add a string - bf.add("foo"); - testing.expectEqual(true, bf.contains("foo")); - { - // try adding same string again. make sure popcount is the same - const old_popcount = bf.popCount(); - testing.expect(old_popcount > 0); - bf.add("foo"); - testing.expectEqual(true, bf.contains("foo")); - testing.expectEqual(old_popcount, bf.popCount()); - } - - // Get back to empty filter via .reset - bf.reset(); - // Double check that .reset worked - i = 0; - while (i < BF.items) : (i += 1) { - testing.expectEqual(emptyCell, bf.getCell(@intCast(BF.Index, i))); - } - testing.expectEqual(@as(BF.Index, 0), bf.popCount()); - testing.expectEqual(@as(f64, 0), bf.estimateItems()); - - comptime var teststrings = [_][]const u8{ - "foo", - "bar", - "a longer string", - "some more", - "the quick brown fox", - "unique string", - }; - inline for (teststrings) |str| { - bf.add(str); - } - inline for (teststrings) |str| { - testing.expectEqual(true, bf.contains(str)); - } - - { // estimate should be close for low packing - const est = bf.estimateItems(); - testing.expect(est > @intToFloat(f64, teststrings.len) - 1); - testing.expect(est < @intToFloat(f64, teststrings.len) + 1); - } - - const larger_bf = bf.resize(4096); - inline for (teststrings) |str| { - testing.expectEqual(true, larger_bf.contains(str)); - } - testing.expectEqual(@as(u12, bf.popCount()) * (4096 / 1024), larger_bf.popCount()); - - const smaller_bf = bf.resize(64); - inline for (teststrings) |str| { - testing.expectEqual(true, smaller_bf.contains(str)); - } - testing.expect(bf.popCount() <= @as(u10, smaller_bf.popCount()) * (1024 / 64)); - } -} diff --git a/lib/std/std.zig b/lib/std/std.zig index 1ee3944d3b..5b12046ed8 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -14,7 +14,6 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; pub const AutoHashMap = hash_map.AutoHashMap; pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; -pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; pub const BufMap = @import("buf_map.zig").BufMap; pub const BufSet = @import("buf_set.zig").BufSet; pub const ChildProcess = @import("child_process.zig").ChildProcess; -- cgit v1.2.3 From 402f967ed5339fa3d828b7fe1d57cdb5bf38dbf2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 29 Sep 2020 17:39:35 -0700 Subject: move std.http to the standard library orphanage I want to take the design of this in a different direction. I think this abstraction is too high level. I want to start bottom-up. std-lib-orphanage commit 179ae67d61455758d71037434704fd4a17a635a9 --- lib/std/http.zig | 10 - lib/std/http/headers.zig | 597 ----------------------------------------------- lib/std/std.zig | 1 - 3 files changed, 608 deletions(-) delete mode 100644 lib/std/http.zig delete mode 100644 lib/std/http/headers.zig (limited to 'lib/std/std.zig') diff --git a/lib/std/http.zig b/lib/std/http.zig deleted file mode 100644 index 2b8495db71..0000000000 --- a/lib/std/http.zig +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -test "std.http" { - _ = @import("http/headers.zig"); -} - -pub const Headers = @import("http/headers.zig").Headers; diff --git a/lib/std/http/headers.zig b/lib/std/http/headers.zig deleted file mode 100644 index 0ce642865c..0000000000 --- a/lib/std/http/headers.zig +++ /dev/null @@ -1,597 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -// HTTP Header data structure/type -// Based on lua-http's http.header module -// -// Design criteria: -// - the same header field is allowed more than once -// - must be able to fetch separate occurrences (important for some headers e.g. Set-Cookie) -// - optionally available as comma separated list -// - http2 adds flag to headers that they should never be indexed -// - header order should be recoverable -// -// Headers are implemented as an array of entries. -// An index of field name => array indices is kept. - -const std = @import("../std.zig"); -const debug = std.debug; -const assert = debug.assert; -const testing = std.testing; -const mem = std.mem; -const Allocator = mem.Allocator; - -fn never_index_default(name: []const u8) bool { - if (mem.eql(u8, "authorization", name)) return true; - if (mem.eql(u8, "proxy-authorization", name)) return true; - if (mem.eql(u8, "cookie", name)) return true; - if (mem.eql(u8, "set-cookie", name)) return true; - return false; -} - -const HeaderEntry = struct { - name: []const u8, - value: []u8, - never_index: bool, - - const Self = @This(); - - fn init(allocator: *Allocator, name: []const u8, value: []const u8, never_index: ?bool) !Self { - return Self{ - .name = name, // takes reference - .value = try allocator.dupe(u8, value), - .never_index = never_index orelse never_index_default(name), - }; - } - - fn deinit(self: Self, allocator: *Allocator) void { - allocator.free(self.value); - } - - pub fn modify(self: *Self, allocator: *Allocator, value: []const u8, never_index: ?bool) !void { - const old_len = self.value.len; - if (value.len > old_len) { - self.value = try allocator.realloc(self.value, value.len); - } else if (value.len < old_len) { - self.value = allocator.shrink(self.value, value.len); - } - mem.copy(u8, self.value, value); - self.never_index = never_index orelse never_index_default(self.name); - } - - fn compare(context: void, a: HeaderEntry, b: HeaderEntry) bool { - if (a.name.ptr != b.name.ptr and a.name.len != b.name.len) { - // Things beginning with a colon *must* be before others - const a_is_colon = a.name[0] == ':'; - const b_is_colon = b.name[0] == ':'; - if (a_is_colon and !b_is_colon) { - return true; - } else if (!a_is_colon and b_is_colon) { - return false; - } - - // Sort lexicographically on header name - return mem.order(u8, a.name, b.name) == .lt; - } - - // Sort lexicographically on header value - if (!mem.eql(u8, a.value, b.value)) { - return mem.order(u8, a.value, b.value) == .lt; - } - - // Doesn't matter here; need to pick something for sort consistency - return a.never_index; - } -}; - -test "HeaderEntry" { - var e = try HeaderEntry.init(testing.allocator, "foo", "bar", null); - defer e.deinit(testing.allocator); - testing.expectEqualSlices(u8, "foo", e.name); - testing.expectEqualSlices(u8, "bar", e.value); - testing.expectEqual(false, e.never_index); - - try e.modify(testing.allocator, "longer value", null); - testing.expectEqualSlices(u8, "longer value", e.value); - - // shorter value - try e.modify(testing.allocator, "x", null); - testing.expectEqualSlices(u8, "x", e.value); -} - -const HeaderList = std.ArrayListUnmanaged(HeaderEntry); -const HeaderIndexList = std.ArrayListUnmanaged(usize); -const HeaderIndex = std.StringHashMapUnmanaged(HeaderIndexList); - -pub const Headers = struct { - // the owned header field name is stored in the index as part of the key - allocator: *Allocator, - data: HeaderList, - index: HeaderIndex, - - const Self = @This(); - - pub fn init(allocator: *Allocator) Self { - return Self{ - .allocator = allocator, - .data = HeaderList{}, - .index = HeaderIndex{}, - }; - } - - pub fn deinit(self: *Self) void { - { - var it = self.index.iterator(); - while (it.next()) |entry| { - entry.value.deinit(self.allocator); - self.allocator.free(entry.key); - } - self.index.deinit(self.allocator); - } - { - for (self.data.items) |entry| { - entry.deinit(self.allocator); - } - self.data.deinit(self.allocator); - } - self.* = undefined; - } - - pub fn clone(self: Self, allocator: *Allocator) !Self { - var other = Headers.init(allocator); - errdefer other.deinit(); - try other.data.ensureCapacity(allocator, self.data.items.len); - try other.index.initCapacity(allocator, self.index.entries.len); - for (self.data.items) |entry| { - try other.append(entry.name, entry.value, entry.never_index); - } - return other; - } - - pub fn toSlice(self: Self) []const HeaderEntry { - return self.data.items; - } - - pub fn append(self: *Self, name: []const u8, value: []const u8, never_index: ?bool) !void { - const n = self.data.items.len + 1; - try self.data.ensureCapacity(self.allocator, n); - var entry: HeaderEntry = undefined; - if (self.index.getEntry(name)) |kv| { - entry = try HeaderEntry.init(self.allocator, kv.key, value, never_index); - errdefer entry.deinit(self.allocator); - const dex = &kv.value; - try dex.append(self.allocator, n - 1); - } else { - const name_dup = try self.allocator.dupe(u8, name); - errdefer self.allocator.free(name_dup); - entry = try HeaderEntry.init(self.allocator, name_dup, value, never_index); - errdefer entry.deinit(self.allocator); - var dex = HeaderIndexList{}; - try dex.append(self.allocator, n - 1); - errdefer dex.deinit(self.allocator); - _ = try self.index.put(self.allocator, name_dup, dex); - } - self.data.appendAssumeCapacity(entry); - } - - /// If the header already exists, replace the current value, otherwise append it to the list of headers. - /// If the header has multiple entries then returns an error. - pub fn upsert(self: *Self, name: []const u8, value: []const u8, never_index: ?bool) !void { - if (self.index.get(name)) |kv| { - const dex = kv.value; - if (dex.len != 1) - return error.CannotUpsertMultiValuedField; - var e = &self.data.at(dex.at(0)); - try e.modify(value, never_index); - } else { - try self.append(name, value, never_index); - } - } - - /// Returns boolean indicating if the field is present. - pub fn contains(self: Self, name: []const u8) bool { - return self.index.contains(name); - } - - /// Returns boolean indicating if something was deleted. - pub fn delete(self: *Self, name: []const u8) bool { - if (self.index.remove(name)) |*kv| { - const dex = &kv.value; - // iterate backwards - var i = dex.items.len; - while (i > 0) { - i -= 1; - const data_index = dex.items[i]; - const removed = self.data.orderedRemove(data_index); - assert(mem.eql(u8, removed.name, name)); - removed.deinit(self.allocator); - } - dex.deinit(self.allocator); - self.allocator.free(kv.key); - self.rebuildIndex(); - return true; - } else { - return false; - } - } - - /// Removes the element at the specified index. - /// Moves items down to fill the empty space. - /// TODO this implementation can be replaced by adding - /// orderedRemove to the new hash table implementation as an - /// alternative to swapRemove. - pub fn orderedRemove(self: *Self, i: usize) void { - const removed = self.data.orderedRemove(i); - const kv = self.index.getEntry(removed.name).?; - const dex = &kv.value; - if (dex.items.len == 1) { - // was last item; delete the index - dex.deinit(self.allocator); - removed.deinit(self.allocator); - const key = kv.key; - _ = self.index.remove(key); // invalidates `kv` and `dex` - self.allocator.free(key); - } else { - dex.shrink(self.allocator, dex.items.len - 1); - removed.deinit(self.allocator); - } - // if it was the last item; no need to rebuild index - if (i != self.data.items.len) { - self.rebuildIndex(); - } - } - - /// Removes the element at the specified index. - /// The empty slot is filled from the end of the list. - /// TODO this implementation can be replaced by simply using the - /// new hash table which does swap removal. - pub fn swapRemove(self: *Self, i: usize) void { - const removed = self.data.swapRemove(i); - const kv = self.index.getEntry(removed.name).?; - const dex = &kv.value; - if (dex.items.len == 1) { - // was last item; delete the index - dex.deinit(self.allocator); - removed.deinit(self.allocator); - const key = kv.key; - _ = self.index.remove(key); // invalidates `kv` and `dex` - self.allocator.free(key); - } else { - dex.shrink(self.allocator, dex.items.len - 1); - removed.deinit(self.allocator); - } - // if it was the last item; no need to rebuild index - if (i != self.data.items.len) { - self.rebuildIndex(); - } - } - - /// Access the header at the specified index. - pub fn at(self: Self, i: usize) HeaderEntry { - return self.data.items[i]; - } - - /// Returns a list of indices containing headers with the given name. - /// The returned list should not be modified by the caller. - pub fn getIndices(self: Self, name: []const u8) ?HeaderIndexList { - return self.index.get(name); - } - - /// Returns a slice containing each header with the given name. - pub fn get(self: Self, allocator: *Allocator, name: []const u8) !?[]const HeaderEntry { - const dex = self.getIndices(name) orelse return null; - - const buf = try allocator.alloc(HeaderEntry, dex.items.len); - var n: usize = 0; - for (dex.items) |idx| { - buf[n] = self.data.items[idx]; - n += 1; - } - return buf; - } - - /// Returns all headers with the given name as a comma separated string. - /// - /// Useful for HTTP headers that follow RFC-7230 section 3.2.2: - /// A recipient MAY combine multiple header fields with the same field - /// name into one "field-name: field-value" pair, without changing the - /// semantics of the message, by appending each subsequent field value to - /// the combined field value in order, separated by a comma. The order - /// in which header fields with the same field name are received is - /// therefore significant to the interpretation of the combined field - /// value - pub fn getCommaSeparated(self: Self, allocator: *Allocator, name: []const u8) !?[]u8 { - const dex = self.getIndices(name) orelse return null; - - // adapted from mem.join - const total_len = blk: { - var sum: usize = dex.items.len - 1; // space for separator(s) - for (dex.items) |idx| - sum += self.data.items[idx].value.len; - break :blk sum; - }; - - const buf = try allocator.alloc(u8, total_len); - errdefer allocator.free(buf); - - const first_value = self.data.items[dex.items[0]].value; - mem.copy(u8, buf, first_value); - var buf_index: usize = first_value.len; - for (dex.items[1..]) |idx| { - const value = self.data.items[idx].value; - buf[buf_index] = ','; - buf_index += 1; - mem.copy(u8, buf[buf_index..], value); - buf_index += value.len; - } - - // No need for shrink since buf is exactly the correct size. - return buf; - } - - fn rebuildIndex(self: *Self) void { - // clear out the indexes - var it = self.index.iterator(); - while (it.next()) |entry| { - entry.value.shrinkRetainingCapacity(0); - } - // fill up indexes again; we know capacity is fine from before - for (self.data.items) |entry, i| { - self.index.getEntry(entry.name).?.value.appendAssumeCapacity(i); - } - } - - pub fn sort(self: *Self) void { - std.sort.sort(HeaderEntry, self.data.items, {}, HeaderEntry.compare); - self.rebuildIndex(); - } - - pub fn format( - self: Self, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - out_stream: anytype, - ) !void { - for (self.toSlice()) |entry| { - try out_stream.writeAll(entry.name); - try out_stream.writeAll(": "); - try out_stream.writeAll(entry.value); - try out_stream.writeAll("\n"); - } - } -}; - -test "Headers.iterator" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("cookie", "somevalue", null); - - var count: i32 = 0; - for (h.toSlice()) |e| { - if (count == 0) { - testing.expectEqualSlices(u8, "foo", e.name); - testing.expectEqualSlices(u8, "bar", e.value); - testing.expectEqual(false, e.never_index); - } else if (count == 1) { - testing.expectEqualSlices(u8, "cookie", e.name); - testing.expectEqualSlices(u8, "somevalue", e.value); - testing.expectEqual(true, e.never_index); - } - count += 1; - } - testing.expectEqual(@as(i32, 2), count); -} - -test "Headers.contains" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("cookie", "somevalue", null); - - testing.expectEqual(true, h.contains("foo")); - testing.expectEqual(false, h.contains("flooble")); -} - -test "Headers.delete" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("baz", "qux", null); - try h.append("cookie", "somevalue", null); - - testing.expectEqual(false, h.delete("not-present")); - testing.expectEqual(@as(usize, 3), h.toSlice().len); - - testing.expectEqual(true, h.delete("foo")); - testing.expectEqual(@as(usize, 2), h.toSlice().len); - { - const e = h.at(0); - testing.expectEqualSlices(u8, "baz", e.name); - testing.expectEqualSlices(u8, "qux", e.value); - testing.expectEqual(false, e.never_index); - } - { - const e = h.at(1); - testing.expectEqualSlices(u8, "cookie", e.name); - testing.expectEqualSlices(u8, "somevalue", e.value); - testing.expectEqual(true, e.never_index); - } - - testing.expectEqual(false, h.delete("foo")); -} - -test "Headers.orderedRemove" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("baz", "qux", null); - try h.append("cookie", "somevalue", null); - - h.orderedRemove(0); - testing.expectEqual(@as(usize, 2), h.toSlice().len); - { - const e = h.at(0); - testing.expectEqualSlices(u8, "baz", e.name); - testing.expectEqualSlices(u8, "qux", e.value); - testing.expectEqual(false, e.never_index); - } - { - const e = h.at(1); - testing.expectEqualSlices(u8, "cookie", e.name); - testing.expectEqualSlices(u8, "somevalue", e.value); - testing.expectEqual(true, e.never_index); - } -} - -test "Headers.swapRemove" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("baz", "qux", null); - try h.append("cookie", "somevalue", null); - - h.swapRemove(0); - testing.expectEqual(@as(usize, 2), h.toSlice().len); - { - const e = h.at(0); - testing.expectEqualSlices(u8, "cookie", e.name); - testing.expectEqualSlices(u8, "somevalue", e.value); - testing.expectEqual(true, e.never_index); - } - { - const e = h.at(1); - testing.expectEqualSlices(u8, "baz", e.name); - testing.expectEqualSlices(u8, "qux", e.value); - testing.expectEqual(false, e.never_index); - } -} - -test "Headers.at" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("cookie", "somevalue", null); - - { - const e = h.at(0); - testing.expectEqualSlices(u8, "foo", e.name); - testing.expectEqualSlices(u8, "bar", e.value); - testing.expectEqual(false, e.never_index); - } - { - const e = h.at(1); - testing.expectEqualSlices(u8, "cookie", e.name); - testing.expectEqualSlices(u8, "somevalue", e.value); - testing.expectEqual(true, e.never_index); - } -} - -test "Headers.getIndices" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("set-cookie", "x=1", null); - try h.append("set-cookie", "y=2", null); - - testing.expect(null == h.getIndices("not-present")); - testing.expectEqualSlices(usize, &[_]usize{0}, h.getIndices("foo").?.items); - testing.expectEqualSlices(usize, &[_]usize{ 1, 2 }, h.getIndices("set-cookie").?.items); -} - -test "Headers.get" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("set-cookie", "x=1", null); - try h.append("set-cookie", "y=2", null); - - { - const v = try h.get(testing.allocator, "not-present"); - testing.expect(null == v); - } - { - const v = (try h.get(testing.allocator, "foo")).?; - defer testing.allocator.free(v); - const e = v[0]; - testing.expectEqualSlices(u8, "foo", e.name); - testing.expectEqualSlices(u8, "bar", e.value); - testing.expectEqual(false, e.never_index); - } - { - const v = (try h.get(testing.allocator, "set-cookie")).?; - defer testing.allocator.free(v); - { - const e = v[0]; - testing.expectEqualSlices(u8, "set-cookie", e.name); - testing.expectEqualSlices(u8, "x=1", e.value); - testing.expectEqual(true, e.never_index); - } - { - const e = v[1]; - testing.expectEqualSlices(u8, "set-cookie", e.name); - testing.expectEqualSlices(u8, "y=2", e.value); - testing.expectEqual(true, e.never_index); - } - } -} - -test "Headers.getCommaSeparated" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("set-cookie", "x=1", null); - try h.append("set-cookie", "y=2", null); - - { - const v = try h.getCommaSeparated(testing.allocator, "not-present"); - testing.expect(null == v); - } - { - const v = (try h.getCommaSeparated(testing.allocator, "foo")).?; - defer testing.allocator.free(v); - testing.expectEqualSlices(u8, "bar", v); - } - { - const v = (try h.getCommaSeparated(testing.allocator, "set-cookie")).?; - defer testing.allocator.free(v); - testing.expectEqualSlices(u8, "x=1,y=2", v); - } -} - -test "Headers.sort" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("cookie", "somevalue", null); - - h.sort(); - { - const e = h.at(0); - testing.expectEqualSlices(u8, "cookie", e.name); - testing.expectEqualSlices(u8, "somevalue", e.value); - testing.expectEqual(true, e.never_index); - } - { - const e = h.at(1); - testing.expectEqualSlices(u8, "foo", e.name); - testing.expectEqualSlices(u8, "bar", e.value); - testing.expectEqual(false, e.never_index); - } -} - -test "Headers.format" { - var h = Headers.init(testing.allocator); - defer h.deinit(); - try h.append("foo", "bar", null); - try h.append("cookie", "somevalue", null); - - var buf: [100]u8 = undefined; - testing.expectEqualSlices(u8, - \\foo: bar - \\cookie: somevalue - \\ - , try std.fmt.bufPrint(buf[0..], "{}", .{h})); -} diff --git a/lib/std/std.zig b/lib/std/std.zig index 5b12046ed8..e21e428c77 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -62,7 +62,6 @@ pub const fs = @import("fs.zig"); pub const hash = @import("hash.zig"); pub const hash_map = @import("hash_map.zig"); pub const heap = @import("heap.zig"); -pub const http = @import("http.zig"); pub const io = @import("io.zig"); pub const json = @import("json.zig"); pub const log = @import("log.zig"); -- cgit v1.2.3