| author | |
| committer | |
| log | 338f155a02b72117ff710f72c8578e7d2f8eb296 |
| tree | a902526d5dc901de7458ef318f52f5ac0dad77e7 |
| parent | c354f074fa91d3d1672469ba4bbc49a1730e1d01 |
| parent | 88724b2a89157ecc3a8eea03aa0f8a6b66829915 |
56 files changed, 4686 insertions(+), 2143 deletions(-)
cmake/Findclang.cmake+2| ... | ... | @@ -25,6 +25,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB) |
| 25 | 25 | clang-cpp |
| 26 | 26 | PATHS |
| 27 | 27 | ${CLANG_LIBDIRS} |
| 28 | /usr/lib/llvm/11/lib | |
| 29 | /usr/lib/llvm/11/lib64 | |
| 28 | 30 | /usr/lib/llvm-11/lib |
| 29 | 31 | /usr/local/llvm110/lib |
| 30 | 32 | /usr/local/llvm11/lib |
cmake/Findllvm.cmake+2| ... | ... | @@ -26,6 +26,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB) |
| 26 | 26 | LLVM |
| 27 | 27 | PATHS |
| 28 | 28 | ${LLVM_LIBDIRS} |
| 29 | /usr/lib/llvm/11/lib | |
| 30 | /usr/lib/llvm/11/lib64 | |
| 29 | 31 | /usr/lib/llvm-11/lib |
| 30 | 32 | /usr/local/llvm11/lib |
| 31 | 33 | /usr/local/llvm110/lib |
lib/std/array_hash_map.zig created+1087| ... | ... | @@ -0,0 +1,1087 @@ |
| 1 | // SPDX-License-Identifier: MIT | |
| 2 | // Copyright (c) 2015-2020 Zig Contributors | |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | |
| 4 | // The MIT license requires this copyright notice to be included in all copies | |
| 5 | // and substantial portions of the software. | |
| 6 | const std = @import("std.zig"); | |
| 7 | const debug = std.debug; | |
| 8 | const assert = debug.assert; | |
| 9 | const testing = std.testing; | |
| 10 | const math = std.math; | |
| 11 | const mem = std.mem; | |
| 12 | const meta = std.meta; | |
| 13 | const trait = meta.trait; | |
| 14 | const autoHash = std.hash.autoHash; | |
| 15 | const Wyhash = std.hash.Wyhash; | |
| 16 | const Allocator = mem.Allocator; | |
| 17 | const builtin = @import("builtin"); | |
| 18 | const hash_map = @This(); | |
| 19 | ||
| 20 | pub fn AutoArrayHashMap(comptime K: type, comptime V: type) type { | |
| 21 | return ArrayHashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); | |
| 22 | } | |
| 23 | ||
| 24 | pub fn AutoArrayHashMapUnmanaged(comptime K: type, comptime V: type) type { | |
| 25 | return ArrayHashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); | |
| 26 | } | |
| 27 | ||
| 28 | /// Builtin hashmap for strings as keys. | |
| 29 | pub fn StringArrayHashMap(comptime V: type) type { | |
| 30 | return ArrayHashMap([]const u8, V, hashString, eqlString, true); | |
| 31 | } | |
| 32 | ||
| 33 | pub fn StringArrayHashMapUnmanaged(comptime V: type) type { | |
| 34 | return ArrayHashMapUnmanaged([]const u8, V, hashString, eqlString, true); | |
| 35 | } | |
| 36 | ||
| 37 | pub fn eqlString(a: []const u8, b: []const u8) bool { | |
| 38 | return mem.eql(u8, a, b); | |
| 39 | } | |
| 40 | ||
| 41 | pub fn hashString(s: []const u8) u32 { | |
| 42 | return @truncate(u32, std.hash.Wyhash.hash(0, s)); | |
| 43 | } | |
| 44 | ||
| 45 | /// Insertion order is preserved. | |
| 46 | /// Deletions perform a "swap removal" on the entries list. | |
| 47 | /// Modifying the hash map while iterating is allowed, however one must understand | |
| 48 | /// the (well defined) behavior when mixing insertions and deletions with iteration. | |
| 49 | /// For a hash map that can be initialized directly that does not store an Allocator | |
| 50 | /// field, see `ArrayHashMapUnmanaged`. | |
| 51 | /// When `store_hash` is `false`, this data structure is biased towards cheap `eql` | |
| 52 | /// functions. It does not store each item's hash in the table. Setting `store_hash` | |
| 53 | /// to `true` incurs slightly more memory cost by storing each key's hash in the table | |
| 54 | /// but only has to call `eql` for hash collisions. | |
| 55 | /// If typical operations (except iteration over entries) need to be faster, prefer | |
| 56 | /// the alternative `std.HashMap`. | |
| 57 | pub fn ArrayHashMap( | |
| 58 | comptime K: type, | |
| 59 | comptime V: type, | |
| 60 | comptime hash: fn (key: K) u32, | |
| 61 | comptime eql: fn (a: K, b: K) bool, | |
| 62 | comptime store_hash: bool, | |
| 63 | ) type { | |
| 64 | return struct { | |
| 65 | unmanaged: Unmanaged, | |
| 66 | allocator: *Allocator, | |
| 67 | ||
| 68 | pub const Unmanaged = ArrayHashMapUnmanaged(K, V, hash, eql, store_hash); | |
| 69 | pub const Entry = Unmanaged.Entry; | |
| 70 | pub const Hash = Unmanaged.Hash; | |
| 71 | pub const GetOrPutResult = Unmanaged.GetOrPutResult; | |
| 72 | ||
| 73 | /// Deprecated. Iterate using `items`. | |
| 74 | pub const Iterator = struct { | |
| 75 | hm: *const Self, | |
| 76 | /// Iterator through the entry array. | |
| 77 | index: usize, | |
| 78 | ||
| 79 | pub fn next(it: *Iterator) ?*Entry { | |
| 80 | if (it.index >= it.hm.unmanaged.entries.items.len) return null; | |
| 81 | const result = &it.hm.unmanaged.entries.items[it.index]; | |
| 82 | it.index += 1; | |
| 83 | return result; | |
| 84 | } | |
| 85 | ||
| 86 | /// Reset the iterator to the initial index | |
| 87 | pub fn reset(it: *Iterator) void { | |
| 88 | it.index = 0; | |
| 89 | } | |
| 90 | }; | |
| 91 | ||
| 92 | const Self = @This(); | |
| 93 | const Index = Unmanaged.Index; | |
| 94 | ||
| 95 | pub fn init(allocator: *Allocator) Self { | |
| 96 | return .{ | |
| 97 | .unmanaged = .{}, | |
| 98 | .allocator = allocator, | |
| 99 | }; | |
| 100 | } | |
| 101 | ||
| 102 | pub fn deinit(self: *Self) void { | |
| 103 | self.unmanaged.deinit(self.allocator); | |
| 104 | self.* = undefined; | |
| 105 | } | |
| 106 | ||
| 107 | pub fn clearRetainingCapacity(self: *Self) void { | |
| 108 | return self.unmanaged.clearRetainingCapacity(); | |
| 109 | } | |
| 110 | ||
| 111 | pub fn clearAndFree(self: *Self) void { | |
| 112 | return self.unmanaged.clearAndFree(self.allocator); | |
| 113 | } | |
| 114 | ||
| 115 | /// Deprecated. Use `items().len`. | |
| 116 | pub fn count(self: Self) usize { | |
| 117 | return self.items().len; | |
| 118 | } | |
| 119 | ||
| 120 | /// Deprecated. Iterate using `items`. | |
| 121 | pub fn iterator(self: *const Self) Iterator { | |
| 122 | return Iterator{ | |
| 123 | .hm = self, | |
| 124 | .index = 0, | |
| 125 | }; | |
| 126 | } | |
| 127 | ||
| 128 | /// If key exists this function cannot fail. | |
| 129 | /// If there is an existing item with `key`, then the result | |
| 130 | /// `Entry` pointer points to it, and found_existing is true. | |
| 131 | /// Otherwise, puts a new item with undefined value, and | |
| 132 | /// the `Entry` pointer points to it. Caller should then initialize | |
| 133 | /// the value (but not the key). | |
| 134 | pub fn getOrPut(self: *Self, key: K) !GetOrPutResult { | |
| 135 | return self.unmanaged.getOrPut(self.allocator, key); | |
| 136 | } | |
| 137 | ||
| 138 | /// If there is an existing item with `key`, then the result | |
| 139 | /// `Entry` pointer points to it, and found_existing is true. | |
| 140 | /// Otherwise, puts a new item with undefined value, and | |
| 141 | /// the `Entry` pointer points to it. Caller should then initialize | |
| 142 | /// the value (but not the key). | |
| 143 | /// If a new entry needs to be stored, this function asserts there | |
| 144 | /// is enough capacity to store it. | |
| 145 | pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { | |
| 146 | return self.unmanaged.getOrPutAssumeCapacity(key); | |
| 147 | } | |
| 148 | ||
| 149 | pub fn getOrPutValue(self: *Self, key: K, value: V) !*Entry { | |
| 150 | return self.unmanaged.getOrPutValue(self.allocator, key, value); | |
| 151 | } | |
| 152 | ||
| 153 | /// Increases capacity, guaranteeing that insertions up until the | |
| 154 | /// `expected_count` will not cause an allocation, and therefore cannot fail. | |
| 155 | pub fn ensureCapacity(self: *Self, new_capacity: usize) !void { | |
| 156 | return self.unmanaged.ensureCapacity(self.allocator, new_capacity); | |
| 157 | } | |
| 158 | ||
| 159 | /// Returns the number of total elements which may be present before it is | |
| 160 | /// no longer guaranteed that no allocations will be performed. | |
| 161 | pub fn capacity(self: *Self) usize { | |
| 162 | return self.unmanaged.capacity(); | |
| 163 | } | |
| 164 | ||
| 165 | /// Clobbers any existing data. To detect if a put would clobber | |
| 166 | /// existing data, see `getOrPut`. | |
| 167 | pub fn put(self: *Self, key: K, value: V) !void { | |
| 168 | return self.unmanaged.put(self.allocator, key, value); | |
| 169 | } | |
| 170 | ||
| 171 | /// Inserts a key-value pair into the hash map, asserting that no previous | |
| 172 | /// entry with the same key is already present | |
| 173 | pub fn putNoClobber(self: *Self, key: K, value: V) !void { | |
| 174 | return self.unmanaged.putNoClobber(self.allocator, key, value); | |
| 175 | } | |
| 176 | ||
| 177 | /// Asserts there is enough capacity to store the new key-value pair. | |
| 178 | /// Clobbers any existing data. To detect if a put would clobber | |
| 179 | /// existing data, see `getOrPutAssumeCapacity`. | |
| 180 | pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { | |
| 181 | return self.unmanaged.putAssumeCapacity(key, value); | |
| 182 | } | |
| 183 | ||
| 184 | /// Asserts there is enough capacity to store the new key-value pair. | |
| 185 | /// Asserts that it does not clobber any existing data. | |
| 186 | /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`. | |
| 187 | pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { | |
| 188 | return self.unmanaged.putAssumeCapacityNoClobber(key, value); | |
| 189 | } | |
| 190 | ||
| 191 | /// Inserts a new `Entry` into the hash map, returning the previous one, if any. | |
| 192 | pub fn fetchPut(self: *Self, key: K, value: V) !?Entry { | |
| 193 | return self.unmanaged.fetchPut(self.allocator, key, value); | |
| 194 | } | |
| 195 | ||
| 196 | /// Inserts a new `Entry` into the hash map, returning the previous one, if any. | |
| 197 | /// If insertion happuns, asserts there is enough capacity without allocating. | |
| 198 | pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry { | |
| 199 | return self.unmanaged.fetchPutAssumeCapacity(key, value); | |
| 200 | } | |
| 201 | ||
| 202 | pub fn getEntry(self: Self, key: K) ?*Entry { | |
| 203 | return self.unmanaged.getEntry(key); | |
| 204 | } | |
| 205 | ||
| 206 | pub fn getIndex(self: Self, key: K) ?usize { | |
| 207 | return self.unmanaged.getIndex(key); | |
| 208 | } | |
| 209 | ||
| 210 | pub fn get(self: Self, key: K) ?V { | |
| 211 | return self.unmanaged.get(key); | |
| 212 | } | |
| 213 | ||
| 214 | pub fn contains(self: Self, key: K) bool { | |
| 215 | return self.unmanaged.contains(key); | |
| 216 | } | |
| 217 | ||
| 218 | /// If there is an `Entry` with a matching key, it is deleted from | |
| 219 | /// the hash map, and then returned from this function. | |
| 220 | pub fn remove(self: *Self, key: K) ?Entry { | |
| 221 | return self.unmanaged.remove(key); | |
| 222 | } | |
| 223 | ||
| 224 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, | |
| 225 | /// and discards it. | |
| 226 | pub fn removeAssertDiscard(self: *Self, key: K) void { | |
| 227 | return self.unmanaged.removeAssertDiscard(key); | |
| 228 | } | |
| 229 | ||
| 230 | pub fn items(self: Self) []Entry { | |
| 231 | return self.unmanaged.items(); | |
| 232 | } | |
| 233 | ||
| 234 | pub fn clone(self: Self) !Self { | |
| 235 | var other = try self.unmanaged.clone(self.allocator); | |
| 236 | return other.promote(self.allocator); | |
| 237 | } | |
| 238 | }; | |
| 239 | } | |
| 240 | ||
| 241 | /// General purpose hash table. | |
| 242 | /// Insertion order is preserved. | |
| 243 | /// Deletions perform a "swap removal" on the entries list. | |
| 244 | /// Modifying the hash map while iterating is allowed, however one must understand | |
| 245 | /// the (well defined) behavior when mixing insertions and deletions with iteration. | |
| 246 | /// This type does not store an Allocator field - the Allocator must be passed in | |
| 247 | /// with each function call that requires it. See `ArrayHashMap` for a type that stores | |
| 248 | /// an Allocator field for convenience. | |
| 249 | /// Can be initialized directly using the default field values. | |
| 250 | /// This type is designed to have low overhead for small numbers of entries. When | |
| 251 | /// `store_hash` is `false` and the number of entries in the map is less than 9, | |
| 252 | /// the overhead cost of using `ArrayHashMapUnmanaged` rather than `std.ArrayList` is | |
| 253 | /// only a single pointer-sized integer. | |
| 254 | /// When `store_hash` is `false`, this data structure is biased towards cheap `eql` | |
| 255 | /// functions. It does not store each item's hash in the table. Setting `store_hash` | |
| 256 | /// to `true` incurs slightly more memory cost by storing each key's hash in the table | |
| 257 | /// but guarantees only one call to `eql` per insertion/deletion. | |
| 258 | pub fn ArrayHashMapUnmanaged( | |
| 259 | comptime K: type, | |
| 260 | comptime V: type, | |
| 261 | comptime hash: fn (key: K) u32, | |
| 262 | comptime eql: fn (a: K, b: K) bool, | |
| 263 | comptime store_hash: bool, | |
| 264 | ) type { | |
| 265 | return struct { | |
| 266 | /// It is permitted to access this field directly. | |
| 267 | entries: std.ArrayListUnmanaged(Entry) = .{}, | |
| 268 | ||
| 269 | /// When entries length is less than `linear_scan_max`, this remains `null`. | |
| 270 | /// Once entries length grows big enough, this field is allocated. There is | |
| 271 | /// an IndexHeader followed by an array of Index(I) structs, where I is defined | |
| 272 | /// by how many total indexes there are. | |
| 273 | index_header: ?*IndexHeader = null, | |
| 274 | ||
| 275 | /// Modifying the key is illegal behavior. | |
| 276 | /// Modifying the value is allowed. | |
| 277 | /// Entry pointers become invalid whenever this ArrayHashMap is modified, | |
| 278 | /// unless `ensureCapacity` was previously used. | |
| 279 | pub const Entry = struct { | |
| 280 | /// This field is `void` if `store_hash` is `false`. | |
| 281 | hash: Hash, | |
| 282 | key: K, | |
| 283 | value: V, | |
| 284 | }; | |
| 285 | ||
| 286 | pub const Hash = if (store_hash) u32 else void; | |
| 287 | ||
| 288 | pub const GetOrPutResult = struct { | |
| 289 | entry: *Entry, | |
| 290 | found_existing: bool, | |
| 291 | }; | |
| 292 | ||
| 293 | pub const Managed = ArrayHashMap(K, V, hash, eql, store_hash); | |
| 294 | ||
| 295 | const Self = @This(); | |
| 296 | ||
| 297 | const linear_scan_max = 8; | |
| 298 | ||
| 299 | pub fn promote(self: Self, allocator: *Allocator) Managed { | |
| 300 | return .{ | |
| 301 | .unmanaged = self, | |
| 302 | .allocator = allocator, | |
| 303 | }; | |
| 304 | } | |
| 305 | ||
| 306 | pub fn deinit(self: *Self, allocator: *Allocator) void { | |
| 307 | self.entries.deinit(allocator); | |
| 308 | if (self.index_header) |header| { | |
| 309 | header.free(allocator); | |
| 310 | } | |
| 311 | self.* = undefined; | |
| 312 | } | |
| 313 | ||
| 314 | pub fn clearRetainingCapacity(self: *Self) void { | |
| 315 | self.entries.items.len = 0; | |
| 316 | if (self.index_header) |header| { | |
| 317 | header.max_distance_from_start_index = 0; | |
| 318 | switch (header.capacityIndexType()) { | |
| 319 | .u8 => mem.set(Index(u8), header.indexes(u8), Index(u8).empty), | |
| 320 | .u16 => mem.set(Index(u16), header.indexes(u16), Index(u16).empty), | |
| 321 | .u32 => mem.set(Index(u32), header.indexes(u32), Index(u32).empty), | |
| 322 | .usize => mem.set(Index(usize), header.indexes(usize), Index(usize).empty), | |
| 323 | } | |
| 324 | } | |
| 325 | } | |
| 326 | ||
| 327 | pub fn clearAndFree(self: *Self, allocator: *Allocator) void { | |
| 328 | self.entries.shrink(allocator, 0); | |
| 329 | if (self.index_header) |header| { | |
| 330 | header.free(allocator); | |
| 331 | self.index_header = null; | |
| 332 | } | |
| 333 | } | |
| 334 | ||
| 335 | /// If key exists this function cannot fail. | |
| 336 | /// If there is an existing item with `key`, then the result | |
| 337 | /// `Entry` pointer points to it, and found_existing is true. | |
| 338 | /// Otherwise, puts a new item with undefined value, and | |
| 339 | /// the `Entry` pointer points to it. Caller should then initialize | |
| 340 | /// the value (but not the key). | |
| 341 | pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { | |
| 342 | self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| { | |
| 343 | // "If key exists this function cannot fail." | |
| 344 | return GetOrPutResult{ | |
| 345 | .entry = self.getEntry(key) orelse return err, | |
| 346 | .found_existing = true, | |
| 347 | }; | |
| 348 | }; | |
| 349 | return self.getOrPutAssumeCapacity(key); | |
| 350 | } | |
| 351 | ||
| 352 | /// If there is an existing item with `key`, then the result | |
| 353 | /// `Entry` pointer points to it, and found_existing is true. | |
| 354 | /// Otherwise, puts a new item with undefined value, and | |
| 355 | /// the `Entry` pointer points to it. Caller should then initialize | |
| 356 | /// the value (but not the key). | |
| 357 | /// If a new entry needs to be stored, this function asserts there | |
| 358 | /// is enough capacity to store it. | |
| 359 | pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { | |
| 360 | const header = self.index_header orelse { | |
| 361 | // Linear scan. | |
| 362 | const h = if (store_hash) hash(key) else {}; | |
| 363 | for (self.entries.items) |*item| { | |
| 364 | if (item.hash == h and eql(key, item.key)) { | |
| 365 | return GetOrPutResult{ | |
| 366 | .entry = item, | |
| 367 | .found_existing = true, | |
| 368 | }; | |
| 369 | } | |
| 370 | } | |
| 371 | const new_entry = self.entries.addOneAssumeCapacity(); | |
| 372 | new_entry.* = .{ | |
| 373 | .hash = if (store_hash) h else {}, | |
| 374 | .key = key, | |
| 375 | .value = undefined, | |
| 376 | }; | |
| 377 | return GetOrPutResult{ | |
| 378 | .entry = new_entry, | |
| 379 | .found_existing = false, | |
| 380 | }; | |
| 381 | }; | |
| 382 | ||
| 383 | switch (header.capacityIndexType()) { | |
| 384 | .u8 => return self.getOrPutInternal(key, header, u8), | |
| 385 | .u16 => return self.getOrPutInternal(key, header, u16), | |
| 386 | .u32 => return self.getOrPutInternal(key, header, u32), | |
| 387 | .usize => return self.getOrPutInternal(key, header, usize), | |
| 388 | } | |
| 389 | } | |
| 390 | ||
| 391 | pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry { | |
| 392 | const res = try self.getOrPut(allocator, key); | |
| 393 | if (!res.found_existing) | |
| 394 | res.entry.value = value; | |
| 395 | ||
| 396 | return res.entry; | |
| 397 | } | |
| 398 | ||
| 399 | /// Increases capacity, guaranteeing that insertions up until the | |
| 400 | /// `expected_count` will not cause an allocation, and therefore cannot fail. | |
| 401 | pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void { | |
| 402 | try self.entries.ensureCapacity(allocator, new_capacity); | |
| 403 | if (new_capacity <= linear_scan_max) return; | |
| 404 | ||
| 405 | // Ensure that the indexes will be at most 60% full if | |
| 406 | // `new_capacity` items are put into it. | |
| 407 | const needed_len = new_capacity * 5 / 3; | |
| 408 | if (self.index_header) |header| { | |
| 409 | if (needed_len > header.indexes_len) { | |
| 410 | // An overflow here would mean the amount of memory required would not | |
| 411 | // be representable in the address space. | |
| 412 | const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable; | |
| 413 | const new_header = try IndexHeader.alloc(allocator, new_indexes_len); | |
| 414 | self.insertAllEntriesIntoNewHeader(new_header); | |
| 415 | header.free(allocator); | |
| 416 | self.index_header = new_header; | |
| 417 | } | |
| 418 | } else { | |
| 419 | // An overflow here would mean the amount of memory required would not | |
| 420 | // be representable in the address space. | |
| 421 | const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable; | |
| 422 | const header = try IndexHeader.alloc(allocator, new_indexes_len); | |
| 423 | self.insertAllEntriesIntoNewHeader(header); | |
| 424 | self.index_header = header; | |
| 425 | } | |
| 426 | } | |
| 427 | ||
| 428 | /// Returns the number of total elements which may be present before it is | |
| 429 | /// no longer guaranteed that no allocations will be performed. | |
| 430 | pub fn capacity(self: Self) usize { | |
| 431 | const entry_cap = self.entries.capacity; | |
| 432 | const header = self.index_header orelse return math.min(linear_scan_max, entry_cap); | |
| 433 | const indexes_cap = (header.indexes_len + 1) * 3 / 4; | |
| 434 | return math.min(entry_cap, indexes_cap); | |
| 435 | } | |
| 436 | ||
| 437 | /// Clobbers any existing data. To detect if a put would clobber | |
| 438 | /// existing data, see `getOrPut`. | |
| 439 | pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void { | |
| 440 | const result = try self.getOrPut(allocator, key); | |
| 441 | result.entry.value = value; | |
| 442 | } | |
| 443 | ||
| 444 | /// Inserts a key-value pair into the hash map, asserting that no previous | |
| 445 | /// entry with the same key is already present | |
| 446 | pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void { | |
| 447 | const result = try self.getOrPut(allocator, key); | |
| 448 | assert(!result.found_existing); | |
| 449 | result.entry.value = value; | |
| 450 | } | |
| 451 | ||
| 452 | /// Asserts there is enough capacity to store the new key-value pair. | |
| 453 | /// Clobbers any existing data. To detect if a put would clobber | |
| 454 | /// existing data, see `getOrPutAssumeCapacity`. | |
| 455 | pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { | |
| 456 | const result = self.getOrPutAssumeCapacity(key); | |
| 457 | result.entry.value = value; | |
| 458 | } | |
| 459 | ||
| 460 | /// Asserts there is enough capacity to store the new key-value pair. | |
| 461 | /// Asserts that it does not clobber any existing data. | |
| 462 | /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`. | |
| 463 | pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { | |
| 464 | const result = self.getOrPutAssumeCapacity(key); | |
| 465 | assert(!result.found_existing); | |
| 466 | result.entry.value = value; | |
| 467 | } | |
| 468 | ||
| 469 | /// Inserts a new `Entry` into the hash map, returning the previous one, if any. | |
| 470 | pub fn fetchPut(self: *Self, allocator: *Allocator, key: K, value: V) !?Entry { | |
| 471 | const gop = try self.getOrPut(allocator, key); | |
| 472 | var result: ?Entry = null; | |
| 473 | if (gop.found_existing) { | |
| 474 | result = gop.entry.*; | |
| 475 | } | |
| 476 | gop.entry.value = value; | |
| 477 | return result; | |
| 478 | } | |
| 479 | ||
| 480 | /// Inserts a new `Entry` into the hash map, returning the previous one, if any. | |
| 481 | /// If insertion happens, asserts there is enough capacity without allocating. | |
| 482 | pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry { | |
| 483 | const gop = self.getOrPutAssumeCapacity(key); | |
| 484 | var result: ?Entry = null; | |
| 485 | if (gop.found_existing) { | |
| 486 | result = gop.entry.*; | |
| 487 | } | |
| 488 | gop.entry.value = value; | |
| 489 | return result; | |
| 490 | } | |
| 491 | ||
| 492 | pub fn getEntry(self: Self, key: K) ?*Entry { | |
| 493 | const index = self.getIndex(key) orelse return null; | |
| 494 | return &self.entries.items[index]; | |
| 495 | } | |
| 496 | ||
| 497 | pub fn getIndex(self: Self, key: K) ?usize { | |
| 498 | const header = self.index_header orelse { | |
| 499 | // Linear scan. | |
| 500 | const h = if (store_hash) hash(key) else {}; | |
| 501 | for (self.entries.items) |*item, i| { | |
| 502 | if (item.hash == h and eql(key, item.key)) { | |
| 503 | return i; | |
| 504 | } | |
| 505 | } | |
| 506 | return null; | |
| 507 | }; | |
| 508 | switch (header.capacityIndexType()) { | |
| 509 | .u8 => return self.getInternal(key, header, u8), | |
| 510 | .u16 => return self.getInternal(key, header, u16), | |
| 511 | .u32 => return self.getInternal(key, header, u32), | |
| 512 | .usize => return self.getInternal(key, header, usize), | |
| 513 | } | |
| 514 | } | |
| 515 | ||
| 516 | pub fn get(self: Self, key: K) ?V { | |
| 517 | return if (self.getEntry(key)) |entry| entry.value else null; | |
| 518 | } | |
| 519 | ||
| 520 | pub fn contains(self: Self, key: K) bool { | |
| 521 | return self.getEntry(key) != null; | |
| 522 | } | |
| 523 | ||
| 524 | /// If there is an `Entry` with a matching key, it is deleted from | |
| 525 | /// the hash map, and then returned from this function. | |
| 526 | pub fn remove(self: *Self, key: K) ?Entry { | |
| 527 | const header = self.index_header orelse { | |
| 528 | // Linear scan. | |
| 529 | const h = if (store_hash) hash(key) else {}; | |
| 530 | for (self.entries.items) |item, i| { | |
| 531 | if (item.hash == h and eql(key, item.key)) { | |
| 532 | return self.entries.swapRemove(i); | |
| 533 | } | |
| 534 | } | |
| 535 | return null; | |
| 536 | }; | |
| 537 | switch (header.capacityIndexType()) { | |
| 538 | .u8 => return self.removeInternal(key, header, u8), | |
| 539 | .u16 => return self.removeInternal(key, header, u16), | |
| 540 | .u32 => return self.removeInternal(key, header, u32), | |
| 541 | .usize => return self.removeInternal(key, header, usize), | |
| 542 | } | |
| 543 | } | |
| 544 | ||
| 545 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, | |
| 546 | /// and discards it. | |
| 547 | pub fn removeAssertDiscard(self: *Self, key: K) void { | |
| 548 | assert(self.remove(key) != null); | |
| 549 | } | |
| 550 | ||
| 551 | pub fn items(self: Self) []Entry { | |
| 552 | return self.entries.items; | |
| 553 | } | |
| 554 | ||
| 555 | pub fn clone(self: Self, allocator: *Allocator) !Self { | |
| 556 | var other: Self = .{}; | |
| 557 | try other.entries.appendSlice(allocator, self.entries.items); | |
| 558 | ||
| 559 | if (self.index_header) |header| { | |
| 560 | const new_header = try IndexHeader.alloc(allocator, header.indexes_len); | |
| 561 | other.insertAllEntriesIntoNewHeader(new_header); | |
| 562 | other.index_header = new_header; | |
| 563 | } | |
| 564 | return other; | |
| 565 | } | |
| 566 | ||
| 567 | fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry { | |
| 568 | const indexes = header.indexes(I); | |
| 569 | const h = hash(key); | |
| 570 | const start_index = header.constrainIndex(h); | |
| 571 | var roll_over: usize = 0; | |
| 572 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { | |
| 573 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 574 | var index = &indexes[index_index]; | |
| 575 | if (index.isEmpty()) | |
| 576 | return null; | |
| 577 | ||
| 578 | const entry = &self.entries.items[index.entry_index]; | |
| 579 | ||
| 580 | const hash_match = if (store_hash) h == entry.hash else true; | |
| 581 | if (!hash_match or !eql(key, entry.key)) | |
| 582 | continue; | |
| 583 | ||
| 584 | const removed_entry = self.entries.swapRemove(index.entry_index); | |
| 585 | if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) { | |
| 586 | // Because of the swap remove, now we need to update the index that was | |
| 587 | // pointing to the last entry and is now pointing to this removed item slot. | |
| 588 | self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes); | |
| 589 | } | |
| 590 | ||
| 591 | // Now we have to shift over the following indexes. | |
| 592 | roll_over += 1; | |
| 593 | while (roll_over < header.indexes_len) : (roll_over += 1) { | |
| 594 | const next_index_index = header.constrainIndex(start_index + roll_over); | |
| 595 | const next_index = &indexes[next_index_index]; | |
| 596 | if (next_index.isEmpty() or next_index.distance_from_start_index == 0) { | |
| 597 | index.setEmpty(); | |
| 598 | return removed_entry; | |
| 599 | } | |
| 600 | index.* = next_index.*; | |
| 601 | index.distance_from_start_index -= 1; | |
| 602 | index = next_index; | |
| 603 | } | |
| 604 | unreachable; | |
| 605 | } | |
| 606 | return null; | |
| 607 | } | |
| 608 | ||
| 609 | fn updateEntryIndex( | |
| 610 | self: *Self, | |
| 611 | header: *IndexHeader, | |
| 612 | old_entry_index: usize, | |
| 613 | new_entry_index: usize, | |
| 614 | comptime I: type, | |
| 615 | indexes: []Index(I), | |
| 616 | ) void { | |
| 617 | const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key); | |
| 618 | const start_index = header.constrainIndex(h); | |
| 619 | var roll_over: usize = 0; | |
| 620 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { | |
| 621 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 622 | const index = &indexes[index_index]; | |
| 623 | if (index.entry_index == old_entry_index) { | |
| 624 | index.entry_index = @intCast(I, new_entry_index); | |
| 625 | return; | |
| 626 | } | |
| 627 | } | |
| 628 | unreachable; | |
| 629 | } | |
| 630 | ||
| 631 | /// Must ensureCapacity before calling this. | |
| 632 | fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult { | |
| 633 | const indexes = header.indexes(I); | |
| 634 | const h = hash(key); | |
| 635 | const start_index = header.constrainIndex(h); | |
| 636 | var roll_over: usize = 0; | |
| 637 | var distance_from_start_index: usize = 0; | |
| 638 | while (roll_over <= header.indexes_len) : ({ | |
| 639 | roll_over += 1; | |
| 640 | distance_from_start_index += 1; | |
| 641 | }) { | |
| 642 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 643 | const index = indexes[index_index]; | |
| 644 | if (index.isEmpty()) { | |
| 645 | indexes[index_index] = .{ | |
| 646 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 647 | .entry_index = @intCast(I, self.entries.items.len), | |
| 648 | }; | |
| 649 | header.maybeBumpMax(distance_from_start_index); | |
| 650 | const new_entry = self.entries.addOneAssumeCapacity(); | |
| 651 | new_entry.* = .{ | |
| 652 | .hash = if (store_hash) h else {}, | |
| 653 | .key = key, | |
| 654 | .value = undefined, | |
| 655 | }; | |
| 656 | return .{ | |
| 657 | .found_existing = false, | |
| 658 | .entry = new_entry, | |
| 659 | }; | |
| 660 | } | |
| 661 | ||
| 662 | // This pointer survives the following append because we call | |
| 663 | // entries.ensureCapacity before getOrPutInternal. | |
| 664 | const entry = &self.entries.items[index.entry_index]; | |
| 665 | const hash_match = if (store_hash) h == entry.hash else true; | |
| 666 | if (hash_match and eql(key, entry.key)) { | |
| 667 | return .{ | |
| 668 | .found_existing = true, | |
| 669 | .entry = entry, | |
| 670 | }; | |
| 671 | } | |
| 672 | if (index.distance_from_start_index < distance_from_start_index) { | |
| 673 | // In this case, we did not find the item. We will put a new entry. | |
| 674 | // However, we will use this index for the new entry, and move | |
| 675 | // the previous index down the line, to keep the max_distance_from_start_index | |
| 676 | // as small as possible. | |
| 677 | indexes[index_index] = .{ | |
| 678 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 679 | .entry_index = @intCast(I, self.entries.items.len), | |
| 680 | }; | |
| 681 | header.maybeBumpMax(distance_from_start_index); | |
| 682 | const new_entry = self.entries.addOneAssumeCapacity(); | |
| 683 | new_entry.* = .{ | |
| 684 | .hash = if (store_hash) h else {}, | |
| 685 | .key = key, | |
| 686 | .value = undefined, | |
| 687 | }; | |
| 688 | ||
| 689 | distance_from_start_index = index.distance_from_start_index; | |
| 690 | var prev_entry_index = index.entry_index; | |
| 691 | ||
| 692 | // Find somewhere to put the index we replaced by shifting | |
| 693 | // following indexes backwards. | |
| 694 | roll_over += 1; | |
| 695 | distance_from_start_index += 1; | |
| 696 | while (roll_over < header.indexes_len) : ({ | |
| 697 | roll_over += 1; | |
| 698 | distance_from_start_index += 1; | |
| 699 | }) { | |
| 700 | const next_index_index = header.constrainIndex(start_index + roll_over); | |
| 701 | const next_index = indexes[next_index_index]; | |
| 702 | if (next_index.isEmpty()) { | |
| 703 | header.maybeBumpMax(distance_from_start_index); | |
| 704 | indexes[next_index_index] = .{ | |
| 705 | .entry_index = prev_entry_index, | |
| 706 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 707 | }; | |
| 708 | return .{ | |
| 709 | .found_existing = false, | |
| 710 | .entry = new_entry, | |
| 711 | }; | |
| 712 | } | |
| 713 | if (next_index.distance_from_start_index < distance_from_start_index) { | |
| 714 | header.maybeBumpMax(distance_from_start_index); | |
| 715 | indexes[next_index_index] = .{ | |
| 716 | .entry_index = prev_entry_index, | |
| 717 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 718 | }; | |
| 719 | distance_from_start_index = next_index.distance_from_start_index; | |
| 720 | prev_entry_index = next_index.entry_index; | |
| 721 | } | |
| 722 | } | |
| 723 | unreachable; | |
| 724 | } | |
| 725 | } | |
| 726 | unreachable; | |
| 727 | } | |
| 728 | ||
| 729 | fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize { | |
| 730 | const indexes = header.indexes(I); | |
| 731 | const h = hash(key); | |
| 732 | const start_index = header.constrainIndex(h); | |
| 733 | var roll_over: usize = 0; | |
| 734 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { | |
| 735 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 736 | const index = indexes[index_index]; | |
| 737 | if (index.isEmpty()) | |
| 738 | return null; | |
| 739 | ||
| 740 | const entry = &self.entries.items[index.entry_index]; | |
| 741 | const hash_match = if (store_hash) h == entry.hash else true; | |
| 742 | if (hash_match and eql(key, entry.key)) | |
| 743 | return index.entry_index; | |
| 744 | } | |
| 745 | return null; | |
| 746 | } | |
| 747 | ||
| 748 | fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void { | |
| 749 | switch (header.capacityIndexType()) { | |
| 750 | .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8), | |
| 751 | .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16), | |
| 752 | .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32), | |
| 753 | .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize), | |
| 754 | } | |
| 755 | } | |
| 756 | ||
| 757 | fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, header: *IndexHeader, comptime I: type) void { | |
| 758 | const indexes = header.indexes(I); | |
| 759 | entry_loop: for (self.entries.items) |entry, i| { | |
| 760 | const h = if (store_hash) entry.hash else hash(entry.key); | |
| 761 | const start_index = header.constrainIndex(h); | |
| 762 | var entry_index = i; | |
| 763 | var roll_over: usize = 0; | |
| 764 | var distance_from_start_index: usize = 0; | |
| 765 | while (roll_over < header.indexes_len) : ({ | |
| 766 | roll_over += 1; | |
| 767 | distance_from_start_index += 1; | |
| 768 | }) { | |
| 769 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 770 | const next_index = indexes[index_index]; | |
| 771 | if (next_index.isEmpty()) { | |
| 772 | header.maybeBumpMax(distance_from_start_index); | |
| 773 | indexes[index_index] = .{ | |
| 774 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 775 | .entry_index = @intCast(I, entry_index), | |
| 776 | }; | |
| 777 | continue :entry_loop; | |
| 778 | } | |
| 779 | if (next_index.distance_from_start_index < distance_from_start_index) { | |
| 780 | header.maybeBumpMax(distance_from_start_index); | |
| 781 | indexes[index_index] = .{ | |
| 782 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 783 | .entry_index = @intCast(I, entry_index), | |
| 784 | }; | |
| 785 | distance_from_start_index = next_index.distance_from_start_index; | |
| 786 | entry_index = next_index.entry_index; | |
| 787 | } | |
| 788 | } | |
| 789 | unreachable; | |
| 790 | } | |
| 791 | } | |
| 792 | }; | |
| 793 | } | |
| 794 | ||
| 795 | const CapacityIndexType = enum { u8, u16, u32, usize }; | |
| 796 | ||
| 797 | fn capacityIndexType(indexes_len: usize) CapacityIndexType { | |
| 798 | if (indexes_len < math.maxInt(u8)) | |
| 799 | return .u8; | |
| 800 | if (indexes_len < math.maxInt(u16)) | |
| 801 | return .u16; | |
| 802 | if (indexes_len < math.maxInt(u32)) | |
| 803 | return .u32; | |
| 804 | return .usize; | |
| 805 | } | |
| 806 | ||
| 807 | fn capacityIndexSize(indexes_len: usize) usize { | |
| 808 | switch (capacityIndexType(indexes_len)) { | |
| 809 | .u8 => return @sizeOf(Index(u8)), | |
| 810 | .u16 => return @sizeOf(Index(u16)), | |
| 811 | .u32 => return @sizeOf(Index(u32)), | |
| 812 | .usize => return @sizeOf(Index(usize)), | |
| 813 | } | |
| 814 | } | |
| 815 | ||
| 816 | fn Index(comptime I: type) type { | |
| 817 | return extern struct { | |
| 818 | entry_index: I, | |
| 819 | distance_from_start_index: I, | |
| 820 | ||
| 821 | const Self = @This(); | |
| 822 | ||
| 823 | const empty = Self{ | |
| 824 | .entry_index = math.maxInt(I), | |
| 825 | .distance_from_start_index = undefined, | |
| 826 | }; | |
| 827 | ||
| 828 | fn isEmpty(idx: Self) bool { | |
| 829 | return idx.entry_index == math.maxInt(I); | |
| 830 | } | |
| 831 | ||
| 832 | fn setEmpty(idx: *Self) void { | |
| 833 | idx.entry_index = math.maxInt(I); | |
| 834 | } | |
| 835 | }; | |
| 836 | } | |
| 837 | ||
| 838 | /// This struct is trailed by an array of `Index(I)`, where `I` | |
| 839 | /// and the array length are determined by `indexes_len`. | |
| 840 | const IndexHeader = struct { | |
| 841 | max_distance_from_start_index: usize, | |
| 842 | indexes_len: usize, | |
| 843 | ||
| 844 | fn constrainIndex(header: IndexHeader, i: usize) usize { | |
| 845 | // This is an optimization for modulo of power of two integers; | |
| 846 | // it requires `indexes_len` to always be a power of two. | |
| 847 | return i & (header.indexes_len - 1); | |
| 848 | } | |
| 849 | ||
| 850 | fn indexes(header: *IndexHeader, comptime I: type) []Index(I) { | |
| 851 | const start = @ptrCast([*]Index(I), @ptrCast([*]u8, header) + @sizeOf(IndexHeader)); | |
| 852 | return start[0..header.indexes_len]; | |
| 853 | } | |
| 854 | ||
| 855 | fn capacityIndexType(header: IndexHeader) CapacityIndexType { | |
| 856 | return hash_map.capacityIndexType(header.indexes_len); | |
| 857 | } | |
| 858 | ||
| 859 | fn maybeBumpMax(header: *IndexHeader, distance_from_start_index: usize) void { | |
| 860 | if (distance_from_start_index > header.max_distance_from_start_index) { | |
| 861 | header.max_distance_from_start_index = distance_from_start_index; | |
| 862 | } | |
| 863 | } | |
| 864 | ||
| 865 | fn alloc(allocator: *Allocator, len: usize) !*IndexHeader { | |
| 866 | const index_size = hash_map.capacityIndexSize(len); | |
| 867 | const nbytes = @sizeOf(IndexHeader) + index_size * len; | |
| 868 | const bytes = try allocator.allocAdvanced(u8, @alignOf(IndexHeader), nbytes, .exact); | |
| 869 | @memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader)); | |
| 870 | const result = @ptrCast(*IndexHeader, bytes.ptr); | |
| 871 | result.* = .{ | |
| 872 | .max_distance_from_start_index = 0, | |
| 873 | .indexes_len = len, | |
| 874 | }; | |
| 875 | return result; | |
| 876 | } | |
| 877 | ||
| 878 | fn free(header: *IndexHeader, allocator: *Allocator) void { | |
| 879 | const index_size = hash_map.capacityIndexSize(header.indexes_len); | |
| 880 | const ptr = @ptrCast([*]u8, header); | |
| 881 | const slice = ptr[0 .. @sizeOf(IndexHeader) + header.indexes_len * index_size]; | |
| 882 | allocator.free(slice); | |
| 883 | } | |
| 884 | }; | |
| 885 | ||
| 886 | test "basic hash map usage" { | |
| 887 | var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); | |
| 888 | defer map.deinit(); | |
| 889 | ||
| 890 | testing.expect((try map.fetchPut(1, 11)) == null); | |
| 891 | testing.expect((try map.fetchPut(2, 22)) == null); | |
| 892 | testing.expect((try map.fetchPut(3, 33)) == null); | |
| 893 | testing.expect((try map.fetchPut(4, 44)) == null); | |
| 894 | ||
| 895 | try map.putNoClobber(5, 55); | |
| 896 | testing.expect((try map.fetchPut(5, 66)).?.value == 55); | |
| 897 | testing.expect((try map.fetchPut(5, 55)).?.value == 66); | |
| 898 | ||
| 899 | const gop1 = try map.getOrPut(5); | |
| 900 | testing.expect(gop1.found_existing == true); | |
| 901 | testing.expect(gop1.entry.value == 55); | |
| 902 | gop1.entry.value = 77; | |
| 903 | testing.expect(map.getEntry(5).?.value == 77); | |
| 904 | ||
| 905 | const gop2 = try map.getOrPut(99); | |
| 906 | testing.expect(gop2.found_existing == false); | |
| 907 | gop2.entry.value = 42; | |
| 908 | testing.expect(map.getEntry(99).?.value == 42); | |
| 909 | ||
| 910 | const gop3 = try map.getOrPutValue(5, 5); | |
| 911 | testing.expect(gop3.value == 77); | |
| 912 | ||
| 913 | const gop4 = try map.getOrPutValue(100, 41); | |
| 914 | testing.expect(gop4.value == 41); | |
| 915 | ||
| 916 | testing.expect(map.contains(2)); | |
| 917 | testing.expect(map.getEntry(2).?.value == 22); | |
| 918 | testing.expect(map.get(2).? == 22); | |
| 919 | ||
| 920 | const rmv1 = map.remove(2); | |
| 921 | testing.expect(rmv1.?.key == 2); | |
| 922 | testing.expect(rmv1.?.value == 22); | |
| 923 | testing.expect(map.remove(2) == null); | |
| 924 | testing.expect(map.getEntry(2) == null); | |
| 925 | testing.expect(map.get(2) == null); | |
| 926 | ||
| 927 | map.removeAssertDiscard(3); | |
| 928 | } | |
| 929 | ||
| 930 | test "iterator hash map" { | |
| 931 | // https://github.com/ziglang/zig/issues/5127 | |
| 932 | if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest; | |
| 933 | ||
| 934 | var reset_map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); | |
| 935 | defer reset_map.deinit(); | |
| 936 | ||
| 937 | // test ensureCapacity with a 0 parameter | |
| 938 | try reset_map.ensureCapacity(0); | |
| 939 | ||
| 940 | try reset_map.putNoClobber(0, 11); | |
| 941 | try reset_map.putNoClobber(1, 22); | |
| 942 | try reset_map.putNoClobber(2, 33); | |
| 943 | ||
| 944 | var keys = [_]i32{ | |
| 945 | 0, 2, 1, | |
| 946 | }; | |
| 947 | ||
| 948 | var values = [_]i32{ | |
| 949 | 11, 33, 22, | |
| 950 | }; | |
| 951 | ||
| 952 | var buffer = [_]i32{ | |
| 953 | 0, 0, 0, | |
| 954 | }; | |
| 955 | ||
| 956 | var it = reset_map.iterator(); | |
| 957 | const first_entry = it.next().?; | |
| 958 | it.reset(); | |
| 959 | ||
| 960 | var count: usize = 0; | |
| 961 | while (it.next()) |entry| : (count += 1) { | |
| 962 | buffer[@intCast(usize, entry.key)] = entry.value; | |
| 963 | } | |
| 964 | testing.expect(count == 3); | |
| 965 | testing.expect(it.next() == null); | |
| 966 | ||
| 967 | for (buffer) |v, i| { | |
| 968 | testing.expect(buffer[@intCast(usize, keys[i])] == values[i]); | |
| 969 | } | |
| 970 | ||
| 971 | it.reset(); | |
| 972 | count = 0; | |
| 973 | while (it.next()) |entry| { | |
| 974 | buffer[@intCast(usize, entry.key)] = entry.value; | |
| 975 | count += 1; | |
| 976 | if (count >= 2) break; | |
| 977 | } | |
| 978 | ||
| 979 | for (buffer[0..2]) |v, i| { | |
| 980 | testing.expect(buffer[@intCast(usize, keys[i])] == values[i]); | |
| 981 | } | |
| 982 | ||
| 983 | it.reset(); | |
| 984 | var entry = it.next().?; | |
| 985 | testing.expect(entry.key == first_entry.key); | |
| 986 | testing.expect(entry.value == first_entry.value); | |
| 987 | } | |
| 988 | ||
| 989 | test "ensure capacity" { | |
| 990 | var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); | |
| 991 | defer map.deinit(); | |
| 992 | ||
| 993 | try map.ensureCapacity(20); | |
| 994 | const initial_capacity = map.capacity(); | |
| 995 | testing.expect(initial_capacity >= 20); | |
| 996 | var i: i32 = 0; | |
| 997 | while (i < 20) : (i += 1) { | |
| 998 | testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null); | |
| 999 | } | |
| 1000 | // shouldn't resize from putAssumeCapacity | |
| 1001 | testing.expect(initial_capacity == map.capacity()); | |
| 1002 | } | |
| 1003 | ||
| 1004 | test "clone" { | |
| 1005 | var original = AutoArrayHashMap(i32, i32).init(std.testing.allocator); | |
| 1006 | defer original.deinit(); | |
| 1007 | ||
| 1008 | // put more than `linear_scan_max` so we can test that the index header is properly cloned | |
| 1009 | var i: u8 = 0; | |
| 1010 | while (i < 10) : (i += 1) { | |
| 1011 | try original.putNoClobber(i, i * 10); | |
| 1012 | } | |
| 1013 | ||
| 1014 | var copy = try original.clone(); | |
| 1015 | defer copy.deinit(); | |
| 1016 | ||
| 1017 | i = 0; | |
| 1018 | while (i < 10) : (i += 1) { | |
| 1019 | testing.expect(copy.get(i).? == i * 10); | |
| 1020 | } | |
| 1021 | } | |
| 1022 | ||
| 1023 | pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { | |
| 1024 | return struct { | |
| 1025 | fn hash(key: K) u32 { | |
| 1026 | return getAutoHashFn(usize)(@ptrToInt(key)); | |
| 1027 | } | |
| 1028 | }.hash; | |
| 1029 | } | |
| 1030 | ||
| 1031 | pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { | |
| 1032 | return struct { | |
| 1033 | fn eql(a: K, b: K) bool { | |
| 1034 | return a == b; | |
| 1035 | } | |
| 1036 | }.eql; | |
| 1037 | } | |
| 1038 | ||
| 1039 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { | |
| 1040 | return struct { | |
| 1041 | fn hash(key: K) u32 { | |
| 1042 | if (comptime trait.hasUniqueRepresentation(K)) { | |
| 1043 | return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key))); | |
| 1044 | } else { | |
| 1045 | var hasher = Wyhash.init(0); | |
| 1046 | autoHash(&hasher, key); | |
| 1047 | return @truncate(u32, hasher.final()); | |
| 1048 | } | |
| 1049 | } | |
| 1050 | }.hash; | |
| 1051 | } | |
| 1052 | ||
| 1053 | pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { | |
| 1054 | return struct { | |
| 1055 | fn eql(a: K, b: K) bool { | |
| 1056 | return meta.eql(a, b); | |
| 1057 | } | |
| 1058 | }.eql; | |
| 1059 | } | |
| 1060 | ||
| 1061 | pub fn autoEqlIsCheap(comptime K: type) bool { | |
| 1062 | return switch (@typeInfo(K)) { | |
| 1063 | .Bool, | |
| 1064 | .Int, | |
| 1065 | .Float, | |
| 1066 | .Pointer, | |
| 1067 | .ComptimeFloat, | |
| 1068 | .ComptimeInt, | |
| 1069 | .Enum, | |
| 1070 | .Fn, | |
| 1071 | .ErrorSet, | |
| 1072 | .AnyFrame, | |
| 1073 | .EnumLiteral, | |
| 1074 | => true, | |
| 1075 | else => false, | |
| 1076 | }; | |
| 1077 | } | |
| 1078 | ||
| 1079 | pub fn getAutoHashStratFn(comptime K: type, comptime strategy: std.hash.Strategy) (fn (K) u32) { | |
| 1080 | return struct { | |
| 1081 | fn hash(key: K) u32 { | |
| 1082 | var hasher = Wyhash.init(0); | |
| 1083 | std.hash.autoHashStrat(&hasher, key, strategy); | |
| 1084 | return @truncate(u32, hasher.final()); | |
| 1085 | } | |
| 1086 | }.hash; | |
| 1087 | } |
lib/std/buf_set.zig+2-1| ... | ... | @@ -20,7 +20,8 @@ pub const BufSet = struct { |
| 20 | 20 | } |
| 21 | 21 | |
| 22 | 22 | pub fn deinit(self: *BufSet) void { |
| 23 | for (self.hash_map.items()) |entry| { | |
| 23 | var it = self.hash_map.iterator(); | |
| 24 | while (it.next()) |entry| { | |
| 24 | 25 | self.free(entry.key); |
| 25 | 26 | } |
| 26 | 27 | self.hash_map.deinit(); |
lib/std/builtin.zig+1| ... | ... | @@ -261,6 +261,7 @@ pub const TypeInfo = union(enum) { |
| 261 | 261 | name: []const u8, |
| 262 | 262 | field_type: type, |
| 263 | 263 | default_value: anytype, |
| 264 | is_comptime: bool, | |
| 264 | 265 | }; |
| 265 | 266 | |
| 266 | 267 | /// This data structure is used by the Zig language code generation and |
lib/std/c.zig+5| ... | ... | @@ -330,3 +330,8 @@ pub const FILE = @Type(.Opaque); |
| 330 | 330 | pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*c_void; |
| 331 | 331 | pub extern "c" fn dlclose(handle: *c_void) c_int; |
| 332 | 332 | pub extern "c" fn dlsym(handle: ?*c_void, symbol: [*:0]const u8) ?*c_void; |
| 333 | ||
| 334 | pub extern "c" fn sync() void; | |
| 335 | pub extern "c" fn syncfs(fd: c_int) c_int; | |
| 336 | pub extern "c" fn fsync(fd: c_int) c_int; | |
| 337 | pub extern "c" fn fdatasync(fd: c_int) c_int; |
lib/std/child_process.zig+2-2| ... | ... | @@ -44,10 +44,10 @@ pub const ChildProcess = struct { |
| 44 | 44 | stderr_behavior: StdIo, |
| 45 | 45 | |
| 46 | 46 | /// Set to change the user id when spawning the child process. |
| 47 | uid: if (builtin.os.tag == .windows) void else ?u32, | |
| 47 | uid: if (builtin.os.tag == .windows or builtin.os.tag == .wasi) void else ?os.uid_t, | |
| 48 | 48 | |
| 49 | 49 | /// Set to change the group id when spawning the child process. |
| 50 | gid: if (builtin.os.tag == .windows) void else ?u32, | |
| 50 | gid: if (builtin.os.tag == .windows or builtin.os.tag == .wasi) void else ?os.gid_t, | |
| 51 | 51 | |
| 52 | 52 | /// Set to change the current working directory when spawning the child process. |
| 53 | 53 | cwd: ?[]const u8, |
lib/std/fmt.zig+17| ... | ... | @@ -66,6 +66,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool { |
| 66 | 66 | /// - output numeric value in hexadecimal notation |
| 67 | 67 | /// - `s`: print a pointer-to-many as a c-string, use zero-termination |
| 68 | 68 | /// - `B` and `Bi`: output a memory size in either metric (1000) or power-of-two (1024) based notation. works for both float and integer values. |
| 69 | /// - `e` and `E`: if printing a string, escape non-printable characters | |
| 69 | 70 | /// - `e`: output floating point value in scientific notation |
| 70 | 71 | /// - `d`: output numeric value in decimal notation |
| 71 | 72 | /// - `b`: output integer value in binary notation |
| ... | ... | @@ -599,6 +600,16 @@ pub fn formatText( |
| 599 | 600 | try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, writer); |
| 600 | 601 | } |
| 601 | 602 | return; |
| 603 | } else if (comptime (std.mem.eql(u8, fmt, "e") or std.mem.eql(u8, fmt, "E"))) { | |
| 604 | for (bytes) |c| { | |
| 605 | if (std.ascii.isPrint(c)) { | |
| 606 | try writer.writeByte(c); | |
| 607 | } else { | |
| 608 | try writer.writeAll("\\x"); | |
| 609 | try formatInt(c, 16, fmt[0] == 'E', FormatOptions{ .width = 2, .fill = '0' }, writer); | |
| 610 | } | |
| 611 | } | |
| 612 | return; | |
| 602 | 613 | } else { |
| 603 | 614 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| 604 | 615 | } |
| ... | ... | @@ -1319,6 +1330,12 @@ test "slice" { |
| 1319 | 1330 | try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); |
| 1320 | 1331 | } |
| 1321 | 1332 | |
| 1333 | test "escape non-printable" { | |
| 1334 | try testFmt("abc", "{e}", .{"abc"}); | |
| 1335 | try testFmt("ab\\xffc", "{e}", .{"ab\xffc"}); | |
| 1336 | try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"}); | |
| 1337 | } | |
| 1338 | ||
| 1322 | 1339 | test "pointer" { |
| 1323 | 1340 | { |
| 1324 | 1341 | const value = @intToPtr(*align(1) i32, 0xdeadbeef); |
lib/std/fmt/parse_float.zig+4-1| ... | ... | @@ -37,7 +37,9 @@ |
| 37 | 37 | const std = @import("../std.zig"); |
| 38 | 38 | const ascii = std.ascii; |
| 39 | 39 | |
| 40 | const max_digits = 25; | |
| 40 | // The mantissa field in FloatRepr is 64bit wide and holds only 19 digits | |
| 41 | // without overflowing | |
| 42 | const max_digits = 19; | |
| 41 | 43 | |
| 42 | 44 | const f64_plus_zero: u64 = 0x0000000000000000; |
| 43 | 45 | const f64_minus_zero: u64 = 0x8000000000000000; |
| ... | ... | @@ -409,6 +411,7 @@ test "fmt.parseFloat" { |
| 409 | 411 | expect(approxEq(T, try parseFloat(T, "123142.1"), 123142.1, epsilon)); |
| 410 | 412 | expect(approxEq(T, try parseFloat(T, "-123142.1124"), @as(T, -123142.1124), epsilon)); |
| 411 | 413 | expect(approxEq(T, try parseFloat(T, "0.7062146892655368"), @as(T, 0.7062146892655368), epsilon)); |
| 414 | expect(approxEq(T, try parseFloat(T, "2.71828182845904523536"), @as(T, 2.718281828459045), epsilon)); | |
| 412 | 415 | } |
| 413 | 416 | } |
| 414 | 417 | } |
lib/std/hash_map.zig+846-697| ... | ... | @@ -4,91 +4,94 @@ |
| 4 | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | 5 | // and substantial portions of the software. |
| 6 | 6 | const std = @import("std.zig"); |
| 7 | const debug = std.debug; | |
| 7 | const builtin = @import("builtin"); | |
| 8 | 8 | const assert = debug.assert; |
| 9 | const testing = std.testing; | |
| 9 | const autoHash = std.hash.autoHash; | |
| 10 | const debug = std.debug; | |
| 11 | const warn = debug.warn; | |
| 10 | 12 | const math = std.math; |
| 11 | 13 | const mem = std.mem; |
| 12 | 14 | const meta = std.meta; |
| 13 | 15 | const trait = meta.trait; |
| 14 | const autoHash = std.hash.autoHash; | |
| 15 | const Wyhash = std.hash.Wyhash; | |
| 16 | 16 | const Allocator = mem.Allocator; |
| 17 | const builtin = @import("builtin"); | |
| 18 | const hash_map = @This(); | |
| 17 | const Wyhash = std.hash.Wyhash; | |
| 18 | ||
| 19 | pub fn getAutoHashFn(comptime K: type) (fn (K) u64) { | |
| 20 | return struct { | |
| 21 | fn hash(key: K) u64 { | |
| 22 | if (comptime trait.hasUniqueRepresentation(K)) { | |
| 23 | return Wyhash.hash(0, std.mem.asBytes(&key)); | |
| 24 | } else { | |
| 25 | var hasher = Wyhash.init(0); | |
| 26 | autoHash(&hasher, key); | |
| 27 | return hasher.final(); | |
| 28 | } | |
| 29 | } | |
| 30 | }.hash; | |
| 31 | } | |
| 32 | ||
| 33 | pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { | |
| 34 | return struct { | |
| 35 | fn eql(a: K, b: K) bool { | |
| 36 | return meta.eql(a, b); | |
| 37 | } | |
| 38 | }.eql; | |
| 39 | } | |
| 19 | 40 | |
| 20 | 41 | pub fn AutoHashMap(comptime K: type, comptime V: type) type { |
| 21 | return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); | |
| 42 | return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage); | |
| 22 | 43 | } |
| 23 | 44 | |
| 24 | 45 | pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type { |
| 25 | return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); | |
| 46 | return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage); | |
| 26 | 47 | } |
| 27 | 48 | |
| 28 | 49 | /// Builtin hashmap for strings as keys. |
| 29 | 50 | pub fn StringHashMap(comptime V: type) type { |
| 30 | return HashMap([]const u8, V, hashString, eqlString, true); | |
| 51 | return HashMap([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage); | |
| 31 | 52 | } |
| 32 | 53 | |
| 33 | 54 | pub fn StringHashMapUnmanaged(comptime V: type) type { |
| 34 | return HashMapUnmanaged([]const u8, V, hashString, eqlString, true); | |
| 55 | return HashMapUnmanaged([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage); | |
| 35 | 56 | } |
| 36 | 57 | |
| 37 | 58 | pub fn eqlString(a: []const u8, b: []const u8) bool { |
| 38 | 59 | return mem.eql(u8, a, b); |
| 39 | 60 | } |
| 40 | 61 | |
| 41 | pub fn hashString(s: []const u8) u32 { | |
| 42 | return @truncate(u32, std.hash.Wyhash.hash(0, s)); | |
| 62 | pub fn hashString(s: []const u8) u64 { | |
| 63 | return std.hash.Wyhash.hash(0, s); | |
| 43 | 64 | } |
| 44 | 65 | |
| 45 | /// Insertion order is preserved. | |
| 46 | /// Deletions perform a "swap removal" on the entries list. | |
| 47 | /// Modifying the hash map while iterating is allowed, however one must understand | |
| 48 | /// the (well defined) behavior when mixing insertions and deletions with iteration. | |
| 66 | pub const DefaultMaxLoadPercentage = 80; | |
| 67 | ||
| 68 | /// General purpose hash table. | |
| 69 | /// No order is guaranteed and any modification invalidates live iterators. | |
| 70 | /// It provides fast operations (lookup, insertion, deletion) with quite high | |
| 71 | /// load factors (up to 80% by default) for a low memory usage. | |
| 49 | 72 | /// For a hash map that can be initialized directly that does not store an Allocator |
| 50 | 73 | /// field, see `HashMapUnmanaged`. |
| 51 | /// When `store_hash` is `false`, this data structure is biased towards cheap `eql` | |
| 52 | /// functions. It does not store each item's hash in the table. Setting `store_hash` | |
| 53 | /// to `true` incurs slightly more memory cost by storing each key's hash in the table | |
| 54 | /// but only has to call `eql` for hash collisions. | |
| 74 | /// If iterating over the table entries is a strong usecase and needs to be fast, | |
| 75 | /// prefer the alternative `std.ArrayHashMap`. | |
| 55 | 76 | pub fn HashMap( |
| 56 | 77 | comptime K: type, |
| 57 | 78 | comptime V: type, |
| 58 | comptime hash: fn (key: K) u32, | |
| 59 | comptime eql: fn (a: K, b: K) bool, | |
| 60 | comptime store_hash: bool, | |
| 79 | comptime hashFn: fn (key: K) u64, | |
| 80 | comptime eqlFn: fn (a: K, b: K) bool, | |
| 81 | comptime MaxLoadPercentage: u64, | |
| 61 | 82 | ) type { |
| 62 | 83 | return struct { |
| 63 | 84 | unmanaged: Unmanaged, |
| 64 | 85 | allocator: *Allocator, |
| 65 | 86 | |
| 66 | pub const Unmanaged = HashMapUnmanaged(K, V, hash, eql, store_hash); | |
| 87 | pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, MaxLoadPercentage); | |
| 67 | 88 | pub const Entry = Unmanaged.Entry; |
| 68 | 89 | pub const Hash = Unmanaged.Hash; |
| 90 | pub const Iterator = Unmanaged.Iterator; | |
| 91 | pub const Size = Unmanaged.Size; | |
| 69 | 92 | pub const GetOrPutResult = Unmanaged.GetOrPutResult; |
| 70 | 93 | |
| 71 | /// Deprecated. Iterate using `items`. | |
| 72 | pub const Iterator = struct { | |
| 73 | hm: *const Self, | |
| 74 | /// Iterator through the entry array. | |
| 75 | index: usize, | |
| 76 | ||
| 77 | pub fn next(it: *Iterator) ?*Entry { | |
| 78 | if (it.index >= it.hm.unmanaged.entries.items.len) return null; | |
| 79 | const result = &it.hm.unmanaged.entries.items[it.index]; | |
| 80 | it.index += 1; | |
| 81 | return result; | |
| 82 | } | |
| 83 | ||
| 84 | /// Reset the iterator to the initial index | |
| 85 | pub fn reset(it: *Iterator) void { | |
| 86 | it.index = 0; | |
| 87 | } | |
| 88 | }; | |
| 89 | ||
| 90 | 94 | const Self = @This(); |
| 91 | const Index = Unmanaged.Index; | |
| 92 | 95 | |
| 93 | 96 | pub fn init(allocator: *Allocator) Self { |
| 94 | 97 | return .{ |
| ... | ... | @@ -110,17 +113,12 @@ pub fn HashMap( |
| 110 | 113 | return self.unmanaged.clearAndFree(self.allocator); |
| 111 | 114 | } |
| 112 | 115 | |
| 113 | /// Deprecated. Use `items().len`. | |
| 114 | 116 | pub fn count(self: Self) usize { |
| 115 | return self.items().len; | |
| 117 | return self.unmanaged.count(); | |
| 116 | 118 | } |
| 117 | 119 | |
| 118 | /// Deprecated. Iterate using `items`. | |
| 119 | 120 | pub fn iterator(self: *const Self) Iterator { |
| 120 | return Iterator{ | |
| 121 | .hm = self, | |
| 122 | .index = 0, | |
| 123 | }; | |
| 121 | return self.unmanaged.iterator(); | |
| 124 | 122 | } |
| 125 | 123 | |
| 126 | 124 | /// If key exists this function cannot fail. |
| ... | ... | @@ -150,13 +148,13 @@ pub fn HashMap( |
| 150 | 148 | |
| 151 | 149 | /// Increases capacity, guaranteeing that insertions up until the |
| 152 | 150 | /// `expected_count` will not cause an allocation, and therefore cannot fail. |
| 153 | pub fn ensureCapacity(self: *Self, new_capacity: usize) !void { | |
| 154 | return self.unmanaged.ensureCapacity(self.allocator, new_capacity); | |
| 151 | pub fn ensureCapacity(self: *Self, expected_count: Size) !void { | |
| 152 | return self.unmanaged.ensureCapacity(self.allocator, expected_count); | |
| 155 | 153 | } |
| 156 | 154 | |
| 157 | 155 | /// Returns the number of total elements which may be present before it is |
| 158 | 156 | /// no longer guaranteed that no allocations will be performed. |
| 159 | pub fn capacity(self: *Self) usize { | |
| 157 | pub fn capacity(self: *Self) Size { | |
| 160 | 158 | return self.unmanaged.capacity(); |
| 161 | 159 | } |
| 162 | 160 | |
| ... | ... | @@ -197,18 +195,14 @@ pub fn HashMap( |
| 197 | 195 | return self.unmanaged.fetchPutAssumeCapacity(key, value); |
| 198 | 196 | } |
| 199 | 197 | |
| 200 | pub fn getEntry(self: Self, key: K) ?*Entry { | |
| 201 | return self.unmanaged.getEntry(key); | |
| 202 | } | |
| 203 | ||
| 204 | pub fn getIndex(self: Self, key: K) ?usize { | |
| 205 | return self.unmanaged.getIndex(key); | |
| 206 | } | |
| 207 | ||
| 208 | 198 | pub fn get(self: Self, key: K) ?V { |
| 209 | 199 | return self.unmanaged.get(key); |
| 210 | 200 | } |
| 211 | 201 | |
| 202 | pub fn getEntry(self: Self, key: K) ?*Entry { | |
| 203 | return self.unmanaged.getEntry(key); | |
| 204 | } | |
| 205 | ||
| 212 | 206 | pub fn contains(self: Self, key: K) bool { |
| 213 | 207 | return self.unmanaged.contains(key); |
| 214 | 208 | } |
| ... | ... | @@ -225,10 +219,6 @@ pub fn HashMap( |
| 225 | 219 | return self.unmanaged.removeAssertDiscard(key); |
| 226 | 220 | } |
| 227 | 221 | |
| 228 | pub fn items(self: Self) []Entry { | |
| 229 | return self.unmanaged.items(); | |
| 230 | } | |
| 231 | ||
| 232 | 222 | pub fn clone(self: Self) !Self { |
| 233 | 223 | var other = try self.unmanaged.clone(self.allocator); |
| 234 | 224 | return other.promote(self.allocator); |
| ... | ... | @@ -236,63 +226,152 @@ pub fn HashMap( |
| 236 | 226 | }; |
| 237 | 227 | } |
| 238 | 228 | |
| 239 | /// General purpose hash table. | |
| 240 | /// Insertion order is preserved. | |
| 241 | /// Deletions perform a "swap removal" on the entries list. | |
| 242 | /// Modifying the hash map while iterating is allowed, however one must understand | |
| 243 | /// the (well defined) behavior when mixing insertions and deletions with iteration. | |
| 244 | /// This type does not store an Allocator field - the Allocator must be passed in | |
| 245 | /// with each function call that requires it. See `HashMap` for a type that stores | |
| 246 | /// an Allocator field for convenience. | |
| 247 | /// Can be initialized directly using the default field values. | |
| 248 | /// This type is designed to have low overhead for small numbers of entries. When | |
| 249 | /// `store_hash` is `false` and the number of entries in the map is less than 9, | |
| 250 | /// the overhead cost of using `HashMapUnmanaged` rather than `std.ArrayList` is | |
| 251 | /// only a single pointer-sized integer. | |
| 252 | /// When `store_hash` is `false`, this data structure is biased towards cheap `eql` | |
| 253 | /// functions. It does not store each item's hash in the table. Setting `store_hash` | |
| 254 | /// to `true` incurs slightly more memory cost by storing each key's hash in the table | |
| 255 | /// but guarantees only one call to `eql` per insertion/deletion. | |
| 229 | /// A HashMap based on open addressing and linear probing. | |
| 230 | /// A lookup or modification typically occurs only 2 cache misses. | |
| 231 | /// No order is guaranteed and any modification invalidates live iterators. | |
| 232 | /// It achieves good performance with quite high load factors (by default, | |
| 233 | /// grow is triggered at 80% full) and only one byte of overhead per element. | |
| 234 | /// The struct itself is only 16 bytes for a small footprint. This comes at | |
| 235 | /// the price of handling size with u32, which should be reasonnable enough | |
| 236 | /// for almost all uses. | |
| 237 | /// Deletions are achieved with tombstones. | |
| 256 | 238 | pub fn HashMapUnmanaged( |
| 257 | 239 | comptime K: type, |
| 258 | 240 | comptime V: type, |
| 259 | comptime hash: fn (key: K) u32, | |
| 260 | comptime eql: fn (a: K, b: K) bool, | |
| 261 | comptime store_hash: bool, | |
| 241 | hashFn: fn (key: K) u64, | |
| 242 | eqlFn: fn (a: K, b: K) bool, | |
| 243 | comptime MaxLoadPercentage: u64, | |
| 262 | 244 | ) type { |
| 245 | comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100); | |
| 246 | ||
| 263 | 247 | return struct { |
| 264 | /// It is permitted to access this field directly. | |
| 265 | entries: std.ArrayListUnmanaged(Entry) = .{}, | |
| 266 | ||
| 267 | /// When entries length is less than `linear_scan_max`, this remains `null`. | |
| 268 | /// Once entries length grows big enough, this field is allocated. There is | |
| 269 | /// an IndexHeader followed by an array of Index(I) structs, where I is defined | |
| 270 | /// by how many total indexes there are. | |
| 271 | index_header: ?*IndexHeader = null, | |
| 272 | ||
| 273 | /// Modifying the key is illegal behavior. | |
| 274 | /// Modifying the value is allowed. | |
| 275 | /// Entry pointers become invalid whenever this HashMap is modified, | |
| 276 | /// unless `ensureCapacity` was previously used. | |
| 248 | const Self = @This(); | |
| 249 | ||
| 250 | // This is actually a midway pointer to the single buffer containing | |
| 251 | // a `Header` field, the `Metadata`s and `Entry`s. | |
| 252 | // At `-@sizeOf(Header)` is the Header field. | |
| 253 | // At `sizeOf(Metadata) * capacity + offset`, which is pointed to by | |
| 254 | // self.header().entries, is the array of entries. | |
| 255 | // This means that the hashmap only holds one live allocation, to | |
| 256 | // reduce memory fragmentation and struct size. | |
| 257 | /// Pointer to the metadata. | |
| 258 | metadata: ?[*]Metadata = null, | |
| 259 | ||
| 260 | /// Current number of elements in the hashmap. | |
| 261 | size: Size = 0, | |
| 262 | ||
| 263 | // Having a countdown to grow reduces the number of instructions to | |
| 264 | // execute when determining if the hashmap has enough capacity already. | |
| 265 | /// Number of available slots before a grow is needed to satisfy the | |
| 266 | /// `MaxLoadPercentage`. | |
| 267 | available: Size = 0, | |
| 268 | ||
| 269 | // This is purely empirical and not a /very smart magic constant™/. | |
| 270 | /// Capacity of the first grow when bootstrapping the hashmap. | |
| 271 | const MinimalCapacity = 8; | |
| 272 | ||
| 273 | // This hashmap is specially designed for sizes that fit in a u32. | |
| 274 | const Size = u32; | |
| 275 | ||
| 276 | // u64 hashes guarantee us that the fingerprint bits will never be used | |
| 277 | // to compute the index of a slot, maximizing the use of entropy. | |
| 278 | const Hash = u64; | |
| 279 | ||
| 277 | 280 | pub const Entry = struct { |
| 278 | /// This field is `void` if `store_hash` is `false`. | |
| 279 | hash: Hash, | |
| 280 | 281 | key: K, |
| 281 | 282 | value: V, |
| 282 | 283 | }; |
| 283 | 284 | |
| 284 | pub const Hash = if (store_hash) u32 else void; | |
| 285 | const Header = packed struct { | |
| 286 | entries: [*]Entry, | |
| 287 | capacity: Size, | |
| 288 | }; | |
| 289 | ||
| 290 | /// Metadata for a slot. It can be in three states: empty, used or | |
| 291 | /// tombstone. Tombstones indicate that an entry was previously used, | |
| 292 | /// they are a simple way to handle removal. | |
| 293 | /// To this state, we add 6 bits from the slot's key hash. These are | |
| 294 | /// used as a fast way to disambiguate between entries without | |
| 295 | /// having to use the equality function. If two fingerprints are | |
| 296 | /// different, we know that we don't have to compare the keys at all. | |
| 297 | /// The 6 bits are the highest ones from a 64 bit hash. This way, not | |
| 298 | /// only we use the `log2(capacity)` lowest bits from the hash to determine | |
| 299 | /// a slot index, but we use 6 more bits to quickly resolve collisions | |
| 300 | /// when multiple elements with different hashes end up wanting to be in / the same slot. | |
| 301 | /// Not using the equality function means we don't have to read into | |
| 302 | /// the entries array, avoiding a likely cache miss. | |
| 303 | const Metadata = packed struct { | |
| 304 | const FingerPrint = u6; | |
| 305 | ||
| 306 | used: u1 = 0, | |
| 307 | tombstone: u1 = 0, | |
| 308 | fingerprint: FingerPrint = 0, | |
| 309 | ||
| 310 | pub fn isUsed(self: Metadata) bool { | |
| 311 | return self.used == 1; | |
| 312 | } | |
| 313 | ||
| 314 | pub fn isTombstone(self: Metadata) bool { | |
| 315 | return self.tombstone == 1; | |
| 316 | } | |
| 317 | ||
| 318 | pub fn takeFingerprint(hash: Hash) FingerPrint { | |
| 319 | const hash_bits = @typeInfo(Hash).Int.bits; | |
| 320 | const fp_bits = @typeInfo(FingerPrint).Int.bits; | |
| 321 | return @truncate(FingerPrint, hash >> (hash_bits - fp_bits)); | |
| 322 | } | |
| 323 | ||
| 324 | pub fn fill(self: *Metadata, fp: FingerPrint) void { | |
| 325 | self.used = 1; | |
| 326 | self.tombstone = 0; | |
| 327 | self.fingerprint = fp; | |
| 328 | } | |
| 329 | ||
| 330 | pub fn remove(self: *Metadata) void { | |
| 331 | self.used = 0; | |
| 332 | self.tombstone = 1; | |
| 333 | self.fingerprint = 0; | |
| 334 | } | |
| 335 | }; | |
| 336 | ||
| 337 | comptime { | |
| 338 | assert(@sizeOf(Metadata) == 1); | |
| 339 | assert(@alignOf(Metadata) == 1); | |
| 340 | } | |
| 341 | ||
| 342 | const Iterator = struct { | |
| 343 | hm: *const Self, | |
| 344 | index: Size = 0, | |
| 345 | ||
| 346 | pub fn next(it: *Iterator) ?*Entry { | |
| 347 | assert(it.index <= it.hm.capacity()); | |
| 348 | if (it.hm.size == 0) return null; | |
| 349 | ||
| 350 | const cap = it.hm.capacity(); | |
| 351 | const end = it.hm.metadata.? + cap; | |
| 352 | var metadata = it.hm.metadata.? + it.index; | |
| 353 | ||
| 354 | while (metadata != end) : ({ | |
| 355 | metadata += 1; | |
| 356 | it.index += 1; | |
| 357 | }) { | |
| 358 | if (metadata[0].isUsed()) { | |
| 359 | const entry = &it.hm.entries()[it.index]; | |
| 360 | it.index += 1; | |
| 361 | return entry; | |
| 362 | } | |
| 363 | } | |
| 364 | ||
| 365 | return null; | |
| 366 | } | |
| 367 | }; | |
| 285 | 368 | |
| 286 | 369 | pub const GetOrPutResult = struct { |
| 287 | 370 | entry: *Entry, |
| 288 | 371 | found_existing: bool, |
| 289 | 372 | }; |
| 290 | 373 | |
| 291 | pub const Managed = HashMap(K, V, hash, eql, store_hash); | |
| 292 | ||
| 293 | const Self = @This(); | |
| 294 | ||
| 295 | const linear_scan_max = 8; | |
| 374 | pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage); | |
| 296 | 375 | |
| 297 | 376 | pub fn promote(self: Self, allocator: *Allocator) Managed { |
| 298 | 377 | return .{ |
| ... | ... | @@ -301,167 +380,156 @@ pub fn HashMapUnmanaged( |
| 301 | 380 | }; |
| 302 | 381 | } |
| 303 | 382 | |
| 383 | fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool { | |
| 384 | return size * 100 < MaxLoadPercentage * cap; | |
| 385 | } | |
| 386 | ||
| 387 | pub fn init(allocator: *Allocator) Self { | |
| 388 | return .{}; | |
| 389 | } | |
| 390 | ||
| 304 | 391 | pub fn deinit(self: *Self, allocator: *Allocator) void { |
| 305 | self.entries.deinit(allocator); | |
| 306 | if (self.index_header) |header| { | |
| 307 | header.free(allocator); | |
| 308 | } | |
| 392 | self.deallocate(allocator); | |
| 309 | 393 | self.* = undefined; |
| 310 | 394 | } |
| 311 | 395 | |
| 312 | pub fn clearRetainingCapacity(self: *Self) void { | |
| 313 | self.entries.items.len = 0; | |
| 314 | if (self.index_header) |header| { | |
| 315 | header.max_distance_from_start_index = 0; | |
| 316 | switch (header.capacityIndexType()) { | |
| 317 | .u8 => mem.set(Index(u8), header.indexes(u8), Index(u8).empty), | |
| 318 | .u16 => mem.set(Index(u16), header.indexes(u16), Index(u16).empty), | |
| 319 | .u32 => mem.set(Index(u32), header.indexes(u32), Index(u32).empty), | |
| 320 | .usize => mem.set(Index(usize), header.indexes(usize), Index(usize).empty), | |
| 321 | } | |
| 322 | } | |
| 323 | } | |
| 396 | fn deallocate(self: *Self, allocator: *Allocator) void { | |
| 397 | if (self.metadata == null) return; | |
| 324 | 398 | |
| 325 | pub fn clearAndFree(self: *Self, allocator: *Allocator) void { | |
| 326 | self.entries.shrink(allocator, 0); | |
| 327 | if (self.index_header) |header| { | |
| 328 | header.free(allocator); | |
| 329 | self.index_header = null; | |
| 330 | } | |
| 399 | const cap = self.capacity(); | |
| 400 | const meta_size = @sizeOf(Header) + cap * @sizeOf(Metadata); | |
| 401 | ||
| 402 | const alignment = @alignOf(Entry) - 1; | |
| 403 | const entries_size = @as(usize, cap) * @sizeOf(Entry) + alignment; | |
| 404 | ||
| 405 | const total_size = meta_size + entries_size; | |
| 406 | ||
| 407 | var slice: []u8 = undefined; | |
| 408 | slice.ptr = @intToPtr([*]u8, @ptrToInt(self.header())); | |
| 409 | slice.len = total_size; | |
| 410 | allocator.free(slice); | |
| 411 | ||
| 412 | self.metadata = null; | |
| 413 | self.available = 0; | |
| 331 | 414 | } |
| 332 | 415 | |
| 333 | /// If key exists this function cannot fail. | |
| 334 | /// If there is an existing item with `key`, then the result | |
| 335 | /// `Entry` pointer points to it, and found_existing is true. | |
| 336 | /// Otherwise, puts a new item with undefined value, and | |
| 337 | /// the `Entry` pointer points to it. Caller should then initialize | |
| 338 | /// the value (but not the key). | |
| 339 | pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { | |
| 340 | self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| { | |
| 341 | // "If key exists this function cannot fail." | |
| 342 | return GetOrPutResult{ | |
| 343 | .entry = self.getEntry(key) orelse return err, | |
| 344 | .found_existing = true, | |
| 345 | }; | |
| 346 | }; | |
| 347 | return self.getOrPutAssumeCapacity(key); | |
| 416 | fn capacityForSize(size: Size) Size { | |
| 417 | var new_cap = @truncate(u32, (@as(u64, size) * 100) / MaxLoadPercentage + 1); | |
| 418 | new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable; | |
| 419 | return new_cap; | |
| 348 | 420 | } |
| 349 | 421 | |
| 350 | /// If there is an existing item with `key`, then the result | |
| 351 | /// `Entry` pointer points to it, and found_existing is true. | |
| 352 | /// Otherwise, puts a new item with undefined value, and | |
| 353 | /// the `Entry` pointer points to it. Caller should then initialize | |
| 354 | /// the value (but not the key). | |
| 355 | /// If a new entry needs to be stored, this function asserts there | |
| 356 | /// is enough capacity to store it. | |
| 357 | pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { | |
| 358 | const header = self.index_header orelse { | |
| 359 | // Linear scan. | |
| 360 | const h = if (store_hash) hash(key) else {}; | |
| 361 | for (self.entries.items) |*item| { | |
| 362 | if (item.hash == h and eql(key, item.key)) { | |
| 363 | return GetOrPutResult{ | |
| 364 | .entry = item, | |
| 365 | .found_existing = true, | |
| 366 | }; | |
| 367 | } | |
| 368 | } | |
| 369 | const new_entry = self.entries.addOneAssumeCapacity(); | |
| 370 | new_entry.* = .{ | |
| 371 | .hash = if (store_hash) h else {}, | |
| 372 | .key = key, | |
| 373 | .value = undefined, | |
| 374 | }; | |
| 375 | return GetOrPutResult{ | |
| 376 | .entry = new_entry, | |
| 377 | .found_existing = false, | |
| 378 | }; | |
| 379 | }; | |
| 422 | pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_size: Size) !void { | |
| 423 | if (new_size > self.size) | |
| 424 | try self.growIfNeeded(allocator, new_size - self.size); | |
| 425 | } | |
| 380 | 426 | |
| 381 | switch (header.capacityIndexType()) { | |
| 382 | .u8 => return self.getOrPutInternal(key, header, u8), | |
| 383 | .u16 => return self.getOrPutInternal(key, header, u16), | |
| 384 | .u32 => return self.getOrPutInternal(key, header, u32), | |
| 385 | .usize => return self.getOrPutInternal(key, header, usize), | |
| 427 | pub fn clearRetainingCapacity(self: *Self) void { | |
| 428 | if (self.metadata) |_| { | |
| 429 | self.initMetadatas(); | |
| 430 | self.size = 0; | |
| 431 | self.available = 0; | |
| 386 | 432 | } |
| 387 | 433 | } |
| 388 | 434 | |
| 389 | pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry { | |
| 390 | const res = try self.getOrPut(allocator, key); | |
| 391 | if (!res.found_existing) | |
| 392 | res.entry.value = value; | |
| 435 | pub fn clearAndFree(self: *Self, allocator: *Allocator) void { | |
| 436 | self.deallocate(allocator); | |
| 437 | self.size = 0; | |
| 438 | self.available = 0; | |
| 439 | } | |
| 393 | 440 | |
| 394 | return res.entry; | |
| 441 | pub fn count(self: *const Self) Size { | |
| 442 | return self.size; | |
| 395 | 443 | } |
| 396 | 444 | |
| 397 | /// Increases capacity, guaranteeing that insertions up until the | |
| 398 | /// `expected_count` will not cause an allocation, and therefore cannot fail. | |
| 399 | pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void { | |
| 400 | try self.entries.ensureCapacity(allocator, new_capacity); | |
| 401 | if (new_capacity <= linear_scan_max) return; | |
| 402 | ||
| 403 | // Ensure that the indexes will be at most 60% full if | |
| 404 | // `new_capacity` items are put into it. | |
| 405 | const needed_len = new_capacity * 5 / 3; | |
| 406 | if (self.index_header) |header| { | |
| 407 | if (needed_len > header.indexes_len) { | |
| 408 | // An overflow here would mean the amount of memory required would not | |
| 409 | // be representable in the address space. | |
| 410 | const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable; | |
| 411 | const new_header = try IndexHeader.alloc(allocator, new_indexes_len); | |
| 412 | self.insertAllEntriesIntoNewHeader(new_header); | |
| 413 | header.free(allocator); | |
| 414 | self.index_header = new_header; | |
| 415 | } | |
| 416 | } else { | |
| 417 | // An overflow here would mean the amount of memory required would not | |
| 418 | // be representable in the address space. | |
| 419 | const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable; | |
| 420 | const header = try IndexHeader.alloc(allocator, new_indexes_len); | |
| 421 | self.insertAllEntriesIntoNewHeader(header); | |
| 422 | self.index_header = header; | |
| 423 | } | |
| 445 | fn header(self: *const Self) *Header { | |
| 446 | return @ptrCast(*Header, @ptrCast([*]Header, self.metadata.?) - 1); | |
| 424 | 447 | } |
| 425 | 448 | |
| 426 | /// Returns the number of total elements which may be present before it is | |
| 427 | /// no longer guaranteed that no allocations will be performed. | |
| 428 | pub fn capacity(self: Self) usize { | |
| 429 | const entry_cap = self.entries.capacity; | |
| 430 | const header = self.index_header orelse return math.min(linear_scan_max, entry_cap); | |
| 431 | const indexes_cap = (header.indexes_len + 1) * 3 / 4; | |
| 432 | return math.min(entry_cap, indexes_cap); | |
| 449 | fn entries(self: *const Self) [*]Entry { | |
| 450 | return self.header().entries; | |
| 433 | 451 | } |
| 434 | 452 | |
| 435 | /// Clobbers any existing data. To detect if a put would clobber | |
| 436 | /// existing data, see `getOrPut`. | |
| 437 | pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void { | |
| 438 | const result = try self.getOrPut(allocator, key); | |
| 439 | result.entry.value = value; | |
| 453 | pub fn capacity(self: *const Self) Size { | |
| 454 | if (self.metadata == null) return 0; | |
| 455 | ||
| 456 | return self.header().capacity; | |
| 440 | 457 | } |
| 441 | 458 | |
| 442 | /// Inserts a key-value pair into the hash map, asserting that no previous | |
| 443 | /// entry with the same key is already present | |
| 459 | pub fn iterator(self: *const Self) Iterator { | |
| 460 | return .{ .hm = self }; | |
| 461 | } | |
| 462 | ||
| 463 | /// Insert an entry in the map. Assumes it is not already present. | |
| 444 | 464 | pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void { |
| 445 | const result = try self.getOrPut(allocator, key); | |
| 446 | assert(!result.found_existing); | |
| 447 | result.entry.value = value; | |
| 465 | assert(!self.contains(key)); | |
| 466 | try self.growIfNeeded(allocator, 1); | |
| 467 | ||
| 468 | self.putAssumeCapacityNoClobber(key, value); | |
| 448 | 469 | } |
| 449 | 470 | |
| 450 | /// Asserts there is enough capacity to store the new key-value pair. | |
| 451 | /// Clobbers any existing data. To detect if a put would clobber | |
| 452 | /// existing data, see `getOrPutAssumeCapacity`. | |
| 453 | 471 | pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { |
| 454 | const result = self.getOrPutAssumeCapacity(key); | |
| 455 | result.entry.value = value; | |
| 472 | const hash = hashFn(key); | |
| 473 | const mask = self.capacity() - 1; | |
| 474 | const fingerprint = Metadata.takeFingerprint(hash); | |
| 475 | var idx = @truncate(usize, hash & mask); | |
| 476 | ||
| 477 | var first_tombstone_idx: usize = self.capacity(); // invalid index | |
| 478 | var metadata = self.metadata.? + idx; | |
| 479 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { | |
| 480 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { | |
| 481 | const entry = &self.entries()[idx]; | |
| 482 | if (eqlFn(entry.key, key)) { | |
| 483 | return; | |
| 484 | } | |
| 485 | } else if (first_tombstone_idx == self.capacity() and metadata[0].isTombstone()) { | |
| 486 | first_tombstone_idx = idx; | |
| 487 | } | |
| 488 | ||
| 489 | idx = (idx + 1) & mask; | |
| 490 | metadata = self.metadata.? + idx; | |
| 491 | } | |
| 492 | ||
| 493 | if (first_tombstone_idx < self.capacity()) { | |
| 494 | // Cheap try to lower probing lengths after deletions. Recycle a tombstone. | |
| 495 | idx = first_tombstone_idx; | |
| 496 | metadata = self.metadata.? + idx; | |
| 497 | } else { | |
| 498 | // We're using a slot previously free. | |
| 499 | self.available -= 1; | |
| 500 | } | |
| 501 | ||
| 502 | metadata[0].fill(fingerprint); | |
| 503 | const entry = &self.entries()[idx]; | |
| 504 | entry.* = .{ .key = key, .value = undefined }; | |
| 505 | self.size += 1; | |
| 456 | 506 | } |
| 457 | 507 | |
| 458 | /// Asserts there is enough capacity to store the new key-value pair. | |
| 459 | /// Asserts that it does not clobber any existing data. | |
| 460 | /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`. | |
| 508 | /// Insert an entry in the map. Assumes it is not already present, | |
| 509 | /// and that no allocation is needed. | |
| 461 | 510 | pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { |
| 462 | const result = self.getOrPutAssumeCapacity(key); | |
| 463 | assert(!result.found_existing); | |
| 464 | result.entry.value = value; | |
| 511 | assert(!self.contains(key)); | |
| 512 | ||
| 513 | const hash = hashFn(key); | |
| 514 | const mask = self.capacity() - 1; | |
| 515 | var idx = @truncate(usize, hash & mask); | |
| 516 | ||
| 517 | var metadata = self.metadata.? + idx; | |
| 518 | while (metadata[0].isUsed()) { | |
| 519 | idx = (idx + 1) & mask; | |
| 520 | metadata = self.metadata.? + idx; | |
| 521 | } | |
| 522 | ||
| 523 | if (!metadata[0].isTombstone()) { | |
| 524 | assert(self.available > 0); | |
| 525 | self.available -= 1; | |
| 526 | } | |
| 527 | ||
| 528 | const fingerprint = Metadata.takeFingerprint(hash); | |
| 529 | metadata[0].fill(fingerprint); | |
| 530 | self.entries()[idx] = Entry{ .key = key, .value = value }; | |
| 531 | ||
| 532 | self.size += 1; | |
| 465 | 533 | } |
| 466 | 534 | |
| 467 | 535 | /// Inserts a new `Entry` into the hash map, returning the previous one, if any. |
| ... | ... | @@ -488,400 +556,622 @@ pub fn HashMapUnmanaged( |
| 488 | 556 | } |
| 489 | 557 | |
| 490 | 558 | pub fn getEntry(self: Self, key: K) ?*Entry { |
| 491 | const index = self.getIndex(key) orelse return null; | |
| 492 | return &self.entries.items[index]; | |
| 493 | } | |
| 559 | if (self.size == 0) { | |
| 560 | return null; | |
| 561 | } | |
| 494 | 562 | |
| 495 | pub fn getIndex(self: Self, key: K) ?usize { | |
| 496 | const header = self.index_header orelse { | |
| 497 | // Linear scan. | |
| 498 | const h = if (store_hash) hash(key) else {}; | |
| 499 | for (self.entries.items) |*item, i| { | |
| 500 | if (item.hash == h and eql(key, item.key)) { | |
| 501 | return i; | |
| 563 | const hash = hashFn(key); | |
| 564 | const mask = self.capacity() - 1; | |
| 565 | const fingerprint = Metadata.takeFingerprint(hash); | |
| 566 | var idx = @truncate(usize, hash & mask); | |
| 567 | ||
| 568 | var metadata = self.metadata.? + idx; | |
| 569 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { | |
| 570 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { | |
| 571 | const entry = &self.entries()[idx]; | |
| 572 | if (eqlFn(entry.key, key)) { | |
| 573 | return entry; | |
| 502 | 574 | } |
| 503 | 575 | } |
| 504 | return null; | |
| 505 | }; | |
| 506 | switch (header.capacityIndexType()) { | |
| 507 | .u8 => return self.getInternal(key, header, u8), | |
| 508 | .u16 => return self.getInternal(key, header, u16), | |
| 509 | .u32 => return self.getInternal(key, header, u32), | |
| 510 | .usize => return self.getInternal(key, header, usize), | |
| 576 | idx = (idx + 1) & mask; | |
| 577 | metadata = self.metadata.? + idx; | |
| 511 | 578 | } |
| 512 | } | |
| 513 | 579 | |
| 514 | pub fn get(self: Self, key: K) ?V { | |
| 515 | return if (self.getEntry(key)) |entry| entry.value else null; | |
| 580 | return null; | |
| 516 | 581 | } |
| 517 | 582 | |
| 518 | pub fn contains(self: Self, key: K) bool { | |
| 519 | return self.getEntry(key) != null; | |
| 583 | /// Insert an entry if the associated key is not already present, otherwise update preexisting value. | |
| 584 | /// Returns true if the key was already present. | |
| 585 | pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void { | |
| 586 | const result = try self.getOrPut(allocator, key); | |
| 587 | result.entry.value = value; | |
| 520 | 588 | } |
| 521 | 589 | |
| 522 | /// If there is an `Entry` with a matching key, it is deleted from | |
| 523 | /// the hash map, and then returned from this function. | |
| 524 | pub fn remove(self: *Self, key: K) ?Entry { | |
| 525 | const header = self.index_header orelse { | |
| 526 | // Linear scan. | |
| 527 | const h = if (store_hash) hash(key) else {}; | |
| 528 | for (self.entries.items) |item, i| { | |
| 529 | if (item.hash == h and eql(key, item.key)) { | |
| 530 | return self.entries.swapRemove(i); | |
| 590 | /// Get an optional pointer to the value associated with key, if present. | |
| 591 | pub fn get(self: Self, key: K) ?V { | |
| 592 | if (self.size == 0) { | |
| 593 | return null; | |
| 594 | } | |
| 595 | ||
| 596 | const hash = hashFn(key); | |
| 597 | const mask = self.capacity() - 1; | |
| 598 | const fingerprint = Metadata.takeFingerprint(hash); | |
| 599 | var idx = @truncate(usize, hash & mask); | |
| 600 | ||
| 601 | var metadata = self.metadata.? + idx; | |
| 602 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { | |
| 603 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { | |
| 604 | const entry = &self.entries()[idx]; | |
| 605 | if (eqlFn(entry.key, key)) { | |
| 606 | return entry.value; | |
| 531 | 607 | } |
| 532 | 608 | } |
| 533 | return null; | |
| 534 | }; | |
| 535 | switch (header.capacityIndexType()) { | |
| 536 | .u8 => return self.removeInternal(key, header, u8), | |
| 537 | .u16 => return self.removeInternal(key, header, u16), | |
| 538 | .u32 => return self.removeInternal(key, header, u32), | |
| 539 | .usize => return self.removeInternal(key, header, usize), | |
| 609 | idx = (idx + 1) & mask; | |
| 610 | metadata = self.metadata.? + idx; | |
| 540 | 611 | } |
| 541 | } | |
| 542 | 612 | |
| 543 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, | |
| 544 | /// and discards it. | |
| 545 | pub fn removeAssertDiscard(self: *Self, key: K) void { | |
| 546 | assert(self.remove(key) != null); | |
| 613 | return null; | |
| 547 | 614 | } |
| 548 | 615 | |
| 549 | pub fn items(self: Self) []Entry { | |
| 550 | return self.entries.items; | |
| 616 | pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { | |
| 617 | try self.growIfNeeded(allocator, 1); | |
| 618 | ||
| 619 | return self.getOrPutAssumeCapacity(key); | |
| 551 | 620 | } |
| 552 | 621 | |
| 553 | pub fn clone(self: Self, allocator: *Allocator) !Self { | |
| 554 | var other: Self = .{}; | |
| 555 | try other.entries.appendSlice(allocator, self.entries.items); | |
| 622 | pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { | |
| 623 | const hash = hashFn(key); | |
| 624 | const mask = self.capacity() - 1; | |
| 625 | const fingerprint = Metadata.takeFingerprint(hash); | |
| 626 | var idx = @truncate(usize, hash & mask); | |
| 627 | ||
| 628 | var first_tombstone_idx: usize = self.capacity(); // invalid index | |
| 629 | var metadata = self.metadata.? + idx; | |
| 630 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { | |
| 631 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { | |
| 632 | const entry = &self.entries()[idx]; | |
| 633 | if (eqlFn(entry.key, key)) { | |
| 634 | return GetOrPutResult{ .entry = entry, .found_existing = true }; | |
| 635 | } | |
| 636 | } else if (first_tombstone_idx == self.capacity() and metadata[0].isTombstone()) { | |
| 637 | first_tombstone_idx = idx; | |
| 638 | } | |
| 556 | 639 | |
| 557 | if (self.index_header) |header| { | |
| 558 | const new_header = try IndexHeader.alloc(allocator, header.indexes_len); | |
| 559 | other.insertAllEntriesIntoNewHeader(new_header); | |
| 560 | other.index_header = new_header; | |
| 640 | idx = (idx + 1) & mask; | |
| 641 | metadata = self.metadata.? + idx; | |
| 561 | 642 | } |
| 562 | return other; | |
| 643 | ||
| 644 | if (first_tombstone_idx < self.capacity()) { | |
| 645 | // Cheap try to lower probing lengths after deletions. Recycle a tombstone. | |
| 646 | idx = first_tombstone_idx; | |
| 647 | metadata = self.metadata.? + idx; | |
| 648 | } else { | |
| 649 | // We're using a slot previously free. | |
| 650 | self.available -= 1; | |
| 651 | } | |
| 652 | ||
| 653 | metadata[0].fill(fingerprint); | |
| 654 | const entry = &self.entries()[idx]; | |
| 655 | entry.* = .{ .key = key, .value = undefined }; | |
| 656 | self.size += 1; | |
| 657 | ||
| 658 | return GetOrPutResult{ .entry = entry, .found_existing = false }; | |
| 563 | 659 | } |
| 564 | 660 | |
| 565 | fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry { | |
| 566 | const indexes = header.indexes(I); | |
| 567 | const h = hash(key); | |
| 568 | const start_index = header.constrainIndex(h); | |
| 569 | var roll_over: usize = 0; | |
| 570 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { | |
| 571 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 572 | var index = &indexes[index_index]; | |
| 573 | if (index.isEmpty()) | |
| 574 | return null; | |
| 575 | ||
| 576 | const entry = &self.entries.items[index.entry_index]; | |
| 577 | ||
| 578 | const hash_match = if (store_hash) h == entry.hash else true; | |
| 579 | if (!hash_match or !eql(key, entry.key)) | |
| 580 | continue; | |
| 581 | ||
| 582 | const removed_entry = self.entries.swapRemove(index.entry_index); | |
| 583 | if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) { | |
| 584 | // Because of the swap remove, now we need to update the index that was | |
| 585 | // pointing to the last entry and is now pointing to this removed item slot. | |
| 586 | self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes); | |
| 587 | } | |
| 661 | pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry { | |
| 662 | const res = try self.getOrPut(allocator, key); | |
| 663 | if (!res.found_existing) res.entry.value = value; | |
| 664 | return res.entry; | |
| 665 | } | |
| 588 | 666 | |
| 589 | // Now we have to shift over the following indexes. | |
| 590 | roll_over += 1; | |
| 591 | while (roll_over < header.indexes_len) : (roll_over += 1) { | |
| 592 | const next_index_index = header.constrainIndex(start_index + roll_over); | |
| 593 | const next_index = &indexes[next_index_index]; | |
| 594 | if (next_index.isEmpty() or next_index.distance_from_start_index == 0) { | |
| 595 | index.setEmpty(); | |
| 667 | /// Return true if there is a value associated with key in the map. | |
| 668 | pub fn contains(self: *const Self, key: K) bool { | |
| 669 | return self.get(key) != null; | |
| 670 | } | |
| 671 | ||
| 672 | /// If there is an `Entry` with a matching key, it is deleted from | |
| 673 | /// the hash map, and then returned from this function. | |
| 674 | pub fn remove(self: *Self, key: K) ?Entry { | |
| 675 | if (self.size == 0) return null; | |
| 676 | ||
| 677 | const hash = hashFn(key); | |
| 678 | const mask = self.capacity() - 1; | |
| 679 | const fingerprint = Metadata.takeFingerprint(hash); | |
| 680 | var idx = @truncate(usize, hash & mask); | |
| 681 | ||
| 682 | var metadata = self.metadata.? + idx; | |
| 683 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { | |
| 684 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { | |
| 685 | const entry = &self.entries()[idx]; | |
| 686 | if (eqlFn(entry.key, key)) { | |
| 687 | const removed_entry = entry.*; | |
| 688 | metadata[0].remove(); | |
| 689 | entry.* = undefined; | |
| 690 | self.size -= 1; | |
| 596 | 691 | return removed_entry; |
| 597 | 692 | } |
| 598 | index.* = next_index.*; | |
| 599 | index.distance_from_start_index -= 1; | |
| 600 | index = next_index; | |
| 601 | 693 | } |
| 602 | unreachable; | |
| 694 | idx = (idx + 1) & mask; | |
| 695 | metadata = self.metadata.? + idx; | |
| 603 | 696 | } |
| 697 | ||
| 604 | 698 | return null; |
| 605 | 699 | } |
| 606 | 700 | |
| 607 | fn updateEntryIndex( | |
| 608 | self: *Self, | |
| 609 | header: *IndexHeader, | |
| 610 | old_entry_index: usize, | |
| 611 | new_entry_index: usize, | |
| 612 | comptime I: type, | |
| 613 | indexes: []Index(I), | |
| 614 | ) void { | |
| 615 | const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key); | |
| 616 | const start_index = header.constrainIndex(h); | |
| 617 | var roll_over: usize = 0; | |
| 618 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { | |
| 619 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 620 | const index = &indexes[index_index]; | |
| 621 | if (index.entry_index == old_entry_index) { | |
| 622 | index.entry_index = @intCast(I, new_entry_index); | |
| 623 | return; | |
| 701 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, | |
| 702 | /// and discards it. | |
| 703 | pub fn removeAssertDiscard(self: *Self, key: K) void { | |
| 704 | assert(self.contains(key)); | |
| 705 | ||
| 706 | const hash = hashFn(key); | |
| 707 | const mask = self.capacity() - 1; | |
| 708 | const fingerprint = Metadata.takeFingerprint(hash); | |
| 709 | var idx = @truncate(usize, hash & mask); | |
| 710 | ||
| 711 | var metadata = self.metadata.? + idx; | |
| 712 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { | |
| 713 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { | |
| 714 | const entry = &self.entries()[idx]; | |
| 715 | if (eqlFn(entry.key, key)) { | |
| 716 | metadata[0].remove(); | |
| 717 | entry.* = undefined; | |
| 718 | self.size -= 1; | |
| 719 | return; | |
| 720 | } | |
| 624 | 721 | } |
| 722 | idx = (idx + 1) & mask; | |
| 723 | metadata = self.metadata.? + idx; | |
| 625 | 724 | } |
| 725 | ||
| 626 | 726 | unreachable; |
| 627 | 727 | } |
| 628 | 728 | |
| 629 | /// Must ensureCapacity before calling this. | |
| 630 | fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult { | |
| 631 | const indexes = header.indexes(I); | |
| 632 | const h = hash(key); | |
| 633 | const start_index = header.constrainIndex(h); | |
| 634 | var roll_over: usize = 0; | |
| 635 | var distance_from_start_index: usize = 0; | |
| 636 | while (roll_over <= header.indexes_len) : ({ | |
| 637 | roll_over += 1; | |
| 638 | distance_from_start_index += 1; | |
| 639 | }) { | |
| 640 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 641 | const index = indexes[index_index]; | |
| 642 | if (index.isEmpty()) { | |
| 643 | indexes[index_index] = .{ | |
| 644 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 645 | .entry_index = @intCast(I, self.entries.items.len), | |
| 646 | }; | |
| 647 | header.maybeBumpMax(distance_from_start_index); | |
| 648 | const new_entry = self.entries.addOneAssumeCapacity(); | |
| 649 | new_entry.* = .{ | |
| 650 | .hash = if (store_hash) h else {}, | |
| 651 | .key = key, | |
| 652 | .value = undefined, | |
| 653 | }; | |
| 654 | return .{ | |
| 655 | .found_existing = false, | |
| 656 | .entry = new_entry, | |
| 657 | }; | |
| 658 | } | |
| 729 | fn initMetadatas(self: *Self) void { | |
| 730 | @memset(@ptrCast([*]u8, self.metadata.?), 0, @sizeOf(Metadata) * self.capacity()); | |
| 731 | } | |
| 659 | 732 | |
| 660 | // This pointer survives the following append because we call | |
| 661 | // entries.ensureCapacity before getOrPutInternal. | |
| 662 | const entry = &self.entries.items[index.entry_index]; | |
| 663 | const hash_match = if (store_hash) h == entry.hash else true; | |
| 664 | if (hash_match and eql(key, entry.key)) { | |
| 665 | return .{ | |
| 666 | .found_existing = true, | |
| 667 | .entry = entry, | |
| 668 | }; | |
| 669 | } | |
| 670 | if (index.distance_from_start_index < distance_from_start_index) { | |
| 671 | // In this case, we did not find the item. We will put a new entry. | |
| 672 | // However, we will use this index for the new entry, and move | |
| 673 | // the previous index down the line, to keep the max_distance_from_start_index | |
| 674 | // as small as possible. | |
| 675 | indexes[index_index] = .{ | |
| 676 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 677 | .entry_index = @intCast(I, self.entries.items.len), | |
| 678 | }; | |
| 679 | header.maybeBumpMax(distance_from_start_index); | |
| 680 | const new_entry = self.entries.addOneAssumeCapacity(); | |
| 681 | new_entry.* = .{ | |
| 682 | .hash = if (store_hash) h else {}, | |
| 683 | .key = key, | |
| 684 | .value = undefined, | |
| 685 | }; | |
| 686 | ||
| 687 | distance_from_start_index = index.distance_from_start_index; | |
| 688 | var prev_entry_index = index.entry_index; | |
| 689 | ||
| 690 | // Find somewhere to put the index we replaced by shifting | |
| 691 | // following indexes backwards. | |
| 692 | roll_over += 1; | |
| 693 | distance_from_start_index += 1; | |
| 694 | while (roll_over < header.indexes_len) : ({ | |
| 695 | roll_over += 1; | |
| 696 | distance_from_start_index += 1; | |
| 697 | }) { | |
| 698 | const next_index_index = header.constrainIndex(start_index + roll_over); | |
| 699 | const next_index = indexes[next_index_index]; | |
| 700 | if (next_index.isEmpty()) { | |
| 701 | header.maybeBumpMax(distance_from_start_index); | |
| 702 | indexes[next_index_index] = .{ | |
| 703 | .entry_index = prev_entry_index, | |
| 704 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 705 | }; | |
| 706 | return .{ | |
| 707 | .found_existing = false, | |
| 708 | .entry = new_entry, | |
| 709 | }; | |
| 710 | } | |
| 711 | if (next_index.distance_from_start_index < distance_from_start_index) { | |
| 712 | header.maybeBumpMax(distance_from_start_index); | |
| 713 | indexes[next_index_index] = .{ | |
| 714 | .entry_index = prev_entry_index, | |
| 715 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 716 | }; | |
| 717 | distance_from_start_index = next_index.distance_from_start_index; | |
| 718 | prev_entry_index = next_index.entry_index; | |
| 719 | } | |
| 720 | } | |
| 721 | unreachable; | |
| 722 | } | |
| 723 | } | |
| 724 | unreachable; | |
| 733 | // This counts the number of occupied slots, used + tombstones, which is | |
| 734 | // what has to stay under the MaxLoadPercentage of capacity. | |
| 735 | fn load(self: *const Self) Size { | |
| 736 | const max_load = (self.capacity() * MaxLoadPercentage) / 100; | |
| 737 | assert(max_load >= self.available); | |
| 738 | return @truncate(Size, max_load - self.available); | |
| 725 | 739 | } |
| 726 | 740 | |
| 727 | fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize { | |
| 728 | const indexes = header.indexes(I); | |
| 729 | const h = hash(key); | |
| 730 | const start_index = header.constrainIndex(h); | |
| 731 | var roll_over: usize = 0; | |
| 732 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { | |
| 733 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 734 | const index = indexes[index_index]; | |
| 735 | if (index.isEmpty()) | |
| 736 | return null; | |
| 737 | ||
| 738 | const entry = &self.entries.items[index.entry_index]; | |
| 739 | const hash_match = if (store_hash) h == entry.hash else true; | |
| 740 | if (hash_match and eql(key, entry.key)) | |
| 741 | return index.entry_index; | |
| 741 | fn growIfNeeded(self: *Self, allocator: *Allocator, new_count: Size) !void { | |
| 742 | if (new_count > self.available) { | |
| 743 | try self.grow(allocator, capacityForSize(self.load() + new_count)); | |
| 742 | 744 | } |
| 743 | return null; | |
| 744 | 745 | } |
| 745 | 746 | |
| 746 | fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void { | |
| 747 | switch (header.capacityIndexType()) { | |
| 748 | .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8), | |
| 749 | .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16), | |
| 750 | .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32), | |
| 751 | .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize), | |
| 747 | pub fn clone(self: Self, allocator: *Allocator) !Self { | |
| 748 | var other = Self{}; | |
| 749 | if (self.size == 0) | |
| 750 | return other; | |
| 751 | ||
| 752 | const new_cap = capacityForSize(self.size); | |
| 753 | try other.allocate(allocator, new_cap); | |
| 754 | other.initMetadatas(); | |
| 755 | other.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100); | |
| 756 | ||
| 757 | var i: Size = 0; | |
| 758 | var metadata = self.metadata.?; | |
| 759 | var entr = self.entries(); | |
| 760 | while (i < self.capacity()) : (i += 1) { | |
| 761 | if (metadata[i].isUsed()) { | |
| 762 | const entry = &entr[i]; | |
| 763 | other.putAssumeCapacityNoClobber(entry.key, entry.value); | |
| 764 | if (other.size == self.size) | |
| 765 | break; | |
| 766 | } | |
| 752 | 767 | } |
| 768 | ||
| 769 | return other; | |
| 753 | 770 | } |
| 754 | 771 | |
| 755 | fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, header: *IndexHeader, comptime I: type) void { | |
| 756 | const indexes = header.indexes(I); | |
| 757 | entry_loop: for (self.entries.items) |entry, i| { | |
| 758 | const h = if (store_hash) entry.hash else hash(entry.key); | |
| 759 | const start_index = header.constrainIndex(h); | |
| 760 | var entry_index = i; | |
| 761 | var roll_over: usize = 0; | |
| 762 | var distance_from_start_index: usize = 0; | |
| 763 | while (roll_over < header.indexes_len) : ({ | |
| 764 | roll_over += 1; | |
| 765 | distance_from_start_index += 1; | |
| 766 | }) { | |
| 767 | const index_index = header.constrainIndex(start_index + roll_over); | |
| 768 | const next_index = indexes[index_index]; | |
| 769 | if (next_index.isEmpty()) { | |
| 770 | header.maybeBumpMax(distance_from_start_index); | |
| 771 | indexes[index_index] = .{ | |
| 772 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 773 | .entry_index = @intCast(I, entry_index), | |
| 774 | }; | |
| 775 | continue :entry_loop; | |
| 776 | } | |
| 777 | if (next_index.distance_from_start_index < distance_from_start_index) { | |
| 778 | header.maybeBumpMax(distance_from_start_index); | |
| 779 | indexes[index_index] = .{ | |
| 780 | .distance_from_start_index = @intCast(I, distance_from_start_index), | |
| 781 | .entry_index = @intCast(I, entry_index), | |
| 782 | }; | |
| 783 | distance_from_start_index = next_index.distance_from_start_index; | |
| 784 | entry_index = next_index.entry_index; | |
| 772 | fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void { | |
| 773 | const new_cap = std.math.max(new_capacity, MinimalCapacity); | |
| 774 | assert(new_cap > self.capacity()); | |
| 775 | assert(std.math.isPowerOfTwo(new_cap)); | |
| 776 | ||
| 777 | var map = Self{}; | |
| 778 | defer map.deinit(allocator); | |
| 779 | try map.allocate(allocator, new_cap); | |
| 780 | map.initMetadatas(); | |
| 781 | map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100); | |
| 782 | ||
| 783 | if (self.size != 0) { | |
| 784 | const old_capacity = self.capacity(); | |
| 785 | var i: Size = 0; | |
| 786 | var metadata = self.metadata.?; | |
| 787 | var entr = self.entries(); | |
| 788 | while (i < old_capacity) : (i += 1) { | |
| 789 | if (metadata[i].isUsed()) { | |
| 790 | const entry = &entr[i]; | |
| 791 | map.putAssumeCapacityNoClobber(entry.key, entry.value); | |
| 792 | if (map.size == self.size) | |
| 793 | break; | |
| 785 | 794 | } |
| 786 | 795 | } |
| 787 | unreachable; | |
| 788 | 796 | } |
| 797 | ||
| 798 | self.size = 0; | |
| 799 | std.mem.swap(Self, self, &map); | |
| 800 | } | |
| 801 | ||
| 802 | fn allocate(self: *Self, allocator: *Allocator, new_capacity: Size) !void { | |
| 803 | const meta_size = @sizeOf(Header) + new_capacity * @sizeOf(Metadata); | |
| 804 | ||
| 805 | const alignment = @alignOf(Entry) - 1; | |
| 806 | const entries_size = @as(usize, new_capacity) * @sizeOf(Entry) + alignment; | |
| 807 | ||
| 808 | const total_size = meta_size + entries_size; | |
| 809 | ||
| 810 | const slice = try allocator.alignedAlloc(u8, @alignOf(Header), total_size); | |
| 811 | const ptr = @ptrToInt(slice.ptr); | |
| 812 | ||
| 813 | const metadata = ptr + @sizeOf(Header); | |
| 814 | var entry_ptr = ptr + meta_size; | |
| 815 | entry_ptr = (entry_ptr + alignment) & ~@as(usize, alignment); | |
| 816 | assert(entry_ptr + @as(usize, new_capacity) * @sizeOf(Entry) <= ptr + total_size); | |
| 817 | ||
| 818 | const hdr = @intToPtr(*Header, ptr); | |
| 819 | hdr.entries = @intToPtr([*]Entry, entry_ptr); | |
| 820 | hdr.capacity = new_capacity; | |
| 821 | self.metadata = @intToPtr([*]Metadata, metadata); | |
| 789 | 822 | } |
| 790 | 823 | }; |
| 791 | 824 | } |
| 792 | 825 | |
| 793 | const CapacityIndexType = enum { u8, u16, u32, usize }; | |
| 826 | const testing = std.testing; | |
| 827 | const expect = std.testing.expect; | |
| 828 | const expectEqual = std.testing.expectEqual; | |
| 829 | ||
| 830 | test "std.hash_map basic usage" { | |
| 831 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 832 | defer map.deinit(); | |
| 833 | ||
| 834 | const count = 5; | |
| 835 | var i: u32 = 0; | |
| 836 | var total: u32 = 0; | |
| 837 | while (i < count) : (i += 1) { | |
| 838 | try map.put(i, i); | |
| 839 | total += i; | |
| 840 | } | |
| 841 | ||
| 842 | var sum: u32 = 0; | |
| 843 | var it = map.iterator(); | |
| 844 | while (it.next()) |kv| { | |
| 845 | sum += kv.key; | |
| 846 | } | |
| 847 | expect(sum == total); | |
| 848 | ||
| 849 | i = 0; | |
| 850 | sum = 0; | |
| 851 | while (i < count) : (i += 1) { | |
| 852 | expectEqual(map.get(i).?, i); | |
| 853 | sum += map.get(i).?; | |
| 854 | } | |
| 855 | expectEqual(total, sum); | |
| 856 | } | |
| 857 | ||
| 858 | test "std.hash_map ensureCapacity" { | |
| 859 | var map = AutoHashMap(i32, i32).init(std.testing.allocator); | |
| 860 | defer map.deinit(); | |
| 794 | 861 | |
| 795 | fn capacityIndexType(indexes_len: usize) CapacityIndexType { | |
| 796 | if (indexes_len < math.maxInt(u8)) | |
| 797 | return .u8; | |
| 798 | if (indexes_len < math.maxInt(u16)) | |
| 799 | return .u16; | |
| 800 | if (indexes_len < math.maxInt(u32)) | |
| 801 | return .u32; | |
| 802 | return .usize; | |
| 862 | try map.ensureCapacity(20); | |
| 863 | const initial_capacity = map.capacity(); | |
| 864 | testing.expect(initial_capacity >= 20); | |
| 865 | var i: i32 = 0; | |
| 866 | while (i < 20) : (i += 1) { | |
| 867 | testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null); | |
| 868 | } | |
| 869 | // shouldn't resize from putAssumeCapacity | |
| 870 | testing.expect(initial_capacity == map.capacity()); | |
| 803 | 871 | } |
| 804 | 872 | |
| 805 | fn capacityIndexSize(indexes_len: usize) usize { | |
| 806 | switch (capacityIndexType(indexes_len)) { | |
| 807 | .u8 => return @sizeOf(Index(u8)), | |
| 808 | .u16 => return @sizeOf(Index(u16)), | |
| 809 | .u32 => return @sizeOf(Index(u32)), | |
| 810 | .usize => return @sizeOf(Index(usize)), | |
| 873 | test "std.hash_map ensureCapacity with tombstones" { | |
| 874 | var map = AutoHashMap(i32, i32).init(std.testing.allocator); | |
| 875 | defer map.deinit(); | |
| 876 | ||
| 877 | var i: i32 = 0; | |
| 878 | while (i < 100) : (i += 1) { | |
| 879 | try map.ensureCapacity(@intCast(u32, map.count() + 1)); | |
| 880 | map.putAssumeCapacity(i, i); | |
| 881 | // Remove to create tombstones that still count as load in the hashmap. | |
| 882 | _ = map.remove(i); | |
| 811 | 883 | } |
| 812 | 884 | } |
| 813 | 885 | |
| 814 | fn Index(comptime I: type) type { | |
| 815 | return extern struct { | |
| 816 | entry_index: I, | |
| 817 | distance_from_start_index: I, | |
| 886 | test "std.hash_map clearRetainingCapacity" { | |
| 887 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 888 | defer map.deinit(); | |
| 889 | ||
| 890 | map.clearRetainingCapacity(); | |
| 818 | 891 | |
| 819 | const Self = @This(); | |
| 892 | try map.put(1, 1); | |
| 893 | expectEqual(map.get(1).?, 1); | |
| 894 | expectEqual(map.count(), 1); | |
| 820 | 895 | |
| 821 | const empty = Self{ | |
| 822 | .entry_index = math.maxInt(I), | |
| 823 | .distance_from_start_index = undefined, | |
| 824 | }; | |
| 896 | const cap = map.capacity(); | |
| 897 | expect(cap > 0); | |
| 898 | ||
| 899 | map.clearRetainingCapacity(); | |
| 900 | map.clearRetainingCapacity(); | |
| 901 | expectEqual(map.count(), 0); | |
| 902 | expectEqual(map.capacity(), cap); | |
| 903 | expect(!map.contains(1)); | |
| 904 | } | |
| 905 | ||
| 906 | test "std.hash_map grow" { | |
| 907 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 908 | defer map.deinit(); | |
| 825 | 909 | |
| 826 | fn isEmpty(idx: Self) bool { | |
| 827 | return idx.entry_index == math.maxInt(I); | |
| 910 | const growTo = 12456; | |
| 911 | ||
| 912 | var i: u32 = 0; | |
| 913 | while (i < growTo) : (i += 1) { | |
| 914 | try map.put(i, i); | |
| 915 | } | |
| 916 | expectEqual(map.count(), growTo); | |
| 917 | ||
| 918 | i = 0; | |
| 919 | var it = map.iterator(); | |
| 920 | while (it.next()) |kv| { | |
| 921 | expectEqual(kv.key, kv.value); | |
| 922 | i += 1; | |
| 923 | } | |
| 924 | expectEqual(i, growTo); | |
| 925 | ||
| 926 | i = 0; | |
| 927 | while (i < growTo) : (i += 1) { | |
| 928 | expectEqual(map.get(i).?, i); | |
| 929 | } | |
| 930 | } | |
| 931 | ||
| 932 | test "std.hash_map clone" { | |
| 933 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 934 | defer map.deinit(); | |
| 935 | ||
| 936 | var a = try map.clone(); | |
| 937 | defer a.deinit(); | |
| 938 | ||
| 939 | expectEqual(a.count(), 0); | |
| 940 | ||
| 941 | try a.put(1, 1); | |
| 942 | try a.put(2, 2); | |
| 943 | try a.put(3, 3); | |
| 944 | ||
| 945 | var b = try a.clone(); | |
| 946 | defer b.deinit(); | |
| 947 | ||
| 948 | expectEqual(b.count(), 3); | |
| 949 | expectEqual(b.get(1), 1); | |
| 950 | expectEqual(b.get(2), 2); | |
| 951 | expectEqual(b.get(3), 3); | |
| 952 | } | |
| 953 | ||
| 954 | test "std.hash_map ensureCapacity with existing elements" { | |
| 955 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 956 | defer map.deinit(); | |
| 957 | ||
| 958 | try map.put(0, 0); | |
| 959 | expectEqual(map.count(), 1); | |
| 960 | expectEqual(map.capacity(), @TypeOf(map).Unmanaged.MinimalCapacity); | |
| 961 | ||
| 962 | try map.ensureCapacity(65); | |
| 963 | expectEqual(map.count(), 1); | |
| 964 | expectEqual(map.capacity(), 128); | |
| 965 | } | |
| 966 | ||
| 967 | test "std.hash_map ensureCapacity satisfies max load factor" { | |
| 968 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 969 | defer map.deinit(); | |
| 970 | ||
| 971 | try map.ensureCapacity(127); | |
| 972 | expectEqual(map.capacity(), 256); | |
| 973 | } | |
| 974 | ||
| 975 | test "std.hash_map remove" { | |
| 976 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 977 | defer map.deinit(); | |
| 978 | ||
| 979 | var i: u32 = 0; | |
| 980 | while (i < 16) : (i += 1) { | |
| 981 | try map.put(i, i); | |
| 982 | } | |
| 983 | ||
| 984 | i = 0; | |
| 985 | while (i < 16) : (i += 1) { | |
| 986 | if (i % 3 == 0) { | |
| 987 | _ = map.remove(i); | |
| 828 | 988 | } |
| 989 | } | |
| 990 | expectEqual(map.count(), 10); | |
| 991 | var it = map.iterator(); | |
| 992 | while (it.next()) |kv| { | |
| 993 | expectEqual(kv.key, kv.value); | |
| 994 | expect(kv.key % 3 != 0); | |
| 995 | } | |
| 829 | 996 | |
| 830 | fn setEmpty(idx: *Self) void { | |
| 831 | idx.entry_index = math.maxInt(I); | |
| 997 | i = 0; | |
| 998 | while (i < 16) : (i += 1) { | |
| 999 | if (i % 3 == 0) { | |
| 1000 | expect(!map.contains(i)); | |
| 1001 | } else { | |
| 1002 | expectEqual(map.get(i).?, i); | |
| 832 | 1003 | } |
| 833 | }; | |
| 1004 | } | |
| 834 | 1005 | } |
| 835 | 1006 | |
| 836 | /// This struct is trailed by an array of `Index(I)`, where `I` | |
| 837 | /// and the array length are determined by `indexes_len`. | |
| 838 | const IndexHeader = struct { | |
| 839 | max_distance_from_start_index: usize, | |
| 840 | indexes_len: usize, | |
| 1007 | test "std.hash_map reverse removes" { | |
| 1008 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 1009 | defer map.deinit(); | |
| 841 | 1010 | |
| 842 | fn constrainIndex(header: IndexHeader, i: usize) usize { | |
| 843 | // This is an optimization for modulo of power of two integers; | |
| 844 | // it requires `indexes_len` to always be a power of two. | |
| 845 | return i & (header.indexes_len - 1); | |
| 1011 | var i: u32 = 0; | |
| 1012 | while (i < 16) : (i += 1) { | |
| 1013 | try map.putNoClobber(i, i); | |
| 846 | 1014 | } |
| 847 | 1015 | |
| 848 | fn indexes(header: *IndexHeader, comptime I: type) []Index(I) { | |
| 849 | const start = @ptrCast([*]Index(I), @ptrCast([*]u8, header) + @sizeOf(IndexHeader)); | |
| 850 | return start[0..header.indexes_len]; | |
| 1016 | i = 16; | |
| 1017 | while (i > 0) : (i -= 1) { | |
| 1018 | _ = map.remove(i - 1); | |
| 1019 | expect(!map.contains(i - 1)); | |
| 1020 | var j: u32 = 0; | |
| 1021 | while (j < i - 1) : (j += 1) { | |
| 1022 | expectEqual(map.get(j).?, j); | |
| 1023 | } | |
| 851 | 1024 | } |
| 852 | 1025 | |
| 853 | fn capacityIndexType(header: IndexHeader) CapacityIndexType { | |
| 854 | return hash_map.capacityIndexType(header.indexes_len); | |
| 1026 | expectEqual(map.count(), 0); | |
| 1027 | } | |
| 1028 | ||
| 1029 | test "std.hash_map multiple removes on same metadata" { | |
| 1030 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 1031 | defer map.deinit(); | |
| 1032 | ||
| 1033 | var i: u32 = 0; | |
| 1034 | while (i < 16) : (i += 1) { | |
| 1035 | try map.put(i, i); | |
| 855 | 1036 | } |
| 856 | 1037 | |
| 857 | fn maybeBumpMax(header: *IndexHeader, distance_from_start_index: usize) void { | |
| 858 | if (distance_from_start_index > header.max_distance_from_start_index) { | |
| 859 | header.max_distance_from_start_index = distance_from_start_index; | |
| 1038 | _ = map.remove(7); | |
| 1039 | _ = map.remove(15); | |
| 1040 | _ = map.remove(14); | |
| 1041 | _ = map.remove(13); | |
| 1042 | expect(!map.contains(7)); | |
| 1043 | expect(!map.contains(15)); | |
| 1044 | expect(!map.contains(14)); | |
| 1045 | expect(!map.contains(13)); | |
| 1046 | ||
| 1047 | i = 0; | |
| 1048 | while (i < 13) : (i += 1) { | |
| 1049 | if (i == 7) { | |
| 1050 | expect(!map.contains(i)); | |
| 1051 | } else { | |
| 1052 | expectEqual(map.get(i).?, i); | |
| 860 | 1053 | } |
| 861 | 1054 | } |
| 862 | 1055 | |
| 863 | fn alloc(allocator: *Allocator, len: usize) !*IndexHeader { | |
| 864 | const index_size = hash_map.capacityIndexSize(len); | |
| 865 | const nbytes = @sizeOf(IndexHeader) + index_size * len; | |
| 866 | const bytes = try allocator.allocAdvanced(u8, @alignOf(IndexHeader), nbytes, .exact); | |
| 867 | @memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader)); | |
| 868 | const result = @ptrCast(*IndexHeader, bytes.ptr); | |
| 869 | result.* = .{ | |
| 870 | .max_distance_from_start_index = 0, | |
| 871 | .indexes_len = len, | |
| 872 | }; | |
| 873 | return result; | |
| 1056 | try map.put(15, 15); | |
| 1057 | try map.put(13, 13); | |
| 1058 | try map.put(14, 14); | |
| 1059 | try map.put(7, 7); | |
| 1060 | i = 0; | |
| 1061 | while (i < 16) : (i += 1) { | |
| 1062 | expectEqual(map.get(i).?, i); | |
| 1063 | } | |
| 1064 | } | |
| 1065 | ||
| 1066 | test "std.hash_map put and remove loop in random order" { | |
| 1067 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 1068 | defer map.deinit(); | |
| 1069 | ||
| 1070 | var keys = std.ArrayList(u32).init(std.testing.allocator); | |
| 1071 | defer keys.deinit(); | |
| 1072 | ||
| 1073 | const size = 32; | |
| 1074 | const iterations = 100; | |
| 1075 | ||
| 1076 | var i: u32 = 0; | |
| 1077 | while (i < size) : (i += 1) { | |
| 1078 | try keys.append(i); | |
| 1079 | } | |
| 1080 | var rng = std.rand.DefaultPrng.init(0); | |
| 1081 | ||
| 1082 | while (i < iterations) : (i += 1) { | |
| 1083 | std.rand.Random.shuffle(&rng.random, u32, keys.items); | |
| 1084 | ||
| 1085 | for (keys.items) |key| { | |
| 1086 | try map.put(key, key); | |
| 1087 | } | |
| 1088 | expectEqual(map.count(), size); | |
| 1089 | ||
| 1090 | for (keys.items) |key| { | |
| 1091 | _ = map.remove(key); | |
| 1092 | } | |
| 1093 | expectEqual(map.count(), 0); | |
| 1094 | } | |
| 1095 | } | |
| 1096 | ||
| 1097 | test "std.hash_map remove one million elements in random order" { | |
| 1098 | const Map = AutoHashMap(u32, u32); | |
| 1099 | const n = 1000 * 1000; | |
| 1100 | var map = Map.init(std.heap.page_allocator); | |
| 1101 | defer map.deinit(); | |
| 1102 | ||
| 1103 | var keys = std.ArrayList(u32).init(std.heap.page_allocator); | |
| 1104 | defer keys.deinit(); | |
| 1105 | ||
| 1106 | var i: u32 = 0; | |
| 1107 | while (i < n) : (i += 1) { | |
| 1108 | keys.append(i) catch unreachable; | |
| 1109 | } | |
| 1110 | ||
| 1111 | var rng = std.rand.DefaultPrng.init(0); | |
| 1112 | std.rand.Random.shuffle(&rng.random, u32, keys.items); | |
| 1113 | ||
| 1114 | for (keys.items) |key| { | |
| 1115 | map.put(key, key) catch unreachable; | |
| 1116 | } | |
| 1117 | ||
| 1118 | std.rand.Random.shuffle(&rng.random, u32, keys.items); | |
| 1119 | i = 0; | |
| 1120 | while (i < n) : (i += 1) { | |
| 1121 | const key = keys.items[i]; | |
| 1122 | _ = map.remove(key); | |
| 1123 | } | |
| 1124 | } | |
| 1125 | ||
| 1126 | test "std.hash_map put" { | |
| 1127 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 1128 | defer map.deinit(); | |
| 1129 | ||
| 1130 | var i: u32 = 0; | |
| 1131 | while (i < 16) : (i += 1) { | |
| 1132 | _ = try map.put(i, i); | |
| 1133 | } | |
| 1134 | ||
| 1135 | i = 0; | |
| 1136 | while (i < 16) : (i += 1) { | |
| 1137 | expectEqual(map.get(i).?, i); | |
| 1138 | } | |
| 1139 | ||
| 1140 | i = 0; | |
| 1141 | while (i < 16) : (i += 1) { | |
| 1142 | try map.put(i, i * 16 + 1); | |
| 1143 | } | |
| 1144 | ||
| 1145 | i = 0; | |
| 1146 | while (i < 16) : (i += 1) { | |
| 1147 | expectEqual(map.get(i).?, i * 16 + 1); | |
| 1148 | } | |
| 1149 | } | |
| 1150 | ||
| 1151 | test "std.hash_map getOrPut" { | |
| 1152 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); | |
| 1153 | defer map.deinit(); | |
| 1154 | ||
| 1155 | var i: u32 = 0; | |
| 1156 | while (i < 10) : (i += 1) { | |
| 1157 | try map.put(i * 2, 2); | |
| 874 | 1158 | } |
| 875 | 1159 | |
| 876 | fn free(header: *IndexHeader, allocator: *Allocator) void { | |
| 877 | const index_size = hash_map.capacityIndexSize(header.indexes_len); | |
| 878 | const ptr = @ptrCast([*]u8, header); | |
| 879 | const slice = ptr[0 .. @sizeOf(IndexHeader) + header.indexes_len * index_size]; | |
| 880 | allocator.free(slice); | |
| 1160 | i = 0; | |
| 1161 | while (i < 20) : (i += 1) { | |
| 1162 | var n = try map.getOrPutValue(i, 1); | |
| 881 | 1163 | } |
| 882 | }; | |
| 883 | 1164 | |
| 884 | test "basic hash map usage" { | |
| 1165 | i = 0; | |
| 1166 | var sum = i; | |
| 1167 | while (i < 20) : (i += 1) { | |
| 1168 | sum += map.get(i).?; | |
| 1169 | } | |
| 1170 | ||
| 1171 | expectEqual(sum, 30); | |
| 1172 | } | |
| 1173 | ||
| 1174 | test "std.hash_map basic hash map usage" { | |
| 885 | 1175 | var map = AutoHashMap(i32, i32).init(std.testing.allocator); |
| 886 | 1176 | defer map.deinit(); |
| 887 | 1177 | |
| ... | ... | @@ -925,85 +1215,10 @@ test "basic hash map usage" { |
| 925 | 1215 | map.removeAssertDiscard(3); |
| 926 | 1216 | } |
| 927 | 1217 | |
| 928 | test "iterator hash map" { | |
| 929 | // https://github.com/ziglang/zig/issues/5127 | |
| 930 | if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest; | |
| 931 | ||
| 932 | var reset_map = AutoHashMap(i32, i32).init(std.testing.allocator); | |
| 933 | defer reset_map.deinit(); | |
| 934 | ||
| 935 | // test ensureCapacity with a 0 parameter | |
| 936 | try reset_map.ensureCapacity(0); | |
| 937 | ||
| 938 | try reset_map.putNoClobber(0, 11); | |
| 939 | try reset_map.putNoClobber(1, 22); | |
| 940 | try reset_map.putNoClobber(2, 33); | |
| 941 | ||
| 942 | var keys = [_]i32{ | |
| 943 | 0, 2, 1, | |
| 944 | }; | |
| 945 | ||
| 946 | var values = [_]i32{ | |
| 947 | 11, 33, 22, | |
| 948 | }; | |
| 949 | ||
| 950 | var buffer = [_]i32{ | |
| 951 | 0, 0, 0, | |
| 952 | }; | |
| 953 | ||
| 954 | var it = reset_map.iterator(); | |
| 955 | const first_entry = it.next().?; | |
| 956 | it.reset(); | |
| 957 | ||
| 958 | var count: usize = 0; | |
| 959 | while (it.next()) |entry| : (count += 1) { | |
| 960 | buffer[@intCast(usize, entry.key)] = entry.value; | |
| 961 | } | |
| 962 | testing.expect(count == 3); | |
| 963 | testing.expect(it.next() == null); | |
| 964 | ||
| 965 | for (buffer) |v, i| { | |
| 966 | testing.expect(buffer[@intCast(usize, keys[i])] == values[i]); | |
| 967 | } | |
| 968 | ||
| 969 | it.reset(); | |
| 970 | count = 0; | |
| 971 | while (it.next()) |entry| { | |
| 972 | buffer[@intCast(usize, entry.key)] = entry.value; | |
| 973 | count += 1; | |
| 974 | if (count >= 2) break; | |
| 975 | } | |
| 976 | ||
| 977 | for (buffer[0..2]) |v, i| { | |
| 978 | testing.expect(buffer[@intCast(usize, keys[i])] == values[i]); | |
| 979 | } | |
| 980 | ||
| 981 | it.reset(); | |
| 982 | var entry = it.next().?; | |
| 983 | testing.expect(entry.key == first_entry.key); | |
| 984 | testing.expect(entry.value == first_entry.value); | |
| 985 | } | |
| 986 | ||
| 987 | test "ensure capacity" { | |
| 988 | var map = AutoHashMap(i32, i32).init(std.testing.allocator); | |
| 989 | defer map.deinit(); | |
| 990 | ||
| 991 | try map.ensureCapacity(20); | |
| 992 | const initial_capacity = map.capacity(); | |
| 993 | testing.expect(initial_capacity >= 20); | |
| 994 | var i: i32 = 0; | |
| 995 | while (i < 20) : (i += 1) { | |
| 996 | testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null); | |
| 997 | } | |
| 998 | // shouldn't resize from putAssumeCapacity | |
| 999 | testing.expect(initial_capacity == map.capacity()); | |
| 1000 | } | |
| 1001 | ||
| 1002 | test "clone" { | |
| 1218 | test "std.hash_map clone" { | |
| 1003 | 1219 | var original = AutoHashMap(i32, i32).init(std.testing.allocator); |
| 1004 | 1220 | defer original.deinit(); |
| 1005 | 1221 | |
| 1006 | // put more than `linear_scan_max` so we can test that the index header is properly cloned | |
| 1007 | 1222 | var i: u8 = 0; |
| 1008 | 1223 | while (i < 10) : (i += 1) { |
| 1009 | 1224 | try original.putNoClobber(i, i * 10); |
| ... | ... | @@ -1017,69 +1232,3 @@ test "clone" { |
| 1017 | 1232 | testing.expect(copy.get(i).? == i * 10); |
| 1018 | 1233 | } |
| 1019 | 1234 | } |
| 1020 | ||
| 1021 | pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { | |
| 1022 | return struct { | |
| 1023 | fn hash(key: K) u32 { | |
| 1024 | return getAutoHashFn(usize)(@ptrToInt(key)); | |
| 1025 | } | |
| 1026 | }.hash; | |
| 1027 | } | |
| 1028 | ||
| 1029 | pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) { | |
| 1030 | return struct { | |
| 1031 | fn eql(a: K, b: K) bool { | |
| 1032 | return a == b; | |
| 1033 | } | |
| 1034 | }.eql; | |
| 1035 | } | |
| 1036 | ||
| 1037 | pub fn getAutoHashFn(comptime K: type) (fn (K) u32) { | |
| 1038 | return struct { | |
| 1039 | fn hash(key: K) u32 { | |
| 1040 | if (comptime trait.hasUniqueRepresentation(K)) { | |
| 1041 | return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key))); | |
| 1042 | } else { | |
| 1043 | var hasher = Wyhash.init(0); | |
| 1044 | autoHash(&hasher, key); | |
| 1045 | return @truncate(u32, hasher.final()); | |
| 1046 | } | |
| 1047 | } | |
| 1048 | }.hash; | |
| 1049 | } | |
| 1050 | ||
| 1051 | pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { | |
| 1052 | return struct { | |
| 1053 | fn eql(a: K, b: K) bool { | |
| 1054 | return meta.eql(a, b); | |
| 1055 | } | |
| 1056 | }.eql; | |
| 1057 | } | |
| 1058 | ||
| 1059 | pub fn autoEqlIsCheap(comptime K: type) bool { | |
| 1060 | return switch (@typeInfo(K)) { | |
| 1061 | .Bool, | |
| 1062 | .Int, | |
| 1063 | .Float, | |
| 1064 | .Pointer, | |
| 1065 | .ComptimeFloat, | |
| 1066 | .ComptimeInt, | |
| 1067 | .Enum, | |
| 1068 | .Fn, | |
| 1069 | .ErrorSet, | |
| 1070 | .AnyFrame, | |
| 1071 | .EnumLiteral, | |
| 1072 | => true, | |
| 1073 | else => false, | |
| 1074 | }; | |
| 1075 | } | |
| 1076 | ||
| 1077 | pub fn getAutoHashStratFn(comptime K: type, comptime strategy: std.hash.Strategy) (fn (K) u32) { | |
| 1078 | return struct { | |
| 1079 | fn hash(key: K) u32 { | |
| 1080 | var hasher = Wyhash.init(0); | |
| 1081 | std.hash.autoHashStrat(&hasher, key, strategy); | |
| 1082 | return @truncate(u32, hasher.final()); | |
| 1083 | } | |
| 1084 | }.hash; | |
| 1085 | } |
lib/std/heap/general_purpose_allocator.zig+3-2| ... | ... | @@ -325,7 +325,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 325 | 325 | break; |
| 326 | 326 | } |
| 327 | 327 | } |
| 328 | for (self.large_allocations.items()) |*large_alloc| { | |
| 328 | var it = self.large_allocations.iterator(); | |
| 329 | while (it.next()) |large_alloc| { | |
| 329 | 330 | log.err("Memory leak detected: {}", .{large_alloc.value.getStackTrace()}); |
| 330 | 331 | leaks = true; |
| 331 | 332 | } |
| ... | ... | @@ -584,7 +585,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 584 | 585 | if (new_aligned_size > largest_bucket_object_size) { |
| 585 | 586 | try self.large_allocations.ensureCapacity( |
| 586 | 587 | self.backing_allocator, |
| 587 | self.large_allocations.entries.items.len + 1, | |
| 588 | self.large_allocations.count() + 1, | |
| 588 | 589 | ); |
| 589 | 590 | |
| 590 | 591 | const slice = try self.backing_allocator.allocFn(self.backing_allocator, len, ptr_align, len_align, ret_addr); |
lib/std/http/headers.zig+5-4| ... | ... | @@ -123,9 +123,9 @@ pub const Headers = struct { |
| 123 | 123 | |
| 124 | 124 | pub fn deinit(self: *Self) void { |
| 125 | 125 | { |
| 126 | for (self.index.items()) |*entry| { | |
| 127 | const dex = &entry.value; | |
| 128 | dex.deinit(self.allocator); | |
| 126 | var it = self.index.iterator(); | |
| 127 | while (it.next()) |entry| { | |
| 128 | entry.value.deinit(self.allocator); | |
| 129 | 129 | self.allocator.free(entry.key); |
| 130 | 130 | } |
| 131 | 131 | self.index.deinit(self.allocator); |
| ... | ... | @@ -333,7 +333,8 @@ pub const Headers = struct { |
| 333 | 333 | |
| 334 | 334 | fn rebuildIndex(self: *Self) void { |
| 335 | 335 | // clear out the indexes |
| 336 | for (self.index.items()) |*entry| { | |
| 336 | var it = self.index.iterator(); | |
| 337 | while (it.next()) |entry| { | |
| 337 | 338 | entry.value.shrinkRetainingCapacity(0); |
| 338 | 339 | } |
| 339 | 340 | // fill up indexes again; we know capacity is fine from before |
lib/std/io.zig+9| ... | ... | @@ -169,6 +169,15 @@ pub const BitOutStream = BitWriter; |
| 169 | 169 | /// Deprecated: use `bitWriter` |
| 170 | 170 | pub const bitOutStream = bitWriter; |
| 171 | 171 | |
| 172 | pub const AutoIndentingStream = @import("io/auto_indenting_stream.zig").AutoIndentingStream; | |
| 173 | pub const autoIndentingStream = @import("io/auto_indenting_stream.zig").autoIndentingStream; | |
| 174 | ||
| 175 | pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream; | |
| 176 | pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream; | |
| 177 | ||
| 178 | pub const FindByteOutStream = @import("io/find_byte_out_stream.zig").FindByteOutStream; | |
| 179 | pub const findByteOutStream = @import("io/find_byte_out_stream.zig").findByteOutStream; | |
| 180 | ||
| 172 | 181 | pub const Packing = @import("io/serialization.zig").Packing; |
| 173 | 182 | |
| 174 | 183 | pub const Serializer = @import("io/serialization.zig").Serializer; |
lib/std/io/auto_indenting_stream.zig created+148| ... | ... | @@ -0,0 +1,148 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const mem = std.mem; | |
| 4 | const assert = std.debug.assert; | |
| 5 | ||
| 6 | /// Automatically inserts indentation of written data by keeping | |
| 7 | /// track of the current indentation level | |
| 8 | pub fn AutoIndentingStream(comptime UnderlyingWriter: type) type { | |
| 9 | return struct { | |
| 10 | const Self = @This(); | |
| 11 | pub const Error = UnderlyingWriter.Error; | |
| 12 | pub const Writer = io.Writer(*Self, Error, write); | |
| 13 | ||
| 14 | underlying_writer: UnderlyingWriter, | |
| 15 | ||
| 16 | indent_count: usize = 0, | |
| 17 | indent_delta: usize, | |
| 18 | current_line_empty: bool = true, | |
| 19 | indent_one_shot_count: usize = 0, // automatically popped when applied | |
| 20 | applied_indent: usize = 0, // the most recently applied indent | |
| 21 | indent_next_line: usize = 0, // not used until the next line | |
| 22 | ||
| 23 | pub fn writer(self: *Self) Writer { | |
| 24 | return .{ .context = self }; | |
| 25 | } | |
| 26 | ||
| 27 | pub fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 28 | if (bytes.len == 0) | |
| 29 | return @as(usize, 0); | |
| 30 | ||
| 31 | try self.applyIndent(); | |
| 32 | return self.writeNoIndent(bytes); | |
| 33 | } | |
| 34 | ||
| 35 | // Change the indent delta without changing the final indentation level | |
| 36 | pub fn setIndentDelta(self: *Self, indent_delta: usize) void { | |
| 37 | if (self.indent_delta == indent_delta) { | |
| 38 | return; | |
| 39 | } else if (self.indent_delta > indent_delta) { | |
| 40 | assert(self.indent_delta % indent_delta == 0); | |
| 41 | self.indent_count = self.indent_count * (self.indent_delta / indent_delta); | |
| 42 | } else { | |
| 43 | // assert that the current indentation (in spaces) in a multiple of the new delta | |
| 44 | assert((self.indent_count * self.indent_delta) % indent_delta == 0); | |
| 45 | self.indent_count = self.indent_count / (indent_delta / self.indent_delta); | |
| 46 | } | |
| 47 | self.indent_delta = indent_delta; | |
| 48 | } | |
| 49 | ||
| 50 | fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize { | |
| 51 | if (bytes.len == 0) | |
| 52 | return @as(usize, 0); | |
| 53 | ||
| 54 | try self.underlying_writer.writeAll(bytes); | |
| 55 | if (bytes[bytes.len - 1] == '\n') | |
| 56 | self.resetLine(); | |
| 57 | return bytes.len; | |
| 58 | } | |
| 59 | ||
| 60 | pub fn insertNewline(self: *Self) Error!void { | |
| 61 | _ = try self.writeNoIndent("\n"); | |
| 62 | } | |
| 63 | ||
| 64 | fn resetLine(self: *Self) void { | |
| 65 | self.current_line_empty = true; | |
| 66 | self.indent_next_line = 0; | |
| 67 | } | |
| 68 | ||
| 69 | /// Insert a newline unless the current line is blank | |
| 70 | pub fn maybeInsertNewline(self: *Self) Error!void { | |
| 71 | if (!self.current_line_empty) | |
| 72 | try self.insertNewline(); | |
| 73 | } | |
| 74 | ||
| 75 | /// Push default indentation | |
| 76 | pub fn pushIndent(self: *Self) void { | |
| 77 | // Doesn't actually write any indentation. | |
| 78 | // Just primes the stream to be able to write the correct indentation if it needs to. | |
| 79 | self.indent_count += 1; | |
| 80 | } | |
| 81 | ||
| 82 | /// Push an indent that is automatically popped after being applied | |
| 83 | pub fn pushIndentOneShot(self: *Self) void { | |
| 84 | self.indent_one_shot_count += 1; | |
| 85 | self.pushIndent(); | |
| 86 | } | |
| 87 | ||
| 88 | /// Turns all one-shot indents into regular indents | |
| 89 | /// Returns number of indents that must now be manually popped | |
| 90 | pub fn lockOneShotIndent(self: *Self) usize { | |
| 91 | var locked_count = self.indent_one_shot_count; | |
| 92 | self.indent_one_shot_count = 0; | |
| 93 | return locked_count; | |
| 94 | } | |
| 95 | ||
| 96 | /// Push an indent that should not take effect until the next line | |
| 97 | pub fn pushIndentNextLine(self: *Self) void { | |
| 98 | self.indent_next_line += 1; | |
| 99 | self.pushIndent(); | |
| 100 | } | |
| 101 | ||
| 102 | pub fn popIndent(self: *Self) void { | |
| 103 | assert(self.indent_count != 0); | |
| 104 | self.indent_count -= 1; | |
| 105 | ||
| 106 | if (self.indent_next_line > 0) | |
| 107 | self.indent_next_line -= 1; | |
| 108 | } | |
| 109 | ||
| 110 | /// Writes ' ' bytes if the current line is empty | |
| 111 | fn applyIndent(self: *Self) Error!void { | |
| 112 | const current_indent = self.currentIndent(); | |
| 113 | if (self.current_line_empty and current_indent > 0) { | |
| 114 | try self.underlying_writer.writeByteNTimes(' ', current_indent); | |
| 115 | self.applied_indent = current_indent; | |
| 116 | } | |
| 117 | ||
| 118 | self.indent_count -= self.indent_one_shot_count; | |
| 119 | self.indent_one_shot_count = 0; | |
| 120 | self.current_line_empty = false; | |
| 121 | } | |
| 122 | ||
| 123 | /// Checks to see if the most recent indentation exceeds the currently pushed indents | |
| 124 | pub fn isLineOverIndented(self: *Self) bool { | |
| 125 | if (self.current_line_empty) return false; | |
| 126 | return self.applied_indent > self.currentIndent(); | |
| 127 | } | |
| 128 | ||
| 129 | fn currentIndent(self: *Self) usize { | |
| 130 | var indent_current: usize = 0; | |
| 131 | if (self.indent_count > 0) { | |
| 132 | const indent_count = self.indent_count - self.indent_next_line; | |
| 133 | indent_current = indent_count * self.indent_delta; | |
| 134 | } | |
| 135 | return indent_current; | |
| 136 | } | |
| 137 | }; | |
| 138 | } | |
| 139 | ||
| 140 | pub fn autoIndentingStream( | |
| 141 | indent_delta: usize, | |
| 142 | underlying_writer: anytype, | |
| 143 | ) AutoIndentingStream(@TypeOf(underlying_writer)) { | |
| 144 | return AutoIndentingStream(@TypeOf(underlying_writer)){ | |
| 145 | .underlying_writer = underlying_writer, | |
| 146 | .indent_delta = indent_delta, | |
| 147 | }; | |
| 148 | } |
lib/std/io/change_detection_stream.zig created+55| ... | ... | @@ -0,0 +1,55 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const mem = std.mem; | |
| 4 | const assert = std.debug.assert; | |
| 5 | ||
| 6 | /// Used to detect if the data written to a stream differs from a source buffer | |
| 7 | pub fn ChangeDetectionStream(comptime WriterType: type) type { | |
| 8 | return struct { | |
| 9 | const Self = @This(); | |
| 10 | pub const Error = WriterType.Error; | |
| 11 | pub const Writer = io.Writer(*Self, Error, write); | |
| 12 | ||
| 13 | anything_changed: bool, | |
| 14 | underlying_writer: WriterType, | |
| 15 | source_index: usize, | |
| 16 | source: []const u8, | |
| 17 | ||
| 18 | pub fn writer(self: *Self) Writer { | |
| 19 | return .{ .context = self }; | |
| 20 | } | |
| 21 | ||
| 22 | fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 23 | if (!self.anything_changed) { | |
| 24 | const end = self.source_index + bytes.len; | |
| 25 | if (end > self.source.len) { | |
| 26 | self.anything_changed = true; | |
| 27 | } else { | |
| 28 | const src_slice = self.source[self.source_index..end]; | |
| 29 | self.source_index += bytes.len; | |
| 30 | if (!mem.eql(u8, bytes, src_slice)) { | |
| 31 | self.anything_changed = true; | |
| 32 | } | |
| 33 | } | |
| 34 | } | |
| 35 | ||
| 36 | return self.underlying_writer.write(bytes); | |
| 37 | } | |
| 38 | ||
| 39 | pub fn changeDetected(self: *Self) bool { | |
| 40 | return self.anything_changed or (self.source_index != self.source.len); | |
| 41 | } | |
| 42 | }; | |
| 43 | } | |
| 44 | ||
| 45 | pub fn changeDetectionStream( | |
| 46 | source: []const u8, | |
| 47 | underlying_writer: anytype, | |
| 48 | ) ChangeDetectionStream(@TypeOf(underlying_writer)) { | |
| 49 | return ChangeDetectionStream(@TypeOf(underlying_writer)){ | |
| 50 | .anything_changed = false, | |
| 51 | .underlying_writer = underlying_writer, | |
| 52 | .source_index = 0, | |
| 53 | .source = source, | |
| 54 | }; | |
| 55 | } |
lib/std/io/find_byte_out_stream.zig created+40| ... | ... | @@ -0,0 +1,40 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const io = std.io; | |
| 3 | const assert = std.debug.assert; | |
| 4 | ||
| 5 | /// An OutStream that returns whether the given character has been written to it. | |
| 6 | /// The contents are not written to anything. | |
| 7 | pub fn FindByteOutStream(comptime UnderlyingWriter: type) type { | |
| 8 | return struct { | |
| 9 | const Self = @This(); | |
| 10 | pub const Error = UnderlyingWriter.Error; | |
| 11 | pub const Writer = io.Writer(*Self, Error, write); | |
| 12 | ||
| 13 | underlying_writer: UnderlyingWriter, | |
| 14 | byte_found: bool, | |
| 15 | byte: u8, | |
| 16 | ||
| 17 | pub fn writer(self: *Self) Writer { | |
| 18 | return .{ .context = self }; | |
| 19 | } | |
| 20 | ||
| 21 | fn write(self: *Self, bytes: []const u8) Error!usize { | |
| 22 | if (!self.byte_found) { | |
| 23 | self.byte_found = blk: { | |
| 24 | for (bytes) |b| | |
| 25 | if (b == self.byte) break :blk true; | |
| 26 | break :blk false; | |
| 27 | }; | |
| 28 | } | |
| 29 | return self.underlying_writer.write(bytes); | |
| 30 | } | |
| 31 | }; | |
| 32 | } | |
| 33 | ||
| 34 | pub fn findByteOutStream(byte: u8, underlying_writer: anytype) FindByteOutStream(@TypeOf(underlying_writer)) { | |
| 35 | return FindByteOutStream(@TypeOf(underlying_writer)){ | |
| 36 | .underlying_writer = underlying_writer, | |
| 37 | .byte = byte, | |
| 38 | .byte_found = false, | |
| 39 | }; | |
| 40 | } |
lib/std/meta.zig+8-8| ... | ... | @@ -705,34 +705,34 @@ pub fn Vector(comptime len: u32, comptime child: type) type { |
| 705 | 705 | pub fn cast(comptime DestType: type, target: anytype) DestType { |
| 706 | 706 | const TargetType = @TypeOf(target); |
| 707 | 707 | switch (@typeInfo(DestType)) { |
| 708 | .Pointer => { | |
| 708 | .Pointer => |dest_ptr| { | |
| 709 | 709 | switch (@typeInfo(TargetType)) { |
| 710 | 710 | .Int, .ComptimeInt => { |
| 711 | 711 | return @intToPtr(DestType, target); |
| 712 | 712 | }, |
| 713 | 713 | .Pointer => |ptr| { |
| 714 | return @ptrCast(DestType, @alignCast(ptr.alignment, target)); | |
| 714 | return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); | |
| 715 | 715 | }, |
| 716 | 716 | .Optional => |opt| { |
| 717 | 717 | if (@typeInfo(opt.child) == .Pointer) { |
| 718 | return @ptrCast(DestType, @alignCast(@alignOf(opt.child.Child), target)); | |
| 718 | return @ptrCast(DestType, @alignCast(dest_ptr, target)); | |
| 719 | 719 | } |
| 720 | 720 | }, |
| 721 | 721 | else => {}, |
| 722 | 722 | } |
| 723 | 723 | }, |
| 724 | .Optional => |opt| { | |
| 725 | if (@typeInfo(opt.child) == .Pointer) { | |
| 724 | .Optional => |dest_opt| { | |
| 725 | if (@typeInfo(dest_opt.child) == .Pointer) { | |
| 726 | 726 | switch (@typeInfo(TargetType)) { |
| 727 | 727 | .Int, .ComptimeInt => { |
| 728 | 728 | return @intToPtr(DestType, target); |
| 729 | 729 | }, |
| 730 | .Pointer => |ptr| { | |
| 731 | return @ptrCast(DestType, @alignCast(ptr.alignment, target)); | |
| 730 | .Pointer => { | |
| 731 | return @ptrCast(DestType, @alignCast(@alignOf(dest_opt.child.Child), target)); | |
| 732 | 732 | }, |
| 733 | 733 | .Optional => |target_opt| { |
| 734 | 734 | if (@typeInfo(target_opt.child) == .Pointer) { |
| 735 | return @ptrCast(DestType, @alignCast(@alignOf(target_opt.child.Child), target)); | |
| 735 | return @ptrCast(DestType, @alignCast(@alignOf(dest_opt.child.Child), target)); | |
| 736 | 736 | } |
| 737 | 737 | }, |
| 738 | 738 | else => {}, |
lib/std/meta/trailer_flags.zig+1| ... | ... | @@ -46,6 +46,7 @@ pub fn TrailerFlags(comptime Fields: type) type { |
| 46 | 46 | ??struct_field.field_type, |
| 47 | 47 | @as(?struct_field.field_type, null), |
| 48 | 48 | ), |
| 49 | .is_comptime = false, | |
| 49 | 50 | }; |
| 50 | 51 | } |
| 51 | 52 | break :blk @Type(.{ |
lib/std/net.zig+4-1| ... | ... | @@ -1164,7 +1164,7 @@ fn linuxLookupNameFromDnsSearch( |
| 1164 | 1164 | } |
| 1165 | 1165 | |
| 1166 | 1166 | const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, ".")) |
| 1167 | &[_]u8{} | |
| 1167 | "" | |
| 1168 | 1168 | else |
| 1169 | 1169 | rc.search.span(); |
| 1170 | 1170 | |
| ... | ... | @@ -1641,6 +1641,9 @@ pub const StreamServer = struct { |
| 1641 | 1641 | /// by the socket buffer limits, not by the system memory. |
| 1642 | 1642 | SystemResources, |
| 1643 | 1643 | |
| 1644 | /// Socket is not listening for new connections. | |
| 1645 | SocketNotListening, | |
| 1646 | ||
| 1644 | 1647 | ProtocolFailure, |
| 1645 | 1648 | |
| 1646 | 1649 | /// Firewall rules forbid connection. |
lib/std/os.zig+98-8| ... | ... | @@ -2512,13 +2512,14 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read |
| 2512 | 2512 | } |
| 2513 | 2513 | } |
| 2514 | 2514 | |
| 2515 | pub const SetIdError = error{ | |
| 2516 | ResourceLimitReached, | |
| 2515 | pub const SetEidError = error{ | |
| 2517 | 2516 | InvalidUserId, |
| 2518 | 2517 | PermissionDenied, |
| 2519 | } || UnexpectedError; | |
| 2518 | }; | |
| 2520 | 2519 | |
| 2521 | pub fn setuid(uid: u32) SetIdError!void { | |
| 2520 | pub const SetIdError = error{ResourceLimitReached} || SetEidError || UnexpectedError; | |
| 2521 | ||
| 2522 | pub fn setuid(uid: uid_t) SetIdError!void { | |
| 2522 | 2523 | switch (errno(system.setuid(uid))) { |
| 2523 | 2524 | 0 => return, |
| 2524 | 2525 | EAGAIN => return error.ResourceLimitReached, |
| ... | ... | @@ -2528,7 +2529,16 @@ pub fn setuid(uid: u32) SetIdError!void { |
| 2528 | 2529 | } |
| 2529 | 2530 | } |
| 2530 | 2531 | |
| 2531 | pub fn setreuid(ruid: u32, euid: u32) SetIdError!void { | |
| 2532 | pub fn seteuid(uid: uid_t) SetEidError!void { | |
| 2533 | switch (errno(system.seteuid(uid))) { | |
| 2534 | 0 => return, | |
| 2535 | EINVAL => return error.InvalidUserId, | |
| 2536 | EPERM => return error.PermissionDenied, | |
| 2537 | else => |err| return unexpectedErrno(err), | |
| 2538 | } | |
| 2539 | } | |
| 2540 | ||
| 2541 | pub fn setreuid(ruid: uid_t, euid: uid_t) SetIdError!void { | |
| 2532 | 2542 | switch (errno(system.setreuid(ruid, euid))) { |
| 2533 | 2543 | 0 => return, |
| 2534 | 2544 | EAGAIN => return error.ResourceLimitReached, |
| ... | ... | @@ -2538,7 +2548,7 @@ pub fn setreuid(ruid: u32, euid: u32) SetIdError!void { |
| 2538 | 2548 | } |
| 2539 | 2549 | } |
| 2540 | 2550 | |
| 2541 | pub fn setgid(gid: u32) SetIdError!void { | |
| 2551 | pub fn setgid(gid: gid_t) SetIdError!void { | |
| 2542 | 2552 | switch (errno(system.setgid(gid))) { |
| 2543 | 2553 | 0 => return, |
| 2544 | 2554 | EAGAIN => return error.ResourceLimitReached, |
| ... | ... | @@ -2548,7 +2558,16 @@ pub fn setgid(gid: u32) SetIdError!void { |
| 2548 | 2558 | } |
| 2549 | 2559 | } |
| 2550 | 2560 | |
| 2551 | pub fn setregid(rgid: u32, egid: u32) SetIdError!void { | |
| 2561 | pub fn setegid(uid: uid_t) SetEidError!void { | |
| 2562 | switch (errno(system.setegid(uid))) { | |
| 2563 | 0 => return, | |
| 2564 | EINVAL => return error.InvalidUserId, | |
| 2565 | EPERM => return error.PermissionDenied, | |
| 2566 | else => |err| return unexpectedErrno(err), | |
| 2567 | } | |
| 2568 | } | |
| 2569 | ||
| 2570 | pub fn setregid(rgid: gid_t, egid: gid_t) SetIdError!void { | |
| 2552 | 2571 | switch (errno(system.setregid(rgid, egid))) { |
| 2553 | 2572 | 0 => return, |
| 2554 | 2573 | EAGAIN => return error.ResourceLimitReached, |
| ... | ... | @@ -2802,6 +2821,9 @@ pub const AcceptError = error{ |
| 2802 | 2821 | /// by the socket buffer limits, not by the system memory. |
| 2803 | 2822 | SystemResources, |
| 2804 | 2823 | |
| 2824 | /// Socket is not listening for new connections. | |
| 2825 | SocketNotListening, | |
| 2826 | ||
| 2805 | 2827 | ProtocolFailure, |
| 2806 | 2828 | |
| 2807 | 2829 | /// Firewall rules forbid connection. |
| ... | ... | @@ -2870,7 +2892,7 @@ pub fn accept( |
| 2870 | 2892 | EBADF => unreachable, // always a race condition |
| 2871 | 2893 | ECONNABORTED => return error.ConnectionAborted, |
| 2872 | 2894 | EFAULT => unreachable, |
| 2873 | EINVAL => unreachable, | |
| 2895 | EINVAL => return error.SocketNotListening, | |
| 2874 | 2896 | ENOTSOCK => unreachable, |
| 2875 | 2897 | EMFILE => return error.ProcessFdQuotaExceeded, |
| 2876 | 2898 | ENFILE => return error.SystemFdQuotaExceeded, |
| ... | ... | @@ -5328,3 +5350,71 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t { |
| 5328 | 5350 | else => |err| return std.os.unexpectedErrno(err), |
| 5329 | 5351 | } |
| 5330 | 5352 | } |
| 5353 | ||
| 5354 | pub const SyncError = error{ | |
| 5355 | InputOutput, | |
| 5356 | NoSpaceLeft, | |
| 5357 | DiskQuota, | |
| 5358 | AccessDenied, | |
| 5359 | } || UnexpectedError; | |
| 5360 | ||
| 5361 | /// Write all pending file contents and metadata modifications to all filesystems. | |
| 5362 | pub fn sync() void { | |
| 5363 | system.sync(); | |
| 5364 | } | |
| 5365 | ||
| 5366 | /// Write all pending file contents and metadata modifications to the filesystem which contains the specified file. | |
| 5367 | pub fn syncfs(fd: fd_t) SyncError!void { | |
| 5368 | const rc = system.syncfs(fd); | |
| 5369 | switch (errno(rc)) { | |
| 5370 | 0 => return, | |
| 5371 | EBADF, EINVAL, EROFS => unreachable, | |
| 5372 | EIO => return error.InputOutput, | |
| 5373 | ENOSPC => return error.NoSpaceLeft, | |
| 5374 | EDQUOT => return error.DiskQuota, | |
| 5375 | else => |err| return std.os.unexpectedErrno(err), | |
| 5376 | } | |
| 5377 | } | |
| 5378 | ||
| 5379 | /// Write all pending file contents and metadata modifications for the specified file descriptor to the underlying filesystem. | |
| 5380 | pub fn fsync(fd: fd_t) SyncError!void { | |
| 5381 | if (std.Target.current.os.tag == .windows) { | |
| 5382 | if (windows.kernel32.FlushFileBuffers(fd) != 0) | |
| 5383 | return; | |
| 5384 | switch (windows.kernel32.GetLastError()) { | |
| 5385 | .SUCCESS => return, | |
| 5386 | .INVALID_HANDLE => unreachable, | |
| 5387 | .ACCESS_DENIED => return error.AccessDenied, // a sync was performed but the system couldn't update the access time | |
| 5388 | .UNEXP_NET_ERR => return error.InputOutput, | |
| 5389 | else => return error.InputOutput, | |
| 5390 | } | |
| 5391 | } | |
| 5392 | const rc = system.fsync(fd); | |
| 5393 | switch (errno(rc)) { | |
| 5394 | 0 => return, | |
| 5395 | EBADF, EINVAL, EROFS => unreachable, | |
| 5396 | EIO => return error.InputOutput, | |
| 5397 | ENOSPC => return error.NoSpaceLeft, | |
| 5398 | EDQUOT => return error.DiskQuota, | |
| 5399 | else => |err| return std.os.unexpectedErrno(err), | |
| 5400 | } | |
| 5401 | } | |
| 5402 | ||
| 5403 | /// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata. | |
| 5404 | pub fn fdatasync(fd: fd_t) SyncError!void { | |
| 5405 | if (std.Target.current.os.tag == .windows) { | |
| 5406 | return fsync(fd) catch |err| switch (err) { | |
| 5407 | SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced | |
| 5408 | else => return err, | |
| 5409 | }; | |
| 5410 | } | |
| 5411 | const rc = system.fdatasync(fd); | |
| 5412 | switch (errno(rc)) { | |
| 5413 | 0 => return, | |
| 5414 | EBADF, EINVAL, EROFS => unreachable, | |
| 5415 | EIO => return error.InputOutput, | |
| 5416 | ENOSPC => return error.NoSpaceLeft, | |
| 5417 | EDQUOT => return error.DiskQuota, | |
| 5418 | else => |err| return std.os.unexpectedErrno(err), | |
| 5419 | } | |
| 5420 | } |
lib/std/os/bits/darwin.zig+6-2| ... | ... | @@ -7,9 +7,13 @@ const std = @import("../../std.zig"); |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 8 | const maxInt = std.math.maxInt; |
| 9 | 9 | |
| 10 | // See: https://opensource.apple.com/source/xnu/xnu-6153.141.1/bsd/sys/_types.h.auto.html | |
| 11 | // TODO: audit mode_t/pid_t, should likely be u16/i32 | |
| 10 | 12 | pub const fd_t = c_int; |
| 11 | 13 | pub const pid_t = c_int; |
| 12 | 14 | pub const mode_t = c_uint; |
| 15 | pub const uid_t = u32; | |
| 16 | pub const gid_t = u32; | |
| 13 | 17 | |
| 14 | 18 | pub const in_port_t = u16; |
| 15 | 19 | pub const sa_family_t = u8; |
| ... | ... | @@ -79,8 +83,8 @@ pub const Stat = extern struct { |
| 79 | 83 | mode: u16, |
| 80 | 84 | nlink: u16, |
| 81 | 85 | ino: ino_t, |
| 82 | uid: u32, | |
| 83 | gid: u32, | |
| 86 | uid: uid_t, | |
| 87 | gid: gid_t, | |
| 84 | 88 | rdev: i32, |
| 85 | 89 | atimesec: isize, |
| 86 | 90 | atimensec: isize, |
lib/std/os/bits/dragonfly.zig+10-3| ... | ... | @@ -9,10 +9,17 @@ const maxInt = std.math.maxInt; |
| 9 | 9 | pub fn S_ISCHR(m: u32) bool { |
| 10 | 10 | return m & S_IFMT == S_IFCHR; |
| 11 | 11 | } |
| 12 | ||
| 13 | // See: | |
| 14 | // - https://gitweb.dragonflybsd.org/dragonfly.git/blob/HEAD:/include/unistd.h | |
| 15 | // - https://gitweb.dragonflybsd.org/dragonfly.git/blob/HEAD:/sys/sys/types.h | |
| 16 | // TODO: mode_t should probably be changed to a u16, audit pid_t/off_t as well | |
| 12 | 17 | pub const fd_t = c_int; |
| 13 | 18 | pub const pid_t = c_int; |
| 14 | 19 | pub const off_t = c_long; |
| 15 | 20 | pub const mode_t = c_uint; |
| 21 | pub const uid_t = u32; | |
| 22 | pub const gid_t = u32; | |
| 16 | 23 | |
| 17 | 24 | pub const ENOTSUP = EOPNOTSUPP; |
| 18 | 25 | pub const EWOULDBLOCK = EAGAIN; |
| ... | ... | @@ -151,8 +158,8 @@ pub const Stat = extern struct { |
| 151 | 158 | dev: c_uint, |
| 152 | 159 | mode: c_ushort, |
| 153 | 160 | padding1: u16, |
| 154 | uid: c_uint, | |
| 155 | gid: c_uint, | |
| 161 | uid: uid_t, | |
| 162 | gid: gid_t, | |
| 156 | 163 | rdev: c_uint, |
| 157 | 164 | atim: timespec, |
| 158 | 165 | mtim: timespec, |
| ... | ... | @@ -511,7 +518,7 @@ pub const siginfo_t = extern struct { |
| 511 | 518 | si_errno: c_int, |
| 512 | 519 | si_code: c_int, |
| 513 | 520 | si_pid: c_int, |
| 514 | si_uid: c_uint, | |
| 521 | si_uid: uid_t, | |
| 515 | 522 | si_status: c_int, |
| 516 | 523 | si_addr: ?*c_void, |
| 517 | 524 | si_value: union_sigval, |
lib/std/os/bits/freebsd.zig+6-2| ... | ... | @@ -6,8 +6,12 @@ |
| 6 | 6 | const std = @import("../../std.zig"); |
| 7 | 7 | const maxInt = std.math.maxInt; |
| 8 | 8 | |
| 9 | // See https://svnweb.freebsd.org/base/head/sys/sys/_types.h?view=co | |
| 10 | // TODO: audit pid_t/mode_t. They should likely be i32 and u16, respectively | |
| 9 | 11 | pub const fd_t = c_int; |
| 10 | 12 | pub const pid_t = c_int; |
| 13 | pub const uid_t = u32; | |
| 14 | pub const gid_t = u32; | |
| 11 | 15 | pub const mode_t = c_uint; |
| 12 | 16 | |
| 13 | 17 | pub const socklen_t = u32; |
| ... | ... | @@ -128,8 +132,8 @@ pub const Stat = extern struct { |
| 128 | 132 | |
| 129 | 133 | mode: u16, |
| 130 | 134 | __pad0: u16, |
| 131 | uid: u32, | |
| 132 | gid: u32, | |
| 135 | uid: uid_t, | |
| 136 | gid: gid_t, | |
| 133 | 137 | __pad1: u32, |
| 134 | 138 | rdev: u64, |
| 135 | 139 |
lib/std/os/bits/linux.zig+4-4| ... | ... | @@ -29,7 +29,7 @@ const is_mips = builtin.arch.isMIPS(); |
| 29 | 29 | |
| 30 | 30 | pub const pid_t = i32; |
| 31 | 31 | pub const fd_t = i32; |
| 32 | pub const uid_t = i32; | |
| 32 | pub const uid_t = u32; | |
| 33 | 33 | pub const gid_t = u32; |
| 34 | 34 | pub const clock_t = isize; |
| 35 | 35 | |
| ... | ... | @@ -853,7 +853,7 @@ pub const signalfd_siginfo = extern struct { |
| 853 | 853 | errno: i32, |
| 854 | 854 | code: i32, |
| 855 | 855 | pid: u32, |
| 856 | uid: u32, | |
| 856 | uid: uid_t, | |
| 857 | 857 | fd: i32, |
| 858 | 858 | tid: u32, |
| 859 | 859 | band: u32, |
| ... | ... | @@ -1491,10 +1491,10 @@ pub const Statx = extern struct { |
| 1491 | 1491 | nlink: u32, |
| 1492 | 1492 | |
| 1493 | 1493 | /// User ID of owner |
| 1494 | uid: u32, | |
| 1494 | uid: uid_t, | |
| 1495 | 1495 | |
| 1496 | 1496 | /// Group ID of owner |
| 1497 | gid: u32, | |
| 1497 | gid: gid_t, | |
| 1498 | 1498 | |
| 1499 | 1499 | /// File type and mode |
| 1500 | 1500 | mode: u16, |
lib/std/os/bits/linux/x86_64.zig+3-2| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | const std = @import("../../../std.zig"); |
| 8 | 8 | const pid_t = linux.pid_t; |
| 9 | 9 | const uid_t = linux.uid_t; |
| 10 | const gid_t = linux.gid_t; | |
| 10 | 11 | const clock_t = linux.clock_t; |
| 11 | 12 | const stack_t = linux.stack_t; |
| 12 | 13 | const sigset_t = linux.sigset_t; |
| ... | ... | @@ -523,8 +524,8 @@ pub const Stat = extern struct { |
| 523 | 524 | nlink: usize, |
| 524 | 525 | |
| 525 | 526 | mode: u32, |
| 526 | uid: u32, | |
| 527 | gid: u32, | |
| 527 | uid: uid_t, | |
| 528 | gid: gid_t, | |
| 528 | 529 | __pad0: u32, |
| 529 | 530 | rdev: u64, |
| 530 | 531 | size: off_t, |
lib/std/os/linux.zig+56-26| ... | ... | @@ -655,7 +655,7 @@ pub fn nanosleep(req: *const timespec, rem: ?*timespec) usize { |
| 655 | 655 | return syscall2(.nanosleep, @ptrToInt(req), @ptrToInt(rem)); |
| 656 | 656 | } |
| 657 | 657 | |
| 658 | pub fn setuid(uid: u32) usize { | |
| 658 | pub fn setuid(uid: uid_t) usize { | |
| 659 | 659 | if (@hasField(SYS, "setuid32")) { |
| 660 | 660 | return syscall1(.setuid32, uid); |
| 661 | 661 | } else { |
| ... | ... | @@ -663,7 +663,7 @@ pub fn setuid(uid: u32) usize { |
| 663 | 663 | } |
| 664 | 664 | } |
| 665 | 665 | |
| 666 | pub fn setgid(gid: u32) usize { | |
| 666 | pub fn setgid(gid: gid_t) usize { | |
| 667 | 667 | if (@hasField(SYS, "setgid32")) { |
| 668 | 668 | return syscall1(.setgid32, gid); |
| 669 | 669 | } else { |
| ... | ... | @@ -671,7 +671,7 @@ pub fn setgid(gid: u32) usize { |
| 671 | 671 | } |
| 672 | 672 | } |
| 673 | 673 | |
| 674 | pub fn setreuid(ruid: u32, euid: u32) usize { | |
| 674 | pub fn setreuid(ruid: uid_t, euid: uid_t) usize { | |
| 675 | 675 | if (@hasField(SYS, "setreuid32")) { |
| 676 | 676 | return syscall2(.setreuid32, ruid, euid); |
| 677 | 677 | } else { |
| ... | ... | @@ -679,7 +679,7 @@ pub fn setreuid(ruid: u32, euid: u32) usize { |
| 679 | 679 | } |
| 680 | 680 | } |
| 681 | 681 | |
| 682 | pub fn setregid(rgid: u32, egid: u32) usize { | |
| 682 | pub fn setregid(rgid: gid_t, egid: gid_t) usize { | |
| 683 | 683 | if (@hasField(SYS, "setregid32")) { |
| 684 | 684 | return syscall2(.setregid32, rgid, egid); |
| 685 | 685 | } else { |
| ... | ... | @@ -687,47 +687,61 @@ pub fn setregid(rgid: u32, egid: u32) usize { |
| 687 | 687 | } |
| 688 | 688 | } |
| 689 | 689 | |
| 690 | pub fn getuid() u32 { | |
| 690 | pub fn getuid() uid_t { | |
| 691 | 691 | if (@hasField(SYS, "getuid32")) { |
| 692 | return @as(u32, syscall0(.getuid32)); | |
| 692 | return @as(uid_t, syscall0(.getuid32)); | |
| 693 | 693 | } else { |
| 694 | return @as(u32, syscall0(.getuid)); | |
| 694 | return @as(uid_t, syscall0(.getuid)); | |
| 695 | 695 | } |
| 696 | 696 | } |
| 697 | 697 | |
| 698 | pub fn getgid() u32 { | |
| 698 | pub fn getgid() gid_t { | |
| 699 | 699 | if (@hasField(SYS, "getgid32")) { |
| 700 | return @as(u32, syscall0(.getgid32)); | |
| 700 | return @as(gid_t, syscall0(.getgid32)); | |
| 701 | 701 | } else { |
| 702 | return @as(u32, syscall0(.getgid)); | |
| 702 | return @as(gid_t, syscall0(.getgid)); | |
| 703 | 703 | } |
| 704 | 704 | } |
| 705 | 705 | |
| 706 | pub fn geteuid() u32 { | |
| 706 | pub fn geteuid() uid_t { | |
| 707 | 707 | if (@hasField(SYS, "geteuid32")) { |
| 708 | return @as(u32, syscall0(.geteuid32)); | |
| 708 | return @as(uid_t, syscall0(.geteuid32)); | |
| 709 | 709 | } else { |
| 710 | return @as(u32, syscall0(.geteuid)); | |
| 710 | return @as(uid_t, syscall0(.geteuid)); | |
| 711 | 711 | } |
| 712 | 712 | } |
| 713 | 713 | |
| 714 | pub fn getegid() u32 { | |
| 714 | pub fn getegid() gid_t { | |
| 715 | 715 | if (@hasField(SYS, "getegid32")) { |
| 716 | return @as(u32, syscall0(.getegid32)); | |
| 716 | return @as(gid_t, syscall0(.getegid32)); | |
| 717 | 717 | } else { |
| 718 | return @as(u32, syscall0(.getegid)); | |
| 718 | return @as(gid_t, syscall0(.getegid)); | |
| 719 | 719 | } |
| 720 | 720 | } |
| 721 | 721 | |
| 722 | pub fn seteuid(euid: u32) usize { | |
| 723 | return setreuid(std.math.maxInt(u32), euid); | |
| 722 | pub fn seteuid(euid: uid_t) usize { | |
| 723 | // We use setresuid here instead of setreuid to ensure that the saved uid | |
| 724 | // is not changed. This is what musl and recent glibc versions do as well. | |
| 725 | // | |
| 726 | // The setresuid(2) man page says that if -1 is passed the corresponding | |
| 727 | // id will not be changed. Since uid_t is unsigned, this wraps around to the | |
| 728 | // max value in C. | |
| 729 | comptime assert(@typeInfo(uid_t) == .Int and !@typeInfo(uid_t).Int.is_signed); | |
| 730 | return setresuid(std.math.maxInt(uid_t), euid, std.math.maxInt(uid_t)); | |
| 724 | 731 | } |
| 725 | 732 | |
| 726 | pub fn setegid(egid: u32) usize { | |
| 727 | return setregid(std.math.maxInt(u32), egid); | |
| 733 | pub fn setegid(egid: gid_t) usize { | |
| 734 | // We use setresgid here instead of setregid to ensure that the saved uid | |
| 735 | // is not changed. This is what musl and recent glibc versions do as well. | |
| 736 | // | |
| 737 | // The setresgid(2) man page says that if -1 is passed the corresponding | |
| 738 | // id will not be changed. Since gid_t is unsigned, this wraps around to the | |
| 739 | // max value in C. | |
| 740 | comptime assert(@typeInfo(uid_t) == .Int and !@typeInfo(uid_t).Int.is_signed); | |
| 741 | return setresgid(std.math.maxInt(gid_t), egid, std.math.maxInt(gid_t)); | |
| 728 | 742 | } |
| 729 | 743 | |
| 730 | pub fn getresuid(ruid: *u32, euid: *u32, suid: *u32) usize { | |
| 744 | pub fn getresuid(ruid: *uid_t, euid: *uid_t, suid: *uid_t) usize { | |
| 731 | 745 | if (@hasField(SYS, "getresuid32")) { |
| 732 | 746 | return syscall3(.getresuid32, @ptrToInt(ruid), @ptrToInt(euid), @ptrToInt(suid)); |
| 733 | 747 | } else { |
| ... | ... | @@ -735,7 +749,7 @@ pub fn getresuid(ruid: *u32, euid: *u32, suid: *u32) usize { |
| 735 | 749 | } |
| 736 | 750 | } |
| 737 | 751 | |
| 738 | pub fn getresgid(rgid: *u32, egid: *u32, sgid: *u32) usize { | |
| 752 | pub fn getresgid(rgid: *gid_t, egid: *gid_t, sgid: *gid_t) usize { | |
| 739 | 753 | if (@hasField(SYS, "getresgid32")) { |
| 740 | 754 | return syscall3(.getresgid32, @ptrToInt(rgid), @ptrToInt(egid), @ptrToInt(sgid)); |
| 741 | 755 | } else { |
| ... | ... | @@ -743,7 +757,7 @@ pub fn getresgid(rgid: *u32, egid: *u32, sgid: *u32) usize { |
| 743 | 757 | } |
| 744 | 758 | } |
| 745 | 759 | |
| 746 | pub fn setresuid(ruid: u32, euid: u32, suid: u32) usize { | |
| 760 | pub fn setresuid(ruid: uid_t, euid: uid_t, suid: uid_t) usize { | |
| 747 | 761 | if (@hasField(SYS, "setresuid32")) { |
| 748 | 762 | return syscall3(.setresuid32, ruid, euid, suid); |
| 749 | 763 | } else { |
| ... | ... | @@ -751,7 +765,7 @@ pub fn setresuid(ruid: u32, euid: u32, suid: u32) usize { |
| 751 | 765 | } |
| 752 | 766 | } |
| 753 | 767 | |
| 754 | pub fn setresgid(rgid: u32, egid: u32, sgid: u32) usize { | |
| 768 | pub fn setresgid(rgid: gid_t, egid: gid_t, sgid: gid_t) usize { | |
| 755 | 769 | if (@hasField(SYS, "setresgid32")) { |
| 756 | 770 | return syscall3(.setresgid32, rgid, egid, sgid); |
| 757 | 771 | } else { |
| ... | ... | @@ -759,7 +773,7 @@ pub fn setresgid(rgid: u32, egid: u32, sgid: u32) usize { |
| 759 | 773 | } |
| 760 | 774 | } |
| 761 | 775 | |
| 762 | pub fn getgroups(size: usize, list: *u32) usize { | |
| 776 | pub fn getgroups(size: usize, list: *gid_t) usize { | |
| 763 | 777 | if (@hasField(SYS, "getgroups32")) { |
| 764 | 778 | return syscall2(.getgroups32, size, @ptrToInt(list)); |
| 765 | 779 | } else { |
| ... | ... | @@ -767,7 +781,7 @@ pub fn getgroups(size: usize, list: *u32) usize { |
| 767 | 781 | } |
| 768 | 782 | } |
| 769 | 783 | |
| 770 | pub fn setgroups(size: usize, list: *const u32) usize { | |
| 784 | pub fn setgroups(size: usize, list: *const gid_t) usize { | |
| 771 | 785 | if (@hasField(SYS, "setgroups32")) { |
| 772 | 786 | return syscall2(.setgroups32, size, @ptrToInt(list)); |
| 773 | 787 | } else { |
| ... | ... | @@ -1226,6 +1240,22 @@ pub fn bpf(cmd: BPF.Cmd, attr: *BPF.Attr, size: u32) usize { |
| 1226 | 1240 | return syscall3(.bpf, @enumToInt(cmd), @ptrToInt(attr), size); |
| 1227 | 1241 | } |
| 1228 | 1242 | |
| 1243 | pub fn sync() void { | |
| 1244 | _ = syscall0(.sync); | |
| 1245 | } | |
| 1246 | ||
| 1247 | pub fn syncfs(fd: fd_t) usize { | |
| 1248 | return syscall1(.syncfs, @bitCast(usize, @as(isize, fd))); | |
| 1249 | } | |
| 1250 | ||
| 1251 | pub fn fsync(fd: fd_t) usize { | |
| 1252 | return syscall1(.fsync, @bitCast(usize, @as(isize, fd))); | |
| 1253 | } | |
| 1254 | ||
| 1255 | pub fn fdatasync(fd: fd_t) usize { | |
| 1256 | return syscall1(.fdatasync, @bitCast(usize, @as(isize, fd))); | |
| 1257 | } | |
| 1258 | ||
| 1229 | 1259 | test "" { |
| 1230 | 1260 | if (builtin.os.tag == .linux) { |
| 1231 | 1261 | _ = @import("linux/test.zig"); |
lib/std/os/test.zig+36| ... | ... | @@ -555,3 +555,39 @@ test "signalfd" { |
| 555 | 555 | return error.SkipZigTest; |
| 556 | 556 | _ = std.os.signalfd; |
| 557 | 557 | } |
| 558 | ||
| 559 | test "sync" { | |
| 560 | if (builtin.os.tag != .linux) | |
| 561 | return error.SkipZigTest; | |
| 562 | ||
| 563 | var tmp = tmpDir(.{}); | |
| 564 | defer tmp.cleanup(); | |
| 565 | ||
| 566 | const test_out_file = "os_tmp_test"; | |
| 567 | const file = try tmp.dir.createFile(test_out_file, .{}); | |
| 568 | defer { | |
| 569 | file.close(); | |
| 570 | tmp.dir.deleteFile(test_out_file) catch {}; | |
| 571 | } | |
| 572 | ||
| 573 | os.sync(); | |
| 574 | try os.syncfs(file.handle); | |
| 575 | } | |
| 576 | ||
| 577 | test "fsync" { | |
| 578 | if (builtin.os.tag != .linux and builtin.os.tag != .windows) | |
| 579 | return error.SkipZigTest; | |
| 580 | ||
| 581 | var tmp = tmpDir(.{}); | |
| 582 | defer tmp.cleanup(); | |
| 583 | ||
| 584 | const test_out_file = "os_tmp_test"; | |
| 585 | const file = try tmp.dir.createFile(test_out_file, .{}); | |
| 586 | defer { | |
| 587 | file.close(); | |
| 588 | tmp.dir.deleteFile(test_out_file) catch {}; | |
| 589 | } | |
| 590 | ||
| 591 | try os.fsync(file.handle); | |
| 592 | try os.fdatasync(file.handle); | |
| 593 | } |
lib/std/os/windows/kernel32.zig+2| ... | ... | @@ -287,3 +287,5 @@ pub extern "kernel32" fn K32GetWsChangesEx(hProcess: HANDLE, lpWatchInfoEx: PPSA |
| 287 | 287 | pub extern "kernel32" fn K32InitializeProcessForWsWatch(hProcess: HANDLE) callconv(.Stdcall) BOOL; |
| 288 | 288 | pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL; |
| 289 | 289 | pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL; |
| 290 | ||
| 291 | pub extern "kernel32" fn FlushFileBuffers(hFile: HANDLE) callconv(.Stdcall) BOOL; |
lib/std/process.zig+4-4| ... | ... | @@ -578,8 +578,8 @@ fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []cons |
| 578 | 578 | } |
| 579 | 579 | |
| 580 | 580 | pub const UserInfo = struct { |
| 581 | uid: u32, | |
| 582 | gid: u32, | |
| 581 | uid: os.uid_t, | |
| 582 | gid: os.gid_t, | |
| 583 | 583 | }; |
| 584 | 584 | |
| 585 | 585 | /// POSIX function which gets a uid from username. |
| ... | ... | @@ -607,8 +607,8 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo { |
| 607 | 607 | var buf: [std.mem.page_size]u8 = undefined; |
| 608 | 608 | var name_index: usize = 0; |
| 609 | 609 | var state = State.Start; |
| 610 | var uid: u32 = 0; | |
| 611 | var gid: u32 = 0; | |
| 610 | var uid: os.uid_t = 0; | |
| 611 | var gid: os.gid_t = 0; | |
| 612 | 612 | |
| 613 | 613 | while (true) { |
| 614 | 614 | const amt_read = try reader.read(buf[0..]); |
lib/std/progress.zig+3-3| ... | ... | @@ -197,7 +197,7 @@ pub const Progress = struct { |
| 197 | 197 | var maybe_node: ?*Node = &self.root; |
| 198 | 198 | while (maybe_node) |node| { |
| 199 | 199 | if (need_ellipse) { |
| 200 | self.bufWrite(&end, "...", .{}); | |
| 200 | self.bufWrite(&end, "... ", .{}); | |
| 201 | 201 | } |
| 202 | 202 | need_ellipse = false; |
| 203 | 203 | if (node.name.len != 0 or node.estimated_total_items != null) { |
| ... | ... | @@ -218,7 +218,7 @@ pub const Progress = struct { |
| 218 | 218 | maybe_node = node.recently_updated_child; |
| 219 | 219 | } |
| 220 | 220 | if (need_ellipse) { |
| 221 | self.bufWrite(&end, "...", .{}); | |
| 221 | self.bufWrite(&end, "... ", .{}); | |
| 222 | 222 | } |
| 223 | 223 | } |
| 224 | 224 | |
| ... | ... | @@ -253,7 +253,7 @@ pub const Progress = struct { |
| 253 | 253 | const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11; |
| 254 | 254 | const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end; |
| 255 | 255 | if (end.* > max_end) { |
| 256 | const suffix = "..."; | |
| 256 | const suffix = "... "; | |
| 257 | 257 | self.columns_written = self.columns_written - (end.* - max_end) + suffix.len; |
| 258 | 258 | std.mem.copy(u8, self.output_buffer[max_end..], suffix); |
| 259 | 259 | end.* = max_end + suffix.len; |
lib/std/special/test_runner.zig+1-1| ... | ... | @@ -40,7 +40,7 @@ pub fn main() anyerror!void { |
| 40 | 40 | test_node.activate(); |
| 41 | 41 | progress.refresh(); |
| 42 | 42 | if (progress.terminal == null) { |
| 43 | std.debug.print("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name }); | |
| 43 | std.debug.print("{}/{} {}... ", .{ i + 1, test_fn_list.len, test_fn.name }); | |
| 44 | 44 | } |
| 45 | 45 | const result = if (test_fn.async_frame_size) |size| switch (io_mode) { |
| 46 | 46 | .evented => blk: { |
lib/std/std.zig+7| ... | ... | @@ -3,11 +3,15 @@ |
| 3 | 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. |
| 4 | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | 5 | // and substantial portions of the software. |
| 6 | pub const ArrayHashMap = array_hash_map.ArrayHashMap; | |
| 7 | pub const ArrayHashMapUnmanaged = array_hash_map.ArrayHashMapUnmanaged; | |
| 6 | 8 | pub const ArrayList = @import("array_list.zig").ArrayList; |
| 7 | 9 | pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned; |
| 8 | 10 | pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged; |
| 9 | 11 | pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; |
| 10 | 12 | pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged; |
| 13 | pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap; | |
| 14 | pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged; | |
| 11 | 15 | pub const AutoHashMap = hash_map.AutoHashMap; |
| 12 | 16 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; |
| 13 | 17 | pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; |
| ... | ... | @@ -32,10 +36,13 @@ pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; |
| 32 | 36 | pub const SpinLock = @import("spinlock.zig").SpinLock; |
| 33 | 37 | pub const StringHashMap = hash_map.StringHashMap; |
| 34 | 38 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; |
| 39 | pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; | |
| 40 | pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged; | |
| 35 | 41 | pub const TailQueue = @import("linked_list.zig").TailQueue; |
| 36 | 42 | pub const Target = @import("target.zig").Target; |
| 37 | 43 | pub const Thread = @import("thread.zig").Thread; |
| 38 | 44 | |
| 45 | pub const array_hash_map = @import("array_hash_map.zig"); | |
| 39 | 46 | pub const atomic = @import("atomic.zig"); |
| 40 | 47 | pub const base64 = @import("base64.zig"); |
| 41 | 48 | pub const build = @import("build.zig"); |
lib/std/target.zig+1-1| ... | ... | @@ -101,7 +101,7 @@ pub const Target = struct { |
| 101 | 101 | |
| 102 | 102 | /// Latest Windows version that the Zig Standard Library is aware of |
| 103 | 103 | pub const latest = WindowsVersion.win10_20h1; |
| 104 | ||
| 104 | ||
| 105 | 105 | pub const Range = struct { |
| 106 | 106 | min: WindowsVersion, |
| 107 | 107 | max: WindowsVersion, |
lib/std/zig/parser_test.zig+114-6| ... | ... | @@ -615,6 +615,17 @@ test "zig fmt: infix operator and then multiline string literal" { |
| 615 | 615 | ); |
| 616 | 616 | } |
| 617 | 617 | |
| 618 | test "zig fmt: infix operator and then multiline string literal" { | |
| 619 | try testCanonical( | |
| 620 | \\const x = "" ++ | |
| 621 | \\ \\ hi0 | |
| 622 | \\ \\ hi1 | |
| 623 | \\ \\ hi2 | |
| 624 | \\; | |
| 625 | \\ | |
| 626 | ); | |
| 627 | } | |
| 628 | ||
| 618 | 629 | test "zig fmt: C pointers" { |
| 619 | 630 | try testCanonical( |
| 620 | 631 | \\const Ptr = [*c]i32; |
| ... | ... | @@ -885,6 +896,28 @@ test "zig fmt: 2nd arg multiline string" { |
| 885 | 896 | ); |
| 886 | 897 | } |
| 887 | 898 | |
| 899 | test "zig fmt: 2nd arg multiline string many args" { | |
| 900 | try testCanonical( | |
| 901 | \\comptime { | |
| 902 | \\ cases.addAsm("hello world linux x86_64", | |
| 903 | \\ \\.text | |
| 904 | \\ , "Hello, world!\n", "Hello, world!\n"); | |
| 905 | \\} | |
| 906 | \\ | |
| 907 | ); | |
| 908 | } | |
| 909 | ||
| 910 | test "zig fmt: final arg multiline string" { | |
| 911 | try testCanonical( | |
| 912 | \\comptime { | |
| 913 | \\ cases.addAsm("hello world linux x86_64", "Hello, world!\n", | |
| 914 | \\ \\.text | |
| 915 | \\ ); | |
| 916 | \\} | |
| 917 | \\ | |
| 918 | ); | |
| 919 | } | |
| 920 | ||
| 888 | 921 | test "zig fmt: if condition wraps" { |
| 889 | 922 | try testTransform( |
| 890 | 923 | \\comptime { |
| ... | ... | @@ -915,6 +948,11 @@ test "zig fmt: if condition wraps" { |
| 915 | 948 | \\ var a = if (a) |*f| x: { |
| 916 | 949 | \\ break :x &a.b; |
| 917 | 950 | \\ } else |err| err; |
| 951 | \\ var a = if (cond and | |
| 952 | \\ cond) |*f| | |
| 953 | \\ x: { | |
| 954 | \\ break :x &a.b; | |
| 955 | \\ } else |err| err; | |
| 918 | 956 | \\} |
| 919 | 957 | , |
| 920 | 958 | \\comptime { |
| ... | ... | @@ -951,6 +989,35 @@ test "zig fmt: if condition wraps" { |
| 951 | 989 | \\ var a = if (a) |*f| x: { |
| 952 | 990 | \\ break :x &a.b; |
| 953 | 991 | \\ } else |err| err; |
| 992 | \\ var a = if (cond and | |
| 993 | \\ cond) |*f| | |
| 994 | \\ x: { | |
| 995 | \\ break :x &a.b; | |
| 996 | \\ } else |err| err; | |
| 997 | \\} | |
| 998 | \\ | |
| 999 | ); | |
| 1000 | } | |
| 1001 | ||
| 1002 | test "zig fmt: if condition has line break but must not wrap" { | |
| 1003 | try testCanonical( | |
| 1004 | \\comptime { | |
| 1005 | \\ if (self.user_input_options.put( | |
| 1006 | \\ name, | |
| 1007 | \\ UserInputOption{ | |
| 1008 | \\ .name = name, | |
| 1009 | \\ .used = false, | |
| 1010 | \\ }, | |
| 1011 | \\ ) catch unreachable) |*prev_value| { | |
| 1012 | \\ foo(); | |
| 1013 | \\ bar(); | |
| 1014 | \\ } | |
| 1015 | \\ if (put( | |
| 1016 | \\ a, | |
| 1017 | \\ b, | |
| 1018 | \\ )) { | |
| 1019 | \\ foo(); | |
| 1020 | \\ } | |
| 954 | 1021 | \\} |
| 955 | 1022 | \\ |
| 956 | 1023 | ); |
| ... | ... | @@ -977,6 +1044,18 @@ test "zig fmt: if condition has line break but must not wrap" { |
| 977 | 1044 | ); |
| 978 | 1045 | } |
| 979 | 1046 | |
| 1047 | test "zig fmt: function call with multiline argument" { | |
| 1048 | try testCanonical( | |
| 1049 | \\comptime { | |
| 1050 | \\ self.user_input_options.put(name, UserInputOption{ | |
| 1051 | \\ .name = name, | |
| 1052 | \\ .used = false, | |
| 1053 | \\ }); | |
| 1054 | \\} | |
| 1055 | \\ | |
| 1056 | ); | |
| 1057 | } | |
| 1058 | ||
| 980 | 1059 | test "zig fmt: same-line doc comment on variable declaration" { |
| 981 | 1060 | try testTransform( |
| 982 | 1061 | \\pub const MAP_ANONYMOUS = 0x1000; /// allocated from memory, swap space |
| ... | ... | @@ -1228,7 +1307,7 @@ test "zig fmt: array literal with hint" { |
| 1228 | 1307 | \\const a = []u8{ |
| 1229 | 1308 | \\ 1, 2, |
| 1230 | 1309 | \\ 3, // |
| 1231 | \\ 4, | |
| 1310 | \\ 4, | |
| 1232 | 1311 | \\ 5, 6, |
| 1233 | 1312 | \\ 7, |
| 1234 | 1313 | \\}; |
| ... | ... | @@ -1293,7 +1372,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" { |
| 1293 | 1372 | \\ \\ZIG_C_HEADER_FILES {} |
| 1294 | 1373 | \\ \\ZIG_DIA_GUIDS_LIB {} |
| 1295 | 1374 | \\ \\ |
| 1296 | \\ , | |
| 1375 | \\ , | |
| 1297 | 1376 | \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR), |
| 1298 | 1377 | \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER), |
| 1299 | 1378 | \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB), |
| ... | ... | @@ -2885,20 +2964,20 @@ test "zig fmt: multiline string in array" { |
| 2885 | 2964 | try testCanonical( |
| 2886 | 2965 | \\const Foo = [][]const u8{ |
| 2887 | 2966 | \\ \\aaa |
| 2888 | \\, | |
| 2967 | \\ , | |
| 2889 | 2968 | \\ \\bbb |
| 2890 | 2969 | \\}; |
| 2891 | 2970 | \\ |
| 2892 | 2971 | \\fn bar() void { |
| 2893 | 2972 | \\ const Foo = [][]const u8{ |
| 2894 | 2973 | \\ \\aaa |
| 2895 | \\ , | |
| 2974 | \\ , | |
| 2896 | 2975 | \\ \\bbb |
| 2897 | 2976 | \\ }; |
| 2898 | 2977 | \\ const Bar = [][]const u8{ // comment here |
| 2899 | 2978 | \\ \\aaa |
| 2900 | 2979 | \\ \\ |
| 2901 | \\ , // and another comment can go here | |
| 2980 | \\ , // and another comment can go here | |
| 2902 | 2981 | \\ \\bbb |
| 2903 | 2982 | \\ }; |
| 2904 | 2983 | \\} |
| ... | ... | @@ -3214,6 +3293,34 @@ test "zig fmt: C var args" { |
| 3214 | 3293 | ); |
| 3215 | 3294 | } |
| 3216 | 3295 | |
| 3296 | test "zig fmt: Only indent multiline string literals in function calls" { | |
| 3297 | try testCanonical( | |
| 3298 | \\test "zig fmt:" { | |
| 3299 | \\ try testTransform( | |
| 3300 | \\ \\const X = struct { | |
| 3301 | \\ \\ foo: i32, bar: i8 }; | |
| 3302 | \\ , | |
| 3303 | \\ \\const X = struct { | |
| 3304 | \\ \\ foo: i32, bar: i8 | |
| 3305 | \\ \\}; | |
| 3306 | \\ \\ | |
| 3307 | \\ ); | |
| 3308 | \\} | |
| 3309 | \\ | |
| 3310 | ); | |
| 3311 | } | |
| 3312 | ||
| 3313 | test "zig fmt: Don't add extra newline after if" { | |
| 3314 | try testCanonical( | |
| 3315 | \\pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { | |
| 3316 | \\ if (cwd().symLink(existing_path, new_path, .{})) { | |
| 3317 | \\ return; | |
| 3318 | \\ } | |
| 3319 | \\} | |
| 3320 | \\ | |
| 3321 | ); | |
| 3322 | } | |
| 3323 | ||
| 3217 | 3324 | const std = @import("std"); |
| 3218 | 3325 | const mem = std.mem; |
| 3219 | 3326 | const warn = std.debug.warn; |
| ... | ... | @@ -3256,7 +3363,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b |
| 3256 | 3363 | var buffer = std.ArrayList(u8).init(allocator); |
| 3257 | 3364 | errdefer buffer.deinit(); |
| 3258 | 3365 | |
| 3259 | anything_changed.* = try std.zig.render(allocator, buffer.outStream(), tree); | |
| 3366 | const outStream = buffer.outStream(); | |
| 3367 | anything_changed.* = try std.zig.render(allocator, outStream, tree); | |
| 3260 | 3368 | return buffer.toOwnedSlice(); |
| 3261 | 3369 | } |
| 3262 | 3370 | fn testTransform(source: []const u8, expected_source: []const u8) !void { |
lib/std/zig/render.zig+763-912| ... | ... | @@ -6,10 +6,12 @@ |
| 6 | 6 | const std = @import("../std.zig"); |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | 8 | const mem = std.mem; |
| 9 | const meta = std.meta; | |
| 9 | 10 | const ast = std.zig.ast; |
| 10 | 11 | const Token = std.zig.Token; |
| 11 | 12 | |
| 12 | 13 | const indent_delta = 4; |
| 14 | const asm_indent_delta = 2; | |
| 13 | 15 | |
| 14 | 16 | pub const Error = error{ |
| 15 | 17 | /// Ran out of memory allocating call stack frames to complete rendering. |
| ... | ... | @@ -21,70 +23,32 @@ pub fn render(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree) (@Typ |
| 21 | 23 | // cannot render an invalid tree |
| 22 | 24 | std.debug.assert(tree.errors.len == 0); |
| 23 | 25 | |
| 24 | // make a passthrough stream that checks whether something changed | |
| 25 | const MyStream = struct { | |
| 26 | const MyStream = @This(); | |
| 27 | const StreamError = @TypeOf(stream).Error; | |
| 28 | ||
| 29 | child_stream: @TypeOf(stream), | |
| 30 | anything_changed: bool, | |
| 31 | source_index: usize, | |
| 32 | source: []const u8, | |
| 33 | ||
| 34 | fn write(self: *MyStream, bytes: []const u8) StreamError!usize { | |
| 35 | if (!self.anything_changed) { | |
| 36 | const end = self.source_index + bytes.len; | |
| 37 | if (end > self.source.len) { | |
| 38 | self.anything_changed = true; | |
| 39 | } else { | |
| 40 | const src_slice = self.source[self.source_index..end]; | |
| 41 | self.source_index += bytes.len; | |
| 42 | if (!mem.eql(u8, bytes, src_slice)) { | |
| 43 | self.anything_changed = true; | |
| 44 | } | |
| 45 | } | |
| 46 | } | |
| 47 | ||
| 48 | return self.child_stream.write(bytes); | |
| 49 | } | |
| 50 | }; | |
| 51 | var my_stream = MyStream{ | |
| 52 | .child_stream = stream, | |
| 53 | .anything_changed = false, | |
| 54 | .source_index = 0, | |
| 55 | .source = tree.source, | |
| 56 | }; | |
| 57 | const my_stream_stream: std.io.Writer(*MyStream, MyStream.StreamError, MyStream.write) = .{ | |
| 58 | .context = &my_stream, | |
| 59 | }; | |
| 26 | var change_detection_stream = std.io.changeDetectionStream(tree.source, stream); | |
| 27 | var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, change_detection_stream.writer()); | |
| 60 | 28 | |
| 61 | try renderRoot(allocator, my_stream_stream, tree); | |
| 29 | try renderRoot(allocator, &auto_indenting_stream, tree); | |
| 62 | 30 | |
| 63 | if (my_stream.source_index != my_stream.source.len) { | |
| 64 | my_stream.anything_changed = true; | |
| 65 | } | |
| 66 | ||
| 67 | return my_stream.anything_changed; | |
| 31 | return change_detection_stream.changeDetected(); | |
| 68 | 32 | } |
| 69 | 33 | |
| 70 | 34 | fn renderRoot( |
| 71 | 35 | allocator: *mem.Allocator, |
| 72 | stream: anytype, | |
| 36 | ais: anytype, | |
| 73 | 37 | tree: *ast.Tree, |
| 74 | ) (@TypeOf(stream).Error || Error)!void { | |
| 38 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 39 | ||
| 75 | 40 | // render all the line comments at the beginning of the file |
| 76 | 41 | for (tree.token_ids) |token_id, i| { |
| 77 | 42 | if (token_id != .LineComment) break; |
| 78 | 43 | const token_loc = tree.token_locs[i]; |
| 79 | try stream.print("{}\n", .{mem.trimRight(u8, tree.tokenSliceLoc(token_loc), " ")}); | |
| 44 | try ais.writer().print("{}\n", .{mem.trimRight(u8, tree.tokenSliceLoc(token_loc), " ")}); | |
| 80 | 45 | const next_token = tree.token_locs[i + 1]; |
| 81 | 46 | const loc = tree.tokenLocationLoc(token_loc.end, next_token); |
| 82 | 47 | if (loc.line >= 2) { |
| 83 | try stream.writeByte('\n'); | |
| 48 | try ais.insertNewline(); | |
| 84 | 49 | } |
| 85 | 50 | } |
| 86 | 51 | |
| 87 | var start_col: usize = 0; | |
| 88 | 52 | var decl_i: ast.NodeIndex = 0; |
| 89 | 53 | const root_decls = tree.root_node.decls(); |
| 90 | 54 | |
| ... | ... | @@ -145,7 +109,7 @@ fn renderRoot( |
| 145 | 109 | // If there's no next reformatted `decl`, just copy the |
| 146 | 110 | // remaining input tokens and bail out. |
| 147 | 111 | const start = tree.token_locs[copy_start_token_index].start; |
| 148 | try copyFixingWhitespace(stream, tree.source[start..]); | |
| 112 | try copyFixingWhitespace(ais, tree.source[start..]); | |
| 149 | 113 | return; |
| 150 | 114 | } |
| 151 | 115 | decl = root_decls[decl_i]; |
| ... | ... | @@ -186,26 +150,25 @@ fn renderRoot( |
| 186 | 150 | |
| 187 | 151 | const start = tree.token_locs[copy_start_token_index].start; |
| 188 | 152 | const end = tree.token_locs[copy_end_token_index].start; |
| 189 | try copyFixingWhitespace(stream, tree.source[start..end]); | |
| 153 | try copyFixingWhitespace(ais, tree.source[start..end]); | |
| 190 | 154 | } |
| 191 | 155 | |
| 192 | try renderTopLevelDecl(allocator, stream, tree, 0, &start_col, decl); | |
| 156 | try renderTopLevelDecl(allocator, ais, tree, decl); | |
| 193 | 157 | decl_i += 1; |
| 194 | 158 | if (decl_i >= root_decls.len) return; |
| 195 | try renderExtraNewline(tree, stream, &start_col, root_decls[decl_i]); | |
| 159 | try renderExtraNewline(tree, ais, root_decls[decl_i]); | |
| 196 | 160 | } |
| 197 | 161 | } |
| 198 | 162 | |
| 199 | fn renderExtraNewline(tree: *ast.Tree, stream: anytype, start_col: *usize, node: *ast.Node) @TypeOf(stream).Error!void { | |
| 200 | return renderExtraNewlineToken(tree, stream, start_col, node.firstToken()); | |
| 163 | fn renderExtraNewline(tree: *ast.Tree, ais: anytype, node: *ast.Node) @TypeOf(ais.*).Error!void { | |
| 164 | return renderExtraNewlineToken(tree, ais, node.firstToken()); | |
| 201 | 165 | } |
| 202 | 166 | |
| 203 | 167 | fn renderExtraNewlineToken( |
| 204 | 168 | tree: *ast.Tree, |
| 205 | stream: anytype, | |
| 206 | start_col: *usize, | |
| 169 | ais: anytype, | |
| 207 | 170 | first_token: ast.TokenIndex, |
| 208 | ) @TypeOf(stream).Error!void { | |
| 171 | ) @TypeOf(ais.*).Error!void { | |
| 209 | 172 | var prev_token = first_token; |
| 210 | 173 | if (prev_token == 0) return; |
| 211 | 174 | var newline_threshold: usize = 2; |
| ... | ... | @@ -218,28 +181,27 @@ fn renderExtraNewlineToken( |
| 218 | 181 | const prev_token_end = tree.token_locs[prev_token - 1].end; |
| 219 | 182 | const loc = tree.tokenLocation(prev_token_end, first_token); |
| 220 | 183 | if (loc.line >= newline_threshold) { |
| 221 | try stream.writeByte('\n'); | |
| 222 | start_col.* = 0; | |
| 184 | try ais.insertNewline(); | |
| 223 | 185 | } |
| 224 | 186 | } |
| 225 | 187 | |
| 226 | fn renderTopLevelDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node) (@TypeOf(stream).Error || Error)!void { | |
| 227 | try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Newline); | |
| 188 | fn renderTopLevelDecl(allocator: *mem.Allocator, ais: anytype, tree: *ast.Tree, decl: *ast.Node) (@TypeOf(ais.*).Error || Error)!void { | |
| 189 | try renderContainerDecl(allocator, ais, tree, decl, .Newline); | |
| 228 | 190 | } |
| 229 | 191 | |
| 230 | fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node, space: Space) (@TypeOf(stream).Error || Error)!void { | |
| 192 | fn renderContainerDecl(allocator: *mem.Allocator, ais: anytype, tree: *ast.Tree, decl: *ast.Node, space: Space) (@TypeOf(ais.*).Error || Error)!void { | |
| 231 | 193 | switch (decl.tag) { |
| 232 | 194 | .FnProto => { |
| 233 | 195 | const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); |
| 234 | 196 | |
| 235 | try renderDocComments(tree, stream, fn_proto, fn_proto.getDocComments(), indent, start_col); | |
| 197 | try renderDocComments(tree, ais, fn_proto, fn_proto.getDocComments()); | |
| 236 | 198 | |
| 237 | 199 | if (fn_proto.getBodyNode()) |body_node| { |
| 238 | try renderExpression(allocator, stream, tree, indent, start_col, decl, .Space); | |
| 239 | try renderExpression(allocator, stream, tree, indent, start_col, body_node, space); | |
| 200 | try renderExpression(allocator, ais, tree, decl, .Space); | |
| 201 | try renderExpression(allocator, ais, tree, body_node, space); | |
| 240 | 202 | } else { |
| 241 | try renderExpression(allocator, stream, tree, indent, start_col, decl, .None); | |
| 242 | try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, start_col, space); | |
| 203 | try renderExpression(allocator, ais, tree, decl, .None); | |
| 204 | try renderToken(tree, ais, tree.nextToken(decl.lastToken()), space); | |
| 243 | 205 | } |
| 244 | 206 | }, |
| 245 | 207 | |
| ... | ... | @@ -247,35 +209,35 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr |
| 247 | 209 | const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl); |
| 248 | 210 | |
| 249 | 211 | if (use_decl.visib_token) |visib_token| { |
| 250 | try renderToken(tree, stream, visib_token, indent, start_col, .Space); // pub | |
| 212 | try renderToken(tree, ais, visib_token, .Space); // pub | |
| 251 | 213 | } |
| 252 | try renderToken(tree, stream, use_decl.use_token, indent, start_col, .Space); // usingnamespace | |
| 253 | try renderExpression(allocator, stream, tree, indent, start_col, use_decl.expr, .None); | |
| 254 | try renderToken(tree, stream, use_decl.semicolon_token, indent, start_col, space); // ; | |
| 214 | try renderToken(tree, ais, use_decl.use_token, .Space); // usingnamespace | |
| 215 | try renderExpression(allocator, ais, tree, use_decl.expr, .None); | |
| 216 | try renderToken(tree, ais, use_decl.semicolon_token, space); // ; | |
| 255 | 217 | }, |
| 256 | 218 | |
| 257 | 219 | .VarDecl => { |
| 258 | 220 | const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl); |
| 259 | 221 | |
| 260 | try renderDocComments(tree, stream, var_decl, var_decl.getDocComments(), indent, start_col); | |
| 261 | try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl); | |
| 222 | try renderDocComments(tree, ais, var_decl, var_decl.getDocComments()); | |
| 223 | try renderVarDecl(allocator, ais, tree, var_decl); | |
| 262 | 224 | }, |
| 263 | 225 | |
| 264 | 226 | .TestDecl => { |
| 265 | 227 | const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl); |
| 266 | 228 | |
| 267 | try renderDocComments(tree, stream, test_decl, test_decl.doc_comments, indent, start_col); | |
| 268 | try renderToken(tree, stream, test_decl.test_token, indent, start_col, .Space); | |
| 269 | try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, .Space); | |
| 270 | try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, space); | |
| 229 | try renderDocComments(tree, ais, test_decl, test_decl.doc_comments); | |
| 230 | try renderToken(tree, ais, test_decl.test_token, .Space); | |
| 231 | try renderExpression(allocator, ais, tree, test_decl.name, .Space); | |
| 232 | try renderExpression(allocator, ais, tree, test_decl.body_node, space); | |
| 271 | 233 | }, |
| 272 | 234 | |
| 273 | 235 | .ContainerField => { |
| 274 | 236 | const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl); |
| 275 | 237 | |
| 276 | try renderDocComments(tree, stream, field, field.doc_comments, indent, start_col); | |
| 238 | try renderDocComments(tree, ais, field, field.doc_comments); | |
| 277 | 239 | if (field.comptime_token) |t| { |
| 278 | try renderToken(tree, stream, t, indent, start_col, .Space); // comptime | |
| 240 | try renderToken(tree, ais, t, .Space); // comptime | |
| 279 | 241 | } |
| 280 | 242 | |
| 281 | 243 | const src_has_trailing_comma = blk: { |
| ... | ... | @@ -288,68 +250,67 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr |
| 288 | 250 | const last_token_space: Space = if (src_has_trailing_comma) .None else space; |
| 289 | 251 | |
| 290 | 252 | if (field.type_expr == null and field.value_expr == null) { |
| 291 | try renderToken(tree, stream, field.name_token, indent, start_col, last_token_space); // name | |
| 253 | try renderToken(tree, ais, field.name_token, last_token_space); // name | |
| 292 | 254 | } else if (field.type_expr != null and field.value_expr == null) { |
| 293 | try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name | |
| 294 | try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // : | |
| 255 | try renderToken(tree, ais, field.name_token, .None); // name | |
| 256 | try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // : | |
| 295 | 257 | |
| 296 | 258 | if (field.align_expr) |align_value_expr| { |
| 297 | try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type | |
| 259 | try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type | |
| 298 | 260 | const lparen_token = tree.prevToken(align_value_expr.firstToken()); |
| 299 | 261 | const align_kw = tree.prevToken(lparen_token); |
| 300 | 262 | const rparen_token = tree.nextToken(align_value_expr.lastToken()); |
| 301 | try renderToken(tree, stream, align_kw, indent, start_col, .None); // align | |
| 302 | try renderToken(tree, stream, lparen_token, indent, start_col, .None); // ( | |
| 303 | try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment | |
| 304 | try renderToken(tree, stream, rparen_token, indent, start_col, last_token_space); // ) | |
| 263 | try renderToken(tree, ais, align_kw, .None); // align | |
| 264 | try renderToken(tree, ais, lparen_token, .None); // ( | |
| 265 | try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment | |
| 266 | try renderToken(tree, ais, rparen_token, last_token_space); // ) | |
| 305 | 267 | } else { |
| 306 | try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, last_token_space); // type | |
| 268 | try renderExpression(allocator, ais, tree, field.type_expr.?, last_token_space); // type | |
| 307 | 269 | } |
| 308 | 270 | } else if (field.type_expr == null and field.value_expr != null) { |
| 309 | try renderToken(tree, stream, field.name_token, indent, start_col, .Space); // name | |
| 310 | try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // = | |
| 311 | try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value | |
| 271 | try renderToken(tree, ais, field.name_token, .Space); // name | |
| 272 | try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // = | |
| 273 | try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value | |
| 312 | 274 | } else { |
| 313 | try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name | |
| 314 | try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // : | |
| 275 | try renderToken(tree, ais, field.name_token, .None); // name | |
| 276 | try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // : | |
| 315 | 277 | |
| 316 | 278 | if (field.align_expr) |align_value_expr| { |
| 317 | try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type | |
| 279 | try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type | |
| 318 | 280 | const lparen_token = tree.prevToken(align_value_expr.firstToken()); |
| 319 | 281 | const align_kw = tree.prevToken(lparen_token); |
| 320 | 282 | const rparen_token = tree.nextToken(align_value_expr.lastToken()); |
| 321 | try renderToken(tree, stream, align_kw, indent, start_col, .None); // align | |
| 322 | try renderToken(tree, stream, lparen_token, indent, start_col, .None); // ( | |
| 323 | try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment | |
| 324 | try renderToken(tree, stream, rparen_token, indent, start_col, .Space); // ) | |
| 283 | try renderToken(tree, ais, align_kw, .None); // align | |
| 284 | try renderToken(tree, ais, lparen_token, .None); // ( | |
| 285 | try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment | |
| 286 | try renderToken(tree, ais, rparen_token, .Space); // ) | |
| 325 | 287 | } else { |
| 326 | try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type | |
| 288 | try renderExpression(allocator, ais, tree, field.type_expr.?, .Space); // type | |
| 327 | 289 | } |
| 328 | try renderToken(tree, stream, tree.prevToken(field.value_expr.?.firstToken()), indent, start_col, .Space); // = | |
| 329 | try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value | |
| 290 | try renderToken(tree, ais, tree.prevToken(field.value_expr.?.firstToken()), .Space); // = | |
| 291 | try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value | |
| 330 | 292 | } |
| 331 | 293 | |
| 332 | 294 | if (src_has_trailing_comma) { |
| 333 | 295 | const comma = tree.nextToken(field.lastToken()); |
| 334 | try renderToken(tree, stream, comma, indent, start_col, space); | |
| 296 | try renderToken(tree, ais, comma, space); | |
| 335 | 297 | } |
| 336 | 298 | }, |
| 337 | 299 | |
| 338 | 300 | .Comptime => { |
| 339 | 301 | assert(!decl.requireSemiColon()); |
| 340 | try renderExpression(allocator, stream, tree, indent, start_col, decl, space); | |
| 302 | try renderExpression(allocator, ais, tree, decl, space); | |
| 341 | 303 | }, |
| 342 | 304 | |
| 343 | 305 | .DocComment => { |
| 344 | 306 | const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl); |
| 345 | 307 | const kind = tree.token_ids[comment.first_line]; |
| 346 | try renderToken(tree, stream, comment.first_line, indent, start_col, .Newline); | |
| 308 | try renderToken(tree, ais, comment.first_line, .Newline); | |
| 347 | 309 | var tok_i = comment.first_line + 1; |
| 348 | 310 | while (true) : (tok_i += 1) { |
| 349 | 311 | const tok_id = tree.token_ids[tok_i]; |
| 350 | 312 | if (tok_id == kind) { |
| 351 | try stream.writeByteNTimes(' ', indent); | |
| 352 | try renderToken(tree, stream, tok_i, indent, start_col, .Newline); | |
| 313 | try renderToken(tree, ais, tok_i, .Newline); | |
| 353 | 314 | } else if (tok_id == .LineComment) { |
| 354 | 315 | continue; |
| 355 | 316 | } else { |
| ... | ... | @@ -363,13 +324,11 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr |
| 363 | 324 | |
| 364 | 325 | fn renderExpression( |
| 365 | 326 | allocator: *mem.Allocator, |
| 366 | stream: anytype, | |
| 327 | ais: anytype, | |
| 367 | 328 | tree: *ast.Tree, |
| 368 | indent: usize, | |
| 369 | start_col: *usize, | |
| 370 | 329 | base: *ast.Node, |
| 371 | 330 | space: Space, |
| 372 | ) (@TypeOf(stream).Error || Error)!void { | |
| 331 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 373 | 332 | switch (base.tag) { |
| 374 | 333 | .Identifier, |
| 375 | 334 | .IntegerLiteral, |
| ... | ... | @@ -383,18 +342,18 @@ fn renderExpression( |
| 383 | 342 | .UndefinedLiteral, |
| 384 | 343 | => { |
| 385 | 344 | const casted_node = base.cast(ast.Node.OneToken).?; |
| 386 | return renderToken(tree, stream, casted_node.token, indent, start_col, space); | |
| 345 | return renderToken(tree, ais, casted_node.token, space); | |
| 387 | 346 | }, |
| 388 | 347 | |
| 389 | 348 | .AnyType => { |
| 390 | 349 | const any_type = base.castTag(.AnyType).?; |
| 391 | 350 | if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) { |
| 392 | 351 | // TODO remove in next release cycle |
| 393 | try stream.writeAll("anytype"); | |
| 394 | if (space == .Comma) try stream.writeAll(",\n"); | |
| 352 | try ais.writer().writeAll("anytype"); | |
| 353 | if (space == .Comma) try ais.writer().writeAll(",\n"); | |
| 395 | 354 | return; |
| 396 | 355 | } |
| 397 | return renderToken(tree, stream, any_type.token, indent, start_col, space); | |
| 356 | return renderToken(tree, ais, any_type.token, space); | |
| 398 | 357 | }, |
| 399 | 358 | |
| 400 | 359 | .Block, .LabeledBlock => { |
| ... | ... | @@ -424,65 +383,65 @@ fn renderExpression( |
| 424 | 383 | }; |
| 425 | 384 | |
| 426 | 385 | if (block.label) |label| { |
| 427 | try renderToken(tree, stream, label, indent, start_col, Space.None); | |
| 428 | try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); | |
| 386 | try renderToken(tree, ais, label, Space.None); | |
| 387 | try renderToken(tree, ais, tree.nextToken(label), Space.Space); | |
| 429 | 388 | } |
| 430 | 389 | |
| 431 | 390 | if (block.statements.len == 0) { |
| 432 | try renderToken(tree, stream, block.lbrace, indent + indent_delta, start_col, Space.None); | |
| 433 | return renderToken(tree, stream, block.rbrace, indent, start_col, space); | |
| 391 | ais.pushIndentNextLine(); | |
| 392 | defer ais.popIndent(); | |
| 393 | try renderToken(tree, ais, block.lbrace, Space.None); | |
| 434 | 394 | } else { |
| 435 | const block_indent = indent + indent_delta; | |
| 436 | try renderToken(tree, stream, block.lbrace, block_indent, start_col, Space.Newline); | |
| 395 | ais.pushIndentNextLine(); | |
| 396 | defer ais.popIndent(); | |
| 397 | ||
| 398 | try renderToken(tree, ais, block.lbrace, Space.Newline); | |
| 437 | 399 | |
| 438 | 400 | for (block.statements) |statement, i| { |
| 439 | try stream.writeByteNTimes(' ', block_indent); | |
| 440 | try renderStatement(allocator, stream, tree, block_indent, start_col, statement); | |
| 401 | try renderStatement(allocator, ais, tree, statement); | |
| 441 | 402 | |
| 442 | 403 | if (i + 1 < block.statements.len) { |
| 443 | try renderExtraNewline(tree, stream, start_col, block.statements[i + 1]); | |
| 404 | try renderExtraNewline(tree, ais, block.statements[i + 1]); | |
| 444 | 405 | } |
| 445 | 406 | } |
| 446 | ||
| 447 | try stream.writeByteNTimes(' ', indent); | |
| 448 | return renderToken(tree, stream, block.rbrace, indent, start_col, space); | |
| 449 | 407 | } |
| 408 | return renderToken(tree, ais, block.rbrace, space); | |
| 450 | 409 | }, |
| 451 | 410 | |
| 452 | 411 | .Defer => { |
| 453 | 412 | const defer_node = @fieldParentPtr(ast.Node.Defer, "base", base); |
| 454 | 413 | |
| 455 | try renderToken(tree, stream, defer_node.defer_token, indent, start_col, Space.Space); | |
| 414 | try renderToken(tree, ais, defer_node.defer_token, Space.Space); | |
| 456 | 415 | if (defer_node.payload) |payload| { |
| 457 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); | |
| 416 | try renderExpression(allocator, ais, tree, payload, Space.Space); | |
| 458 | 417 | } |
| 459 | return renderExpression(allocator, stream, tree, indent, start_col, defer_node.expr, space); | |
| 418 | return renderExpression(allocator, ais, tree, defer_node.expr, space); | |
| 460 | 419 | }, |
| 461 | 420 | .Comptime => { |
| 462 | 421 | const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", base); |
| 463 | 422 | |
| 464 | try renderToken(tree, stream, comptime_node.comptime_token, indent, start_col, Space.Space); | |
| 465 | return renderExpression(allocator, stream, tree, indent, start_col, comptime_node.expr, space); | |
| 423 | try renderToken(tree, ais, comptime_node.comptime_token, Space.Space); | |
| 424 | return renderExpression(allocator, ais, tree, comptime_node.expr, space); | |
| 466 | 425 | }, |
| 467 | 426 | .Nosuspend => { |
| 468 | 427 | const nosuspend_node = @fieldParentPtr(ast.Node.Nosuspend, "base", base); |
| 469 | 428 | if (mem.eql(u8, tree.tokenSlice(nosuspend_node.nosuspend_token), "noasync")) { |
| 470 | 429 | // TODO: remove this |
| 471 | try stream.writeAll("nosuspend "); | |
| 430 | try ais.writer().writeAll("nosuspend "); | |
| 472 | 431 | } else { |
| 473 | try renderToken(tree, stream, nosuspend_node.nosuspend_token, indent, start_col, Space.Space); | |
| 432 | try renderToken(tree, ais, nosuspend_node.nosuspend_token, Space.Space); | |
| 474 | 433 | } |
| 475 | return renderExpression(allocator, stream, tree, indent, start_col, nosuspend_node.expr, space); | |
| 434 | return renderExpression(allocator, ais, tree, nosuspend_node.expr, space); | |
| 476 | 435 | }, |
| 477 | 436 | |
| 478 | 437 | .Suspend => { |
| 479 | 438 | const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base); |
| 480 | 439 | |
| 481 | 440 | if (suspend_node.body) |body| { |
| 482 | try renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, Space.Space); | |
| 483 | return renderExpression(allocator, stream, tree, indent, start_col, body, space); | |
| 441 | try renderToken(tree, ais, suspend_node.suspend_token, Space.Space); | |
| 442 | return renderExpression(allocator, ais, tree, body, space); | |
| 484 | 443 | } else { |
| 485 | return renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, space); | |
| 444 | return renderToken(tree, ais, suspend_node.suspend_token, space); | |
| 486 | 445 | } |
| 487 | 446 | }, |
| 488 | 447 | |
| ... | ... | @@ -490,26 +449,21 @@ fn renderExpression( |
| 490 | 449 | const infix_op_node = @fieldParentPtr(ast.Node.Catch, "base", base); |
| 491 | 450 | |
| 492 | 451 | const op_space = Space.Space; |
| 493 | try renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.lhs, op_space); | |
| 452 | try renderExpression(allocator, ais, tree, infix_op_node.lhs, op_space); | |
| 494 | 453 | |
| 495 | 454 | const after_op_space = blk: { |
| 496 | const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token)); | |
| 497 | break :blk if (loc.line == 0) op_space else Space.Newline; | |
| 455 | const same_line = tree.tokensOnSameLine(infix_op_node.op_token, tree.nextToken(infix_op_node.op_token)); | |
| 456 | break :blk if (same_line) op_space else Space.Newline; | |
| 498 | 457 | }; |
| 499 | 458 | |
| 500 | try renderToken(tree, stream, infix_op_node.op_token, indent, start_col, after_op_space); | |
| 501 | if (after_op_space == Space.Newline and | |
| 502 | tree.token_ids[tree.nextToken(infix_op_node.op_token)] != .MultilineStringLiteralLine) | |
| 503 | { | |
| 504 | try stream.writeByteNTimes(' ', indent + indent_delta); | |
| 505 | start_col.* = indent + indent_delta; | |
| 506 | } | |
| 459 | try renderToken(tree, ais, infix_op_node.op_token, after_op_space); | |
| 507 | 460 | |
| 508 | 461 | if (infix_op_node.payload) |payload| { |
| 509 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); | |
| 462 | try renderExpression(allocator, ais, tree, payload, Space.Space); | |
| 510 | 463 | } |
| 511 | 464 | |
| 512 | return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space); | |
| 465 | ais.pushIndentOneShot(); | |
| 466 | return renderExpression(allocator, ais, tree, infix_op_node.rhs, space); | |
| 513 | 467 | }, |
| 514 | 468 | |
| 515 | 469 | .Add, |
| ... | ... | @@ -561,22 +515,16 @@ fn renderExpression( |
| 561 | 515 | .Period, .ErrorUnion, .Range => Space.None, |
| 562 | 516 | else => Space.Space, |
| 563 | 517 | }; |
| 564 | try renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.lhs, op_space); | |
| 518 | try renderExpression(allocator, ais, tree, infix_op_node.lhs, op_space); | |
| 565 | 519 | |
| 566 | 520 | const after_op_space = blk: { |
| 567 | 521 | const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token)); |
| 568 | 522 | break :blk if (loc.line == 0) op_space else Space.Newline; |
| 569 | 523 | }; |
| 570 | 524 | |
| 571 | try renderToken(tree, stream, infix_op_node.op_token, indent, start_col, after_op_space); | |
| 572 | if (after_op_space == Space.Newline and | |
| 573 | tree.token_ids[tree.nextToken(infix_op_node.op_token)] != .MultilineStringLiteralLine) | |
| 574 | { | |
| 575 | try stream.writeByteNTimes(' ', indent + indent_delta); | |
| 576 | start_col.* = indent + indent_delta; | |
| 577 | } | |
| 578 | ||
| 579 | return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space); | |
| 525 | try renderToken(tree, ais, infix_op_node.op_token, after_op_space); | |
| 526 | ais.pushIndentOneShot(); | |
| 527 | return renderExpression(allocator, ais, tree, infix_op_node.rhs, space); | |
| 580 | 528 | }, |
| 581 | 529 | |
| 582 | 530 | .BitNot, |
| ... | ... | @@ -587,8 +535,8 @@ fn renderExpression( |
| 587 | 535 | .AddressOf, |
| 588 | 536 | => { |
| 589 | 537 | const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); |
| 590 | try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.None); | |
| 591 | return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space); | |
| 538 | try renderToken(tree, ais, casted_node.op_token, Space.None); | |
| 539 | return renderExpression(allocator, ais, tree, casted_node.rhs, space); | |
| 592 | 540 | }, |
| 593 | 541 | |
| 594 | 542 | .Try, |
| ... | ... | @@ -596,18 +544,16 @@ fn renderExpression( |
| 596 | 544 | .Await, |
| 597 | 545 | => { |
| 598 | 546 | const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); |
| 599 | try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.Space); | |
| 600 | return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space); | |
| 547 | try renderToken(tree, ais, casted_node.op_token, Space.Space); | |
| 548 | return renderExpression(allocator, ais, tree, casted_node.rhs, space); | |
| 601 | 549 | }, |
| 602 | 550 | |
| 603 | 551 | .ArrayType => { |
| 604 | 552 | const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base); |
| 605 | 553 | return renderArrayType( |
| 606 | 554 | allocator, |
| 607 | stream, | |
| 555 | ais, | |
| 608 | 556 | tree, |
| 609 | indent, | |
| 610 | start_col, | |
| 611 | 557 | array_type.op_token, |
| 612 | 558 | array_type.rhs, |
| 613 | 559 | array_type.len_expr, |
| ... | ... | @@ -619,10 +565,8 @@ fn renderExpression( |
| 619 | 565 | const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base); |
| 620 | 566 | return renderArrayType( |
| 621 | 567 | allocator, |
| 622 | stream, | |
| 568 | ais, | |
| 623 | 569 | tree, |
| 624 | indent, | |
| 625 | start_col, | |
| 626 | 570 | array_type.op_token, |
| 627 | 571 | array_type.rhs, |
| 628 | 572 | array_type.len_expr, |
| ... | ... | @@ -635,111 +579,111 @@ fn renderExpression( |
| 635 | 579 | const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base); |
| 636 | 580 | const op_tok_id = tree.token_ids[ptr_type.op_token]; |
| 637 | 581 | switch (op_tok_id) { |
| 638 | .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'), | |
| 582 | .Asterisk, .AsteriskAsterisk => try ais.writer().writeByte('*'), | |
| 639 | 583 | .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier) |
| 640 | try stream.writeAll("[*c") | |
| 584 | try ais.writer().writeAll("[*c") | |
| 641 | 585 | else |
| 642 | try stream.writeAll("[*"), | |
| 586 | try ais.writer().writeAll("[*"), | |
| 643 | 587 | else => unreachable, |
| 644 | 588 | } |
| 645 | 589 | if (ptr_type.ptr_info.sentinel) |sentinel| { |
| 646 | 590 | const colon_token = tree.prevToken(sentinel.firstToken()); |
| 647 | try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : | |
| 591 | try renderToken(tree, ais, colon_token, Space.None); // : | |
| 648 | 592 | const sentinel_space = switch (op_tok_id) { |
| 649 | 593 | .LBracket => Space.None, |
| 650 | 594 | else => Space.Space, |
| 651 | 595 | }; |
| 652 | try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space); | |
| 596 | try renderExpression(allocator, ais, tree, sentinel, sentinel_space); | |
| 653 | 597 | } |
| 654 | 598 | switch (op_tok_id) { |
| 655 | 599 | .Asterisk, .AsteriskAsterisk => {}, |
| 656 | .LBracket => try stream.writeByte(']'), | |
| 600 | .LBracket => try ais.writer().writeByte(']'), | |
| 657 | 601 | else => unreachable, |
| 658 | 602 | } |
| 659 | 603 | if (ptr_type.ptr_info.allowzero_token) |allowzero_token| { |
| 660 | try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero | |
| 604 | try renderToken(tree, ais, allowzero_token, Space.Space); // allowzero | |
| 661 | 605 | } |
| 662 | 606 | if (ptr_type.ptr_info.align_info) |align_info| { |
| 663 | 607 | const lparen_token = tree.prevToken(align_info.node.firstToken()); |
| 664 | 608 | const align_token = tree.prevToken(lparen_token); |
| 665 | 609 | |
| 666 | try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align | |
| 667 | try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( | |
| 610 | try renderToken(tree, ais, align_token, Space.None); // align | |
| 611 | try renderToken(tree, ais, lparen_token, Space.None); // ( | |
| 668 | 612 | |
| 669 | try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None); | |
| 613 | try renderExpression(allocator, ais, tree, align_info.node, Space.None); | |
| 670 | 614 | |
| 671 | 615 | if (align_info.bit_range) |bit_range| { |
| 672 | 616 | const colon1 = tree.prevToken(bit_range.start.firstToken()); |
| 673 | 617 | const colon2 = tree.prevToken(bit_range.end.firstToken()); |
| 674 | 618 | |
| 675 | try renderToken(tree, stream, colon1, indent, start_col, Space.None); // : | |
| 676 | try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None); | |
| 677 | try renderToken(tree, stream, colon2, indent, start_col, Space.None); // : | |
| 678 | try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None); | |
| 619 | try renderToken(tree, ais, colon1, Space.None); // : | |
| 620 | try renderExpression(allocator, ais, tree, bit_range.start, Space.None); | |
| 621 | try renderToken(tree, ais, colon2, Space.None); // : | |
| 622 | try renderExpression(allocator, ais, tree, bit_range.end, Space.None); | |
| 679 | 623 | |
| 680 | 624 | const rparen_token = tree.nextToken(bit_range.end.lastToken()); |
| 681 | try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) | |
| 625 | try renderToken(tree, ais, rparen_token, Space.Space); // ) | |
| 682 | 626 | } else { |
| 683 | 627 | const rparen_token = tree.nextToken(align_info.node.lastToken()); |
| 684 | try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) | |
| 628 | try renderToken(tree, ais, rparen_token, Space.Space); // ) | |
| 685 | 629 | } |
| 686 | 630 | } |
| 687 | 631 | if (ptr_type.ptr_info.const_token) |const_token| { |
| 688 | try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const | |
| 632 | try renderToken(tree, ais, const_token, Space.Space); // const | |
| 689 | 633 | } |
| 690 | 634 | if (ptr_type.ptr_info.volatile_token) |volatile_token| { |
| 691 | try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile | |
| 635 | try renderToken(tree, ais, volatile_token, Space.Space); // volatile | |
| 692 | 636 | } |
| 693 | return renderExpression(allocator, stream, tree, indent, start_col, ptr_type.rhs, space); | |
| 637 | return renderExpression(allocator, ais, tree, ptr_type.rhs, space); | |
| 694 | 638 | }, |
| 695 | 639 | |
| 696 | 640 | .SliceType => { |
| 697 | 641 | const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base); |
| 698 | try renderToken(tree, stream, slice_type.op_token, indent, start_col, Space.None); // [ | |
| 642 | try renderToken(tree, ais, slice_type.op_token, Space.None); // [ | |
| 699 | 643 | if (slice_type.ptr_info.sentinel) |sentinel| { |
| 700 | 644 | const colon_token = tree.prevToken(sentinel.firstToken()); |
| 701 | try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : | |
| 702 | try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); | |
| 703 | try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ] | |
| 645 | try renderToken(tree, ais, colon_token, Space.None); // : | |
| 646 | try renderExpression(allocator, ais, tree, sentinel, Space.None); | |
| 647 | try renderToken(tree, ais, tree.nextToken(sentinel.lastToken()), Space.None); // ] | |
| 704 | 648 | } else { |
| 705 | try renderToken(tree, stream, tree.nextToken(slice_type.op_token), indent, start_col, Space.None); // ] | |
| 649 | try renderToken(tree, ais, tree.nextToken(slice_type.op_token), Space.None); // ] | |
| 706 | 650 | } |
| 707 | 651 | |
| 708 | 652 | if (slice_type.ptr_info.allowzero_token) |allowzero_token| { |
| 709 | try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero | |
| 653 | try renderToken(tree, ais, allowzero_token, Space.Space); // allowzero | |
| 710 | 654 | } |
| 711 | 655 | if (slice_type.ptr_info.align_info) |align_info| { |
| 712 | 656 | const lparen_token = tree.prevToken(align_info.node.firstToken()); |
| 713 | 657 | const align_token = tree.prevToken(lparen_token); |
| 714 | 658 | |
| 715 | try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align | |
| 716 | try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( | |
| 659 | try renderToken(tree, ais, align_token, Space.None); // align | |
| 660 | try renderToken(tree, ais, lparen_token, Space.None); // ( | |
| 717 | 661 | |
| 718 | try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None); | |
| 662 | try renderExpression(allocator, ais, tree, align_info.node, Space.None); | |
| 719 | 663 | |
| 720 | 664 | if (align_info.bit_range) |bit_range| { |
| 721 | 665 | const colon1 = tree.prevToken(bit_range.start.firstToken()); |
| 722 | 666 | const colon2 = tree.prevToken(bit_range.end.firstToken()); |
| 723 | 667 | |
| 724 | try renderToken(tree, stream, colon1, indent, start_col, Space.None); // : | |
| 725 | try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None); | |
| 726 | try renderToken(tree, stream, colon2, indent, start_col, Space.None); // : | |
| 727 | try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None); | |
| 668 | try renderToken(tree, ais, colon1, Space.None); // : | |
| 669 | try renderExpression(allocator, ais, tree, bit_range.start, Space.None); | |
| 670 | try renderToken(tree, ais, colon2, Space.None); // : | |
| 671 | try renderExpression(allocator, ais, tree, bit_range.end, Space.None); | |
| 728 | 672 | |
| 729 | 673 | const rparen_token = tree.nextToken(bit_range.end.lastToken()); |
| 730 | try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) | |
| 674 | try renderToken(tree, ais, rparen_token, Space.Space); // ) | |
| 731 | 675 | } else { |
| 732 | 676 | const rparen_token = tree.nextToken(align_info.node.lastToken()); |
| 733 | try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) | |
| 677 | try renderToken(tree, ais, rparen_token, Space.Space); // ) | |
| 734 | 678 | } |
| 735 | 679 | } |
| 736 | 680 | if (slice_type.ptr_info.const_token) |const_token| { |
| 737 | try renderToken(tree, stream, const_token, indent, start_col, Space.Space); | |
| 681 | try renderToken(tree, ais, const_token, Space.Space); | |
| 738 | 682 | } |
| 739 | 683 | if (slice_type.ptr_info.volatile_token) |volatile_token| { |
| 740 | try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); | |
| 684 | try renderToken(tree, ais, volatile_token, Space.Space); | |
| 741 | 685 | } |
| 742 | return renderExpression(allocator, stream, tree, indent, start_col, slice_type.rhs, space); | |
| 686 | return renderExpression(allocator, ais, tree, slice_type.rhs, space); | |
| 743 | 687 | }, |
| 744 | 688 | |
| 745 | 689 | .ArrayInitializer, .ArrayInitializerDot => { |
| ... | ... | @@ -768,27 +712,33 @@ fn renderExpression( |
| 768 | 712 | |
| 769 | 713 | if (exprs.len == 0) { |
| 770 | 714 | switch (lhs) { |
| 771 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | |
| 772 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None), | |
| 715 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), | |
| 716 | .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), | |
| 717 | } | |
| 718 | ||
| 719 | { | |
| 720 | ais.pushIndent(); | |
| 721 | defer ais.popIndent(); | |
| 722 | try renderToken(tree, ais, lbrace, Space.None); | |
| 773 | 723 | } |
| 774 | try renderToken(tree, stream, lbrace, indent, start_col, Space.None); | |
| 775 | return renderToken(tree, stream, rtoken, indent, start_col, space); | |
| 776 | } | |
| 777 | 724 | |
| 778 | if (exprs.len == 1 and tree.token_ids[exprs[0].lastToken() + 1] == .RBrace) { | |
| 725 | return renderToken(tree, ais, rtoken, space); | |
| 726 | } | |
| 727 | if (exprs.len == 1 and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) { | |
| 779 | 728 | const expr = exprs[0]; |
| 729 | ||
| 780 | 730 | switch (lhs) { |
| 781 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | |
| 782 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None), | |
| 731 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), | |
| 732 | .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), | |
| 783 | 733 | } |
| 784 | try renderToken(tree, stream, lbrace, indent, start_col, Space.None); | |
| 785 | try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None); | |
| 786 | return renderToken(tree, stream, rtoken, indent, start_col, space); | |
| 734 | try renderToken(tree, ais, lbrace, Space.None); | |
| 735 | try renderExpression(allocator, ais, tree, expr, Space.None); | |
| 736 | return renderToken(tree, ais, rtoken, space); | |
| 787 | 737 | } |
| 788 | 738 | |
| 789 | 739 | switch (lhs) { |
| 790 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | |
| 791 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None), | |
| 740 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), | |
| 741 | .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), | |
| 792 | 742 | } |
| 793 | 743 | |
| 794 | 744 | // scan to find row size |
| ... | ... | @@ -830,79 +780,70 @@ fn renderExpression( |
| 830 | 780 | var expr_widths = widths[0 .. widths.len - row_size]; |
| 831 | 781 | var column_widths = widths[widths.len - row_size ..]; |
| 832 | 782 | |
| 833 | // Null stream for counting the printed length of each expression | |
| 783 | // Null ais for counting the printed length of each expression | |
| 834 | 784 | var counting_stream = std.io.countingOutStream(std.io.null_out_stream); |
| 785 | var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer()); | |
| 835 | 786 | |
| 836 | 787 | for (exprs) |expr, i| { |
| 837 | 788 | counting_stream.bytes_written = 0; |
| 838 | var dummy_col: usize = 0; | |
| 839 | try renderExpression(allocator, counting_stream.outStream(), tree, indent, &dummy_col, expr, Space.None); | |
| 789 | try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); | |
| 840 | 790 | const width = @intCast(usize, counting_stream.bytes_written); |
| 841 | 791 | const col = i % row_size; |
| 842 | 792 | column_widths[col] = std.math.max(column_widths[col], width); |
| 843 | 793 | expr_widths[i] = width; |
| 844 | 794 | } |
| 845 | 795 | |
| 846 | var new_indent = indent + indent_delta; | |
| 796 | { | |
| 797 | ais.pushIndentNextLine(); | |
| 798 | defer ais.popIndent(); | |
| 799 | try renderToken(tree, ais, lbrace, Space.Newline); | |
| 847 | 800 | |
| 848 | if (tree.token_ids[tree.nextToken(lbrace)] != .MultilineStringLiteralLine) { | |
| 849 | try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline); | |
| 850 | try stream.writeByteNTimes(' ', new_indent); | |
| 851 | } else { | |
| 852 | new_indent -= indent_delta; | |
| 853 | try renderToken(tree, stream, lbrace, new_indent, start_col, Space.None); | |
| 854 | } | |
| 801 | var col: usize = 1; | |
| 802 | for (exprs) |expr, i| { | |
| 803 | if (i + 1 < exprs.len) { | |
| 804 | const next_expr = exprs[i + 1]; | |
| 805 | try renderExpression(allocator, ais, tree, expr, Space.None); | |
| 855 | 806 | |
| 856 | var col: usize = 1; | |
| 857 | for (exprs) |expr, i| { | |
| 858 | if (i + 1 < exprs.len) { | |
| 859 | const next_expr = exprs[i + 1]; | |
| 860 | try renderExpression(allocator, stream, tree, new_indent, start_col, expr, Space.None); | |
| 807 | const comma = tree.nextToken(expr.*.lastToken()); | |
| 861 | 808 | |
| 862 | const comma = tree.nextToken(expr.lastToken()); | |
| 809 | if (col != row_size) { | |
| 810 | try renderToken(tree, ais, comma, Space.Space); // , | |
| 863 | 811 | |
| 864 | if (col != row_size) { | |
| 865 | try renderToken(tree, stream, comma, new_indent, start_col, Space.Space); // , | |
| 812 | const padding = column_widths[i % row_size] - expr_widths[i]; | |
| 813 | try ais.writer().writeByteNTimes(' ', padding); | |
| 866 | 814 | |
| 867 | const padding = column_widths[i % row_size] - expr_widths[i]; | |
| 868 | try stream.writeByteNTimes(' ', padding); | |
| 815 | col += 1; | |
| 816 | continue; | |
| 817 | } | |
| 818 | col = 1; | |
| 869 | 819 | |
| 870 | col += 1; | |
| 871 | continue; | |
| 872 | } | |
| 873 | col = 1; | |
| 820 | if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) { | |
| 821 | try renderToken(tree, ais, comma, Space.Newline); // , | |
| 822 | } else { | |
| 823 | try renderToken(tree, ais, comma, Space.None); // , | |
| 824 | } | |
| 874 | 825 | |
| 875 | if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) { | |
| 876 | try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // , | |
| 826 | try renderExtraNewline(tree, ais, next_expr); | |
| 877 | 827 | } else { |
| 878 | try renderToken(tree, stream, comma, new_indent, start_col, Space.None); // , | |
| 879 | } | |
| 880 | ||
| 881 | try renderExtraNewline(tree, stream, start_col, next_expr); | |
| 882 | if (next_expr.tag != .MultilineStringLiteral) { | |
| 883 | try stream.writeByteNTimes(' ', new_indent); | |
| 828 | try renderExpression(allocator, ais, tree, expr, Space.Comma); // , | |
| 884 | 829 | } |
| 885 | } else { | |
| 886 | try renderExpression(allocator, stream, tree, new_indent, start_col, expr, Space.Comma); // , | |
| 887 | 830 | } |
| 888 | 831 | } |
| 889 | if (exprs[exprs.len - 1].tag != .MultilineStringLiteral) { | |
| 890 | try stream.writeByteNTimes(' ', indent); | |
| 891 | } | |
| 892 | return renderToken(tree, stream, rtoken, indent, start_col, space); | |
| 832 | return renderToken(tree, ais, rtoken, space); | |
| 893 | 833 | } else { |
| 894 | try renderToken(tree, stream, lbrace, indent, start_col, Space.Space); | |
| 834 | try renderToken(tree, ais, lbrace, Space.Space); | |
| 895 | 835 | for (exprs) |expr, i| { |
| 896 | 836 | if (i + 1 < exprs.len) { |
| 897 | try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None); | |
| 898 | const comma = tree.nextToken(expr.lastToken()); | |
| 899 | try renderToken(tree, stream, comma, indent, start_col, Space.Space); // , | |
| 837 | const next_expr = exprs[i + 1]; | |
| 838 | try renderExpression(allocator, ais, tree, expr, Space.None); | |
| 839 | const comma = tree.nextToken(expr.*.lastToken()); | |
| 840 | try renderToken(tree, ais, comma, Space.Space); // , | |
| 900 | 841 | } else { |
| 901 | try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.Space); | |
| 842 | try renderExpression(allocator, ais, tree, expr, Space.Space); | |
| 902 | 843 | } |
| 903 | 844 | } |
| 904 | 845 | |
| 905 | return renderToken(tree, stream, rtoken, indent, start_col, space); | |
| 846 | return renderToken(tree, ais, rtoken, space); | |
| 906 | 847 | } |
| 907 | 848 | }, |
| 908 | 849 | |
| ... | ... | @@ -932,11 +873,17 @@ fn renderExpression( |
| 932 | 873 | |
| 933 | 874 | if (field_inits.len == 0) { |
| 934 | 875 | switch (lhs) { |
| 935 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | |
| 936 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None), | |
| 876 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), | |
| 877 | .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), | |
| 937 | 878 | } |
| 938 | try renderToken(tree, stream, lbrace, indent + indent_delta, start_col, Space.None); | |
| 939 | return renderToken(tree, stream, rtoken, indent, start_col, space); | |
| 879 | ||
| 880 | { | |
| 881 | ais.pushIndentNextLine(); | |
| 882 | defer ais.popIndent(); | |
| 883 | try renderToken(tree, ais, lbrace, Space.None); | |
| 884 | } | |
| 885 | ||
| 886 | return renderToken(tree, ais, rtoken, space); | |
| 940 | 887 | } |
| 941 | 888 | |
| 942 | 889 | const src_has_trailing_comma = blk: { |
| ... | ... | @@ -952,9 +899,10 @@ fn renderExpression( |
| 952 | 899 | const expr_outputs_one_line = blk: { |
| 953 | 900 | // render field expressions until a LF is found |
| 954 | 901 | for (field_inits) |field_init| { |
| 955 | var find_stream = FindByteOutStream.init('\n'); | |
| 956 | var dummy_col: usize = 0; | |
| 957 | try renderExpression(allocator, find_stream.outStream(), tree, 0, &dummy_col, field_init, Space.None); | |
| 902 | var find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream); | |
| 903 | var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, find_stream.writer()); | |
| 904 | ||
| 905 | try renderExpression(allocator, &auto_indenting_stream, tree, field_init, Space.None); | |
| 958 | 906 | if (find_stream.byte_found) break :blk false; |
| 959 | 907 | } |
| 960 | 908 | break :blk true; |
| ... | ... | @@ -967,7 +915,6 @@ fn renderExpression( |
| 967 | 915 | .StructInitializer, |
| 968 | 916 | .StructInitializerDot, |
| 969 | 917 | => break :blk, |
| 970 | ||
| 971 | 918 | else => {}, |
| 972 | 919 | } |
| 973 | 920 | |
| ... | ... | @@ -977,76 +924,78 @@ fn renderExpression( |
| 977 | 924 | } |
| 978 | 925 | |
| 979 | 926 | switch (lhs) { |
| 980 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | |
| 981 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None), | |
| 927 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), | |
| 928 | .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), | |
| 982 | 929 | } |
| 983 | try renderToken(tree, stream, lbrace, indent, start_col, Space.Space); | |
| 984 | try renderExpression(allocator, stream, tree, indent, start_col, &field_init.base, Space.Space); | |
| 985 | return renderToken(tree, stream, rtoken, indent, start_col, space); | |
| 930 | try renderToken(tree, ais, lbrace, Space.Space); | |
| 931 | try renderExpression(allocator, ais, tree, &field_init.base, Space.Space); | |
| 932 | return renderToken(tree, ais, rtoken, space); | |
| 986 | 933 | } |
| 987 | 934 | |
| 988 | 935 | if (!src_has_trailing_comma and src_same_line and expr_outputs_one_line) { |
| 989 | 936 | // render all on one line, no trailing comma |
| 990 | 937 | switch (lhs) { |
| 991 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | |
| 992 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None), | |
| 938 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), | |
| 939 | .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), | |
| 993 | 940 | } |
| 994 | try renderToken(tree, stream, lbrace, indent, start_col, Space.Space); | |
| 941 | try renderToken(tree, ais, lbrace, Space.Space); | |
| 995 | 942 | |
| 996 | 943 | for (field_inits) |field_init, i| { |
| 997 | 944 | if (i + 1 < field_inits.len) { |
| 998 | try renderExpression(allocator, stream, tree, indent, start_col, field_init, Space.None); | |
| 945 | try renderExpression(allocator, ais, tree, field_init, Space.None); | |
| 999 | 946 | |
| 1000 | 947 | const comma = tree.nextToken(field_init.lastToken()); |
| 1001 | try renderToken(tree, stream, comma, indent, start_col, Space.Space); | |
| 948 | try renderToken(tree, ais, comma, Space.Space); | |
| 1002 | 949 | } else { |
| 1003 | try renderExpression(allocator, stream, tree, indent, start_col, field_init, Space.Space); | |
| 950 | try renderExpression(allocator, ais, tree, field_init, Space.Space); | |
| 1004 | 951 | } |
| 1005 | 952 | } |
| 1006 | 953 | |
| 1007 | return renderToken(tree, stream, rtoken, indent, start_col, space); | |
| 954 | return renderToken(tree, ais, rtoken, space); | |
| 1008 | 955 | } |
| 1009 | 956 | |
| 1010 | const new_indent = indent + indent_delta; | |
| 957 | { | |
| 958 | switch (lhs) { | |
| 959 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), | |
| 960 | .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), | |
| 961 | } | |
| 1011 | 962 | |
| 1012 | switch (lhs) { | |
| 1013 | .dot => |dot| try renderToken(tree, stream, dot, new_indent, start_col, Space.None), | |
| 1014 | .node => |node| try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.None), | |
| 1015 | } | |
| 1016 | try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline); | |
| 963 | ais.pushIndentNextLine(); | |
| 964 | defer ais.popIndent(); | |
| 1017 | 965 | |
| 1018 | for (field_inits) |field_init, i| { | |
| 1019 | try stream.writeByteNTimes(' ', new_indent); | |
| 966 | try renderToken(tree, ais, lbrace, Space.Newline); | |
| 1020 | 967 | |
| 1021 | if (i + 1 < field_inits.len) { | |
| 1022 | try renderExpression(allocator, stream, tree, new_indent, start_col, field_init, Space.None); | |
| 968 | for (field_inits) |field_init, i| { | |
| 969 | if (i + 1 < field_inits.len) { | |
| 970 | const next_field_init = field_inits[i + 1]; | |
| 971 | try renderExpression(allocator, ais, tree, field_init, Space.None); | |
| 1023 | 972 | |
| 1024 | const comma = tree.nextToken(field_init.lastToken()); | |
| 1025 | try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); | |
| 973 | const comma = tree.nextToken(field_init.lastToken()); | |
| 974 | try renderToken(tree, ais, comma, Space.Newline); | |
| 1026 | 975 | |
| 1027 | try renderExtraNewline(tree, stream, start_col, field_inits[i + 1]); | |
| 1028 | } else { | |
| 1029 | try renderExpression(allocator, stream, tree, new_indent, start_col, field_init, Space.Comma); | |
| 976 | try renderExtraNewline(tree, ais, next_field_init); | |
| 977 | } else { | |
| 978 | try renderExpression(allocator, ais, tree, field_init, Space.Comma); | |
| 979 | } | |
| 1030 | 980 | } |
| 1031 | 981 | } |
| 1032 | 982 | |
| 1033 | try stream.writeByteNTimes(' ', indent); | |
| 1034 | return renderToken(tree, stream, rtoken, indent, start_col, space); | |
| 983 | return renderToken(tree, ais, rtoken, space); | |
| 1035 | 984 | }, |
| 1036 | 985 | |
| 1037 | 986 | .Call => { |
| 1038 | 987 | const call = @fieldParentPtr(ast.Node.Call, "base", base); |
| 1039 | 988 | if (call.async_token) |async_token| { |
| 1040 | try renderToken(tree, stream, async_token, indent, start_col, Space.Space); | |
| 989 | try renderToken(tree, ais, async_token, Space.Space); | |
| 1041 | 990 | } |
| 1042 | 991 | |
| 1043 | try renderExpression(allocator, stream, tree, indent, start_col, call.lhs, Space.None); | |
| 992 | try renderExpression(allocator, ais, tree, call.lhs, Space.None); | |
| 1044 | 993 | |
| 1045 | 994 | const lparen = tree.nextToken(call.lhs.lastToken()); |
| 1046 | 995 | |
| 1047 | 996 | if (call.params_len == 0) { |
| 1048 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); | |
| 1049 | return renderToken(tree, stream, call.rtoken, indent, start_col, space); | |
| 997 | try renderToken(tree, ais, lparen, Space.None); | |
| 998 | return renderToken(tree, ais, call.rtoken, space); | |
| 1050 | 999 | } |
| 1051 | 1000 | |
| 1052 | 1001 | const src_has_trailing_comma = blk: { |
| ... | ... | @@ -1055,43 +1004,41 @@ fn renderExpression( |
| 1055 | 1004 | }; |
| 1056 | 1005 | |
| 1057 | 1006 | if (src_has_trailing_comma) { |
| 1058 | const new_indent = indent + indent_delta; | |
| 1059 | try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); | |
| 1007 | try renderToken(tree, ais, lparen, Space.Newline); | |
| 1060 | 1008 | |
| 1061 | 1009 | const params = call.params(); |
| 1062 | 1010 | for (params) |param_node, i| { |
| 1063 | const param_node_new_indent = if (param_node.tag == .MultilineStringLiteral) blk: { | |
| 1064 | break :blk indent; | |
| 1065 | } else blk: { | |
| 1066 | try stream.writeByteNTimes(' ', new_indent); | |
| 1067 | break :blk new_indent; | |
| 1068 | }; | |
| 1011 | ais.pushIndent(); | |
| 1012 | defer ais.popIndent(); | |
| 1069 | 1013 | |
| 1070 | 1014 | if (i + 1 < params.len) { |
| 1071 | try renderExpression(allocator, stream, tree, param_node_new_indent, start_col, param_node, Space.None); | |
| 1015 | const next_node = params[i + 1]; | |
| 1016 | try renderExpression(allocator, ais, tree, param_node, Space.None); | |
| 1072 | 1017 | const comma = tree.nextToken(param_node.lastToken()); |
| 1073 | try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // , | |
| 1074 | try renderExtraNewline(tree, stream, start_col, params[i + 1]); | |
| 1018 | try renderToken(tree, ais, comma, Space.Newline); // , | |
| 1019 | try renderExtraNewline(tree, ais, next_node); | |
| 1075 | 1020 | } else { |
| 1076 | try renderExpression(allocator, stream, tree, param_node_new_indent, start_col, param_node, Space.Comma); | |
| 1077 | try stream.writeByteNTimes(' ', indent); | |
| 1078 | return renderToken(tree, stream, call.rtoken, indent, start_col, space); | |
| 1021 | try renderExpression(allocator, ais, tree, param_node, Space.Comma); | |
| 1079 | 1022 | } |
| 1080 | 1023 | } |
| 1024 | return renderToken(tree, ais, call.rtoken, space); | |
| 1081 | 1025 | } |
| 1082 | 1026 | |
| 1083 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 1027 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 1084 | 1028 | |
| 1085 | 1029 | const params = call.params(); |
| 1086 | 1030 | for (params) |param_node, i| { |
| 1087 | try renderExpression(allocator, stream, tree, indent, start_col, param_node, Space.None); | |
| 1031 | if (param_node.*.tag == .MultilineStringLiteral) ais.pushIndentOneShot(); | |
| 1032 | ||
| 1033 | try renderExpression(allocator, ais, tree, param_node, Space.None); | |
| 1088 | 1034 | |
| 1089 | 1035 | if (i + 1 < params.len) { |
| 1036 | const next_param = params[i + 1]; | |
| 1090 | 1037 | const comma = tree.nextToken(param_node.lastToken()); |
| 1091 | try renderToken(tree, stream, comma, indent, start_col, Space.Space); | |
| 1038 | try renderToken(tree, ais, comma, Space.Space); | |
| 1092 | 1039 | } |
| 1093 | 1040 | } |
| 1094 | return renderToken(tree, stream, call.rtoken, indent, start_col, space); | |
| 1041 | return renderToken(tree, ais, call.rtoken, space); | |
| 1095 | 1042 | }, |
| 1096 | 1043 | |
| 1097 | 1044 | .ArrayAccess => { |
| ... | ... | @@ -1100,26 +1047,25 @@ fn renderExpression( |
| 1100 | 1047 | const lbracket = tree.nextToken(suffix_op.lhs.lastToken()); |
| 1101 | 1048 | const rbracket = tree.nextToken(suffix_op.index_expr.lastToken()); |
| 1102 | 1049 | |
| 1103 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None); | |
| 1104 | try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [ | |
| 1050 | try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); | |
| 1051 | try renderToken(tree, ais, lbracket, Space.None); // [ | |
| 1105 | 1052 | |
| 1106 | 1053 | const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; |
| 1107 | 1054 | const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; |
| 1108 | const new_indent = if (ends_with_comment) indent + indent_delta else indent; | |
| 1109 | const new_space = if (ends_with_comment) Space.Newline else Space.None; | |
| 1110 | try renderExpression(allocator, stream, tree, new_indent, start_col, suffix_op.index_expr, new_space); | |
| 1111 | if (starts_with_comment) { | |
| 1112 | try stream.writeByte('\n'); | |
| 1113 | } | |
| 1114 | if (ends_with_comment or starts_with_comment) { | |
| 1115 | try stream.writeByteNTimes(' ', indent); | |
| 1055 | { | |
| 1056 | const new_space = if (ends_with_comment) Space.Newline else Space.None; | |
| 1057 | ||
| 1058 | ais.pushIndent(); | |
| 1059 | defer ais.popIndent(); | |
| 1060 | try renderExpression(allocator, ais, tree, suffix_op.index_expr, new_space); | |
| 1116 | 1061 | } |
| 1117 | return renderToken(tree, stream, rbracket, indent, start_col, space); // ] | |
| 1062 | if (starts_with_comment) try ais.maybeInsertNewline(); | |
| 1063 | return renderToken(tree, ais, rbracket, space); // ] | |
| 1118 | 1064 | }, |
| 1065 | ||
| 1119 | 1066 | .Slice => { |
| 1120 | 1067 | const suffix_op = base.castTag(.Slice).?; |
| 1121 | ||
| 1122 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None); | |
| 1068 | try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); | |
| 1123 | 1069 | |
| 1124 | 1070 | const lbracket = tree.prevToken(suffix_op.start.firstToken()); |
| 1125 | 1071 | const dotdot = tree.nextToken(suffix_op.start.lastToken()); |
| ... | ... | @@ -1129,32 +1075,33 @@ fn renderExpression( |
| 1129 | 1075 | const after_start_space = if (after_start_space_bool) Space.Space else Space.None; |
| 1130 | 1076 | const after_op_space = if (suffix_op.end != null) after_start_space else Space.None; |
| 1131 | 1077 | |
| 1132 | try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [ | |
| 1133 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.start, after_start_space); | |
| 1134 | try renderToken(tree, stream, dotdot, indent, start_col, after_op_space); // .. | |
| 1078 | try renderToken(tree, ais, lbracket, Space.None); // [ | |
| 1079 | try renderExpression(allocator, ais, tree, suffix_op.start, after_start_space); | |
| 1080 | try renderToken(tree, ais, dotdot, after_op_space); // .. | |
| 1135 | 1081 | if (suffix_op.end) |end| { |
| 1136 | 1082 | const after_end_space = if (suffix_op.sentinel != null) Space.Space else Space.None; |
| 1137 | try renderExpression(allocator, stream, tree, indent, start_col, end, after_end_space); | |
| 1083 | try renderExpression(allocator, ais, tree, end, after_end_space); | |
| 1138 | 1084 | } |
| 1139 | 1085 | if (suffix_op.sentinel) |sentinel| { |
| 1140 | 1086 | const colon = tree.prevToken(sentinel.firstToken()); |
| 1141 | try renderToken(tree, stream, colon, indent, start_col, Space.None); // : | |
| 1142 | try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); | |
| 1087 | try renderToken(tree, ais, colon, Space.None); // : | |
| 1088 | try renderExpression(allocator, ais, tree, sentinel, Space.None); | |
| 1143 | 1089 | } |
| 1144 | return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ] | |
| 1090 | return renderToken(tree, ais, suffix_op.rtoken, space); // ] | |
| 1145 | 1091 | }, |
| 1092 | ||
| 1146 | 1093 | .Deref => { |
| 1147 | 1094 | const suffix_op = base.castTag(.Deref).?; |
| 1148 | 1095 | |
| 1149 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None); | |
| 1150 | return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // .* | |
| 1096 | try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); | |
| 1097 | return renderToken(tree, ais, suffix_op.rtoken, space); // .* | |
| 1151 | 1098 | }, |
| 1152 | 1099 | .UnwrapOptional => { |
| 1153 | 1100 | const suffix_op = base.castTag(.UnwrapOptional).?; |
| 1154 | 1101 | |
| 1155 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None); | |
| 1156 | try renderToken(tree, stream, tree.prevToken(suffix_op.rtoken), indent, start_col, Space.None); // . | |
| 1157 | return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ? | |
| 1102 | try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); | |
| 1103 | try renderToken(tree, ais, tree.prevToken(suffix_op.rtoken), Space.None); // . | |
| 1104 | return renderToken(tree, ais, suffix_op.rtoken, space); // ? | |
| 1158 | 1105 | }, |
| 1159 | 1106 | |
| 1160 | 1107 | .Break => { |
| ... | ... | @@ -1163,145 +1110,152 @@ fn renderExpression( |
| 1163 | 1110 | const maybe_label = flow_expr.getLabel(); |
| 1164 | 1111 | |
| 1165 | 1112 | if (maybe_label == null and maybe_rhs == null) { |
| 1166 | return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // break | |
| 1113 | return renderToken(tree, ais, flow_expr.ltoken, space); // break | |
| 1167 | 1114 | } |
| 1168 | 1115 | |
| 1169 | try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // break | |
| 1116 | try renderToken(tree, ais, flow_expr.ltoken, Space.Space); // break | |
| 1170 | 1117 | if (maybe_label) |label| { |
| 1171 | 1118 | const colon = tree.nextToken(flow_expr.ltoken); |
| 1172 | try renderToken(tree, stream, colon, indent, start_col, Space.None); // : | |
| 1119 | try renderToken(tree, ais, colon, Space.None); // : | |
| 1173 | 1120 | |
| 1174 | 1121 | if (maybe_rhs == null) { |
| 1175 | return renderToken(tree, stream, label, indent, start_col, space); // label | |
| 1122 | return renderToken(tree, ais, label, space); // label | |
| 1176 | 1123 | } |
| 1177 | try renderToken(tree, stream, label, indent, start_col, Space.Space); // label | |
| 1124 | try renderToken(tree, ais, label, Space.Space); // label | |
| 1178 | 1125 | } |
| 1179 | return renderExpression(allocator, stream, tree, indent, start_col, maybe_rhs.?, space); | |
| 1126 | return renderExpression(allocator, ais, tree, maybe_rhs.?, space); | |
| 1180 | 1127 | }, |
| 1181 | 1128 | |
| 1182 | 1129 | .Continue => { |
| 1183 | 1130 | const flow_expr = base.castTag(.Continue).?; |
| 1184 | 1131 | if (flow_expr.getLabel()) |label| { |
| 1185 | try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // continue | |
| 1132 | try renderToken(tree, ais, flow_expr.ltoken, Space.Space); // continue | |
| 1186 | 1133 | const colon = tree.nextToken(flow_expr.ltoken); |
| 1187 | try renderToken(tree, stream, colon, indent, start_col, Space.None); // : | |
| 1188 | return renderToken(tree, stream, label, indent, start_col, space); // label | |
| 1134 | try renderToken(tree, ais, colon, Space.None); // : | |
| 1135 | return renderToken(tree, ais, label, space); // label | |
| 1189 | 1136 | } else { |
| 1190 | return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // continue | |
| 1137 | return renderToken(tree, ais, flow_expr.ltoken, space); // continue | |
| 1191 | 1138 | } |
| 1192 | 1139 | }, |
| 1193 | 1140 | |
| 1194 | 1141 | .Return => { |
| 1195 | 1142 | const flow_expr = base.castTag(.Return).?; |
| 1196 | 1143 | if (flow_expr.getRHS()) |rhs| { |
| 1197 | try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); | |
| 1198 | return renderExpression(allocator, stream, tree, indent, start_col, rhs, space); | |
| 1144 | try renderToken(tree, ais, flow_expr.ltoken, Space.Space); | |
| 1145 | return renderExpression(allocator, ais, tree, rhs, space); | |
| 1199 | 1146 | } else { |
| 1200 | return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); | |
| 1147 | return renderToken(tree, ais, flow_expr.ltoken, space); | |
| 1201 | 1148 | } |
| 1202 | 1149 | }, |
| 1203 | 1150 | |
| 1204 | 1151 | .Payload => { |
| 1205 | 1152 | const payload = @fieldParentPtr(ast.Node.Payload, "base", base); |
| 1206 | 1153 | |
| 1207 | try renderToken(tree, stream, payload.lpipe, indent, start_col, Space.None); | |
| 1208 | try renderExpression(allocator, stream, tree, indent, start_col, payload.error_symbol, Space.None); | |
| 1209 | return renderToken(tree, stream, payload.rpipe, indent, start_col, space); | |
| 1154 | try renderToken(tree, ais, payload.lpipe, Space.None); | |
| 1155 | try renderExpression(allocator, ais, tree, payload.error_symbol, Space.None); | |
| 1156 | return renderToken(tree, ais, payload.rpipe, space); | |
| 1210 | 1157 | }, |
| 1211 | 1158 | |
| 1212 | 1159 | .PointerPayload => { |
| 1213 | 1160 | const payload = @fieldParentPtr(ast.Node.PointerPayload, "base", base); |
| 1214 | 1161 | |
| 1215 | try renderToken(tree, stream, payload.lpipe, indent, start_col, Space.None); | |
| 1162 | try renderToken(tree, ais, payload.lpipe, Space.None); | |
| 1216 | 1163 | if (payload.ptr_token) |ptr_token| { |
| 1217 | try renderToken(tree, stream, ptr_token, indent, start_col, Space.None); | |
| 1164 | try renderToken(tree, ais, ptr_token, Space.None); | |
| 1218 | 1165 | } |
| 1219 | try renderExpression(allocator, stream, tree, indent, start_col, payload.value_symbol, Space.None); | |
| 1220 | return renderToken(tree, stream, payload.rpipe, indent, start_col, space); | |
| 1166 | try renderExpression(allocator, ais, tree, payload.value_symbol, Space.None); | |
| 1167 | return renderToken(tree, ais, payload.rpipe, space); | |
| 1221 | 1168 | }, |
| 1222 | 1169 | |
| 1223 | 1170 | .PointerIndexPayload => { |
| 1224 | 1171 | const payload = @fieldParentPtr(ast.Node.PointerIndexPayload, "base", base); |
| 1225 | 1172 | |
| 1226 | try renderToken(tree, stream, payload.lpipe, indent, start_col, Space.None); | |
| 1173 | try renderToken(tree, ais, payload.lpipe, Space.None); | |
| 1227 | 1174 | if (payload.ptr_token) |ptr_token| { |
| 1228 | try renderToken(tree, stream, ptr_token, indent, start_col, Space.None); | |
| 1175 | try renderToken(tree, ais, ptr_token, Space.None); | |
| 1229 | 1176 | } |
| 1230 | try renderExpression(allocator, stream, tree, indent, start_col, payload.value_symbol, Space.None); | |
| 1177 | try renderExpression(allocator, ais, tree, payload.value_symbol, Space.None); | |
| 1231 | 1178 | |
| 1232 | 1179 | if (payload.index_symbol) |index_symbol| { |
| 1233 | 1180 | const comma = tree.nextToken(payload.value_symbol.lastToken()); |
| 1234 | 1181 | |
| 1235 | try renderToken(tree, stream, comma, indent, start_col, Space.Space); | |
| 1236 | try renderExpression(allocator, stream, tree, indent, start_col, index_symbol, Space.None); | |
| 1182 | try renderToken(tree, ais, comma, Space.Space); | |
| 1183 | try renderExpression(allocator, ais, tree, index_symbol, Space.None); | |
| 1237 | 1184 | } |
| 1238 | 1185 | |
| 1239 | return renderToken(tree, stream, payload.rpipe, indent, start_col, space); | |
| 1186 | return renderToken(tree, ais, payload.rpipe, space); | |
| 1240 | 1187 | }, |
| 1241 | 1188 | |
| 1242 | 1189 | .GroupedExpression => { |
| 1243 | 1190 | const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", base); |
| 1244 | 1191 | |
| 1245 | try renderToken(tree, stream, grouped_expr.lparen, indent, start_col, Space.None); | |
| 1246 | try renderExpression(allocator, stream, tree, indent, start_col, grouped_expr.expr, Space.None); | |
| 1247 | return renderToken(tree, stream, grouped_expr.rparen, indent, start_col, space); | |
| 1192 | try renderToken(tree, ais, grouped_expr.lparen, Space.None); | |
| 1193 | { | |
| 1194 | ais.pushIndentOneShot(); | |
| 1195 | try renderExpression(allocator, ais, tree, grouped_expr.expr, Space.None); | |
| 1196 | } | |
| 1197 | return renderToken(tree, ais, grouped_expr.rparen, space); | |
| 1248 | 1198 | }, |
| 1249 | 1199 | |
| 1250 | 1200 | .FieldInitializer => { |
| 1251 | 1201 | const field_init = @fieldParentPtr(ast.Node.FieldInitializer, "base", base); |
| 1252 | 1202 | |
| 1253 | try renderToken(tree, stream, field_init.period_token, indent, start_col, Space.None); // . | |
| 1254 | try renderToken(tree, stream, field_init.name_token, indent, start_col, Space.Space); // name | |
| 1255 | try renderToken(tree, stream, tree.nextToken(field_init.name_token), indent, start_col, Space.Space); // = | |
| 1256 | return renderExpression(allocator, stream, tree, indent, start_col, field_init.expr, space); | |
| 1203 | try renderToken(tree, ais, field_init.period_token, Space.None); // . | |
| 1204 | try renderToken(tree, ais, field_init.name_token, Space.Space); // name | |
| 1205 | try renderToken(tree, ais, tree.nextToken(field_init.name_token), Space.Space); // = | |
| 1206 | return renderExpression(allocator, ais, tree, field_init.expr, space); | |
| 1257 | 1207 | }, |
| 1258 | 1208 | |
| 1259 | 1209 | .ContainerDecl => { |
| 1260 | 1210 | const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base); |
| 1261 | 1211 | |
| 1262 | 1212 | if (container_decl.layout_token) |layout_token| { |
| 1263 | try renderToken(tree, stream, layout_token, indent, start_col, Space.Space); | |
| 1213 | try renderToken(tree, ais, layout_token, Space.Space); | |
| 1264 | 1214 | } |
| 1265 | 1215 | |
| 1266 | 1216 | switch (container_decl.init_arg_expr) { |
| 1267 | 1217 | .None => { |
| 1268 | try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.Space); // union | |
| 1218 | try renderToken(tree, ais, container_decl.kind_token, Space.Space); // union | |
| 1269 | 1219 | }, |
| 1270 | 1220 | .Enum => |enum_tag_type| { |
| 1271 | try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.None); // union | |
| 1221 | try renderToken(tree, ais, container_decl.kind_token, Space.None); // union | |
| 1272 | 1222 | |
| 1273 | 1223 | const lparen = tree.nextToken(container_decl.kind_token); |
| 1274 | 1224 | const enum_token = tree.nextToken(lparen); |
| 1275 | 1225 | |
| 1276 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 1277 | try renderToken(tree, stream, enum_token, indent, start_col, Space.None); // enum | |
| 1226 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 1227 | try renderToken(tree, ais, enum_token, Space.None); // enum | |
| 1278 | 1228 | |
| 1279 | 1229 | if (enum_tag_type) |expr| { |
| 1280 | try renderToken(tree, stream, tree.nextToken(enum_token), indent, start_col, Space.None); // ( | |
| 1281 | try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None); | |
| 1230 | try renderToken(tree, ais, tree.nextToken(enum_token), Space.None); // ( | |
| 1231 | try renderExpression(allocator, ais, tree, expr, Space.None); | |
| 1282 | 1232 | |
| 1283 | 1233 | const rparen = tree.nextToken(expr.lastToken()); |
| 1284 | try renderToken(tree, stream, rparen, indent, start_col, Space.None); // ) | |
| 1285 | try renderToken(tree, stream, tree.nextToken(rparen), indent, start_col, Space.Space); // ) | |
| 1234 | try renderToken(tree, ais, rparen, Space.None); // ) | |
| 1235 | try renderToken(tree, ais, tree.nextToken(rparen), Space.Space); // ) | |
| 1286 | 1236 | } else { |
| 1287 | try renderToken(tree, stream, tree.nextToken(enum_token), indent, start_col, Space.Space); // ) | |
| 1237 | try renderToken(tree, ais, tree.nextToken(enum_token), Space.Space); // ) | |
| 1288 | 1238 | } |
| 1289 | 1239 | }, |
| 1290 | 1240 | .Type => |type_expr| { |
| 1291 | try renderToken(tree, stream, container_decl.kind_token, indent, start_col, Space.None); // union | |
| 1241 | try renderToken(tree, ais, container_decl.kind_token, Space.None); // union | |
| 1292 | 1242 | |
| 1293 | 1243 | const lparen = tree.nextToken(container_decl.kind_token); |
| 1294 | 1244 | const rparen = tree.nextToken(type_expr.lastToken()); |
| 1295 | 1245 | |
| 1296 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 1297 | try renderExpression(allocator, stream, tree, indent, start_col, type_expr, Space.None); | |
| 1298 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | |
| 1246 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 1247 | try renderExpression(allocator, ais, tree, type_expr, Space.None); | |
| 1248 | try renderToken(tree, ais, rparen, Space.Space); // ) | |
| 1299 | 1249 | }, |
| 1300 | 1250 | } |
| 1301 | 1251 | |
| 1302 | 1252 | if (container_decl.fields_and_decls_len == 0) { |
| 1303 | try renderToken(tree, stream, container_decl.lbrace_token, indent + indent_delta, start_col, Space.None); // { | |
| 1304 | return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // } | |
| 1253 | { | |
| 1254 | ais.pushIndentNextLine(); | |
| 1255 | defer ais.popIndent(); | |
| 1256 | try renderToken(tree, ais, container_decl.lbrace_token, Space.None); // { | |
| 1257 | } | |
| 1258 | return renderToken(tree, ais, container_decl.rbrace_token, space); // } | |
| 1305 | 1259 | } |
| 1306 | 1260 | |
| 1307 | 1261 | const src_has_trailing_comma = blk: { |
| ... | ... | @@ -1332,43 +1286,39 @@ fn renderExpression( |
| 1332 | 1286 | |
| 1333 | 1287 | if (src_has_trailing_comma or !src_has_only_fields) { |
| 1334 | 1288 | // One declaration per line |
| 1335 | const new_indent = indent + indent_delta; | |
| 1336 | try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, .Newline); // { | |
| 1289 | ais.pushIndentNextLine(); | |
| 1290 | defer ais.popIndent(); | |
| 1291 | try renderToken(tree, ais, container_decl.lbrace_token, .Newline); // { | |
| 1337 | 1292 | |
| 1338 | 1293 | for (fields_and_decls) |decl, i| { |
| 1339 | try stream.writeByteNTimes(' ', new_indent); | |
| 1340 | try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, .Newline); | |
| 1294 | try renderContainerDecl(allocator, ais, tree, decl, .Newline); | |
| 1341 | 1295 | |
| 1342 | 1296 | if (i + 1 < fields_and_decls.len) { |
| 1343 | try renderExtraNewline(tree, stream, start_col, fields_and_decls[i + 1]); | |
| 1297 | try renderExtraNewline(tree, ais, fields_and_decls[i + 1]); | |
| 1344 | 1298 | } |
| 1345 | 1299 | } |
| 1346 | ||
| 1347 | try stream.writeByteNTimes(' ', indent); | |
| 1348 | 1300 | } else if (src_has_newline) { |
| 1349 | 1301 | // All the declarations on the same line, but place the items on |
| 1350 | 1302 | // their own line |
| 1351 | try renderToken(tree, stream, container_decl.lbrace_token, indent, start_col, .Newline); // { | |
| 1303 | try renderToken(tree, ais, container_decl.lbrace_token, .Newline); // { | |
| 1352 | 1304 | |
| 1353 | const new_indent = indent + indent_delta; | |
| 1354 | try stream.writeByteNTimes(' ', new_indent); | |
| 1305 | ais.pushIndent(); | |
| 1306 | defer ais.popIndent(); | |
| 1355 | 1307 | |
| 1356 | 1308 | for (fields_and_decls) |decl, i| { |
| 1357 | 1309 | const space_after_decl: Space = if (i + 1 >= fields_and_decls.len) .Newline else .Space; |
| 1358 | try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, space_after_decl); | |
| 1310 | try renderContainerDecl(allocator, ais, tree, decl, space_after_decl); | |
| 1359 | 1311 | } |
| 1360 | ||
| 1361 | try stream.writeByteNTimes(' ', indent); | |
| 1362 | 1312 | } else { |
| 1363 | 1313 | // All the declarations on the same line |
| 1364 | try renderToken(tree, stream, container_decl.lbrace_token, indent, start_col, .Space); // { | |
| 1314 | try renderToken(tree, ais, container_decl.lbrace_token, .Space); // { | |
| 1365 | 1315 | |
| 1366 | 1316 | for (fields_and_decls) |decl| { |
| 1367 | try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Space); | |
| 1317 | try renderContainerDecl(allocator, ais, tree, decl, .Space); | |
| 1368 | 1318 | } |
| 1369 | 1319 | } |
| 1370 | 1320 | |
| 1371 | return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // } | |
| 1321 | return renderToken(tree, ais, container_decl.rbrace_token, space); // } | |
| 1372 | 1322 | }, |
| 1373 | 1323 | |
| 1374 | 1324 | .ErrorSetDecl => { |
| ... | ... | @@ -1377,9 +1327,9 @@ fn renderExpression( |
| 1377 | 1327 | const lbrace = tree.nextToken(err_set_decl.error_token); |
| 1378 | 1328 | |
| 1379 | 1329 | if (err_set_decl.decls_len == 0) { |
| 1380 | try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); | |
| 1381 | try renderToken(tree, stream, lbrace, indent, start_col, Space.None); | |
| 1382 | return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); | |
| 1330 | try renderToken(tree, ais, err_set_decl.error_token, Space.None); | |
| 1331 | try renderToken(tree, ais, lbrace, Space.None); | |
| 1332 | return renderToken(tree, ais, err_set_decl.rbrace_token, space); | |
| 1383 | 1333 | } |
| 1384 | 1334 | |
| 1385 | 1335 | if (err_set_decl.decls_len == 1) blk: { |
| ... | ... | @@ -1393,13 +1343,13 @@ fn renderExpression( |
| 1393 | 1343 | break :blk; |
| 1394 | 1344 | } |
| 1395 | 1345 | |
| 1396 | try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); // error | |
| 1397 | try renderToken(tree, stream, lbrace, indent, start_col, Space.None); // { | |
| 1398 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None); | |
| 1399 | return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // } | |
| 1346 | try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error | |
| 1347 | try renderToken(tree, ais, lbrace, Space.None); // { | |
| 1348 | try renderExpression(allocator, ais, tree, node, Space.None); | |
| 1349 | return renderToken(tree, ais, err_set_decl.rbrace_token, space); // } | |
| 1400 | 1350 | } |
| 1401 | 1351 | |
| 1402 | try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); // error | |
| 1352 | try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error | |
| 1403 | 1353 | |
| 1404 | 1354 | const src_has_trailing_comma = blk: { |
| 1405 | 1355 | const maybe_comma = tree.prevToken(err_set_decl.rbrace_token); |
| ... | ... | @@ -1407,72 +1357,66 @@ fn renderExpression( |
| 1407 | 1357 | }; |
| 1408 | 1358 | |
| 1409 | 1359 | if (src_has_trailing_comma) { |
| 1410 | try renderToken(tree, stream, lbrace, indent, start_col, Space.Newline); // { | |
| 1411 | const new_indent = indent + indent_delta; | |
| 1412 | ||
| 1413 | const decls = err_set_decl.decls(); | |
| 1414 | for (decls) |node, i| { | |
| 1415 | try stream.writeByteNTimes(' ', new_indent); | |
| 1416 | ||
| 1417 | if (i + 1 < decls.len) { | |
| 1418 | try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.None); | |
| 1419 | try renderToken(tree, stream, tree.nextToken(node.lastToken()), new_indent, start_col, Space.Newline); // , | |
| 1420 | ||
| 1421 | try renderExtraNewline(tree, stream, start_col, decls[i + 1]); | |
| 1422 | } else { | |
| 1423 | try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.Comma); | |
| 1360 | { | |
| 1361 | ais.pushIndent(); | |
| 1362 | defer ais.popIndent(); | |
| 1363 | ||
| 1364 | try renderToken(tree, ais, lbrace, Space.Newline); // { | |
| 1365 | const decls = err_set_decl.decls(); | |
| 1366 | for (decls) |node, i| { | |
| 1367 | if (i + 1 < decls.len) { | |
| 1368 | try renderExpression(allocator, ais, tree, node, Space.None); | |
| 1369 | try renderToken(tree, ais, tree.nextToken(node.lastToken()), Space.Newline); // , | |
| 1370 | ||
| 1371 | try renderExtraNewline(tree, ais, decls[i + 1]); | |
| 1372 | } else { | |
| 1373 | try renderExpression(allocator, ais, tree, node, Space.Comma); | |
| 1374 | } | |
| 1424 | 1375 | } |
| 1425 | 1376 | } |
| 1426 | 1377 | |
| 1427 | try stream.writeByteNTimes(' ', indent); | |
| 1428 | return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // } | |
| 1378 | return renderToken(tree, ais, err_set_decl.rbrace_token, space); // } | |
| 1429 | 1379 | } else { |
| 1430 | try renderToken(tree, stream, lbrace, indent, start_col, Space.Space); // { | |
| 1380 | try renderToken(tree, ais, lbrace, Space.Space); // { | |
| 1431 | 1381 | |
| 1432 | 1382 | const decls = err_set_decl.decls(); |
| 1433 | 1383 | for (decls) |node, i| { |
| 1434 | 1384 | if (i + 1 < decls.len) { |
| 1435 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None); | |
| 1385 | try renderExpression(allocator, ais, tree, node, Space.None); | |
| 1436 | 1386 | |
| 1437 | 1387 | const comma_token = tree.nextToken(node.lastToken()); |
| 1438 | 1388 | assert(tree.token_ids[comma_token] == .Comma); |
| 1439 | try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // , | |
| 1440 | try renderExtraNewline(tree, stream, start_col, decls[i + 1]); | |
| 1389 | try renderToken(tree, ais, comma_token, Space.Space); // , | |
| 1390 | try renderExtraNewline(tree, ais, decls[i + 1]); | |
| 1441 | 1391 | } else { |
| 1442 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Space); | |
| 1392 | try renderExpression(allocator, ais, tree, node, Space.Space); | |
| 1443 | 1393 | } |
| 1444 | 1394 | } |
| 1445 | 1395 | |
| 1446 | return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // } | |
| 1396 | return renderToken(tree, ais, err_set_decl.rbrace_token, space); // } | |
| 1447 | 1397 | } |
| 1448 | 1398 | }, |
| 1449 | 1399 | |
| 1450 | 1400 | .ErrorTag => { |
| 1451 | 1401 | const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base); |
| 1452 | 1402 | |
| 1453 | try renderDocComments(tree, stream, tag, tag.doc_comments, indent, start_col); | |
| 1454 | return renderToken(tree, stream, tag.name_token, indent, start_col, space); // name | |
| 1403 | try renderDocComments(tree, ais, tag, tag.doc_comments); | |
| 1404 | return renderToken(tree, ais, tag.name_token, space); // name | |
| 1455 | 1405 | }, |
| 1456 | 1406 | |
| 1457 | 1407 | .MultilineStringLiteral => { |
| 1458 | // TODO: Don't indent in this function, but let the caller indent. | |
| 1459 | // If this has been implemented, a lot of hacky solutions in i.e. ArrayInit and FunctionCall can be removed | |
| 1460 | 1408 | const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base); |
| 1461 | 1409 | |
| 1462 | var skip_first_indent = true; | |
| 1463 | if (tree.token_ids[multiline_str_literal.firstToken() - 1] != .LineComment) { | |
| 1464 | try stream.print("\n", .{}); | |
| 1465 | skip_first_indent = false; | |
| 1466 | } | |
| 1467 | ||
| 1468 | for (multiline_str_literal.lines()) |t| { | |
| 1469 | if (!skip_first_indent) { | |
| 1470 | try stream.writeByteNTimes(' ', indent + indent_delta); | |
| 1410 | { | |
| 1411 | const locked_indents = ais.lockOneShotIndent(); | |
| 1412 | defer { | |
| 1413 | var i: u8 = 0; | |
| 1414 | while (i < locked_indents) : (i += 1) ais.popIndent(); | |
| 1471 | 1415 | } |
| 1472 | try renderToken(tree, stream, t, indent, start_col, Space.None); | |
| 1473 | skip_first_indent = false; | |
| 1416 | try ais.maybeInsertNewline(); | |
| 1417 | ||
| 1418 | for (multiline_str_literal.lines()) |t| try renderToken(tree, ais, t, Space.None); | |
| 1474 | 1419 | } |
| 1475 | try stream.writeByteNTimes(' ', indent); | |
| 1476 | 1420 | }, |
| 1477 | 1421 | |
| 1478 | 1422 | .BuiltinCall => { |
| ... | ... | @@ -1480,9 +1424,9 @@ fn renderExpression( |
| 1480 | 1424 | |
| 1481 | 1425 | // TODO remove after 0.7.0 release |
| 1482 | 1426 | if (mem.eql(u8, tree.tokenSlice(builtin_call.builtin_token), "@OpaqueType")) |
| 1483 | return stream.writeAll("@Type(.Opaque)"); | |
| 1427 | return ais.writer().writeAll("@Type(.Opaque)"); | |
| 1484 | 1428 | |
| 1485 | try renderToken(tree, stream, builtin_call.builtin_token, indent, start_col, Space.None); // @name | |
| 1429 | try renderToken(tree, ais, builtin_call.builtin_token, Space.None); // @name | |
| 1486 | 1430 | |
| 1487 | 1431 | const src_params_trailing_comma = blk: { |
| 1488 | 1432 | if (builtin_call.params_len < 2) break :blk false; |
| ... | ... | @@ -1494,31 +1438,30 @@ fn renderExpression( |
| 1494 | 1438 | const lparen = tree.nextToken(builtin_call.builtin_token); |
| 1495 | 1439 | |
| 1496 | 1440 | if (!src_params_trailing_comma) { |
| 1497 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 1441 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 1498 | 1442 | |
| 1499 | 1443 | // render all on one line, no trailing comma |
| 1500 | 1444 | const params = builtin_call.params(); |
| 1501 | 1445 | for (params) |param_node, i| { |
| 1502 | try renderExpression(allocator, stream, tree, indent, start_col, param_node, Space.None); | |
| 1446 | try renderExpression(allocator, ais, tree, param_node, Space.None); | |
| 1503 | 1447 | |
| 1504 | 1448 | if (i + 1 < params.len) { |
| 1505 | 1449 | const comma_token = tree.nextToken(param_node.lastToken()); |
| 1506 | try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // , | |
| 1450 | try renderToken(tree, ais, comma_token, Space.Space); // , | |
| 1507 | 1451 | } |
| 1508 | 1452 | } |
| 1509 | 1453 | } else { |
| 1510 | 1454 | // one param per line |
| 1511 | const new_indent = indent + indent_delta; | |
| 1512 | try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); // ( | |
| 1455 | ais.pushIndent(); | |
| 1456 | defer ais.popIndent(); | |
| 1457 | try renderToken(tree, ais, lparen, Space.Newline); // ( | |
| 1513 | 1458 | |
| 1514 | 1459 | for (builtin_call.params()) |param_node| { |
| 1515 | try stream.writeByteNTimes(' ', new_indent); | |
| 1516 | try renderExpression(allocator, stream, tree, indent, start_col, param_node, Space.Comma); | |
| 1460 | try renderExpression(allocator, ais, tree, param_node, Space.Comma); | |
| 1517 | 1461 | } |
| 1518 | try stream.writeByteNTimes(' ', indent); | |
| 1519 | 1462 | } |
| 1520 | 1463 | |
| 1521 | return renderToken(tree, stream, builtin_call.rparen_token, indent, start_col, space); // ) | |
| 1464 | return renderToken(tree, ais, builtin_call.rparen_token, space); // ) | |
| 1522 | 1465 | }, |
| 1523 | 1466 | |
| 1524 | 1467 | .FnProto => { |
| ... | ... | @@ -1528,24 +1471,24 @@ fn renderExpression( |
| 1528 | 1471 | const visib_token = tree.token_ids[visib_token_index]; |
| 1529 | 1472 | assert(visib_token == .Keyword_pub or visib_token == .Keyword_export); |
| 1530 | 1473 | |
| 1531 | try renderToken(tree, stream, visib_token_index, indent, start_col, Space.Space); // pub | |
| 1474 | try renderToken(tree, ais, visib_token_index, Space.Space); // pub | |
| 1532 | 1475 | } |
| 1533 | 1476 | |
| 1534 | 1477 | if (fn_proto.getExternExportInlineToken()) |extern_export_inline_token| { |
| 1535 | 1478 | if (fn_proto.getIsExternPrototype() == null) |
| 1536 | try renderToken(tree, stream, extern_export_inline_token, indent, start_col, Space.Space); // extern/export/inline | |
| 1479 | try renderToken(tree, ais, extern_export_inline_token, Space.Space); // extern/export/inline | |
| 1537 | 1480 | } |
| 1538 | 1481 | |
| 1539 | 1482 | if (fn_proto.getLibName()) |lib_name| { |
| 1540 | try renderExpression(allocator, stream, tree, indent, start_col, lib_name, Space.Space); | |
| 1483 | try renderExpression(allocator, ais, tree, lib_name, Space.Space); | |
| 1541 | 1484 | } |
| 1542 | 1485 | |
| 1543 | 1486 | const lparen = if (fn_proto.getNameToken()) |name_token| blk: { |
| 1544 | try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn | |
| 1545 | try renderToken(tree, stream, name_token, indent, start_col, Space.None); // name | |
| 1487 | try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn | |
| 1488 | try renderToken(tree, ais, name_token, Space.None); // name | |
| 1546 | 1489 | break :blk tree.nextToken(name_token); |
| 1547 | 1490 | } else blk: { |
| 1548 | try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn | |
| 1491 | try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn | |
| 1549 | 1492 | break :blk tree.nextToken(fn_proto.fn_token); |
| 1550 | 1493 | }; |
| 1551 | 1494 | assert(tree.token_ids[lparen] == .LParen); |
| ... | ... | @@ -1572,47 +1515,45 @@ fn renderExpression( |
| 1572 | 1515 | }; |
| 1573 | 1516 | |
| 1574 | 1517 | if (!src_params_trailing_comma) { |
| 1575 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 1518 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 1576 | 1519 | |
| 1577 | 1520 | // render all on one line, no trailing comma |
| 1578 | 1521 | for (fn_proto.params()) |param_decl, i| { |
| 1579 | try renderParamDecl(allocator, stream, tree, indent, start_col, param_decl, Space.None); | |
| 1522 | try renderParamDecl(allocator, ais, tree, param_decl, Space.None); | |
| 1580 | 1523 | |
| 1581 | 1524 | if (i + 1 < fn_proto.params_len or fn_proto.getVarArgsToken() != null) { |
| 1582 | 1525 | const comma = tree.nextToken(param_decl.lastToken()); |
| 1583 | try renderToken(tree, stream, comma, indent, start_col, Space.Space); // , | |
| 1526 | try renderToken(tree, ais, comma, Space.Space); // , | |
| 1584 | 1527 | } |
| 1585 | 1528 | } |
| 1586 | 1529 | if (fn_proto.getVarArgsToken()) |var_args_token| { |
| 1587 | try renderToken(tree, stream, var_args_token, indent, start_col, Space.None); | |
| 1530 | try renderToken(tree, ais, var_args_token, Space.None); | |
| 1588 | 1531 | } |
| 1589 | 1532 | } else { |
| 1590 | 1533 | // one param per line |
| 1591 | const new_indent = indent + indent_delta; | |
| 1592 | try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); // ( | |
| 1534 | ais.pushIndent(); | |
| 1535 | defer ais.popIndent(); | |
| 1536 | try renderToken(tree, ais, lparen, Space.Newline); // ( | |
| 1593 | 1537 | |
| 1594 | 1538 | for (fn_proto.params()) |param_decl| { |
| 1595 | try stream.writeByteNTimes(' ', new_indent); | |
| 1596 | try renderParamDecl(allocator, stream, tree, new_indent, start_col, param_decl, Space.Comma); | |
| 1539 | try renderParamDecl(allocator, ais, tree, param_decl, Space.Comma); | |
| 1597 | 1540 | } |
| 1598 | 1541 | if (fn_proto.getVarArgsToken()) |var_args_token| { |
| 1599 | try stream.writeByteNTimes(' ', new_indent); | |
| 1600 | try renderToken(tree, stream, var_args_token, new_indent, start_col, Space.Comma); | |
| 1542 | try renderToken(tree, ais, var_args_token, Space.Comma); | |
| 1601 | 1543 | } |
| 1602 | try stream.writeByteNTimes(' ', indent); | |
| 1603 | 1544 | } |
| 1604 | 1545 | |
| 1605 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | |
| 1546 | try renderToken(tree, ais, rparen, Space.Space); // ) | |
| 1606 | 1547 | |
| 1607 | 1548 | if (fn_proto.getAlignExpr()) |align_expr| { |
| 1608 | 1549 | const align_rparen = tree.nextToken(align_expr.lastToken()); |
| 1609 | 1550 | const align_lparen = tree.prevToken(align_expr.firstToken()); |
| 1610 | 1551 | const align_kw = tree.prevToken(align_lparen); |
| 1611 | 1552 | |
| 1612 | try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align | |
| 1613 | try renderToken(tree, stream, align_lparen, indent, start_col, Space.None); // ( | |
| 1614 | try renderExpression(allocator, stream, tree, indent, start_col, align_expr, Space.None); | |
| 1615 | try renderToken(tree, stream, align_rparen, indent, start_col, Space.Space); // ) | |
| 1553 | try renderToken(tree, ais, align_kw, Space.None); // align | |
| 1554 | try renderToken(tree, ais, align_lparen, Space.None); // ( | |
| 1555 | try renderExpression(allocator, ais, tree, align_expr, Space.None); | |
| 1556 | try renderToken(tree, ais, align_rparen, Space.Space); // ) | |
| 1616 | 1557 | } |
| 1617 | 1558 | |
| 1618 | 1559 | if (fn_proto.getSectionExpr()) |section_expr| { |
| ... | ... | @@ -1620,10 +1561,10 @@ fn renderExpression( |
| 1620 | 1561 | const section_lparen = tree.prevToken(section_expr.firstToken()); |
| 1621 | 1562 | const section_kw = tree.prevToken(section_lparen); |
| 1622 | 1563 | |
| 1623 | try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // section | |
| 1624 | try renderToken(tree, stream, section_lparen, indent, start_col, Space.None); // ( | |
| 1625 | try renderExpression(allocator, stream, tree, indent, start_col, section_expr, Space.None); | |
| 1626 | try renderToken(tree, stream, section_rparen, indent, start_col, Space.Space); // ) | |
| 1564 | try renderToken(tree, ais, section_kw, Space.None); // section | |
| 1565 | try renderToken(tree, ais, section_lparen, Space.None); // ( | |
| 1566 | try renderExpression(allocator, ais, tree, section_expr, Space.None); | |
| 1567 | try renderToken(tree, ais, section_rparen, Space.Space); // ) | |
| 1627 | 1568 | } |
| 1628 | 1569 | |
| 1629 | 1570 | if (fn_proto.getCallconvExpr()) |callconv_expr| { |
| ... | ... | @@ -1631,23 +1572,23 @@ fn renderExpression( |
| 1631 | 1572 | const callconv_lparen = tree.prevToken(callconv_expr.firstToken()); |
| 1632 | 1573 | const callconv_kw = tree.prevToken(callconv_lparen); |
| 1633 | 1574 | |
| 1634 | try renderToken(tree, stream, callconv_kw, indent, start_col, Space.None); // callconv | |
| 1635 | try renderToken(tree, stream, callconv_lparen, indent, start_col, Space.None); // ( | |
| 1636 | try renderExpression(allocator, stream, tree, indent, start_col, callconv_expr, Space.None); | |
| 1637 | try renderToken(tree, stream, callconv_rparen, indent, start_col, Space.Space); // ) | |
| 1575 | try renderToken(tree, ais, callconv_kw, Space.None); // callconv | |
| 1576 | try renderToken(tree, ais, callconv_lparen, Space.None); // ( | |
| 1577 | try renderExpression(allocator, ais, tree, callconv_expr, Space.None); | |
| 1578 | try renderToken(tree, ais, callconv_rparen, Space.Space); // ) | |
| 1638 | 1579 | } else if (fn_proto.getIsExternPrototype() != null) { |
| 1639 | try stream.writeAll("callconv(.C) "); | |
| 1580 | try ais.writer().writeAll("callconv(.C) "); | |
| 1640 | 1581 | } else if (fn_proto.getIsAsync() != null) { |
| 1641 | try stream.writeAll("callconv(.Async) "); | |
| 1582 | try ais.writer().writeAll("callconv(.Async) "); | |
| 1642 | 1583 | } |
| 1643 | 1584 | |
| 1644 | 1585 | switch (fn_proto.return_type) { |
| 1645 | 1586 | .Explicit => |node| { |
| 1646 | return renderExpression(allocator, stream, tree, indent, start_col, node, space); | |
| 1587 | return renderExpression(allocator, ais, tree, node, space); | |
| 1647 | 1588 | }, |
| 1648 | 1589 | .InferErrorSet => |node| { |
| 1649 | try renderToken(tree, stream, tree.prevToken(node.firstToken()), indent, start_col, Space.None); // ! | |
| 1650 | return renderExpression(allocator, stream, tree, indent, start_col, node, space); | |
| 1590 | try renderToken(tree, ais, tree.prevToken(node.firstToken()), Space.None); // ! | |
| 1591 | return renderExpression(allocator, ais, tree, node, space); | |
| 1651 | 1592 | }, |
| 1652 | 1593 | .Invalid => unreachable, |
| 1653 | 1594 | } |
| ... | ... | @@ -1657,11 +1598,11 @@ fn renderExpression( |
| 1657 | 1598 | const anyframe_type = @fieldParentPtr(ast.Node.AnyFrameType, "base", base); |
| 1658 | 1599 | |
| 1659 | 1600 | if (anyframe_type.result) |result| { |
| 1660 | try renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, Space.None); // anyframe | |
| 1661 | try renderToken(tree, stream, result.arrow_token, indent, start_col, Space.None); // -> | |
| 1662 | return renderExpression(allocator, stream, tree, indent, start_col, result.return_type, space); | |
| 1601 | try renderToken(tree, ais, anyframe_type.anyframe_token, Space.None); // anyframe | |
| 1602 | try renderToken(tree, ais, result.arrow_token, Space.None); // -> | |
| 1603 | return renderExpression(allocator, ais, tree, result.return_type, space); | |
| 1663 | 1604 | } else { |
| 1664 | return renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, space); // anyframe | |
| 1605 | return renderToken(tree, ais, anyframe_type.anyframe_token, space); // anyframe | |
| 1665 | 1606 | } |
| 1666 | 1607 | }, |
| 1667 | 1608 | |
| ... | ... | @@ -1670,38 +1611,38 @@ fn renderExpression( |
| 1670 | 1611 | .Switch => { |
| 1671 | 1612 | const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base); |
| 1672 | 1613 | |
| 1673 | try renderToken(tree, stream, switch_node.switch_token, indent, start_col, Space.Space); // switch | |
| 1674 | try renderToken(tree, stream, tree.nextToken(switch_node.switch_token), indent, start_col, Space.None); // ( | |
| 1614 | try renderToken(tree, ais, switch_node.switch_token, Space.Space); // switch | |
| 1615 | try renderToken(tree, ais, tree.nextToken(switch_node.switch_token), Space.None); // ( | |
| 1675 | 1616 | |
| 1676 | 1617 | const rparen = tree.nextToken(switch_node.expr.lastToken()); |
| 1677 | 1618 | const lbrace = tree.nextToken(rparen); |
| 1678 | 1619 | |
| 1679 | 1620 | if (switch_node.cases_len == 0) { |
| 1680 | try renderExpression(allocator, stream, tree, indent, start_col, switch_node.expr, Space.None); | |
| 1681 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | |
| 1682 | try renderToken(tree, stream, lbrace, indent, start_col, Space.None); // { | |
| 1683 | return renderToken(tree, stream, switch_node.rbrace, indent, start_col, space); // } | |
| 1621 | try renderExpression(allocator, ais, tree, switch_node.expr, Space.None); | |
| 1622 | try renderToken(tree, ais, rparen, Space.Space); // ) | |
| 1623 | try renderToken(tree, ais, lbrace, Space.None); // { | |
| 1624 | return renderToken(tree, ais, switch_node.rbrace, space); // } | |
| 1684 | 1625 | } |
| 1685 | 1626 | |
| 1686 | try renderExpression(allocator, stream, tree, indent, start_col, switch_node.expr, Space.None); | |
| 1687 | ||
| 1688 | const new_indent = indent + indent_delta; | |
| 1627 | try renderExpression(allocator, ais, tree, switch_node.expr, Space.None); | |
| 1628 | try renderToken(tree, ais, rparen, Space.Space); // ) | |
| 1689 | 1629 | |
| 1690 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | |
| 1691 | try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline); // { | |
| 1630 | { | |
| 1631 | ais.pushIndentNextLine(); | |
| 1632 | defer ais.popIndent(); | |
| 1633 | try renderToken(tree, ais, lbrace, Space.Newline); // { | |
| 1692 | 1634 | |
| 1693 | const cases = switch_node.cases(); | |
| 1694 | for (cases) |node, i| { | |
| 1695 | try stream.writeByteNTimes(' ', new_indent); | |
| 1696 | try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.Comma); | |
| 1635 | const cases = switch_node.cases(); | |
| 1636 | for (cases) |node, i| { | |
| 1637 | try renderExpression(allocator, ais, tree, node, Space.Comma); | |
| 1697 | 1638 | |
| 1698 | if (i + 1 < cases.len) { | |
| 1699 | try renderExtraNewline(tree, stream, start_col, cases[i + 1]); | |
| 1639 | if (i + 1 < cases.len) { | |
| 1640 | try renderExtraNewline(tree, ais, cases[i + 1]); | |
| 1641 | } | |
| 1700 | 1642 | } |
| 1701 | 1643 | } |
| 1702 | 1644 | |
| 1703 | try stream.writeByteNTimes(' ', indent); | |
| 1704 | return renderToken(tree, stream, switch_node.rbrace, indent, start_col, space); // } | |
| 1645 | return renderToken(tree, ais, switch_node.rbrace, space); // } | |
| 1705 | 1646 | }, |
| 1706 | 1647 | |
| 1707 | 1648 | .SwitchCase => { |
| ... | ... | @@ -1718,43 +1659,41 @@ fn renderExpression( |
| 1718 | 1659 | const items = switch_case.items(); |
| 1719 | 1660 | for (items) |node, i| { |
| 1720 | 1661 | if (i + 1 < items.len) { |
| 1721 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None); | |
| 1662 | try renderExpression(allocator, ais, tree, node, Space.None); | |
| 1722 | 1663 | |
| 1723 | 1664 | const comma_token = tree.nextToken(node.lastToken()); |
| 1724 | try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // , | |
| 1725 | try renderExtraNewline(tree, stream, start_col, items[i + 1]); | |
| 1665 | try renderToken(tree, ais, comma_token, Space.Space); // , | |
| 1666 | try renderExtraNewline(tree, ais, items[i + 1]); | |
| 1726 | 1667 | } else { |
| 1727 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Space); | |
| 1668 | try renderExpression(allocator, ais, tree, node, Space.Space); | |
| 1728 | 1669 | } |
| 1729 | 1670 | } |
| 1730 | 1671 | } else { |
| 1731 | 1672 | const items = switch_case.items(); |
| 1732 | 1673 | for (items) |node, i| { |
| 1733 | 1674 | if (i + 1 < items.len) { |
| 1734 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None); | |
| 1675 | try renderExpression(allocator, ais, tree, node, Space.None); | |
| 1735 | 1676 | |
| 1736 | 1677 | const comma_token = tree.nextToken(node.lastToken()); |
| 1737 | try renderToken(tree, stream, comma_token, indent, start_col, Space.Newline); // , | |
| 1738 | try renderExtraNewline(tree, stream, start_col, items[i + 1]); | |
| 1739 | try stream.writeByteNTimes(' ', indent); | |
| 1678 | try renderToken(tree, ais, comma_token, Space.Newline); // , | |
| 1679 | try renderExtraNewline(tree, ais, items[i + 1]); | |
| 1740 | 1680 | } else { |
| 1741 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Comma); | |
| 1742 | try stream.writeByteNTimes(' ', indent); | |
| 1681 | try renderExpression(allocator, ais, tree, node, Space.Comma); | |
| 1743 | 1682 | } |
| 1744 | 1683 | } |
| 1745 | 1684 | } |
| 1746 | 1685 | |
| 1747 | try renderToken(tree, stream, switch_case.arrow_token, indent, start_col, Space.Space); // => | |
| 1686 | try renderToken(tree, ais, switch_case.arrow_token, Space.Space); // => | |
| 1748 | 1687 | |
| 1749 | 1688 | if (switch_case.payload) |payload| { |
| 1750 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); | |
| 1689 | try renderExpression(allocator, ais, tree, payload, Space.Space); | |
| 1751 | 1690 | } |
| 1752 | 1691 | |
| 1753 | return renderExpression(allocator, stream, tree, indent, start_col, switch_case.expr, space); | |
| 1692 | return renderExpression(allocator, ais, tree, switch_case.expr, space); | |
| 1754 | 1693 | }, |
| 1755 | 1694 | .SwitchElse => { |
| 1756 | 1695 | const switch_else = @fieldParentPtr(ast.Node.SwitchElse, "base", base); |
| 1757 | return renderToken(tree, stream, switch_else.token, indent, start_col, space); | |
| 1696 | return renderToken(tree, ais, switch_else.token, space); | |
| 1758 | 1697 | }, |
| 1759 | 1698 | .Else => { |
| 1760 | 1699 | const else_node = @fieldParentPtr(ast.Node.Else, "base", base); |
| ... | ... | @@ -1763,37 +1702,37 @@ fn renderExpression( |
| 1763 | 1702 | const same_line = body_is_block or tree.tokensOnSameLine(else_node.else_token, else_node.body.lastToken()); |
| 1764 | 1703 | |
| 1765 | 1704 | const after_else_space = if (same_line or else_node.payload != null) Space.Space else Space.Newline; |
| 1766 | try renderToken(tree, stream, else_node.else_token, indent, start_col, after_else_space); | |
| 1705 | try renderToken(tree, ais, else_node.else_token, after_else_space); | |
| 1767 | 1706 | |
| 1768 | 1707 | if (else_node.payload) |payload| { |
| 1769 | 1708 | const payload_space = if (same_line) Space.Space else Space.Newline; |
| 1770 | try renderExpression(allocator, stream, tree, indent, start_col, payload, payload_space); | |
| 1709 | try renderExpression(allocator, ais, tree, payload, payload_space); | |
| 1771 | 1710 | } |
| 1772 | 1711 | |
| 1773 | 1712 | if (same_line) { |
| 1774 | return renderExpression(allocator, stream, tree, indent, start_col, else_node.body, space); | |
| 1713 | return renderExpression(allocator, ais, tree, else_node.body, space); | |
| 1714 | } else { | |
| 1715 | ais.pushIndent(); | |
| 1716 | defer ais.popIndent(); | |
| 1717 | return renderExpression(allocator, ais, tree, else_node.body, space); | |
| 1775 | 1718 | } |
| 1776 | ||
| 1777 | try stream.writeByteNTimes(' ', indent + indent_delta); | |
| 1778 | start_col.* = indent + indent_delta; | |
| 1779 | return renderExpression(allocator, stream, tree, indent, start_col, else_node.body, space); | |
| 1780 | 1719 | }, |
| 1781 | 1720 | |
| 1782 | 1721 | .While => { |
| 1783 | 1722 | const while_node = @fieldParentPtr(ast.Node.While, "base", base); |
| 1784 | 1723 | |
| 1785 | 1724 | if (while_node.label) |label| { |
| 1786 | try renderToken(tree, stream, label, indent, start_col, Space.None); // label | |
| 1787 | try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); // : | |
| 1725 | try renderToken(tree, ais, label, Space.None); // label | |
| 1726 | try renderToken(tree, ais, tree.nextToken(label), Space.Space); // : | |
| 1788 | 1727 | } |
| 1789 | 1728 | |
| 1790 | 1729 | if (while_node.inline_token) |inline_token| { |
| 1791 | try renderToken(tree, stream, inline_token, indent, start_col, Space.Space); // inline | |
| 1730 | try renderToken(tree, ais, inline_token, Space.Space); // inline | |
| 1792 | 1731 | } |
| 1793 | 1732 | |
| 1794 | try renderToken(tree, stream, while_node.while_token, indent, start_col, Space.Space); // while | |
| 1795 | try renderToken(tree, stream, tree.nextToken(while_node.while_token), indent, start_col, Space.None); // ( | |
| 1796 | try renderExpression(allocator, stream, tree, indent, start_col, while_node.condition, Space.None); | |
| 1733 | try renderToken(tree, ais, while_node.while_token, Space.Space); // while | |
| 1734 | try renderToken(tree, ais, tree.nextToken(while_node.while_token), Space.None); // ( | |
| 1735 | try renderExpression(allocator, ais, tree, while_node.condition, Space.None); | |
| 1797 | 1736 | |
| 1798 | 1737 | const cond_rparen = tree.nextToken(while_node.condition.lastToken()); |
| 1799 | 1738 | |
| ... | ... | @@ -1815,12 +1754,12 @@ fn renderExpression( |
| 1815 | 1754 | |
| 1816 | 1755 | { |
| 1817 | 1756 | const rparen_space = if (while_node.payload != null or while_node.continue_expr != null) Space.Space else block_start_space; |
| 1818 | try renderToken(tree, stream, cond_rparen, indent, start_col, rparen_space); // ) | |
| 1757 | try renderToken(tree, ais, cond_rparen, rparen_space); // ) | |
| 1819 | 1758 | } |
| 1820 | 1759 | |
| 1821 | 1760 | if (while_node.payload) |payload| { |
| 1822 | const payload_space = if (while_node.continue_expr != null) Space.Space else block_start_space; | |
| 1823 | try renderExpression(allocator, stream, tree, indent, start_col, payload, payload_space); | |
| 1761 | const payload_space = Space.Space; //if (while_node.continue_expr != null) Space.Space else block_start_space; | |
| 1762 | try renderExpression(allocator, ais, tree, payload, payload_space); | |
| 1824 | 1763 | } |
| 1825 | 1764 | |
| 1826 | 1765 | if (while_node.continue_expr) |continue_expr| { |
| ... | ... | @@ -1828,29 +1767,22 @@ fn renderExpression( |
| 1828 | 1767 | const lparen = tree.prevToken(continue_expr.firstToken()); |
| 1829 | 1768 | const colon = tree.prevToken(lparen); |
| 1830 | 1769 | |
| 1831 | try renderToken(tree, stream, colon, indent, start_col, Space.Space); // : | |
| 1832 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 1770 | try renderToken(tree, ais, colon, Space.Space); // : | |
| 1771 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 1833 | 1772 | |
| 1834 | try renderExpression(allocator, stream, tree, indent, start_col, continue_expr, Space.None); | |
| 1773 | try renderExpression(allocator, ais, tree, continue_expr, Space.None); | |
| 1835 | 1774 | |
| 1836 | try renderToken(tree, stream, rparen, indent, start_col, block_start_space); // ) | |
| 1775 | try renderToken(tree, ais, rparen, block_start_space); // ) | |
| 1837 | 1776 | } |
| 1838 | 1777 | |
| 1839 | var new_indent = indent; | |
| 1840 | if (block_start_space == Space.Newline) { | |
| 1841 | new_indent += indent_delta; | |
| 1842 | try stream.writeByteNTimes(' ', new_indent); | |
| 1843 | start_col.* = new_indent; | |
| 1778 | { | |
| 1779 | if (!body_is_block) ais.pushIndent(); | |
| 1780 | defer if (!body_is_block) ais.popIndent(); | |
| 1781 | try renderExpression(allocator, ais, tree, while_node.body, after_body_space); | |
| 1844 | 1782 | } |
| 1845 | 1783 | |
| 1846 | try renderExpression(allocator, stream, tree, indent, start_col, while_node.body, after_body_space); | |
| 1847 | ||
| 1848 | 1784 | if (while_node.@"else") |@"else"| { |
| 1849 | if (after_body_space == Space.Newline) { | |
| 1850 | try stream.writeByteNTimes(' ', indent); | |
| 1851 | start_col.* = indent; | |
| 1852 | } | |
| 1853 | return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space); | |
| 1785 | return renderExpression(allocator, ais, tree, &@"else".base, space); | |
| 1854 | 1786 | } |
| 1855 | 1787 | }, |
| 1856 | 1788 | |
| ... | ... | @@ -1858,17 +1790,17 @@ fn renderExpression( |
| 1858 | 1790 | const for_node = @fieldParentPtr(ast.Node.For, "base", base); |
| 1859 | 1791 | |
| 1860 | 1792 | if (for_node.label) |label| { |
| 1861 | try renderToken(tree, stream, label, indent, start_col, Space.None); // label | |
| 1862 | try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); // : | |
| 1793 | try renderToken(tree, ais, label, Space.None); // label | |
| 1794 | try renderToken(tree, ais, tree.nextToken(label), Space.Space); // : | |
| 1863 | 1795 | } |
| 1864 | 1796 | |
| 1865 | 1797 | if (for_node.inline_token) |inline_token| { |
| 1866 | try renderToken(tree, stream, inline_token, indent, start_col, Space.Space); // inline | |
| 1798 | try renderToken(tree, ais, inline_token, Space.Space); // inline | |
| 1867 | 1799 | } |
| 1868 | 1800 | |
| 1869 | try renderToken(tree, stream, for_node.for_token, indent, start_col, Space.Space); // for | |
| 1870 | try renderToken(tree, stream, tree.nextToken(for_node.for_token), indent, start_col, Space.None); // ( | |
| 1871 | try renderExpression(allocator, stream, tree, indent, start_col, for_node.array_expr, Space.None); | |
| 1801 | try renderToken(tree, ais, for_node.for_token, Space.Space); // for | |
| 1802 | try renderToken(tree, ais, tree.nextToken(for_node.for_token), Space.None); // ( | |
| 1803 | try renderExpression(allocator, ais, tree, for_node.array_expr, Space.None); | |
| 1872 | 1804 | |
| 1873 | 1805 | const rparen = tree.nextToken(for_node.array_expr.lastToken()); |
| 1874 | 1806 | |
| ... | ... | @@ -1876,10 +1808,10 @@ fn renderExpression( |
| 1876 | 1808 | const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken()); |
| 1877 | 1809 | const body_on_same_line = body_is_block or src_one_line_to_body; |
| 1878 | 1810 | |
| 1879 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | |
| 1811 | try renderToken(tree, ais, rparen, Space.Space); // ) | |
| 1880 | 1812 | |
| 1881 | 1813 | const space_after_payload = if (body_on_same_line) Space.Space else Space.Newline; |
| 1882 | try renderExpression(allocator, stream, tree, indent, start_col, for_node.payload, space_after_payload); // |x| | |
| 1814 | try renderExpression(allocator, ais, tree, for_node.payload, space_after_payload); // |x| | |
| 1883 | 1815 | |
| 1884 | 1816 | const space_after_body = blk: { |
| 1885 | 1817 | if (for_node.@"else") |@"else"| { |
| ... | ... | @@ -1894,13 +1826,14 @@ fn renderExpression( |
| 1894 | 1826 | } |
| 1895 | 1827 | }; |
| 1896 | 1828 | |
| 1897 | const body_indent = if (body_on_same_line) indent else indent + indent_delta; | |
| 1898 | if (!body_on_same_line) try stream.writeByteNTimes(' ', body_indent); | |
| 1899 | try renderExpression(allocator, stream, tree, body_indent, start_col, for_node.body, space_after_body); // { body } | |
| 1829 | { | |
| 1830 | if (!body_on_same_line) ais.pushIndent(); | |
| 1831 | defer if (!body_on_same_line) ais.popIndent(); | |
| 1832 | try renderExpression(allocator, ais, tree, for_node.body, space_after_body); // { body } | |
| 1833 | } | |
| 1900 | 1834 | |
| 1901 | 1835 | if (for_node.@"else") |@"else"| { |
| 1902 | if (space_after_body == Space.Newline) try stream.writeByteNTimes(' ', indent); | |
| 1903 | return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space); // else | |
| 1836 | return renderExpression(allocator, ais, tree, &@"else".base, space); // else | |
| 1904 | 1837 | } |
| 1905 | 1838 | }, |
| 1906 | 1839 | |
| ... | ... | @@ -1910,29 +1843,29 @@ fn renderExpression( |
| 1910 | 1843 | const lparen = tree.nextToken(if_node.if_token); |
| 1911 | 1844 | const rparen = tree.nextToken(if_node.condition.lastToken()); |
| 1912 | 1845 | |
| 1913 | try renderToken(tree, stream, if_node.if_token, indent, start_col, Space.Space); // if | |
| 1914 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 1846 | try renderToken(tree, ais, if_node.if_token, Space.Space); // if | |
| 1847 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 1915 | 1848 | |
| 1916 | try renderExpression(allocator, stream, tree, indent, start_col, if_node.condition, Space.None); // condition | |
| 1849 | try renderExpression(allocator, ais, tree, if_node.condition, Space.None); // condition | |
| 1917 | 1850 | |
| 1918 | 1851 | const body_is_if_block = if_node.body.tag == .If; |
| 1919 | 1852 | const body_is_block = nodeIsBlock(if_node.body); |
| 1920 | 1853 | |
| 1921 | 1854 | if (body_is_if_block) { |
| 1922 | try renderExtraNewline(tree, stream, start_col, if_node.body); | |
| 1855 | try renderExtraNewline(tree, ais, if_node.body); | |
| 1923 | 1856 | } else if (body_is_block) { |
| 1924 | 1857 | const after_rparen_space = if (if_node.payload == null) Space.BlockStart else Space.Space; |
| 1925 | try renderToken(tree, stream, rparen, indent, start_col, after_rparen_space); // ) | |
| 1858 | try renderToken(tree, ais, rparen, after_rparen_space); // ) | |
| 1926 | 1859 | |
| 1927 | 1860 | if (if_node.payload) |payload| { |
| 1928 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.BlockStart); // |x| | |
| 1861 | try renderExpression(allocator, ais, tree, payload, Space.BlockStart); // |x| | |
| 1929 | 1862 | } |
| 1930 | 1863 | |
| 1931 | 1864 | if (if_node.@"else") |@"else"| { |
| 1932 | try renderExpression(allocator, stream, tree, indent, start_col, if_node.body, Space.SpaceOrOutdent); | |
| 1933 | return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space); | |
| 1865 | try renderExpression(allocator, ais, tree, if_node.body, Space.SpaceOrOutdent); | |
| 1866 | return renderExpression(allocator, ais, tree, &@"else".base, space); | |
| 1934 | 1867 | } else { |
| 1935 | return renderExpression(allocator, stream, tree, indent, start_col, if_node.body, space); | |
| 1868 | return renderExpression(allocator, ais, tree, if_node.body, space); | |
| 1936 | 1869 | } |
| 1937 | 1870 | } |
| 1938 | 1871 | |
| ... | ... | @@ -1940,186 +1873,184 @@ fn renderExpression( |
| 1940 | 1873 | |
| 1941 | 1874 | if (src_has_newline) { |
| 1942 | 1875 | const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space; |
| 1943 | try renderToken(tree, stream, rparen, indent, start_col, after_rparen_space); // ) | |
| 1876 | try renderToken(tree, ais, rparen, after_rparen_space); // ) | |
| 1944 | 1877 | |
| 1945 | 1878 | if (if_node.payload) |payload| { |
| 1946 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Newline); | |
| 1879 | try renderExpression(allocator, ais, tree, payload, Space.Newline); | |
| 1947 | 1880 | } |
| 1948 | 1881 | |
| 1949 | const new_indent = indent + indent_delta; | |
| 1950 | try stream.writeByteNTimes(' ', new_indent); | |
| 1951 | ||
| 1952 | 1882 | if (if_node.@"else") |@"else"| { |
| 1953 | 1883 | const else_is_block = nodeIsBlock(@"else".body); |
| 1954 | try renderExpression(allocator, stream, tree, new_indent, start_col, if_node.body, Space.Newline); | |
| 1955 | try stream.writeByteNTimes(' ', indent); | |
| 1884 | ||
| 1885 | { | |
| 1886 | ais.pushIndent(); | |
| 1887 | defer ais.popIndent(); | |
| 1888 | try renderExpression(allocator, ais, tree, if_node.body, Space.Newline); | |
| 1889 | } | |
| 1956 | 1890 | |
| 1957 | 1891 | if (else_is_block) { |
| 1958 | try renderToken(tree, stream, @"else".else_token, indent, start_col, Space.Space); // else | |
| 1892 | try renderToken(tree, ais, @"else".else_token, Space.Space); // else | |
| 1959 | 1893 | |
| 1960 | 1894 | if (@"else".payload) |payload| { |
| 1961 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); | |
| 1895 | try renderExpression(allocator, ais, tree, payload, Space.Space); | |
| 1962 | 1896 | } |
| 1963 | 1897 | |
| 1964 | return renderExpression(allocator, stream, tree, indent, start_col, @"else".body, space); | |
| 1898 | return renderExpression(allocator, ais, tree, @"else".body, space); | |
| 1965 | 1899 | } else { |
| 1966 | 1900 | const after_else_space = if (@"else".payload == null) Space.Newline else Space.Space; |
| 1967 | try renderToken(tree, stream, @"else".else_token, indent, start_col, after_else_space); // else | |
| 1901 | try renderToken(tree, ais, @"else".else_token, after_else_space); // else | |
| 1968 | 1902 | |
| 1969 | 1903 | if (@"else".payload) |payload| { |
| 1970 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Newline); | |
| 1904 | try renderExpression(allocator, ais, tree, payload, Space.Newline); | |
| 1971 | 1905 | } |
| 1972 | try stream.writeByteNTimes(' ', new_indent); | |
| 1973 | 1906 | |
| 1974 | return renderExpression(allocator, stream, tree, new_indent, start_col, @"else".body, space); | |
| 1907 | ais.pushIndent(); | |
| 1908 | defer ais.popIndent(); | |
| 1909 | return renderExpression(allocator, ais, tree, @"else".body, space); | |
| 1975 | 1910 | } |
| 1976 | 1911 | } else { |
| 1977 | return renderExpression(allocator, stream, tree, new_indent, start_col, if_node.body, space); | |
| 1912 | ais.pushIndent(); | |
| 1913 | defer ais.popIndent(); | |
| 1914 | return renderExpression(allocator, ais, tree, if_node.body, space); | |
| 1978 | 1915 | } |
| 1979 | 1916 | } |
| 1980 | 1917 | |
| 1981 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | |
| 1918 | // Single line if statement | |
| 1919 | ||
| 1920 | try renderToken(tree, ais, rparen, Space.Space); // ) | |
| 1982 | 1921 | |
| 1983 | 1922 | if (if_node.payload) |payload| { |
| 1984 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); | |
| 1923 | try renderExpression(allocator, ais, tree, payload, Space.Space); | |
| 1985 | 1924 | } |
| 1986 | 1925 | |
| 1987 | 1926 | if (if_node.@"else") |@"else"| { |
| 1988 | try renderExpression(allocator, stream, tree, indent, start_col, if_node.body, Space.Space); | |
| 1989 | try renderToken(tree, stream, @"else".else_token, indent, start_col, Space.Space); | |
| 1927 | try renderExpression(allocator, ais, tree, if_node.body, Space.Space); | |
| 1928 | try renderToken(tree, ais, @"else".else_token, Space.Space); | |
| 1990 | 1929 | |
| 1991 | 1930 | if (@"else".payload) |payload| { |
| 1992 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); | |
| 1931 | try renderExpression(allocator, ais, tree, payload, Space.Space); | |
| 1993 | 1932 | } |
| 1994 | 1933 | |
| 1995 | return renderExpression(allocator, stream, tree, indent, start_col, @"else".body, space); | |
| 1934 | return renderExpression(allocator, ais, tree, @"else".body, space); | |
| 1996 | 1935 | } else { |
| 1997 | return renderExpression(allocator, stream, tree, indent, start_col, if_node.body, space); | |
| 1936 | return renderExpression(allocator, ais, tree, if_node.body, space); | |
| 1998 | 1937 | } |
| 1999 | 1938 | }, |
| 2000 | 1939 | |
| 2001 | 1940 | .Asm => { |
| 2002 | 1941 | const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base); |
| 2003 | 1942 | |
| 2004 | try renderToken(tree, stream, asm_node.asm_token, indent, start_col, Space.Space); // asm | |
| 1943 | try renderToken(tree, ais, asm_node.asm_token, Space.Space); // asm | |
| 2005 | 1944 | |
| 2006 | 1945 | if (asm_node.volatile_token) |volatile_token| { |
| 2007 | try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile | |
| 2008 | try renderToken(tree, stream, tree.nextToken(volatile_token), indent, start_col, Space.None); // ( | |
| 1946 | try renderToken(tree, ais, volatile_token, Space.Space); // volatile | |
| 1947 | try renderToken(tree, ais, tree.nextToken(volatile_token), Space.None); // ( | |
| 2009 | 1948 | } else { |
| 2010 | try renderToken(tree, stream, tree.nextToken(asm_node.asm_token), indent, start_col, Space.None); // ( | |
| 1949 | try renderToken(tree, ais, tree.nextToken(asm_node.asm_token), Space.None); // ( | |
| 2011 | 1950 | } |
| 2012 | 1951 | |
| 2013 | if (asm_node.outputs.len == 0 and asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) { | |
| 2014 | try renderExpression(allocator, stream, tree, indent, start_col, asm_node.template, Space.None); | |
| 2015 | return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); | |
| 2016 | } | |
| 1952 | asmblk: { | |
| 1953 | ais.pushIndent(); | |
| 1954 | defer ais.popIndent(); | |
| 2017 | 1955 | |
| 2018 | try renderExpression(allocator, stream, tree, indent, start_col, asm_node.template, Space.Newline); | |
| 1956 | if (asm_node.outputs.len == 0 and asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) { | |
| 1957 | try renderExpression(allocator, ais, tree, asm_node.template, Space.None); | |
| 1958 | break :asmblk; | |
| 1959 | } | |
| 2019 | 1960 | |
| 2020 | const indent_once = indent + indent_delta; | |
| 1961 | try renderExpression(allocator, ais, tree, asm_node.template, Space.Newline); | |
| 2021 | 1962 | |
| 2022 | if (asm_node.template.tag == .MultilineStringLiteral) { | |
| 2023 | // After rendering a multiline string literal the cursor is | |
| 2024 | // already offset by indent | |
| 2025 | try stream.writeByteNTimes(' ', indent_delta); | |
| 2026 | } else { | |
| 2027 | try stream.writeByteNTimes(' ', indent_once); | |
| 2028 | } | |
| 1963 | ais.setIndentDelta(asm_indent_delta); | |
| 1964 | defer ais.setIndentDelta(indent_delta); | |
| 2029 | 1965 | |
| 2030 | const colon1 = tree.nextToken(asm_node.template.lastToken()); | |
| 2031 | const indent_extra = indent_once + 2; | |
| 1966 | const colon1 = tree.nextToken(asm_node.template.lastToken()); | |
| 2032 | 1967 | |
| 2033 | const colon2 = if (asm_node.outputs.len == 0) blk: { | |
| 2034 | try renderToken(tree, stream, colon1, indent, start_col, Space.Newline); // : | |
| 2035 | try stream.writeByteNTimes(' ', indent_once); | |
| 1968 | const colon2 = if (asm_node.outputs.len == 0) blk: { | |
| 1969 | try renderToken(tree, ais, colon1, Space.Newline); // : | |
| 2036 | 1970 | |
| 2037 | break :blk tree.nextToken(colon1); | |
| 2038 | } else blk: { | |
| 2039 | try renderToken(tree, stream, colon1, indent, start_col, Space.Space); // : | |
| 2040 | ||
| 2041 | for (asm_node.outputs) |*asm_output, i| { | |
| 2042 | if (i + 1 < asm_node.outputs.len) { | |
| 2043 | const next_asm_output = asm_node.outputs[i + 1]; | |
| 2044 | try renderAsmOutput(allocator, stream, tree, indent_extra, start_col, asm_output, Space.None); | |
| 2045 | ||
| 2046 | const comma = tree.prevToken(next_asm_output.firstToken()); | |
| 2047 | try renderToken(tree, stream, comma, indent_extra, start_col, Space.Newline); // , | |
| 2048 | try renderExtraNewlineToken(tree, stream, start_col, next_asm_output.firstToken()); | |
| 2049 | ||
| 2050 | try stream.writeByteNTimes(' ', indent_extra); | |
| 2051 | } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) { | |
| 2052 | try renderAsmOutput(allocator, stream, tree, indent_extra, start_col, asm_output, Space.Newline); | |
| 2053 | try stream.writeByteNTimes(' ', indent); | |
| 2054 | return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); | |
| 2055 | } else { | |
| 2056 | try renderAsmOutput(allocator, stream, tree, indent_extra, start_col, asm_output, Space.Newline); | |
| 2057 | try stream.writeByteNTimes(' ', indent_once); | |
| 2058 | const comma_or_colon = tree.nextToken(asm_output.lastToken()); | |
| 2059 | break :blk switch (tree.token_ids[comma_or_colon]) { | |
| 2060 | .Comma => tree.nextToken(comma_or_colon), | |
| 2061 | else => comma_or_colon, | |
| 2062 | }; | |
| 2063 | } | |
| 2064 | } | |
| 2065 | unreachable; | |
| 2066 | }; | |
| 1971 | break :blk tree.nextToken(colon1); | |
| 1972 | } else blk: { | |
| 1973 | try renderToken(tree, ais, colon1, Space.Space); // : | |
| 2067 | 1974 | |
| 2068 | const colon3 = if (asm_node.inputs.len == 0) blk: { | |
| 2069 | try renderToken(tree, stream, colon2, indent, start_col, Space.Newline); // : | |
| 2070 | try stream.writeByteNTimes(' ', indent_once); | |
| 1975 | ais.pushIndent(); | |
| 1976 | defer ais.popIndent(); | |
| 2071 | 1977 | |
| 2072 | break :blk tree.nextToken(colon2); | |
| 2073 | } else blk: { | |
| 2074 | try renderToken(tree, stream, colon2, indent, start_col, Space.Space); // : | |
| 2075 | ||
| 2076 | for (asm_node.inputs) |*asm_input, i| { | |
| 2077 | if (i + 1 < asm_node.inputs.len) { | |
| 2078 | const next_asm_input = &asm_node.inputs[i + 1]; | |
| 2079 | try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.None); | |
| 2080 | ||
| 2081 | const comma = tree.prevToken(next_asm_input.firstToken()); | |
| 2082 | try renderToken(tree, stream, comma, indent_extra, start_col, Space.Newline); // , | |
| 2083 | try renderExtraNewlineToken(tree, stream, start_col, next_asm_input.firstToken()); | |
| 2084 | ||
| 2085 | try stream.writeByteNTimes(' ', indent_extra); | |
| 2086 | } else if (asm_node.clobbers.len == 0) { | |
| 2087 | try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.Newline); | |
| 2088 | try stream.writeByteNTimes(' ', indent); | |
| 2089 | return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); // ) | |
| 2090 | } else { | |
| 2091 | try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.Newline); | |
| 2092 | try stream.writeByteNTimes(' ', indent_once); | |
| 2093 | const comma_or_colon = tree.nextToken(asm_input.lastToken()); | |
| 2094 | break :blk switch (tree.token_ids[comma_or_colon]) { | |
| 2095 | .Comma => tree.nextToken(comma_or_colon), | |
| 2096 | else => comma_or_colon, | |
| 2097 | }; | |
| 1978 | for (asm_node.outputs) |*asm_output, i| { | |
| 1979 | if (i + 1 < asm_node.outputs.len) { | |
| 1980 | const next_asm_output = asm_node.outputs[i + 1]; | |
| 1981 | try renderAsmOutput(allocator, ais, tree, asm_output, Space.None); | |
| 1982 | ||
| 1983 | const comma = tree.prevToken(next_asm_output.firstToken()); | |
| 1984 | try renderToken(tree, ais, comma, Space.Newline); // , | |
| 1985 | try renderExtraNewlineToken(tree, ais, next_asm_output.firstToken()); | |
| 1986 | } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) { | |
| 1987 | try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline); | |
| 1988 | break :asmblk; | |
| 1989 | } else { | |
| 1990 | try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline); | |
| 1991 | const comma_or_colon = tree.nextToken(asm_output.lastToken()); | |
| 1992 | break :blk switch (tree.token_ids[comma_or_colon]) { | |
| 1993 | .Comma => tree.nextToken(comma_or_colon), | |
| 1994 | else => comma_or_colon, | |
| 1995 | }; | |
| 1996 | } | |
| 2098 | 1997 | } |
| 2099 | } | |
| 2100 | unreachable; | |
| 2101 | }; | |
| 1998 | unreachable; | |
| 1999 | }; | |
| 2102 | 2000 | |
| 2103 | try renderToken(tree, stream, colon3, indent, start_col, Space.Space); // : | |
| 2001 | const colon3 = if (asm_node.inputs.len == 0) blk: { | |
| 2002 | try renderToken(tree, ais, colon2, Space.Newline); // : | |
| 2003 | break :blk tree.nextToken(colon2); | |
| 2004 | } else blk: { | |
| 2005 | try renderToken(tree, ais, colon2, Space.Space); // : | |
| 2006 | ais.pushIndent(); | |
| 2007 | defer ais.popIndent(); | |
| 2008 | for (asm_node.inputs) |*asm_input, i| { | |
| 2009 | if (i + 1 < asm_node.inputs.len) { | |
| 2010 | const next_asm_input = &asm_node.inputs[i + 1]; | |
| 2011 | try renderAsmInput(allocator, ais, tree, asm_input, Space.None); | |
| 2012 | ||
| 2013 | const comma = tree.prevToken(next_asm_input.firstToken()); | |
| 2014 | try renderToken(tree, ais, comma, Space.Newline); // , | |
| 2015 | try renderExtraNewlineToken(tree, ais, next_asm_input.firstToken()); | |
| 2016 | } else if (asm_node.clobbers.len == 0) { | |
| 2017 | try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline); | |
| 2018 | break :asmblk; | |
| 2019 | } else { | |
| 2020 | try renderAsmInput(allocator, ais, tree, asm_input, Space.Newline); | |
| 2021 | const comma_or_colon = tree.nextToken(asm_input.lastToken()); | |
| 2022 | break :blk switch (tree.token_ids[comma_or_colon]) { | |
| 2023 | .Comma => tree.nextToken(comma_or_colon), | |
| 2024 | else => comma_or_colon, | |
| 2025 | }; | |
| 2026 | } | |
| 2027 | } | |
| 2028 | unreachable; | |
| 2029 | }; | |
| 2104 | 2030 | |
| 2105 | for (asm_node.clobbers) |clobber_node, i| { | |
| 2106 | if (i + 1 >= asm_node.clobbers.len) { | |
| 2107 | try renderExpression(allocator, stream, tree, indent_extra, start_col, clobber_node, Space.Newline); | |
| 2108 | try stream.writeByteNTimes(' ', indent); | |
| 2109 | return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); | |
| 2110 | } else { | |
| 2111 | try renderExpression(allocator, stream, tree, indent_extra, start_col, clobber_node, Space.None); | |
| 2112 | const comma = tree.nextToken(clobber_node.lastToken()); | |
| 2113 | try renderToken(tree, stream, comma, indent_once, start_col, Space.Space); // , | |
| 2031 | try renderToken(tree, ais, colon3, Space.Space); // : | |
| 2032 | ais.pushIndent(); | |
| 2033 | defer ais.popIndent(); | |
| 2034 | for (asm_node.clobbers) |clobber_node, i| { | |
| 2035 | if (i + 1 >= asm_node.clobbers.len) { | |
| 2036 | try renderExpression(allocator, ais, tree, clobber_node, Space.Newline); | |
| 2037 | break :asmblk; | |
| 2038 | } else { | |
| 2039 | try renderExpression(allocator, ais, tree, clobber_node, Space.None); | |
| 2040 | const comma = tree.nextToken(clobber_node.lastToken()); | |
| 2041 | try renderToken(tree, ais, comma, Space.Space); // , | |
| 2042 | } | |
| 2114 | 2043 | } |
| 2115 | 2044 | } |
| 2045 | ||
| 2046 | return renderToken(tree, ais, asm_node.rparen, space); | |
| 2116 | 2047 | }, |
| 2117 | 2048 | |
| 2118 | 2049 | .EnumLiteral => { |
| 2119 | 2050 | const enum_literal = @fieldParentPtr(ast.Node.EnumLiteral, "base", base); |
| 2120 | 2051 | |
| 2121 | try renderToken(tree, stream, enum_literal.dot, indent, start_col, Space.None); // . | |
| 2122 | return renderToken(tree, stream, enum_literal.name, indent, start_col, space); // name | |
| 2052 | try renderToken(tree, ais, enum_literal.dot, Space.None); // . | |
| 2053 | return renderToken(tree, ais, enum_literal.name, space); // name | |
| 2123 | 2054 | }, |
| 2124 | 2055 | |
| 2125 | 2056 | .ContainerField, |
| ... | ... | @@ -2133,118 +2064,115 @@ fn renderExpression( |
| 2133 | 2064 | |
| 2134 | 2065 | fn renderArrayType( |
| 2135 | 2066 | allocator: *mem.Allocator, |
| 2136 | stream: anytype, | |
| 2067 | ais: anytype, | |
| 2137 | 2068 | tree: *ast.Tree, |
| 2138 | indent: usize, | |
| 2139 | start_col: *usize, | |
| 2140 | 2069 | lbracket: ast.TokenIndex, |
| 2141 | 2070 | rhs: *ast.Node, |
| 2142 | 2071 | len_expr: *ast.Node, |
| 2143 | 2072 | opt_sentinel: ?*ast.Node, |
| 2144 | 2073 | space: Space, |
| 2145 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2074 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2146 | 2075 | const rbracket = tree.nextToken(if (opt_sentinel) |sentinel| |
| 2147 | 2076 | sentinel.lastToken() |
| 2148 | 2077 | else |
| 2149 | 2078 | len_expr.lastToken()); |
| 2150 | 2079 | |
| 2151 | try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [ | |
| 2152 | ||
| 2153 | 2080 | const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; |
| 2154 | 2081 | const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; |
| 2155 | const new_indent = if (ends_with_comment) indent + indent_delta else indent; | |
| 2156 | 2082 | const new_space = if (ends_with_comment) Space.Newline else Space.None; |
| 2157 | try renderExpression(allocator, stream, tree, new_indent, start_col, len_expr, new_space); | |
| 2158 | if (starts_with_comment) { | |
| 2159 | try stream.writeByte('\n'); | |
| 2160 | } | |
| 2161 | if (ends_with_comment or starts_with_comment) { | |
| 2162 | try stream.writeByteNTimes(' ', indent); | |
| 2163 | } | |
| 2164 | if (opt_sentinel) |sentinel| { | |
| 2165 | const colon_token = tree.prevToken(sentinel.firstToken()); | |
| 2166 | try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : | |
| 2167 | try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); | |
| 2083 | { | |
| 2084 | const do_indent = (starts_with_comment or ends_with_comment); | |
| 2085 | if (do_indent) ais.pushIndent(); | |
| 2086 | defer if (do_indent) ais.popIndent(); | |
| 2087 | ||
| 2088 | try renderToken(tree, ais, lbracket, Space.None); // [ | |
| 2089 | try renderExpression(allocator, ais, tree, len_expr, new_space); | |
| 2090 | ||
| 2091 | if (starts_with_comment) { | |
| 2092 | try ais.maybeInsertNewline(); | |
| 2093 | } | |
| 2094 | if (opt_sentinel) |sentinel| { | |
| 2095 | const colon_token = tree.prevToken(sentinel.firstToken()); | |
| 2096 | try renderToken(tree, ais, colon_token, Space.None); // : | |
| 2097 | try renderExpression(allocator, ais, tree, sentinel, Space.None); | |
| 2098 | } | |
| 2099 | if (starts_with_comment) { | |
| 2100 | try ais.maybeInsertNewline(); | |
| 2101 | } | |
| 2168 | 2102 | } |
| 2169 | try renderToken(tree, stream, rbracket, indent, start_col, Space.None); // ] | |
| 2103 | try renderToken(tree, ais, rbracket, Space.None); // ] | |
| 2170 | 2104 | |
| 2171 | return renderExpression(allocator, stream, tree, indent, start_col, rhs, space); | |
| 2105 | return renderExpression(allocator, ais, tree, rhs, space); | |
| 2172 | 2106 | } |
| 2173 | 2107 | |
| 2174 | 2108 | fn renderAsmOutput( |
| 2175 | 2109 | allocator: *mem.Allocator, |
| 2176 | stream: anytype, | |
| 2110 | ais: anytype, | |
| 2177 | 2111 | tree: *ast.Tree, |
| 2178 | indent: usize, | |
| 2179 | start_col: *usize, | |
| 2180 | 2112 | asm_output: *const ast.Node.Asm.Output, |
| 2181 | 2113 | space: Space, |
| 2182 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2183 | try stream.writeAll("["); | |
| 2184 | try renderExpression(allocator, stream, tree, indent, start_col, asm_output.symbolic_name, Space.None); | |
| 2185 | try stream.writeAll("] "); | |
| 2186 | try renderExpression(allocator, stream, tree, indent, start_col, asm_output.constraint, Space.None); | |
| 2187 | try stream.writeAll(" ("); | |
| 2114 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2115 | try ais.writer().writeAll("["); | |
| 2116 | try renderExpression(allocator, ais, tree, asm_output.symbolic_name, Space.None); | |
| 2117 | try ais.writer().writeAll("] "); | |
| 2118 | try renderExpression(allocator, ais, tree, asm_output.constraint, Space.None); | |
| 2119 | try ais.writer().writeAll(" ("); | |
| 2188 | 2120 | |
| 2189 | 2121 | switch (asm_output.kind) { |
| 2190 | 2122 | ast.Node.Asm.Output.Kind.Variable => |variable_name| { |
| 2191 | try renderExpression(allocator, stream, tree, indent, start_col, &variable_name.base, Space.None); | |
| 2123 | try renderExpression(allocator, ais, tree, &variable_name.base, Space.None); | |
| 2192 | 2124 | }, |
| 2193 | 2125 | ast.Node.Asm.Output.Kind.Return => |return_type| { |
| 2194 | try stream.writeAll("-> "); | |
| 2195 | try renderExpression(allocator, stream, tree, indent, start_col, return_type, Space.None); | |
| 2126 | try ais.writer().writeAll("-> "); | |
| 2127 | try renderExpression(allocator, ais, tree, return_type, Space.None); | |
| 2196 | 2128 | }, |
| 2197 | 2129 | } |
| 2198 | 2130 | |
| 2199 | return renderToken(tree, stream, asm_output.lastToken(), indent, start_col, space); // ) | |
| 2131 | return renderToken(tree, ais, asm_output.lastToken(), space); // ) | |
| 2200 | 2132 | } |
| 2201 | 2133 | |
| 2202 | 2134 | fn renderAsmInput( |
| 2203 | 2135 | allocator: *mem.Allocator, |
| 2204 | stream: anytype, | |
| 2136 | ais: anytype, | |
| 2205 | 2137 | tree: *ast.Tree, |
| 2206 | indent: usize, | |
| 2207 | start_col: *usize, | |
| 2208 | 2138 | asm_input: *const ast.Node.Asm.Input, |
| 2209 | 2139 | space: Space, |
| 2210 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2211 | try stream.writeAll("["); | |
| 2212 | try renderExpression(allocator, stream, tree, indent, start_col, asm_input.symbolic_name, Space.None); | |
| 2213 | try stream.writeAll("] "); | |
| 2214 | try renderExpression(allocator, stream, tree, indent, start_col, asm_input.constraint, Space.None); | |
| 2215 | try stream.writeAll(" ("); | |
| 2216 | try renderExpression(allocator, stream, tree, indent, start_col, asm_input.expr, Space.None); | |
| 2217 | return renderToken(tree, stream, asm_input.lastToken(), indent, start_col, space); // ) | |
| 2140 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2141 | try ais.writer().writeAll("["); | |
| 2142 | try renderExpression(allocator, ais, tree, asm_input.symbolic_name, Space.None); | |
| 2143 | try ais.writer().writeAll("] "); | |
| 2144 | try renderExpression(allocator, ais, tree, asm_input.constraint, Space.None); | |
| 2145 | try ais.writer().writeAll(" ("); | |
| 2146 | try renderExpression(allocator, ais, tree, asm_input.expr, Space.None); | |
| 2147 | return renderToken(tree, ais, asm_input.lastToken(), space); // ) | |
| 2218 | 2148 | } |
| 2219 | 2149 | |
| 2220 | 2150 | fn renderVarDecl( |
| 2221 | 2151 | allocator: *mem.Allocator, |
| 2222 | stream: anytype, | |
| 2152 | ais: anytype, | |
| 2223 | 2153 | tree: *ast.Tree, |
| 2224 | indent: usize, | |
| 2225 | start_col: *usize, | |
| 2226 | 2154 | var_decl: *ast.Node.VarDecl, |
| 2227 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2155 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2228 | 2156 | if (var_decl.getVisibToken()) |visib_token| { |
| 2229 | try renderToken(tree, stream, visib_token, indent, start_col, Space.Space); // pub | |
| 2157 | try renderToken(tree, ais, visib_token, Space.Space); // pub | |
| 2230 | 2158 | } |
| 2231 | 2159 | |
| 2232 | 2160 | if (var_decl.getExternExportToken()) |extern_export_token| { |
| 2233 | try renderToken(tree, stream, extern_export_token, indent, start_col, Space.Space); // extern | |
| 2161 | try renderToken(tree, ais, extern_export_token, Space.Space); // extern | |
| 2234 | 2162 | |
| 2235 | 2163 | if (var_decl.getLibName()) |lib_name| { |
| 2236 | try renderExpression(allocator, stream, tree, indent, start_col, lib_name, Space.Space); // "lib" | |
| 2164 | try renderExpression(allocator, ais, tree, lib_name, Space.Space); // "lib" | |
| 2237 | 2165 | } |
| 2238 | 2166 | } |
| 2239 | 2167 | |
| 2240 | 2168 | if (var_decl.getComptimeToken()) |comptime_token| { |
| 2241 | try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space); // comptime | |
| 2169 | try renderToken(tree, ais, comptime_token, Space.Space); // comptime | |
| 2242 | 2170 | } |
| 2243 | 2171 | |
| 2244 | 2172 | if (var_decl.getThreadLocalToken()) |thread_local_token| { |
| 2245 | try renderToken(tree, stream, thread_local_token, indent, start_col, Space.Space); // threadlocal | |
| 2173 | try renderToken(tree, ais, thread_local_token, Space.Space); // threadlocal | |
| 2246 | 2174 | } |
| 2247 | try renderToken(tree, stream, var_decl.mut_token, indent, start_col, Space.Space); // var | |
| 2175 | try renderToken(tree, ais, var_decl.mut_token, Space.Space); // var | |
| 2248 | 2176 | |
| 2249 | 2177 | const name_space = if (var_decl.getTypeNode() == null and |
| 2250 | 2178 | (var_decl.getAlignNode() != null or |
| ... | ... | @@ -2253,95 +2181,92 @@ fn renderVarDecl( |
| 2253 | 2181 | Space.Space |
| 2254 | 2182 | else |
| 2255 | 2183 | Space.None; |
| 2256 | try renderToken(tree, stream, var_decl.name_token, indent, start_col, name_space); | |
| 2184 | try renderToken(tree, ais, var_decl.name_token, name_space); | |
| 2257 | 2185 | |
| 2258 | 2186 | if (var_decl.getTypeNode()) |type_node| { |
| 2259 | try renderToken(tree, stream, tree.nextToken(var_decl.name_token), indent, start_col, Space.Space); | |
| 2187 | try renderToken(tree, ais, tree.nextToken(var_decl.name_token), Space.Space); | |
| 2260 | 2188 | const s = if (var_decl.getAlignNode() != null or |
| 2261 | 2189 | var_decl.getSectionNode() != null or |
| 2262 | 2190 | var_decl.getInitNode() != null) Space.Space else Space.None; |
| 2263 | try renderExpression(allocator, stream, tree, indent, start_col, type_node, s); | |
| 2191 | try renderExpression(allocator, ais, tree, type_node, s); | |
| 2264 | 2192 | } |
| 2265 | 2193 | |
| 2266 | 2194 | if (var_decl.getAlignNode()) |align_node| { |
| 2267 | 2195 | const lparen = tree.prevToken(align_node.firstToken()); |
| 2268 | 2196 | const align_kw = tree.prevToken(lparen); |
| 2269 | 2197 | const rparen = tree.nextToken(align_node.lastToken()); |
| 2270 | try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align | |
| 2271 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 2272 | try renderExpression(allocator, stream, tree, indent, start_col, align_node, Space.None); | |
| 2198 | try renderToken(tree, ais, align_kw, Space.None); // align | |
| 2199 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 2200 | try renderExpression(allocator, ais, tree, align_node, Space.None); | |
| 2273 | 2201 | const s = if (var_decl.getSectionNode() != null or var_decl.getInitNode() != null) Space.Space else Space.None; |
| 2274 | try renderToken(tree, stream, rparen, indent, start_col, s); // ) | |
| 2202 | try renderToken(tree, ais, rparen, s); // ) | |
| 2275 | 2203 | } |
| 2276 | 2204 | |
| 2277 | 2205 | if (var_decl.getSectionNode()) |section_node| { |
| 2278 | 2206 | const lparen = tree.prevToken(section_node.firstToken()); |
| 2279 | 2207 | const section_kw = tree.prevToken(lparen); |
| 2280 | 2208 | const rparen = tree.nextToken(section_node.lastToken()); |
| 2281 | try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // linksection | |
| 2282 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | |
| 2283 | try renderExpression(allocator, stream, tree, indent, start_col, section_node, Space.None); | |
| 2209 | try renderToken(tree, ais, section_kw, Space.None); // linksection | |
| 2210 | try renderToken(tree, ais, lparen, Space.None); // ( | |
| 2211 | try renderExpression(allocator, ais, tree, section_node, Space.None); | |
| 2284 | 2212 | const s = if (var_decl.getInitNode() != null) Space.Space else Space.None; |
| 2285 | try renderToken(tree, stream, rparen, indent, start_col, s); // ) | |
| 2213 | try renderToken(tree, ais, rparen, s); // ) | |
| 2286 | 2214 | } |
| 2287 | 2215 | |
| 2288 | 2216 | if (var_decl.getInitNode()) |init_node| { |
| 2289 | 2217 | const s = if (init_node.tag == .MultilineStringLiteral) Space.None else Space.Space; |
| 2290 | try renderToken(tree, stream, var_decl.getEqToken().?, indent, start_col, s); // = | |
| 2291 | try renderExpression(allocator, stream, tree, indent, start_col, init_node, Space.None); | |
| 2218 | try renderToken(tree, ais, var_decl.getEqToken().?, s); // = | |
| 2219 | ais.pushIndentOneShot(); | |
| 2220 | try renderExpression(allocator, ais, tree, init_node, Space.None); | |
| 2292 | 2221 | } |
| 2293 | 2222 | |
| 2294 | try renderToken(tree, stream, var_decl.semicolon_token, indent, start_col, Space.Newline); | |
| 2223 | try renderToken(tree, ais, var_decl.semicolon_token, Space.Newline); | |
| 2295 | 2224 | } |
| 2296 | 2225 | |
| 2297 | 2226 | fn renderParamDecl( |
| 2298 | 2227 | allocator: *mem.Allocator, |
| 2299 | stream: anytype, | |
| 2228 | ais: anytype, | |
| 2300 | 2229 | tree: *ast.Tree, |
| 2301 | indent: usize, | |
| 2302 | start_col: *usize, | |
| 2303 | 2230 | param_decl: ast.Node.FnProto.ParamDecl, |
| 2304 | 2231 | space: Space, |
| 2305 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2306 | try renderDocComments(tree, stream, param_decl, param_decl.doc_comments, indent, start_col); | |
| 2232 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2233 | try renderDocComments(tree, ais, param_decl, param_decl.doc_comments); | |
| 2307 | 2234 | |
| 2308 | 2235 | if (param_decl.comptime_token) |comptime_token| { |
| 2309 | try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space); | |
| 2236 | try renderToken(tree, ais, comptime_token, Space.Space); | |
| 2310 | 2237 | } |
| 2311 | 2238 | if (param_decl.noalias_token) |noalias_token| { |
| 2312 | try renderToken(tree, stream, noalias_token, indent, start_col, Space.Space); | |
| 2239 | try renderToken(tree, ais, noalias_token, Space.Space); | |
| 2313 | 2240 | } |
| 2314 | 2241 | if (param_decl.name_token) |name_token| { |
| 2315 | try renderToken(tree, stream, name_token, indent, start_col, Space.None); | |
| 2316 | try renderToken(tree, stream, tree.nextToken(name_token), indent, start_col, Space.Space); // : | |
| 2242 | try renderToken(tree, ais, name_token, Space.None); | |
| 2243 | try renderToken(tree, ais, tree.nextToken(name_token), Space.Space); // : | |
| 2317 | 2244 | } |
| 2318 | 2245 | switch (param_decl.param_type) { |
| 2319 | .any_type, .type_expr => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, space), | |
| 2246 | .any_type, .type_expr => |node| try renderExpression(allocator, ais, tree, node, space), | |
| 2320 | 2247 | } |
| 2321 | 2248 | } |
| 2322 | 2249 | |
| 2323 | 2250 | fn renderStatement( |
| 2324 | 2251 | allocator: *mem.Allocator, |
| 2325 | stream: anytype, | |
| 2252 | ais: anytype, | |
| 2326 | 2253 | tree: *ast.Tree, |
| 2327 | indent: usize, | |
| 2328 | start_col: *usize, | |
| 2329 | 2254 | base: *ast.Node, |
| 2330 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2255 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2331 | 2256 | switch (base.tag) { |
| 2332 | 2257 | .VarDecl => { |
| 2333 | 2258 | const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base); |
| 2334 | try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl); | |
| 2259 | try renderVarDecl(allocator, ais, tree, var_decl); | |
| 2335 | 2260 | }, |
| 2336 | 2261 | else => { |
| 2337 | 2262 | if (base.requireSemiColon()) { |
| 2338 | try renderExpression(allocator, stream, tree, indent, start_col, base, Space.None); | |
| 2263 | try renderExpression(allocator, ais, tree, base, Space.None); | |
| 2339 | 2264 | |
| 2340 | 2265 | const semicolon_index = tree.nextToken(base.lastToken()); |
| 2341 | 2266 | assert(tree.token_ids[semicolon_index] == .Semicolon); |
| 2342 | try renderToken(tree, stream, semicolon_index, indent, start_col, Space.Newline); | |
| 2267 | try renderToken(tree, ais, semicolon_index, Space.Newline); | |
| 2343 | 2268 | } else { |
| 2344 | try renderExpression(allocator, stream, tree, indent, start_col, base, Space.Newline); | |
| 2269 | try renderExpression(allocator, ais, tree, base, Space.Newline); | |
| 2345 | 2270 | } |
| 2346 | 2271 | }, |
| 2347 | 2272 | } |
| ... | ... | @@ -2360,24 +2285,19 @@ const Space = enum { |
| 2360 | 2285 | |
| 2361 | 2286 | fn renderTokenOffset( |
| 2362 | 2287 | tree: *ast.Tree, |
| 2363 | stream: anytype, | |
| 2288 | ais: anytype, | |
| 2364 | 2289 | token_index: ast.TokenIndex, |
| 2365 | indent: usize, | |
| 2366 | start_col: *usize, | |
| 2367 | 2290 | space: Space, |
| 2368 | 2291 | token_skip_bytes: usize, |
| 2369 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2292 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2370 | 2293 | if (space == Space.BlockStart) { |
| 2371 | if (start_col.* < indent + indent_delta) | |
| 2372 | return renderToken(tree, stream, token_index, indent, start_col, Space.Space); | |
| 2373 | try renderToken(tree, stream, token_index, indent, start_col, Space.Newline); | |
| 2374 | try stream.writeByteNTimes(' ', indent); | |
| 2375 | start_col.* = indent; | |
| 2376 | return; | |
| 2294 | // If placing the lbrace on the current line would cause an uggly gap then put the lbrace on the next line | |
| 2295 | const new_space = if (ais.isLineOverIndented()) Space.Newline else Space.Space; | |
| 2296 | return renderToken(tree, ais, token_index, new_space); | |
| 2377 | 2297 | } |
| 2378 | 2298 | |
| 2379 | 2299 | var token_loc = tree.token_locs[token_index]; |
| 2380 | try stream.writeAll(mem.trimRight(u8, tree.tokenSliceLoc(token_loc)[token_skip_bytes..], " ")); | |
| 2300 | try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(token_loc)[token_skip_bytes..], " ")); | |
| 2381 | 2301 | |
| 2382 | 2302 | if (space == Space.NoComment) |
| 2383 | 2303 | return; |
| ... | ... | @@ -2386,20 +2306,20 @@ fn renderTokenOffset( |
| 2386 | 2306 | var next_token_loc = tree.token_locs[token_index + 1]; |
| 2387 | 2307 | |
| 2388 | 2308 | if (space == Space.Comma) switch (next_token_id) { |
| 2389 | .Comma => return renderToken(tree, stream, token_index + 1, indent, start_col, Space.Newline), | |
| 2309 | .Comma => return renderToken(tree, ais, token_index + 1, Space.Newline), | |
| 2390 | 2310 | .LineComment => { |
| 2391 | try stream.writeAll(", "); | |
| 2392 | return renderToken(tree, stream, token_index + 1, indent, start_col, Space.Newline); | |
| 2311 | try ais.writer().writeAll(", "); | |
| 2312 | return renderToken(tree, ais, token_index + 1, Space.Newline); | |
| 2393 | 2313 | }, |
| 2394 | 2314 | else => { |
| 2395 | 2315 | if (token_index + 2 < tree.token_ids.len and |
| 2396 | 2316 | tree.token_ids[token_index + 2] == .MultilineStringLiteralLine) |
| 2397 | 2317 | { |
| 2398 | try stream.writeAll(","); | |
| 2318 | try ais.writer().writeAll(","); | |
| 2399 | 2319 | return; |
| 2400 | 2320 | } else { |
| 2401 | try stream.writeAll(",\n"); | |
| 2402 | start_col.* = 0; | |
| 2321 | try ais.writer().writeAll(","); | |
| 2322 | try ais.insertNewline(); | |
| 2403 | 2323 | return; |
| 2404 | 2324 | } |
| 2405 | 2325 | }, |
| ... | ... | @@ -2423,15 +2343,14 @@ fn renderTokenOffset( |
| 2423 | 2343 | if (next_token_id == .MultilineStringLiteralLine) { |
| 2424 | 2344 | return; |
| 2425 | 2345 | } else { |
| 2426 | try stream.writeAll("\n"); | |
| 2427 | start_col.* = 0; | |
| 2346 | try ais.insertNewline(); | |
| 2428 | 2347 | return; |
| 2429 | 2348 | } |
| 2430 | 2349 | }, |
| 2431 | 2350 | Space.Space, Space.SpaceOrOutdent => { |
| 2432 | 2351 | if (next_token_id == .MultilineStringLiteralLine) |
| 2433 | 2352 | return; |
| 2434 | try stream.writeByte(' '); | |
| 2353 | try ais.writer().writeByte(' '); | |
| 2435 | 2354 | return; |
| 2436 | 2355 | }, |
| 2437 | 2356 | Space.NoComment, Space.Comma, Space.BlockStart => unreachable, |
| ... | ... | @@ -2448,8 +2367,7 @@ fn renderTokenOffset( |
| 2448 | 2367 | next_token_id = tree.token_ids[token_index + offset]; |
| 2449 | 2368 | next_token_loc = tree.token_locs[token_index + offset]; |
| 2450 | 2369 | if (next_token_id != .LineComment) { |
| 2451 | try stream.writeByte('\n'); | |
| 2452 | start_col.* = 0; | |
| 2370 | try ais.insertNewline(); | |
| 2453 | 2371 | return; |
| 2454 | 2372 | } |
| 2455 | 2373 | }, |
| ... | ... | @@ -2462,7 +2380,7 @@ fn renderTokenOffset( |
| 2462 | 2380 | |
| 2463 | 2381 | var loc = tree.tokenLocationLoc(token_loc.end, next_token_loc); |
| 2464 | 2382 | if (loc.line == 0) { |
| 2465 | try stream.print(" {}", .{mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")}); | |
| 2383 | try ais.writer().print(" {}", .{mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")}); | |
| 2466 | 2384 | offset = 2; |
| 2467 | 2385 | token_loc = next_token_loc; |
| 2468 | 2386 | next_token_loc = tree.token_locs[token_index + offset]; |
| ... | ... | @@ -2470,26 +2388,16 @@ fn renderTokenOffset( |
| 2470 | 2388 | if (next_token_id != .LineComment) { |
| 2471 | 2389 | switch (space) { |
| 2472 | 2390 | Space.None, Space.Space => { |
| 2473 | try stream.writeByte('\n'); | |
| 2474 | const after_comment_token = tree.token_ids[token_index + offset]; | |
| 2475 | const next_line_indent = switch (after_comment_token) { | |
| 2476 | .RParen, .RBrace, .RBracket => indent, | |
| 2477 | else => indent + indent_delta, | |
| 2478 | }; | |
| 2479 | try stream.writeByteNTimes(' ', next_line_indent); | |
| 2480 | start_col.* = next_line_indent; | |
| 2391 | try ais.insertNewline(); | |
| 2481 | 2392 | }, |
| 2482 | 2393 | Space.SpaceOrOutdent => { |
| 2483 | try stream.writeByte('\n'); | |
| 2484 | try stream.writeByteNTimes(' ', indent); | |
| 2485 | start_col.* = indent; | |
| 2394 | try ais.insertNewline(); | |
| 2486 | 2395 | }, |
| 2487 | 2396 | Space.Newline => { |
| 2488 | 2397 | if (next_token_id == .MultilineStringLiteralLine) { |
| 2489 | 2398 | return; |
| 2490 | 2399 | } else { |
| 2491 | try stream.writeAll("\n"); | |
| 2492 | start_col.* = 0; | |
| 2400 | try ais.insertNewline(); | |
| 2493 | 2401 | return; |
| 2494 | 2402 | } |
| 2495 | 2403 | }, |
| ... | ... | @@ -2505,10 +2413,9 @@ fn renderTokenOffset( |
| 2505 | 2413 | // translate-c doesn't generate correct newlines |
| 2506 | 2414 | // in generated code (loc.line == 0) so treat that case |
| 2507 | 2415 | // as though there was meant to be a newline between the tokens |
| 2508 | const newline_count = if (loc.line <= 1) @as(u8, 1) else @as(u8, 2); | |
| 2509 | try stream.writeByteNTimes('\n', newline_count); | |
| 2510 | try stream.writeByteNTimes(' ', indent); | |
| 2511 | try stream.writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")); | |
| 2416 | var newline_count = if (loc.line <= 1) @as(u8, 1) else @as(u8, 2); | |
| 2417 | while (newline_count > 0) : (newline_count -= 1) try ais.insertNewline(); | |
| 2418 | try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")); | |
| 2512 | 2419 | |
| 2513 | 2420 | offset += 1; |
| 2514 | 2421 | token_loc = next_token_loc; |
| ... | ... | @@ -2520,32 +2427,15 @@ fn renderTokenOffset( |
| 2520 | 2427 | if (next_token_id == .MultilineStringLiteralLine) { |
| 2521 | 2428 | return; |
| 2522 | 2429 | } else { |
| 2523 | try stream.writeAll("\n"); | |
| 2524 | start_col.* = 0; | |
| 2430 | try ais.insertNewline(); | |
| 2525 | 2431 | return; |
| 2526 | 2432 | } |
| 2527 | 2433 | }, |
| 2528 | 2434 | Space.None, Space.Space => { |
| 2529 | try stream.writeByte('\n'); | |
| 2530 | ||
| 2531 | const after_comment_token = tree.token_ids[token_index + offset]; | |
| 2532 | const next_line_indent = switch (after_comment_token) { | |
| 2533 | .RParen, .RBrace, .RBracket => blk: { | |
| 2534 | if (indent > indent_delta) { | |
| 2535 | break :blk indent - indent_delta; | |
| 2536 | } else { | |
| 2537 | break :blk 0; | |
| 2538 | } | |
| 2539 | }, | |
| 2540 | else => indent, | |
| 2541 | }; | |
| 2542 | try stream.writeByteNTimes(' ', next_line_indent); | |
| 2543 | start_col.* = next_line_indent; | |
| 2435 | try ais.insertNewline(); | |
| 2544 | 2436 | }, |
| 2545 | 2437 | Space.SpaceOrOutdent => { |
| 2546 | try stream.writeByte('\n'); | |
| 2547 | try stream.writeByteNTimes(' ', indent); | |
| 2548 | start_col.* = indent; | |
| 2438 | try ais.insertNewline(); | |
| 2549 | 2439 | }, |
| 2550 | 2440 | Space.NoNewline => {}, |
| 2551 | 2441 | Space.NoComment, Space.Comma, Space.BlockStart => unreachable, |
| ... | ... | @@ -2558,46 +2448,38 @@ fn renderTokenOffset( |
| 2558 | 2448 | |
| 2559 | 2449 | fn renderToken( |
| 2560 | 2450 | tree: *ast.Tree, |
| 2561 | stream: anytype, | |
| 2451 | ais: anytype, | |
| 2562 | 2452 | token_index: ast.TokenIndex, |
| 2563 | indent: usize, | |
| 2564 | start_col: *usize, | |
| 2565 | 2453 | space: Space, |
| 2566 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2567 | return renderTokenOffset(tree, stream, token_index, indent, start_col, space, 0); | |
| 2454 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2455 | return renderTokenOffset(tree, ais, token_index, space, 0); | |
| 2568 | 2456 | } |
| 2569 | 2457 | |
| 2570 | 2458 | fn renderDocComments( |
| 2571 | 2459 | tree: *ast.Tree, |
| 2572 | stream: anytype, | |
| 2460 | ais: anytype, | |
| 2573 | 2461 | node: anytype, |
| 2574 | 2462 | doc_comments: ?*ast.Node.DocComment, |
| 2575 | indent: usize, | |
| 2576 | start_col: *usize, | |
| 2577 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2463 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2578 | 2464 | const comment = doc_comments orelse return; |
| 2579 | return renderDocCommentsToken(tree, stream, comment, node.firstToken(), indent, start_col); | |
| 2465 | return renderDocCommentsToken(tree, ais, comment, node.firstToken()); | |
| 2580 | 2466 | } |
| 2581 | 2467 | |
| 2582 | 2468 | fn renderDocCommentsToken( |
| 2583 | 2469 | tree: *ast.Tree, |
| 2584 | stream: anytype, | |
| 2470 | ais: anytype, | |
| 2585 | 2471 | comment: *ast.Node.DocComment, |
| 2586 | 2472 | first_token: ast.TokenIndex, |
| 2587 | indent: usize, | |
| 2588 | start_col: *usize, | |
| 2589 | ) (@TypeOf(stream).Error || Error)!void { | |
| 2473 | ) (@TypeOf(ais.*).Error || Error)!void { | |
| 2590 | 2474 | var tok_i = comment.first_line; |
| 2591 | 2475 | while (true) : (tok_i += 1) { |
| 2592 | 2476 | switch (tree.token_ids[tok_i]) { |
| 2593 | 2477 | .DocComment, .ContainerDocComment => { |
| 2594 | 2478 | if (comment.first_line < first_token) { |
| 2595 | try renderToken(tree, stream, tok_i, indent, start_col, Space.Newline); | |
| 2596 | try stream.writeByteNTimes(' ', indent); | |
| 2479 | try renderToken(tree, ais, tok_i, Space.Newline); | |
| 2597 | 2480 | } else { |
| 2598 | try renderToken(tree, stream, tok_i, indent, start_col, Space.NoComment); | |
| 2599 | try stream.writeAll("\n"); | |
| 2600 | try stream.writeByteNTimes(' ', indent); | |
| 2481 | try renderToken(tree, ais, tok_i, Space.NoComment); | |
| 2482 | try ais.insertNewline(); | |
| 2601 | 2483 | } |
| 2602 | 2484 | }, |
| 2603 | 2485 | .LineComment => continue, |
| ... | ... | @@ -2669,41 +2551,10 @@ fn nodeCausesSliceOpSpace(base: *ast.Node) bool { |
| 2669 | 2551 | }; |
| 2670 | 2552 | } |
| 2671 | 2553 | |
| 2672 | /// A `std.io.OutStream` that returns whether the given character has been written to it. | |
| 2673 | /// The contents are not written to anything. | |
| 2674 | const FindByteOutStream = struct { | |
| 2675 | byte_found: bool, | |
| 2676 | byte: u8, | |
| 2677 | ||
| 2678 | pub const Error = error{}; | |
| 2679 | pub const OutStream = std.io.OutStream(*FindByteOutStream, Error, write); | |
| 2680 | ||
| 2681 | pub fn init(byte: u8) FindByteOutStream { | |
| 2682 | return FindByteOutStream{ | |
| 2683 | .byte = byte, | |
| 2684 | .byte_found = false, | |
| 2685 | }; | |
| 2686 | } | |
| 2687 | ||
| 2688 | pub fn write(self: *FindByteOutStream, bytes: []const u8) Error!usize { | |
| 2689 | if (self.byte_found) return bytes.len; | |
| 2690 | self.byte_found = blk: { | |
| 2691 | for (bytes) |b| | |
| 2692 | if (b == self.byte) break :blk true; | |
| 2693 | break :blk false; | |
| 2694 | }; | |
| 2695 | return bytes.len; | |
| 2696 | } | |
| 2697 | ||
| 2698 | pub fn outStream(self: *FindByteOutStream) OutStream { | |
| 2699 | return .{ .context = self }; | |
| 2700 | } | |
| 2701 | }; | |
| 2702 | ||
| 2703 | fn copyFixingWhitespace(stream: anytype, slice: []const u8) @TypeOf(stream).Error!void { | |
| 2554 | fn copyFixingWhitespace(ais: anytype, slice: []const u8) @TypeOf(ais.*).Error!void { | |
| 2704 | 2555 | for (slice) |byte| switch (byte) { |
| 2705 | '\t' => try stream.writeAll(" "), | |
| 2556 | '\t' => try ais.writer().writeAll(" "), | |
| 2706 | 2557 | '\r' => {}, |
| 2707 | else => try stream.writeByte(byte), | |
| 2558 | else => try ais.writer().writeByte(byte), | |
| 2708 | 2559 | }; |
| 2709 | 2560 | } |
lib/std/zig/tokenizer.zig+2-1| ... | ... | @@ -1175,6 +1175,7 @@ pub const Tokenizer = struct { |
| 1175 | 1175 | }, |
| 1176 | 1176 | .num_dot_dec => switch (c) { |
| 1177 | 1177 | '.' => { |
| 1178 | result.id = .IntegerLiteral; | |
| 1178 | 1179 | self.index -= 1; |
| 1179 | 1180 | state = .start; |
| 1180 | 1181 | break; |
| ... | ... | @@ -1183,7 +1184,6 @@ pub const Tokenizer = struct { |
| 1183 | 1184 | state = .float_exponent_unsigned; |
| 1184 | 1185 | }, |
| 1185 | 1186 | '0'...'9' => { |
| 1186 | result.id = .FloatLiteral; | |
| 1187 | 1187 | state = .float_fraction_dec; |
| 1188 | 1188 | }, |
| 1189 | 1189 | else => { |
| ... | ... | @@ -1769,6 +1769,7 @@ test "tokenizer - number literals decimal" { |
| 1769 | 1769 | testTokenize("7", &[_]Token.Id{.IntegerLiteral}); |
| 1770 | 1770 | testTokenize("8", &[_]Token.Id{.IntegerLiteral}); |
| 1771 | 1771 | testTokenize("9", &[_]Token.Id{.IntegerLiteral}); |
| 1772 | testTokenize("1..", &[_]Token.Id{ .IntegerLiteral, .Ellipsis2 }); | |
| 1772 | 1773 | testTokenize("0a", &[_]Token.Id{ .Invalid, .Identifier }); |
| 1773 | 1774 | testTokenize("9b", &[_]Token.Id{ .Invalid, .Identifier }); |
| 1774 | 1775 | testTokenize("1z", &[_]Token.Id{ .Invalid, .Identifier }); |
src-self-hosted/Module.zig+242-130| ... | ... | @@ -36,17 +36,17 @@ bin_file_path: []const u8, |
| 36 | 36 | /// It's rare for a decl to be exported, so we save memory by having a sparse map of |
| 37 | 37 | /// Decl pointers to details about them being exported. |
| 38 | 38 | /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. |
| 39 | decl_exports: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, | |
| 39 | decl_exports: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{}, | |
| 40 | 40 | /// We track which export is associated with the given symbol name for quick |
| 41 | 41 | /// detection of symbol collisions. |
| 42 | symbol_exports: std.StringHashMapUnmanaged(*Export) = .{}, | |
| 42 | symbol_exports: std.StringArrayHashMapUnmanaged(*Export) = .{}, | |
| 43 | 43 | /// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl |
| 44 | 44 | /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that |
| 45 | 45 | /// is performing the export of another Decl. |
| 46 | 46 | /// This table owns the Export memory. |
| 47 | export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, | |
| 47 | export_owners: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{}, | |
| 48 | 48 | /// Maps fully qualified namespaced names to the Decl struct for them. |
| 49 | decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{}, | |
| 49 | decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{}, | |
| 50 | 50 | |
| 51 | 51 | link_error_flags: link.File.ErrorFlags = .{}, |
| 52 | 52 | |
| ... | ... | @@ -57,13 +57,13 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), |
| 57 | 57 | /// The ErrorMsg memory is owned by the decl, using Module's allocator. |
| 58 | 58 | /// Note that a Decl can succeed but the Fn it represents can fail. In this case, |
| 59 | 59 | /// a Decl can have a failed_decls entry but have analysis status of success. |
| 60 | failed_decls: std.AutoHashMapUnmanaged(*Decl, *ErrorMsg) = .{}, | |
| 60 | failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{}, | |
| 61 | 61 | /// Using a map here for consistency with the other fields here. |
| 62 | 62 | /// The ErrorMsg memory is owned by the `Scope`, using Module's allocator. |
| 63 | failed_files: std.AutoHashMapUnmanaged(*Scope, *ErrorMsg) = .{}, | |
| 63 | failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{}, | |
| 64 | 64 | /// Using a map here for consistency with the other fields here. |
| 65 | 65 | /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. |
| 66 | failed_exports: std.AutoHashMapUnmanaged(*Export, *ErrorMsg) = .{}, | |
| 66 | failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{}, | |
| 67 | 67 | |
| 68 | 68 | /// Incrementing integer used to compare against the corresponding Decl |
| 69 | 69 | /// field to determine whether a Decl's status applies to an ongoing update, or a |
| ... | ... | @@ -125,7 +125,7 @@ pub const Decl = struct { |
| 125 | 125 | /// mapping them to an address in the output file. |
| 126 | 126 | /// Memory owned by this decl, using Module's allocator. |
| 127 | 127 | name: [*:0]const u8, |
| 128 | /// The direct parent container of the Decl. This is either a `Scope.File` or `Scope.ZIRModule`. | |
| 128 | /// The direct parent container of the Decl. This is either a `Scope.Container` or `Scope.ZIRModule`. | |
| 129 | 129 | /// Reference to externally owned memory. |
| 130 | 130 | scope: *Scope, |
| 131 | 131 | /// The AST Node decl index or ZIR Inst index that contains this declaration. |
| ... | ... | @@ -201,9 +201,9 @@ pub const Decl = struct { |
| 201 | 201 | /// typed_value may need to be regenerated. |
| 202 | 202 | dependencies: DepsTable = .{}, |
| 203 | 203 | |
| 204 | /// The reason this is not `std.AutoHashMapUnmanaged` is a workaround for | |
| 204 | /// The reason this is not `std.AutoArrayHashMapUnmanaged` is a workaround for | |
| 205 | 205 | /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` |
| 206 | pub const DepsTable = std.HashMapUnmanaged(*Decl, void, std.hash_map.getAutoHashFn(*Decl), std.hash_map.getAutoEqlFn(*Decl), false); | |
| 206 | pub const DepsTable = std.ArrayHashMapUnmanaged(*Decl, void, std.array_hash_map.getAutoHashFn(*Decl), std.array_hash_map.getAutoEqlFn(*Decl), false); | |
| 207 | 207 | |
| 208 | 208 | pub fn destroy(self: *Decl, gpa: *Allocator) void { |
| 209 | 209 | gpa.free(mem.spanZ(self.name)); |
| ... | ... | @@ -217,9 +217,10 @@ pub const Decl = struct { |
| 217 | 217 | |
| 218 | 218 | pub fn src(self: Decl) usize { |
| 219 | 219 | switch (self.scope.tag) { |
| 220 | .file => { | |
| 221 | const file = @fieldParentPtr(Scope.File, "base", self.scope); | |
| 222 | const tree = file.contents.tree; | |
| 220 | .container => { | |
| 221 | const container = @fieldParentPtr(Scope.Container, "base", self.scope); | |
| 222 | const tree = container.file_scope.contents.tree; | |
| 223 | // TODO Container should have it's own decls() | |
| 223 | 224 | const decl_node = tree.root_node.decls()[self.src_index]; |
| 224 | 225 | return tree.token_locs[decl_node.firstToken()].start; |
| 225 | 226 | }, |
| ... | ... | @@ -229,7 +230,7 @@ pub const Decl = struct { |
| 229 | 230 | const src_decl = module.decls[self.src_index]; |
| 230 | 231 | return src_decl.inst.src; |
| 231 | 232 | }, |
| 232 | .block => unreachable, | |
| 233 | .file, .block => unreachable, | |
| 233 | 234 | .gen_zir => unreachable, |
| 234 | 235 | .local_val => unreachable, |
| 235 | 236 | .local_ptr => unreachable, |
| ... | ... | @@ -359,6 +360,7 @@ pub const Scope = struct { |
| 359 | 360 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena, |
| 360 | 361 | .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, |
| 361 | 362 | .file => unreachable, |
| 363 | .container => unreachable, | |
| 362 | 364 | } |
| 363 | 365 | } |
| 364 | 366 | |
| ... | ... | @@ -368,15 +370,16 @@ pub const Scope = struct { |
| 368 | 370 | return switch (self.tag) { |
| 369 | 371 | .block => self.cast(Block).?.decl, |
| 370 | 372 | .gen_zir => self.cast(GenZIR).?.decl, |
| 371 | .local_val => return self.cast(LocalVal).?.gen_zir.decl, | |
| 372 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl, | |
| 373 | .local_val => self.cast(LocalVal).?.gen_zir.decl, | |
| 374 | .local_ptr => self.cast(LocalPtr).?.gen_zir.decl, | |
| 373 | 375 | .decl => self.cast(DeclAnalysis).?.decl, |
| 374 | 376 | .zir_module => null, |
| 375 | 377 | .file => null, |
| 378 | .container => null, | |
| 376 | 379 | }; |
| 377 | 380 | } |
| 378 | 381 | |
| 379 | /// Asserts the scope has a parent which is a ZIRModule or File and | |
| 382 | /// Asserts the scope has a parent which is a ZIRModule or Container and | |
| 380 | 383 | /// returns it. |
| 381 | 384 | pub fn namespace(self: *Scope) *Scope { |
| 382 | 385 | switch (self.tag) { |
| ... | ... | @@ -385,7 +388,8 @@ pub const Scope = struct { |
| 385 | 388 | .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope, |
| 386 | 389 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope, |
| 387 | 390 | .decl => return self.cast(DeclAnalysis).?.decl.scope, |
| 388 | .zir_module, .file => return self, | |
| 391 | .file => return &self.cast(File).?.root_container.base, | |
| 392 | .zir_module, .container => return self, | |
| 389 | 393 | } |
| 390 | 394 | } |
| 391 | 395 | |
| ... | ... | @@ -399,8 +403,9 @@ pub const Scope = struct { |
| 399 | 403 | .local_val => unreachable, |
| 400 | 404 | .local_ptr => unreachable, |
| 401 | 405 | .decl => unreachable, |
| 406 | .file => unreachable, | |
| 402 | 407 | .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name), |
| 403 | .file => return self.cast(File).?.fullyQualifiedNameHash(name), | |
| 408 | .container => return self.cast(Container).?.fullyQualifiedNameHash(name), | |
| 404 | 409 | } |
| 405 | 410 | } |
| 406 | 411 | |
| ... | ... | @@ -409,11 +414,12 @@ pub const Scope = struct { |
| 409 | 414 | switch (self.tag) { |
| 410 | 415 | .file => return self.cast(File).?.contents.tree, |
| 411 | 416 | .zir_module => unreachable, |
| 412 | .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree, | |
| 413 | .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree, | |
| 414 | .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(File).?.contents.tree, | |
| 415 | .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(File).?.contents.tree, | |
| 416 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(File).?.contents.tree, | |
| 417 | .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(Container).?.file_scope.contents.tree, | |
| 418 | .block => return self.cast(Block).?.decl.scope.cast(Container).?.file_scope.contents.tree, | |
| 419 | .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(Container).?.file_scope.contents.tree, | |
| 420 | .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree, | |
| 421 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree, | |
| 422 | .container => return self.cast(Container).?.file_scope.contents.tree, | |
| 417 | 423 | } |
| 418 | 424 | } |
| 419 | 425 | |
| ... | ... | @@ -427,13 +433,15 @@ pub const Scope = struct { |
| 427 | 433 | .decl => unreachable, |
| 428 | 434 | .zir_module => unreachable, |
| 429 | 435 | .file => unreachable, |
| 436 | .container => unreachable, | |
| 430 | 437 | }; |
| 431 | 438 | } |
| 432 | 439 | |
| 433 | /// Asserts the scope has a parent which is a ZIRModule or File and | |
| 440 | /// Asserts the scope has a parent which is a ZIRModule, Contaienr or File and | |
| 434 | 441 | /// returns the sub_file_path field. |
| 435 | 442 | pub fn subFilePath(base: *Scope) []const u8 { |
| 436 | 443 | switch (base.tag) { |
| 444 | .container => return @fieldParentPtr(Container, "base", base).file_scope.sub_file_path, | |
| 437 | 445 | .file => return @fieldParentPtr(File, "base", base).sub_file_path, |
| 438 | 446 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, |
| 439 | 447 | .block => unreachable, |
| ... | ... | @@ -453,11 +461,13 @@ pub const Scope = struct { |
| 453 | 461 | .local_val => unreachable, |
| 454 | 462 | .local_ptr => unreachable, |
| 455 | 463 | .decl => unreachable, |
| 464 | .container => unreachable, | |
| 456 | 465 | } |
| 457 | 466 | } |
| 458 | 467 | |
| 459 | 468 | pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 { |
| 460 | 469 | switch (base.tag) { |
| 470 | .container => return @fieldParentPtr(Container, "base", base).file_scope.getSource(module), | |
| 461 | 471 | .file => return @fieldParentPtr(File, "base", base).getSource(module), |
| 462 | 472 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), |
| 463 | 473 | .gen_zir => unreachable, |
| ... | ... | @@ -471,8 +481,9 @@ pub const Scope = struct { |
| 471 | 481 | /// Asserts the scope is a namespace Scope and removes the Decl from the namespace. |
| 472 | 482 | pub fn removeDecl(base: *Scope, child: *Decl) void { |
| 473 | 483 | switch (base.tag) { |
| 474 | .file => return @fieldParentPtr(File, "base", base).removeDecl(child), | |
| 484 | .container => return @fieldParentPtr(Container, "base", base).removeDecl(child), | |
| 475 | 485 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child), |
| 486 | .file => unreachable, | |
| 476 | 487 | .block => unreachable, |
| 477 | 488 | .gen_zir => unreachable, |
| 478 | 489 | .local_val => unreachable, |
| ... | ... | @@ -499,6 +510,7 @@ pub const Scope = struct { |
| 499 | 510 | .local_val => unreachable, |
| 500 | 511 | .local_ptr => unreachable, |
| 501 | 512 | .decl => unreachable, |
| 513 | .container => unreachable, | |
| 502 | 514 | } |
| 503 | 515 | } |
| 504 | 516 | |
| ... | ... | @@ -515,6 +527,8 @@ pub const Scope = struct { |
| 515 | 527 | zir_module, |
| 516 | 528 | /// .zig source code. |
| 517 | 529 | file, |
| 530 | /// struct, enum or union, every .file contains one of these. | |
| 531 | container, | |
| 518 | 532 | block, |
| 519 | 533 | decl, |
| 520 | 534 | gen_zir, |
| ... | ... | @@ -522,6 +536,33 @@ pub const Scope = struct { |
| 522 | 536 | local_ptr, |
| 523 | 537 | }; |
| 524 | 538 | |
| 539 | pub const Container = struct { | |
| 540 | pub const base_tag: Tag = .container; | |
| 541 | base: Scope = Scope{ .tag = base_tag }, | |
| 542 | ||
| 543 | file_scope: *Scope.File, | |
| 544 | ||
| 545 | /// Direct children of the file. | |
| 546 | decls: std.AutoArrayHashMapUnmanaged(*Decl, void), | |
| 547 | ||
| 548 | // TODO implement container types and put this in a status union | |
| 549 | // ty: Type | |
| 550 | ||
| 551 | pub fn deinit(self: *Container, gpa: *Allocator) void { | |
| 552 | self.decls.deinit(gpa); | |
| 553 | self.* = undefined; | |
| 554 | } | |
| 555 | ||
| 556 | pub fn removeDecl(self: *Container, child: *Decl) void { | |
| 557 | _ = self.decls.remove(child); | |
| 558 | } | |
| 559 | ||
| 560 | pub fn fullyQualifiedNameHash(self: *Container, name: []const u8) NameHash { | |
| 561 | // TODO container scope qualified names. | |
| 562 | return std.zig.hashSrc(name); | |
| 563 | } | |
| 564 | }; | |
| 565 | ||
| 525 | 566 | pub const File = struct { |
| 526 | 567 | pub const base_tag: Tag = .file; |
| 527 | 568 | base: Scope = Scope{ .tag = base_tag }, |
| ... | ... | @@ -544,8 +585,7 @@ pub const Scope = struct { |
| 544 | 585 | loaded_success, |
| 545 | 586 | }, |
| 546 | 587 | |
| 547 | /// Direct children of the file. | |
| 548 | decls: ArrayListUnmanaged(*Decl), | |
| 588 | root_container: Container, | |
| 549 | 589 | |
| 550 | 590 | pub fn unload(self: *File, gpa: *Allocator) void { |
| 551 | 591 | switch (self.status) { |
| ... | ... | @@ -569,20 +609,11 @@ pub const Scope = struct { |
| 569 | 609 | } |
| 570 | 610 | |
| 571 | 611 | pub fn deinit(self: *File, gpa: *Allocator) void { |
| 572 | self.decls.deinit(gpa); | |
| 612 | self.root_container.deinit(gpa); | |
| 573 | 613 | self.unload(gpa); |
| 574 | 614 | self.* = undefined; |
| 575 | 615 | } |
| 576 | 616 | |
| 577 | pub fn removeDecl(self: *File, child: *Decl) void { | |
| 578 | for (self.decls.items) |item, i| { | |
| 579 | if (item == child) { | |
| 580 | _ = self.decls.swapRemove(i); | |
| 581 | return; | |
| 582 | } | |
| 583 | } | |
| 584 | } | |
| 585 | ||
| 586 | 617 | pub fn dumpSrc(self: *File, src: usize) void { |
| 587 | 618 | const loc = std.zig.findLineColumn(self.source.bytes, src); |
| 588 | 619 | std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); |
| ... | ... | @@ -604,11 +635,6 @@ pub const Scope = struct { |
| 604 | 635 | .bytes => |bytes| return bytes, |
| 605 | 636 | } |
| 606 | 637 | } |
| 607 | ||
| 608 | pub fn fullyQualifiedNameHash(self: *File, name: []const u8) NameHash { | |
| 609 | // We don't have struct scopes yet so this is currently just a simple name hash. | |
| 610 | return std.zig.hashSrc(name); | |
| 611 | } | |
| 612 | 638 | }; |
| 613 | 639 | |
| 614 | 640 | pub const ZIRModule = struct { |
| ... | ... | @@ -725,6 +751,7 @@ pub const Scope = struct { |
| 725 | 751 | /// Points to the arena allocator of DeclAnalysis |
| 726 | 752 | arena: *Allocator, |
| 727 | 753 | label: ?Label = null, |
| 754 | is_comptime: bool, | |
| 728 | 755 | |
| 729 | 756 | pub const Label = struct { |
| 730 | 757 | zir_block: *zir.Inst.Block, |
| ... | ... | @@ -860,7 +887,10 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 860 | 887 | .source = .{ .unloaded = {} }, |
| 861 | 888 | .contents = .{ .not_available = {} }, |
| 862 | 889 | .status = .never_loaded, |
| 863 | .decls = .{}, | |
| 890 | .root_container = .{ | |
| 891 | .file_scope = root_scope, | |
| 892 | .decls = .{}, | |
| 893 | }, | |
| 864 | 894 | }; |
| 865 | 895 | break :blk &root_scope.base; |
| 866 | 896 | } else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) { |
| ... | ... | @@ -932,7 +962,8 @@ pub fn deinit(self: *Module) void { |
| 932 | 962 | self.symbol_exports.deinit(gpa); |
| 933 | 963 | self.root_scope.destroy(gpa); |
| 934 | 964 | |
| 935 | for (self.global_error_set.items()) |entry| { | |
| 965 | var it = self.global_error_set.iterator(); | |
| 966 | while (it.next()) |entry| { | |
| 936 | 967 | gpa.free(entry.key); |
| 937 | 968 | } |
| 938 | 969 | self.global_error_set.deinit(gpa); |
| ... | ... | @@ -967,7 +998,7 @@ pub fn update(self: *Module) !void { |
| 967 | 998 | // to force a refresh we unload now. |
| 968 | 999 | if (self.root_scope.cast(Scope.File)) |zig_file| { |
| 969 | 1000 | zig_file.unload(self.gpa); |
| 970 | self.analyzeRootSrcFile(zig_file) catch |err| switch (err) { | |
| 1001 | self.analyzeContainer(&zig_file.root_container) catch |err| switch (err) { | |
| 971 | 1002 | error.AnalysisFail => { |
| 972 | 1003 | assert(self.totalErrorCount() != 0); |
| 973 | 1004 | }, |
| ... | ... | @@ -1235,8 +1266,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1235 | 1266 | const tracy = trace(@src()); |
| 1236 | 1267 | defer tracy.end(); |
| 1237 | 1268 | |
| 1238 | const file_scope = decl.scope.cast(Scope.File).?; | |
| 1239 | const tree = try self.getAstTree(file_scope); | |
| 1269 | const container_scope = decl.scope.cast(Scope.Container).?; | |
| 1270 | const tree = try self.getAstTree(container_scope); | |
| 1240 | 1271 | const ast_node = tree.root_node.decls()[decl.src_index]; |
| 1241 | 1272 | switch (ast_node.tag) { |
| 1242 | 1273 | .FnProto => { |
| ... | ... | @@ -1307,7 +1338,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1307 | 1338 | .return_type = return_type_inst, |
| 1308 | 1339 | .param_types = param_types, |
| 1309 | 1340 | }, .{}); |
| 1310 | _ = try astgen.addZIRUnOp(self, &fn_type_scope.base, fn_src, .@"return", fn_type_inst); | |
| 1311 | 1341 | |
| 1312 | 1342 | // We need the memory for the Type to go into the arena for the Decl |
| 1313 | 1343 | var decl_arena = std.heap.ArenaAllocator.init(self.gpa); |
| ... | ... | @@ -1320,10 +1350,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1320 | 1350 | .decl = decl, |
| 1321 | 1351 | .instructions = .{}, |
| 1322 | 1352 | .arena = &decl_arena.allocator, |
| 1353 | .is_comptime = false, | |
| 1323 | 1354 | }; |
| 1324 | 1355 | defer block_scope.instructions.deinit(self.gpa); |
| 1325 | 1356 | |
| 1326 | const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{ | |
| 1357 | const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, fn_type_inst, .{ | |
| 1327 | 1358 | .instructions = fn_type_scope.instructions.items, |
| 1328 | 1359 | }); |
| 1329 | 1360 | const new_func = try decl_arena.allocator.create(Fn); |
| ... | ... | @@ -1457,6 +1488,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1457 | 1488 | .decl = decl, |
| 1458 | 1489 | .instructions = .{}, |
| 1459 | 1490 | .arena = &decl_arena.allocator, |
| 1491 | .is_comptime = true, | |
| 1460 | 1492 | }; |
| 1461 | 1493 | defer block_scope.instructions.deinit(self.gpa); |
| 1462 | 1494 | |
| ... | ... | @@ -1489,35 +1521,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1489 | 1521 | return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{}); |
| 1490 | 1522 | } |
| 1491 | 1523 | |
| 1492 | const explicit_type = blk: { | |
| 1493 | const type_node = var_decl.getTypeNode() orelse | |
| 1494 | break :blk null; | |
| 1495 | ||
| 1496 | // Temporary arena for the zir instructions. | |
| 1497 | var type_scope_arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 1498 | defer type_scope_arena.deinit(); | |
| 1499 | var type_scope: Scope.GenZIR = .{ | |
| 1500 | .decl = decl, | |
| 1501 | .arena = &type_scope_arena.allocator, | |
| 1502 | .parent = decl.scope, | |
| 1503 | }; | |
| 1504 | defer type_scope.instructions.deinit(self.gpa); | |
| 1505 | ||
| 1506 | const src = tree.token_locs[type_node.firstToken()].start; | |
| 1507 | const type_type = try astgen.addZIRInstConst(self, &type_scope.base, src, .{ | |
| 1508 | .ty = Type.initTag(.type), | |
| 1509 | .val = Value.initTag(.type_type), | |
| 1510 | }); | |
| 1511 | const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node); | |
| 1512 | _ = try astgen.addZIRUnOp(self, &type_scope.base, src, .@"return", var_type); | |
| 1513 | ||
| 1514 | break :blk try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{ | |
| 1515 | .instructions = type_scope.instructions.items, | |
| 1516 | }); | |
| 1517 | }; | |
| 1518 | ||
| 1519 | var var_type: Type = undefined; | |
| 1520 | const value: ?Value = if (var_decl.getInitNode()) |init_node| blk: { | |
| 1524 | const var_info: struct { ty: Type, val: ?Value } = if (var_decl.getInitNode()) |init_node| vi: { | |
| 1521 | 1525 | var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa); |
| 1522 | 1526 | defer gen_scope_arena.deinit(); |
| 1523 | 1527 | var gen_scope: Scope.GenZIR = .{ |
| ... | ... | @@ -1526,11 +1530,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1526 | 1530 | .parent = decl.scope, |
| 1527 | 1531 | }; |
| 1528 | 1532 | defer gen_scope.instructions.deinit(self.gpa); |
| 1529 | const src = tree.token_locs[init_node.firstToken()].start; | |
| 1530 | 1533 | |
| 1531 | // TODO comptime scope here | |
| 1532 | const init_inst = try astgen.expr(self, &gen_scope.base, .none, init_node); | |
| 1533 | _ = try astgen.addZIRUnOp(self, &gen_scope.base, src, .@"return", init_inst); | |
| 1534 | const init_result_loc: astgen.ResultLoc = if (var_decl.getTypeNode()) |type_node| rl: { | |
| 1535 | const src = tree.token_locs[type_node.firstToken()].start; | |
| 1536 | const type_type = try astgen.addZIRInstConst(self, &gen_scope.base, src, .{ | |
| 1537 | .ty = Type.initTag(.type), | |
| 1538 | .val = Value.initTag(.type_type), | |
| 1539 | }); | |
| 1540 | const var_type = try astgen.expr(self, &gen_scope.base, .{ .ty = type_type }, type_node); | |
| 1541 | break :rl .{ .ty = var_type }; | |
| 1542 | } else .none; | |
| 1543 | ||
| 1544 | const src = tree.token_locs[init_node.firstToken()].start; | |
| 1545 | const init_inst = try astgen.expr(self, &gen_scope.base, init_result_loc, init_node); | |
| 1534 | 1546 | |
| 1535 | 1547 | var inner_block: Scope.Block = .{ |
| 1536 | 1548 | .parent = null, |
| ... | ... | @@ -1538,42 +1550,58 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1538 | 1550 | .decl = decl, |
| 1539 | 1551 | .instructions = .{}, |
| 1540 | 1552 | .arena = &gen_scope_arena.allocator, |
| 1553 | .is_comptime = true, | |
| 1541 | 1554 | }; |
| 1542 | 1555 | defer inner_block.instructions.deinit(self.gpa); |
| 1543 | 1556 | try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items }); |
| 1544 | 1557 | |
| 1545 | for (inner_block.instructions.items) |inst| { | |
| 1546 | if (inst.castTag(.ret)) |ret| { | |
| 1547 | const coerced = if (explicit_type) |some| | |
| 1548 | try self.coerce(&inner_block.base, some, ret.operand) | |
| 1549 | else | |
| 1550 | ret.operand; | |
| 1551 | const val = coerced.value() orelse | |
| 1552 | return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); | |
| 1553 | ||
| 1554 | var_type = explicit_type orelse try ret.operand.ty.copy(block_scope.arena); | |
| 1555 | break :blk try val.copy(block_scope.arena); | |
| 1556 | } else { | |
| 1557 | return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); | |
| 1558 | } | |
| 1559 | } | |
| 1560 | unreachable; | |
| 1558 | // The result location guarantees the type coercion. | |
| 1559 | const analyzed_init_inst = init_inst.analyzed_inst.?; | |
| 1560 | // The is_comptime in the Scope.Block guarantees the result is comptime-known. | |
| 1561 | const val = analyzed_init_inst.value().?; | |
| 1562 | ||
| 1563 | const ty = try analyzed_init_inst.ty.copy(block_scope.arena); | |
| 1564 | break :vi .{ | |
| 1565 | .ty = ty, | |
| 1566 | .val = try val.copy(block_scope.arena), | |
| 1567 | }; | |
| 1561 | 1568 | } else if (!is_extern) { |
| 1562 | 1569 | return self.failTok(&block_scope.base, var_decl.firstToken(), "variables must be initialized", .{}); |
| 1563 | } else if (explicit_type) |some| blk: { | |
| 1564 | var_type = some; | |
| 1565 | break :blk null; | |
| 1570 | } else if (var_decl.getTypeNode()) |type_node| vi: { | |
| 1571 | // Temporary arena for the zir instructions. | |
| 1572 | var type_scope_arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 1573 | defer type_scope_arena.deinit(); | |
| 1574 | var type_scope: Scope.GenZIR = .{ | |
| 1575 | .decl = decl, | |
| 1576 | .arena = &type_scope_arena.allocator, | |
| 1577 | .parent = decl.scope, | |
| 1578 | }; | |
| 1579 | defer type_scope.instructions.deinit(self.gpa); | |
| 1580 | ||
| 1581 | const src = tree.token_locs[type_node.firstToken()].start; | |
| 1582 | const type_type = try astgen.addZIRInstConst(self, &type_scope.base, src, .{ | |
| 1583 | .ty = Type.initTag(.type), | |
| 1584 | .val = Value.initTag(.type_type), | |
| 1585 | }); | |
| 1586 | const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node); | |
| 1587 | const ty = try zir_sema.analyzeBodyValueAsType(self, &block_scope, var_type, .{ | |
| 1588 | .instructions = type_scope.instructions.items, | |
| 1589 | }); | |
| 1590 | break :vi .{ | |
| 1591 | .ty = ty, | |
| 1592 | .val = null, | |
| 1593 | }; | |
| 1566 | 1594 | } else { |
| 1567 | 1595 | return self.failTok(&block_scope.base, var_decl.firstToken(), "unable to infer variable type", .{}); |
| 1568 | 1596 | }; |
| 1569 | 1597 | |
| 1570 | if (is_mutable and !var_type.isValidVarType(is_extern)) { | |
| 1571 | return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_type}); | |
| 1598 | if (is_mutable and !var_info.ty.isValidVarType(is_extern)) { | |
| 1599 | return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_info.ty}); | |
| 1572 | 1600 | } |
| 1573 | 1601 | |
| 1574 | 1602 | var type_changed = true; |
| 1575 | 1603 | if (decl.typedValueManaged()) |tvm| { |
| 1576 | type_changed = !tvm.typed_value.ty.eql(var_type); | |
| 1604 | type_changed = !tvm.typed_value.ty.eql(var_info.ty); | |
| 1577 | 1605 | |
| 1578 | 1606 | tvm.deinit(self.gpa); |
| 1579 | 1607 | } |
| ... | ... | @@ -1582,7 +1610,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1582 | 1610 | const var_payload = try decl_arena.allocator.create(Value.Payload.Variable); |
| 1583 | 1611 | new_variable.* = .{ |
| 1584 | 1612 | .owner_decl = decl, |
| 1585 | .init = value orelse undefined, | |
| 1613 | .init = var_info.val orelse undefined, | |
| 1586 | 1614 | .is_extern = is_extern, |
| 1587 | 1615 | .is_mutable = is_mutable, |
| 1588 | 1616 | .is_threadlocal = is_threadlocal, |
| ... | ... | @@ -1593,7 +1621,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1593 | 1621 | decl.typed_value = .{ |
| 1594 | 1622 | .most_recent = .{ |
| 1595 | 1623 | .typed_value = .{ |
| 1596 | .ty = var_type, | |
| 1624 | .ty = var_info.ty, | |
| 1597 | 1625 | .val = Value.initPayload(&var_payload.base), |
| 1598 | 1626 | }, |
| 1599 | 1627 | .arena = decl_arena_state, |
| ... | ... | @@ -1628,8 +1656,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1628 | 1656 | }; |
| 1629 | 1657 | defer gen_scope.instructions.deinit(self.gpa); |
| 1630 | 1658 | |
| 1631 | // TODO comptime scope here | |
| 1632 | _ = try astgen.expr(self, &gen_scope.base, .none, comptime_decl.expr); | |
| 1659 | _ = try astgen.comptimeExpr(self, &gen_scope.base, .none, comptime_decl.expr); | |
| 1633 | 1660 | |
| 1634 | 1661 | var block_scope: Scope.Block = .{ |
| 1635 | 1662 | .parent = null, |
| ... | ... | @@ -1637,6 +1664,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1637 | 1664 | .decl = decl, |
| 1638 | 1665 | .instructions = .{}, |
| 1639 | 1666 | .arena = &analysis_arena.allocator, |
| 1667 | .is_comptime = true, | |
| 1640 | 1668 | }; |
| 1641 | 1669 | defer block_scope.instructions.deinit(self.gpa); |
| 1642 | 1670 | |
| ... | ... | @@ -1699,10 +1727,12 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { |
| 1699 | 1727 | } |
| 1700 | 1728 | } |
| 1701 | 1729 | |
| 1702 | fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { | |
| 1730 | fn getAstTree(self: *Module, container_scope: *Scope.Container) !*ast.Tree { | |
| 1703 | 1731 | const tracy = trace(@src()); |
| 1704 | 1732 | defer tracy.end(); |
| 1705 | 1733 | |
| 1734 | const root_scope = container_scope.file_scope; | |
| 1735 | ||
| 1706 | 1736 | switch (root_scope.status) { |
| 1707 | 1737 | .never_loaded, .unloaded_success => { |
| 1708 | 1738 | try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); |
| ... | ... | @@ -1744,25 +1774,25 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { |
| 1744 | 1774 | } |
| 1745 | 1775 | } |
| 1746 | 1776 | |
| 1747 | fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { | |
| 1777 | fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void { | |
| 1748 | 1778 | const tracy = trace(@src()); |
| 1749 | 1779 | defer tracy.end(); |
| 1750 | 1780 | |
| 1751 | 1781 | // We may be analyzing it for the first time, or this may be |
| 1752 | 1782 | // an incremental update. This code handles both cases. |
| 1753 | const tree = try self.getAstTree(root_scope); | |
| 1783 | const tree = try self.getAstTree(container_scope); | |
| 1754 | 1784 | const decls = tree.root_node.decls(); |
| 1755 | 1785 | |
| 1756 | 1786 | try self.work_queue.ensureUnusedCapacity(decls.len); |
| 1757 | try root_scope.decls.ensureCapacity(self.gpa, decls.len); | |
| 1787 | try container_scope.decls.ensureCapacity(self.gpa, decls.len); | |
| 1758 | 1788 | |
| 1759 | 1789 | // Keep track of the decls that we expect to see in this file so that |
| 1760 | 1790 | // we know which ones have been deleted. |
| 1761 | var deleted_decls = std.AutoHashMap(*Decl, void).init(self.gpa); | |
| 1791 | var deleted_decls = std.AutoArrayHashMap(*Decl, void).init(self.gpa); | |
| 1762 | 1792 | defer deleted_decls.deinit(); |
| 1763 | try deleted_decls.ensureCapacity(root_scope.decls.items.len); | |
| 1764 | for (root_scope.decls.items) |file_decl| { | |
| 1765 | deleted_decls.putAssumeCapacityNoClobber(file_decl, {}); | |
| 1793 | try deleted_decls.ensureCapacity(container_scope.decls.items().len); | |
| 1794 | for (container_scope.decls.items()) |entry| { | |
| 1795 | deleted_decls.putAssumeCapacityNoClobber(entry.key, {}); | |
| 1766 | 1796 | } |
| 1767 | 1797 | |
| 1768 | 1798 | for (decls) |src_decl, decl_i| { |
| ... | ... | @@ -1774,7 +1804,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1774 | 1804 | |
| 1775 | 1805 | const name_loc = tree.token_locs[name_tok]; |
| 1776 | 1806 | const name = tree.tokenSliceLoc(name_loc); |
| 1777 | const name_hash = root_scope.fullyQualifiedNameHash(name); | |
| 1807 | const name_hash = container_scope.fullyQualifiedNameHash(name); | |
| 1778 | 1808 | const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); |
| 1779 | 1809 | if (self.decl_table.get(name_hash)) |decl| { |
| 1780 | 1810 | // Update the AST Node index of the decl, even if its contents are unchanged, it may |
| ... | ... | @@ -1802,8 +1832,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1802 | 1832 | } |
| 1803 | 1833 | } |
| 1804 | 1834 | } else { |
| 1805 | const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); | |
| 1806 | root_scope.decls.appendAssumeCapacity(new_decl); | |
| 1835 | const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); | |
| 1836 | container_scope.decls.putAssumeCapacity(new_decl, {}); | |
| 1807 | 1837 | if (fn_proto.getExternExportInlineToken()) |maybe_export_token| { |
| 1808 | 1838 | if (tree.token_ids[maybe_export_token] == .Keyword_export) { |
| 1809 | 1839 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); |
| ... | ... | @@ -1813,7 +1843,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1813 | 1843 | } else if (src_decl.castTag(.VarDecl)) |var_decl| { |
| 1814 | 1844 | const name_loc = tree.token_locs[var_decl.name_token]; |
| 1815 | 1845 | const name = tree.tokenSliceLoc(name_loc); |
| 1816 | const name_hash = root_scope.fullyQualifiedNameHash(name); | |
| 1846 | const name_hash = container_scope.fullyQualifiedNameHash(name); | |
| 1817 | 1847 | const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); |
| 1818 | 1848 | if (self.decl_table.get(name_hash)) |decl| { |
| 1819 | 1849 | // Update the AST Node index of the decl, even if its contents are unchanged, it may |
| ... | ... | @@ -1829,8 +1859,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1829 | 1859 | decl.contents_hash = contents_hash; |
| 1830 | 1860 | } |
| 1831 | 1861 | } else { |
| 1832 | const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); | |
| 1833 | root_scope.decls.appendAssumeCapacity(new_decl); | |
| 1862 | const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); | |
| 1863 | container_scope.decls.putAssumeCapacity(new_decl, {}); | |
| 1834 | 1864 | if (var_decl.getExternExportToken()) |maybe_export_token| { |
| 1835 | 1865 | if (tree.token_ids[maybe_export_token] == .Keyword_export) { |
| 1836 | 1866 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); |
| ... | ... | @@ -1842,11 +1872,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1842 | 1872 | const name = try std.fmt.allocPrint(self.gpa, "__comptime_{}", .{name_index}); |
| 1843 | 1873 | defer self.gpa.free(name); |
| 1844 | 1874 | |
| 1845 | const name_hash = root_scope.fullyQualifiedNameHash(name); | |
| 1875 | const name_hash = container_scope.fullyQualifiedNameHash(name); | |
| 1846 | 1876 | const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); |
| 1847 | 1877 | |
| 1848 | const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); | |
| 1849 | root_scope.decls.appendAssumeCapacity(new_decl); | |
| 1878 | const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); | |
| 1879 | container_scope.decls.putAssumeCapacity(new_decl, {}); | |
| 1850 | 1880 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); |
| 1851 | 1881 | } else if (src_decl.castTag(.ContainerField)) |container_field| { |
| 1852 | 1882 | log.err("TODO: analyze container field", .{}); |
| ... | ... | @@ -1879,7 +1909,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 1879 | 1909 | |
| 1880 | 1910 | // Keep track of the decls that we expect to see in this file so that |
| 1881 | 1911 | // we know which ones have been deleted. |
| 1882 | var deleted_decls = std.AutoHashMap(*Decl, void).init(self.gpa); | |
| 1912 | var deleted_decls = std.AutoArrayHashMap(*Decl, void).init(self.gpa); | |
| 1883 | 1913 | defer deleted_decls.deinit(); |
| 1884 | 1914 | try deleted_decls.ensureCapacity(self.decl_table.items().len); |
| 1885 | 1915 | for (self.decl_table.items()) |entry| { |
| ... | ... | @@ -2007,6 +2037,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 2007 | 2037 | .decl = decl, |
| 2008 | 2038 | .instructions = .{}, |
| 2009 | 2039 | .arena = &arena.allocator, |
| 2040 | .is_comptime = false, | |
| 2010 | 2041 | }; |
| 2011 | 2042 | defer inner_block.instructions.deinit(self.gpa); |
| 2012 | 2043 | |
| ... | ... | @@ -2088,16 +2119,23 @@ pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanage |
| 2088 | 2119 | errdefer self.global_error_set.removeAssertDiscard(name); |
| 2089 | 2120 | |
| 2090 | 2121 | gop.entry.key = try self.gpa.dupe(u8, name); |
| 2091 | gop.entry.value = @intCast(u16, self.global_error_set.items().len - 1); | |
| 2122 | gop.entry.value = @intCast(u16, self.global_error_set.count() - 1); | |
| 2092 | 2123 | return gop.entry.*; |
| 2093 | 2124 | } |
| 2094 | 2125 | |
| 2095 | /// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites. | |
| 2096 | pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { | |
| 2126 | pub fn requireFunctionBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { | |
| 2097 | 2127 | return scope.cast(Scope.Block) orelse |
| 2098 | 2128 | return self.fail(scope, src, "instruction illegal outside function body", .{}); |
| 2099 | 2129 | } |
| 2100 | 2130 | |
| 2131 | pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { | |
| 2132 | const block = try self.requireFunctionBlock(scope, src); | |
| 2133 | if (block.is_comptime) { | |
| 2134 | return self.fail(scope, src, "unable to resolve comptime value", .{}); | |
| 2135 | } | |
| 2136 | return block; | |
| 2137 | } | |
| 2138 | ||
| 2101 | 2139 | pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value { |
| 2102 | 2140 | return (try self.resolveDefinedValue(scope, base)) orelse |
| 2103 | 2141 | return self.fail(scope, base.src, "unable to resolve comptime value", .{}); |
| ... | ... | @@ -2584,6 +2622,72 @@ pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) In |
| 2584 | 2622 | return self.fail(scope, src, "TODO implement analysis of iserr", .{}); |
| 2585 | 2623 | } |
| 2586 | 2624 | |
| 2625 | pub fn analyzeSlice(self: *Module, scope: *Scope, src: usize, array_ptr: *Inst, start: *Inst, end_opt: ?*Inst, sentinel_opt: ?*Inst) InnerError!*Inst { | |
| 2626 | const ptr_child = switch (array_ptr.ty.zigTypeTag()) { | |
| 2627 | .Pointer => array_ptr.ty.elemType(), | |
| 2628 | else => return self.fail(scope, src, "expected pointer, found '{}'", .{array_ptr.ty}), | |
| 2629 | }; | |
| 2630 | ||
| 2631 | var array_type = ptr_child; | |
| 2632 | const elem_type = switch (ptr_child.zigTypeTag()) { | |
| 2633 | .Array => ptr_child.elemType(), | |
| 2634 | .Pointer => blk: { | |
| 2635 | if (ptr_child.isSinglePointer()) { | |
| 2636 | if (ptr_child.elemType().zigTypeTag() == .Array) { | |
| 2637 | array_type = ptr_child.elemType(); | |
| 2638 | break :blk ptr_child.elemType().elemType(); | |
| 2639 | } | |
| 2640 | ||
| 2641 | return self.fail(scope, src, "slice of single-item pointer", .{}); | |
| 2642 | } | |
| 2643 | break :blk ptr_child.elemType(); | |
| 2644 | }, | |
| 2645 | else => return self.fail(scope, src, "slice of non-array type '{}'", .{ptr_child}), | |
| 2646 | }; | |
| 2647 | ||
| 2648 | const slice_sentinel = if (sentinel_opt) |sentinel| blk: { | |
| 2649 | const casted = try self.coerce(scope, elem_type, sentinel); | |
| 2650 | break :blk try self.resolveConstValue(scope, casted); | |
| 2651 | } else null; | |
| 2652 | ||
| 2653 | var return_ptr_size: std.builtin.TypeInfo.Pointer.Size = .Slice; | |
| 2654 | var return_elem_type = elem_type; | |
| 2655 | if (end_opt) |end| { | |
| 2656 | if (end.value()) |end_val| { | |
| 2657 | if (start.value()) |start_val| { | |
| 2658 | const start_u64 = start_val.toUnsignedInt(); | |
| 2659 | const end_u64 = end_val.toUnsignedInt(); | |
| 2660 | if (start_u64 > end_u64) { | |
| 2661 | return self.fail(scope, src, "out of bounds slice", .{}); | |
| 2662 | } | |
| 2663 | ||
| 2664 | const len = end_u64 - start_u64; | |
| 2665 | const array_sentinel = if (array_type.zigTypeTag() == .Array and end_u64 == array_type.arrayLen()) | |
| 2666 | array_type.sentinel() | |
| 2667 | else | |
| 2668 | slice_sentinel; | |
| 2669 | return_elem_type = try self.arrayType(scope, len, array_sentinel, elem_type); | |
| 2670 | return_ptr_size = .One; | |
| 2671 | } | |
| 2672 | } | |
| 2673 | } | |
| 2674 | const return_type = try self.ptrType( | |
| 2675 | scope, | |
| 2676 | src, | |
| 2677 | return_elem_type, | |
| 2678 | if (end_opt == null) slice_sentinel else null, | |
| 2679 | 0, // TODO alignment | |
| 2680 | 0, | |
| 2681 | 0, | |
| 2682 | !ptr_child.isConstPtr(), | |
| 2683 | ptr_child.isAllowzeroPtr(), | |
| 2684 | ptr_child.isVolatilePtr(), | |
| 2685 | return_ptr_size, | |
| 2686 | ); | |
| 2687 | ||
| 2688 | return self.fail(scope, src, "TODO implement analysis of slice", .{}); | |
| 2689 | } | |
| 2690 | ||
| 2587 | 2691 | /// Asserts that lhs and rhs types are both numeric. |
| 2588 | 2692 | pub fn cmpNumeric( |
| 2589 | 2693 | self: *Module, |
| ... | ... | @@ -2794,6 +2898,12 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty |
| 2794 | 2898 | prev_inst = next_inst; |
| 2795 | 2899 | continue; |
| 2796 | 2900 | } |
| 2901 | if (next_inst.ty.zigTypeTag() == .Undefined) | |
| 2902 | continue; | |
| 2903 | if (prev_inst.ty.zigTypeTag() == .Undefined) { | |
| 2904 | prev_inst = next_inst; | |
| 2905 | continue; | |
| 2906 | } | |
| 2797 | 2907 | if (prev_inst.ty.isInt() and |
| 2798 | 2908 | next_inst.ty.isInt() and |
| 2799 | 2909 | prev_inst.ty.isSignedInt() == next_inst.ty.isSignedInt()) |
| ... | ... | @@ -3045,6 +3155,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err |
| 3045 | 3155 | self.failed_files.putAssumeCapacityNoClobber(scope, err_msg); |
| 3046 | 3156 | }, |
| 3047 | 3157 | .file => unreachable, |
| 3158 | .container => unreachable, | |
| 3048 | 3159 | } |
| 3049 | 3160 | return error.AnalysisFail; |
| 3050 | 3161 | } |
| ... | ... | @@ -3432,6 +3543,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic |
| 3432 | 3543 | .decl = parent_block.decl, |
| 3433 | 3544 | .instructions = .{}, |
| 3434 | 3545 | .arena = parent_block.arena, |
| 3546 | .is_comptime = parent_block.is_comptime, | |
| 3435 | 3547 | }; |
| 3436 | 3548 | defer fail_block.instructions.deinit(mod.gpa); |
| 3437 | 3549 |
src-self-hosted/astgen.zig+147-42| ... | ... | @@ -258,7 +258,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 258 | 258 | .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), |
| 259 | 259 | .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?), |
| 260 | 260 | .Block => return rlWrapVoid(mod, scope, rl, node, try blockExpr(mod, scope, node.castTag(.Block).?)), |
| 261 | .LabeledBlock => return labeledBlockExpr(mod, scope, rl, node.castTag(.LabeledBlock).?), | |
| 261 | .LabeledBlock => return labeledBlockExpr(mod, scope, rl, node.castTag(.LabeledBlock).?, .block), | |
| 262 | 262 | .Break => return rlWrap(mod, scope, rl, try breakExpr(mod, scope, node.castTag(.Break).?)), |
| 263 | 263 | .PtrType => return rlWrap(mod, scope, rl, try ptrType(mod, scope, node.castTag(.PtrType).?)), |
| 264 | 264 | .GroupedExpression => return expr(mod, scope, rl, node.castTag(.GroupedExpression).?.expr), |
| ... | ... | @@ -275,15 +275,16 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 275 | 275 | .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)), |
| 276 | 276 | .For => return forExpr(mod, scope, rl, node.castTag(.For).?), |
| 277 | 277 | .ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?), |
| 278 | .Slice => return rlWrap(mod, scope, rl, try sliceExpr(mod, scope, node.castTag(.Slice).?)), | |
| 278 | 279 | .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), |
| 280 | .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?), | |
| 281 | .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?), | |
| 279 | 282 | |
| 280 | 283 | .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), |
| 281 | 284 | .Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}), |
| 282 | .OrElse => return mod.failNode(scope, node, "TODO implement astgen.expr for .OrElse", .{}), | |
| 283 | 285 | .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}), |
| 284 | 286 | .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}), |
| 285 | 287 | .Try => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}), |
| 286 | .Slice => return mod.failNode(scope, node, "TODO implement astgen.expr for .Slice", .{}), | |
| 287 | 288 | .ArrayInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializer", .{}), |
| 288 | 289 | .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}), |
| 289 | 290 | .StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}), |
| ... | ... | @@ -294,11 +295,46 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 294 | 295 | .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}), |
| 295 | 296 | .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}), |
| 296 | 297 | .ContainerDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ContainerDecl", .{}), |
| 297 | .Comptime => return mod.failNode(scope, node, "TODO implement astgen.expr for .Comptime", .{}), | |
| 298 | 298 | .Nosuspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Nosuspend", .{}), |
| 299 | 299 | } |
| 300 | 300 | } |
| 301 | 301 | |
| 302 | fn comptimeKeyword(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Comptime) InnerError!*zir.Inst { | |
| 303 | const tracy = trace(@src()); | |
| 304 | defer tracy.end(); | |
| 305 | ||
| 306 | return comptimeExpr(mod, scope, rl, node.expr); | |
| 307 | } | |
| 308 | ||
| 309 | pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst { | |
| 310 | const tree = parent_scope.tree(); | |
| 311 | const src = tree.token_locs[node.firstToken()].start; | |
| 312 | ||
| 313 | // Optimization for labeled blocks: don't need to have 2 layers of blocks, we can reuse the existing one. | |
| 314 | if (node.castTag(.LabeledBlock)) |block_node| { | |
| 315 | return labeledBlockExpr(mod, parent_scope, rl, block_node, .block_comptime); | |
| 316 | } | |
| 317 | ||
| 318 | // Make a scope to collect generated instructions in the sub-expression. | |
| 319 | var block_scope: Scope.GenZIR = .{ | |
| 320 | .parent = parent_scope, | |
| 321 | .decl = parent_scope.decl().?, | |
| 322 | .arena = parent_scope.arena(), | |
| 323 | .instructions = .{}, | |
| 324 | }; | |
| 325 | defer block_scope.instructions.deinit(mod.gpa); | |
| 326 | ||
| 327 | // No need to capture the result here because block_comptime_flat implies that the final | |
| 328 | // instruction is the block's result value. | |
| 329 | _ = try expr(mod, &block_scope.base, rl, node); | |
| 330 | ||
| 331 | const block = try addZIRInstBlock(mod, parent_scope, src, .block_comptime_flat, .{ | |
| 332 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), | |
| 333 | }); | |
| 334 | ||
| 335 | return &block.base; | |
| 336 | } | |
| 337 | ||
| 302 | 338 | fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { |
| 303 | 339 | const tree = parent_scope.tree(); |
| 304 | 340 | const src = tree.token_locs[node.ltoken].start; |
| ... | ... | @@ -360,10 +396,13 @@ fn labeledBlockExpr( |
| 360 | 396 | parent_scope: *Scope, |
| 361 | 397 | rl: ResultLoc, |
| 362 | 398 | block_node: *ast.Node.LabeledBlock, |
| 399 | zir_tag: zir.Inst.Tag, | |
| 363 | 400 | ) InnerError!*zir.Inst { |
| 364 | 401 | const tracy = trace(@src()); |
| 365 | 402 | defer tracy.end(); |
| 366 | 403 | |
| 404 | assert(zir_tag == .block or zir_tag == .block_comptime); | |
| 405 | ||
| 367 | 406 | const tree = parent_scope.tree(); |
| 368 | 407 | const src = tree.token_locs[block_node.lbrace].start; |
| 369 | 408 | |
| ... | ... | @@ -373,7 +412,7 @@ fn labeledBlockExpr( |
| 373 | 412 | const block_inst = try gen_zir.arena.create(zir.Inst.Block); |
| 374 | 413 | block_inst.* = .{ |
| 375 | 414 | .base = .{ |
| 376 | .tag = .block, | |
| 415 | .tag = zir_tag, | |
| 377 | 416 | .src = src, |
| 378 | 417 | }, |
| 379 | 418 | .positionals = .{ |
| ... | ... | @@ -751,13 +790,31 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!* |
| 751 | 790 | } |
| 752 | 791 | |
| 753 | 792 | fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) InnerError!*zir.Inst { |
| 793 | return orelseCatchExpr(mod, scope, rl, node.lhs, node.op_token, .iserr, .unwrap_err_unsafe, node.rhs, node.payload); | |
| 794 | } | |
| 795 | ||
| 796 | fn orelseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { | |
| 797 | return orelseCatchExpr(mod, scope, rl, node.lhs, node.op_token, .isnull, .unwrap_optional_unsafe, node.rhs, null); | |
| 798 | } | |
| 799 | ||
| 800 | fn orelseCatchExpr( | |
| 801 | mod: *Module, | |
| 802 | scope: *Scope, | |
| 803 | rl: ResultLoc, | |
| 804 | lhs: *ast.Node, | |
| 805 | op_token: ast.TokenIndex, | |
| 806 | cond_op: zir.Inst.Tag, | |
| 807 | unwrap_op: zir.Inst.Tag, | |
| 808 | rhs: *ast.Node, | |
| 809 | payload_node: ?*ast.Node, | |
| 810 | ) InnerError!*zir.Inst { | |
| 754 | 811 | const tree = scope.tree(); |
| 755 | const src = tree.token_locs[node.op_token].start; | |
| 812 | const src = tree.token_locs[op_token].start; | |
| 756 | 813 | |
| 757 | const err_union_ptr = try expr(mod, scope, .ref, node.lhs); | |
| 758 | // TODO we could avoid an unnecessary copy if .iserr took a pointer | |
| 759 | const err_union = try addZIRUnOp(mod, scope, src, .deref, err_union_ptr); | |
| 760 | const cond = try addZIRUnOp(mod, scope, src, .iserr, err_union); | |
| 814 | const operand_ptr = try expr(mod, scope, .ref, lhs); | |
| 815 | // TODO we could avoid an unnecessary copy if .iserr, .isnull took a pointer | |
| 816 | const err_union = try addZIRUnOp(mod, scope, src, .deref, operand_ptr); | |
| 817 | const cond = try addZIRUnOp(mod, scope, src, cond_op, err_union); | |
| 761 | 818 | |
| 762 | 819 | var block_scope: Scope.GenZIR = .{ |
| 763 | 820 | .parent = scope, |
| ... | ... | @@ -773,7 +830,7 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) |
| 773 | 830 | .else_body = undefined, // populated below |
| 774 | 831 | }, .{}); |
| 775 | 832 | |
| 776 | const block = try addZIRInstBlock(mod, scope, src, .{ | |
| 833 | const block = try addZIRInstBlock(mod, scope, src, .block, .{ | |
| 777 | 834 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), |
| 778 | 835 | }); |
| 779 | 836 | |
| ... | ... | @@ -786,55 +843,55 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) |
| 786 | 843 | .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, |
| 787 | 844 | }; |
| 788 | 845 | |
| 789 | var err_scope: Scope.GenZIR = .{ | |
| 846 | var then_scope: Scope.GenZIR = .{ | |
| 790 | 847 | .parent = scope, |
| 791 | 848 | .decl = block_scope.decl, |
| 792 | 849 | .arena = block_scope.arena, |
| 793 | 850 | .instructions = .{}, |
| 794 | 851 | }; |
| 795 | defer err_scope.instructions.deinit(mod.gpa); | |
| 852 | defer then_scope.instructions.deinit(mod.gpa); | |
| 796 | 853 | |
| 797 | 854 | var err_val_scope: Scope.LocalVal = undefined; |
| 798 | const err_sub_scope = blk: { | |
| 799 | const payload = node.payload orelse | |
| 800 | break :blk &err_scope.base; | |
| 855 | const then_sub_scope = blk: { | |
| 856 | const payload = payload_node orelse | |
| 857 | break :blk &then_scope.base; | |
| 801 | 858 | |
| 802 | 859 | const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken()); |
| 803 | 860 | if (mem.eql(u8, err_name, "_")) |
| 804 | break :blk &err_scope.base; | |
| 861 | break :blk &then_scope.base; | |
| 805 | 862 | |
| 806 | const unwrapped_err_ptr = try addZIRUnOp(mod, &err_scope.base, src, .unwrap_err_code, err_union_ptr); | |
| 863 | const unwrapped_err_ptr = try addZIRUnOp(mod, &then_scope.base, src, .unwrap_err_code, operand_ptr); | |
| 807 | 864 | err_val_scope = .{ |
| 808 | .parent = &err_scope.base, | |
| 809 | .gen_zir = &err_scope, | |
| 865 | .parent = &then_scope.base, | |
| 866 | .gen_zir = &then_scope, | |
| 810 | 867 | .name = err_name, |
| 811 | .inst = try addZIRUnOp(mod, &err_scope.base, src, .deref, unwrapped_err_ptr), | |
| 868 | .inst = try addZIRUnOp(mod, &then_scope.base, src, .deref, unwrapped_err_ptr), | |
| 812 | 869 | }; |
| 813 | 870 | break :blk &err_val_scope.base; |
| 814 | 871 | }; |
| 815 | 872 | |
| 816 | _ = try addZIRInst(mod, &err_scope.base, src, zir.Inst.Break, .{ | |
| 873 | _ = try addZIRInst(mod, &then_scope.base, src, zir.Inst.Break, .{ | |
| 817 | 874 | .block = block, |
| 818 | .operand = try expr(mod, err_sub_scope, branch_rl, node.rhs), | |
| 875 | .operand = try expr(mod, then_sub_scope, branch_rl, rhs), | |
| 819 | 876 | }, .{}); |
| 820 | 877 | |
| 821 | var not_err_scope: Scope.GenZIR = .{ | |
| 878 | var else_scope: Scope.GenZIR = .{ | |
| 822 | 879 | .parent = scope, |
| 823 | 880 | .decl = block_scope.decl, |
| 824 | 881 | .arena = block_scope.arena, |
| 825 | 882 | .instructions = .{}, |
| 826 | 883 | }; |
| 827 | defer not_err_scope.instructions.deinit(mod.gpa); | |
| 884 | defer else_scope.instructions.deinit(mod.gpa); | |
| 828 | 885 | |
| 829 | const unwrapped_payload = try addZIRUnOp(mod, &not_err_scope.base, src, .unwrap_err_unsafe, err_union_ptr); | |
| 830 | _ = try addZIRInst(mod, &not_err_scope.base, src, zir.Inst.Break, .{ | |
| 886 | const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand_ptr); | |
| 887 | _ = try addZIRInst(mod, &else_scope.base, src, zir.Inst.Break, .{ | |
| 831 | 888 | .block = block, |
| 832 | 889 | .operand = unwrapped_payload, |
| 833 | 890 | }, .{}); |
| 834 | 891 | |
| 835 | condbr.positionals.then_body = .{ .instructions = try err_scope.arena.dupe(*zir.Inst, err_scope.instructions.items) }; | |
| 836 | condbr.positionals.else_body = .{ .instructions = try not_err_scope.arena.dupe(*zir.Inst, not_err_scope.instructions.items) }; | |
| 837 | return rlWrap(mod, scope, rl, &block.base); | |
| 892 | condbr.positionals.then_body = .{ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items) }; | |
| 893 | condbr.positionals.else_body = .{ .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items) }; | |
| 894 | return rlWrapPtr(mod, scope, rl, &block.base); | |
| 838 | 895 | } |
| 839 | 896 | |
| 840 | 897 | /// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating. |
| ... | ... | @@ -894,6 +951,36 @@ fn arrayAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Array |
| 894 | 951 | return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{})); |
| 895 | 952 | } |
| 896 | 953 | |
| 954 | fn sliceExpr(mod: *Module, scope: *Scope, node: *ast.Node.Slice) InnerError!*zir.Inst { | |
| 955 | const tree = scope.tree(); | |
| 956 | const src = tree.token_locs[node.rtoken].start; | |
| 957 | ||
| 958 | const usize_type = try addZIRInstConst(mod, scope, src, .{ | |
| 959 | .ty = Type.initTag(.type), | |
| 960 | .val = Value.initTag(.usize_type), | |
| 961 | }); | |
| 962 | ||
| 963 | const array_ptr = try expr(mod, scope, .ref, node.lhs); | |
| 964 | const start = try expr(mod, scope, .{ .ty = usize_type }, node.start); | |
| 965 | ||
| 966 | if (node.end == null and node.sentinel == null) { | |
| 967 | return try addZIRBinOp(mod, scope, src, .slice_start, array_ptr, start); | |
| 968 | } | |
| 969 | ||
| 970 | const end = if (node.end) |end| try expr(mod, scope, .{ .ty = usize_type }, end) else null; | |
| 971 | // we could get the child type here, but it is easier to just do it in semantic analysis. | |
| 972 | const sentinel = if (node.sentinel) |sentinel| try expr(mod, scope, .none, sentinel) else null; | |
| 973 | ||
| 974 | return try addZIRInst( | |
| 975 | mod, | |
| 976 | scope, | |
| 977 | src, | |
| 978 | zir.Inst.Slice, | |
| 979 | .{ .array_ptr = array_ptr, .start = start }, | |
| 980 | .{ .end = end, .sentinel = sentinel }, | |
| 981 | ); | |
| 982 | } | |
| 983 | ||
| 897 | 984 | fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { |
| 898 | 985 | const tree = scope.tree(); |
| 899 | 986 | const src = tree.token_locs[node.rtoken].start; |
| ... | ... | @@ -946,7 +1033,7 @@ fn boolBinOp( |
| 946 | 1033 | .else_body = undefined, // populated below |
| 947 | 1034 | }, .{}); |
| 948 | 1035 | |
| 949 | const block = try addZIRInstBlock(mod, scope, src, .{ | |
| 1036 | const block = try addZIRInstBlock(mod, scope, src, .block, .{ | |
| 950 | 1037 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), |
| 951 | 1038 | }); |
| 952 | 1039 | |
| ... | ... | @@ -1095,7 +1182,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1095 | 1182 | .else_body = undefined, // populated below |
| 1096 | 1183 | }, .{}); |
| 1097 | 1184 | |
| 1098 | const block = try addZIRInstBlock(mod, scope, if_src, .{ | |
| 1185 | const block = try addZIRInstBlock(mod, scope, if_src, .block, .{ | |
| 1099 | 1186 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), |
| 1100 | 1187 | }); |
| 1101 | 1188 | |
| ... | ... | @@ -1218,7 +1305,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W |
| 1218 | 1305 | .then_body = undefined, // populated below |
| 1219 | 1306 | .else_body = undefined, // populated below |
| 1220 | 1307 | }, .{}); |
| 1221 | const cond_block = try addZIRInstBlock(mod, &loop_scope.base, while_src, .{ | |
| 1308 | const cond_block = try addZIRInstBlock(mod, &loop_scope.base, while_src, .block, .{ | |
| 1222 | 1309 | .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items), |
| 1223 | 1310 | }); |
| 1224 | 1311 | // TODO avoid emitting the continue expr when there |
| ... | ... | @@ -1231,7 +1318,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W |
| 1231 | 1318 | const loop = try addZIRInstLoop(mod, &expr_scope.base, while_src, .{ |
| 1232 | 1319 | .instructions = try expr_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items), |
| 1233 | 1320 | }); |
| 1234 | const while_block = try addZIRInstBlock(mod, scope, while_src, .{ | |
| 1321 | const while_block = try addZIRInstBlock(mod, scope, while_src, .block, .{ | |
| 1235 | 1322 | .instructions = try expr_scope.arena.dupe(*zir.Inst, expr_scope.instructions.items), |
| 1236 | 1323 | }); |
| 1237 | 1324 | |
| ... | ... | @@ -1365,7 +1452,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) |
| 1365 | 1452 | .then_body = undefined, // populated below |
| 1366 | 1453 | .else_body = undefined, // populated below |
| 1367 | 1454 | }, .{}); |
| 1368 | const cond_block = try addZIRInstBlock(mod, &loop_scope.base, for_src, .{ | |
| 1455 | const cond_block = try addZIRInstBlock(mod, &loop_scope.base, for_src, .block, .{ | |
| 1369 | 1456 | .instructions = try loop_scope.arena.dupe(*zir.Inst, cond_scope.instructions.items), |
| 1370 | 1457 | }); |
| 1371 | 1458 | |
| ... | ... | @@ -1382,7 +1469,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) |
| 1382 | 1469 | const loop = try addZIRInstLoop(mod, &for_scope.base, for_src, .{ |
| 1383 | 1470 | .instructions = try for_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items), |
| 1384 | 1471 | }); |
| 1385 | const for_block = try addZIRInstBlock(mod, scope, for_src, .{ | |
| 1472 | const for_block = try addZIRInstBlock(mod, scope, for_src, .block, .{ | |
| 1386 | 1473 | .instructions = try for_scope.arena.dupe(*zir.Inst, for_scope.instructions.items), |
| 1387 | 1474 | }); |
| 1388 | 1475 | |
| ... | ... | @@ -2260,6 +2347,30 @@ pub fn addZIRBinOp( |
| 2260 | 2347 | return &inst.base; |
| 2261 | 2348 | } |
| 2262 | 2349 | |
| 2350 | pub fn addZIRInstBlock( | |
| 2351 | mod: *Module, | |
| 2352 | scope: *Scope, | |
| 2353 | src: usize, | |
| 2354 | tag: zir.Inst.Tag, | |
| 2355 | body: zir.Module.Body, | |
| 2356 | ) !*zir.Inst.Block { | |
| 2357 | const gen_zir = scope.getGenZIR(); | |
| 2358 | try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); | |
| 2359 | const inst = try gen_zir.arena.create(zir.Inst.Block); | |
| 2360 | inst.* = .{ | |
| 2361 | .base = .{ | |
| 2362 | .tag = tag, | |
| 2363 | .src = src, | |
| 2364 | }, | |
| 2365 | .positionals = .{ | |
| 2366 | .body = body, | |
| 2367 | }, | |
| 2368 | .kw_args = .{}, | |
| 2369 | }; | |
| 2370 | gen_zir.instructions.appendAssumeCapacity(&inst.base); | |
| 2371 | return inst; | |
| 2372 | } | |
| 2373 | ||
| 2263 | 2374 | pub fn addZIRInst( |
| 2264 | 2375 | mod: *Module, |
| 2265 | 2376 | scope: *Scope, |
| ... | ... | @@ -2278,12 +2389,6 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ |
| 2278 | 2389 | return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); |
| 2279 | 2390 | } |
| 2280 | 2391 | |
| 2281 | /// TODO The existence of this function is a workaround for a bug in stage1. | |
| 2282 | pub fn addZIRInstBlock(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { | |
| 2283 | const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; | |
| 2284 | return addZIRInstSpecial(mod, scope, src, zir.Inst.Block, P{ .body = body }, .{}); | |
| 2285 | } | |
| 2286 | ||
| 2287 | 2392 | /// TODO The existence of this function is a workaround for a bug in stage1. |
| 2288 | 2393 | pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop { |
| 2289 | 2394 | const P = std.meta.fieldInfo(zir.Inst.Loop, "positionals").field_type; |
src-self-hosted/codegen.zig+63-8| ... | ... | @@ -132,7 +132,7 @@ pub fn generateSymbol( |
| 132 | 132 | .Array => { |
| 133 | 133 | // TODO populate .debug_info for the array |
| 134 | 134 | if (typed_value.val.cast(Value.Payload.Bytes)) |payload| { |
| 135 | if (typed_value.ty.arraySentinel()) |sentinel| { | |
| 135 | if (typed_value.ty.sentinel()) |sentinel| { | |
| 136 | 136 | try code.ensureCapacity(code.items.len + payload.data.len + 1); |
| 137 | 137 | code.appendSliceAssumeCapacity(payload.data); |
| 138 | 138 | const prev_len = code.items.len; |
| ... | ... | @@ -359,7 +359,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 359 | 359 | }; |
| 360 | 360 | |
| 361 | 361 | const Branch = struct { |
| 362 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, | |
| 362 | inst_table: std.AutoArrayHashMapUnmanaged(*ir.Inst, MCValue) = .{}, | |
| 363 | 363 | |
| 364 | 364 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 365 | 365 | self.inst_table.deinit(gpa); |
| ... | ... | @@ -436,8 +436,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 436 | 436 | try branch_stack.append(.{}); |
| 437 | 437 | |
| 438 | 438 | const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: { |
| 439 | if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| { | |
| 440 | const tree = scope_file.contents.tree; | |
| 439 | if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| { | |
| 440 | const tree = container_scope.file_scope.contents.tree; | |
| 441 | 441 | const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; |
| 442 | 442 | const block = fn_proto.getBodyNode().?.castTag(.Block).?; |
| 443 | 443 | const lbrace_src = tree.token_locs[block.lbrace].start; |
| ... | ... | @@ -750,7 +750,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 750 | 750 | const ptr_bits = arch.ptrBitWidth(); |
| 751 | 751 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 752 | 752 | if (abi_size <= ptr_bytes) { |
| 753 | try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1); | |
| 753 | try self.registers.ensureCapacity(self.gpa, self.registers.count() + 1); | |
| 754 | 754 | if (self.allocReg(inst)) |reg| { |
| 755 | 755 | return MCValue{ .register = registerAlias(reg, abi_size) }; |
| 756 | 756 | } |
| ... | ... | @@ -788,7 +788,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 788 | 788 | /// `reg_owner` is the instruction that gets associated with the register in the register table. |
| 789 | 789 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| 790 | 790 | fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue { |
| 791 | try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1); | |
| 791 | try self.registers.ensureCapacity(self.gpa, @intCast(u32, self.registers.count() + 1)); | |
| 792 | 792 | |
| 793 | 793 | const reg = self.allocReg(reg_owner) orelse b: { |
| 794 | 794 | // We'll take over the first register. Move the instruction that was previously |
| ... | ... | @@ -1247,7 +1247,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1247 | 1247 | if (inst.base.isUnused()) |
| 1248 | 1248 | return MCValue.dead; |
| 1249 | 1249 | |
| 1250 | try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1); | |
| 1250 | try self.registers.ensureCapacity(self.gpa, self.registers.count() + 1); | |
| 1251 | 1251 | |
| 1252 | 1252 | const result = self.args[self.arg_index]; |
| 1253 | 1253 | self.arg_index += 1; |
| ... | ... | @@ -1443,7 +1443,57 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1443 | 1443 | } |
| 1444 | 1444 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| 1445 | 1445 | switch (arch) { |
| 1446 | .x86_64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for x86_64 arch", .{}), | |
| 1446 | .x86_64 => { | |
| 1447 | for (info.args) |mc_arg, arg_i| { | |
| 1448 | const arg = inst.args[arg_i]; | |
| 1449 | const arg_mcv = try self.resolveInst(inst.args[arg_i]); | |
| 1450 | // Here we do not use setRegOrMem even though the logic is similar, because | |
| 1451 | // the function call will move the stack pointer, so the offsets are different. | |
| 1452 | switch (mc_arg) { | |
| 1453 | .none => continue, | |
| 1454 | .register => |reg| { | |
| 1455 | try self.genSetReg(arg.src, reg, arg_mcv); | |
| 1456 | // TODO interact with the register allocator to mark the instruction as moved. | |
| 1457 | }, | |
| 1458 | .stack_offset => { | |
| 1459 | // Here we need to emit instructions like this: | |
| 1460 | // mov qword ptr [rsp + stack_offset], x | |
| 1461 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); | |
| 1462 | }, | |
| 1463 | .ptr_stack_offset => { | |
| 1464 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_stack_offset arg", .{}); | |
| 1465 | }, | |
| 1466 | .ptr_embedded_in_code => { | |
| 1467 | return self.fail(inst.base.src, "TODO implement calling with MCValue.ptr_embedded_in_code arg", .{}); | |
| 1468 | }, | |
| 1469 | .undef => unreachable, | |
| 1470 | .immediate => unreachable, | |
| 1471 | .unreach => unreachable, | |
| 1472 | .dead => unreachable, | |
| 1473 | .embedded_in_code => unreachable, | |
| 1474 | .memory => unreachable, | |
| 1475 | .compare_flags_signed => unreachable, | |
| 1476 | .compare_flags_unsigned => unreachable, | |
| 1477 | } | |
| 1478 | } | |
| 1479 | ||
| 1480 | if (inst.func.cast(ir.Inst.Constant)) |func_inst| { | |
| 1481 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | |
| 1482 | const func = func_val.func; | |
| 1483 | const got = &macho_file.sections.items[macho_file.got_section_index.?]; | |
| 1484 | const ptr_bytes = 8; | |
| 1485 | const got_addr = @intCast(u32, got.addr + func.owner_decl.link.macho.offset_table_index.? * ptr_bytes); | |
| 1486 | // ff 14 25 xx xx xx xx call [addr] | |
| 1487 | try self.code.ensureCapacity(self.code.items.len + 7); | |
| 1488 | self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 }); | |
| 1489 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr); | |
| 1490 | } else { | |
| 1491 | return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); | |
| 1492 | } | |
| 1493 | } else { | |
| 1494 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); | |
| 1495 | } | |
| 1496 | }, | |
| 1447 | 1497 | .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}), |
| 1448 | 1498 | else => unreachable, |
| 1449 | 1499 | } |
| ... | ... | @@ -2486,6 +2536,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2486 | 2536 | const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; |
| 2487 | 2537 | const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; |
| 2488 | 2538 | return MCValue{ .memory = got_addr }; |
| 2539 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | |
| 2540 | const decl = payload.decl; | |
| 2541 | const got = &macho_file.sections.items[macho_file.got_section_index.?]; | |
| 2542 | const got_addr = got.addr + decl.link.macho.offset_table_index.? * ptr_bytes; | |
| 2543 | return MCValue{ .memory = got_addr }; | |
| 2489 | 2544 | } else { |
| 2490 | 2545 | return self.fail(src, "TODO codegen non-ELF const Decl pointer", .{}); |
| 2491 | 2546 | } |
src-self-hosted/codegen/c.zig+3-2| ... | ... | @@ -85,7 +85,7 @@ fn genArray(file: *C, decl: *Decl) !void { |
| 85 | 85 | const name = try map(file.base.allocator, mem.span(decl.name)); |
| 86 | 86 | defer file.base.allocator.free(name); |
| 87 | 87 | if (tv.val.cast(Value.Payload.Bytes)) |payload| |
| 88 | if (tv.ty.arraySentinel()) |sentinel| | |
| 88 | if (tv.ty.sentinel()) |sentinel| | |
| 89 | 89 | if (sentinel.toUnsignedInt() == 0) |
| 90 | 90 | try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) |
| 91 | 91 | else |
| ... | ... | @@ -110,7 +110,8 @@ const Context = struct { |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | 112 | fn deinit(self: *Context) void { |
| 113 | for (self.inst_map.items()) |kv| { | |
| 113 | var it = self.inst_map.iterator(); | |
| 114 | while (it.next()) |kv| { | |
| 114 | 115 | self.file.base.allocator.free(kv.value); |
| 115 | 116 | } |
| 116 | 117 | self.inst_map.deinit(); |
src-self-hosted/ir.zig+9-1| ... | ... | @@ -189,7 +189,7 @@ pub const Inst = struct { |
| 189 | 189 | } |
| 190 | 190 | |
| 191 | 191 | pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator { |
| 192 | return switch (self.base.tag) { | |
| 192 | return switch (base.tag) { | |
| 193 | 193 | .cmp_lt => .lt, |
| 194 | 194 | .cmp_lte => .lte, |
| 195 | 195 | .cmp_eq => .eq, |
| ... | ... | @@ -220,6 +220,14 @@ pub const Inst = struct { |
| 220 | 220 | unreachable; |
| 221 | 221 | } |
| 222 | 222 | |
| 223 | pub fn breakBlock(base: *Inst) ?*Block { | |
| 224 | return switch (base.tag) { | |
| 225 | .br => base.castTag(.br).?.block, | |
| 226 | .brvoid => base.castTag(.brvoid).?.block, | |
| 227 | else => null, | |
| 228 | }; | |
| 229 | } | |
| 230 | ||
| 223 | 231 | pub const NoOp = struct { |
| 224 | 232 | base: Inst, |
| 225 | 233 |
src-self-hosted/link.zig+1-1| ... | ... | @@ -47,7 +47,7 @@ pub const File = struct { |
| 47 | 47 | }; |
| 48 | 48 | |
| 49 | 49 | /// For DWARF .debug_info. |
| 50 | pub const DbgInfoTypeRelocsTable = std.HashMapUnmanaged(Type, DbgInfoTypeReloc, Type.hash, Type.eql, true); | |
| 50 | pub const DbgInfoTypeRelocsTable = std.HashMapUnmanaged(Type, DbgInfoTypeReloc, Type.hash, Type.eql, std.hash_map.DefaultMaxLoadPercentage); | |
| 51 | 51 | |
| 52 | 52 | /// For DWARF .debug_info. |
| 53 | 53 | pub const DbgInfoTypeReloc = struct { |
src-self-hosted/link/Elf.zig+10-7| ... | ... | @@ -1629,7 +1629,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1629 | 1629 | |
| 1630 | 1630 | var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; |
| 1631 | 1631 | defer { |
| 1632 | for (dbg_info_type_relocs.items()) |*entry| { | |
| 1632 | var it = dbg_info_type_relocs.iterator(); | |
| 1633 | while (it.next()) |entry| { | |
| 1633 | 1634 | entry.value.relocs.deinit(self.base.allocator); |
| 1634 | 1635 | } |
| 1635 | 1636 | dbg_info_type_relocs.deinit(self.base.allocator); |
| ... | ... | @@ -1655,8 +1656,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1655 | 1656 | try dbg_line_buffer.ensureCapacity(26); |
| 1656 | 1657 | |
| 1657 | 1658 | const line_off: u28 = blk: { |
| 1658 | if (decl.scope.cast(Module.Scope.File)) |scope_file| { | |
| 1659 | const tree = scope_file.contents.tree; | |
| 1659 | if (decl.scope.cast(Module.Scope.Container)) |container_scope| { | |
| 1660 | const tree = container_scope.file_scope.contents.tree; | |
| 1660 | 1661 | const file_ast_decls = tree.root_node.decls(); |
| 1661 | 1662 | // TODO Look into improving the performance here by adding a token-index-to-line |
| 1662 | 1663 | // lookup table. Currently this involves scanning over the source code for newlines. |
| ... | ... | @@ -1917,7 +1918,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1917 | 1918 | // Now we emit the .debug_info types of the Decl. These will count towards the size of |
| 1918 | 1919 | // the buffer, so we have to do it before computing the offset, and we can't perform the actual |
| 1919 | 1920 | // relocations yet. |
| 1920 | for (dbg_info_type_relocs.items()) |*entry| { | |
| 1921 | var it = dbg_info_type_relocs.iterator(); | |
| 1922 | while (it.next()) |entry| { | |
| 1921 | 1923 | entry.value.off = @intCast(u32, dbg_info_buffer.items.len); |
| 1922 | 1924 | try self.addDbgInfoType(entry.key, &dbg_info_buffer); |
| 1923 | 1925 | } |
| ... | ... | @@ -1925,7 +1927,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1925 | 1927 | try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len)); |
| 1926 | 1928 | |
| 1927 | 1929 | // Now that we have the offset assigned we can finally perform type relocations. |
| 1928 | for (dbg_info_type_relocs.items()) |entry| { | |
| 1930 | it = dbg_info_type_relocs.iterator(); | |
| 1931 | while (it.next()) |entry| { | |
| 1929 | 1932 | for (entry.value.relocs.items) |off| { |
| 1930 | 1933 | mem.writeInt( |
| 1931 | 1934 | u32, |
| ... | ... | @@ -2154,8 +2157,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec |
| 2154 | 2157 | const tracy = trace(@src()); |
| 2155 | 2158 | defer tracy.end(); |
| 2156 | 2159 | |
| 2157 | const scope_file = decl.scope.cast(Module.Scope.File).?; | |
| 2158 | const tree = scope_file.contents.tree; | |
| 2160 | const container_scope = decl.scope.cast(Module.Scope.Container).?; | |
| 2161 | const tree = container_scope.file_scope.contents.tree; | |
| 2159 | 2162 | const file_ast_decls = tree.root_node.decls(); |
| 2160 | 2163 | // TODO Look into improving the performance here by adding a token-index-to-line |
| 2161 | 2164 | // lookup table. Currently this involves scanning over the source code for newlines. |
src-self-hosted/link/MachO.zig+525-159| ... | ... | @@ -18,36 +18,66 @@ const File = link.File; |
| 18 | 18 | |
| 19 | 19 | pub const base_tag: File.Tag = File.Tag.macho; |
| 20 | 20 | |
| 21 | const LoadCommand = union(enum) { | |
| 22 | Segment: macho.segment_command_64, | |
| 23 | LinkeditData: macho.linkedit_data_command, | |
| 24 | Symtab: macho.symtab_command, | |
| 25 | Dysymtab: macho.dysymtab_command, | |
| 26 | ||
| 27 | pub fn cmdsize(self: LoadCommand) u32 { | |
| 28 | return switch (self) { | |
| 29 | .Segment => |x| x.cmdsize, | |
| 30 | .LinkeditData => |x| x.cmdsize, | |
| 31 | .Symtab => |x| x.cmdsize, | |
| 32 | .Dysymtab => |x| x.cmdsize, | |
| 33 | }; | |
| 34 | } | |
| 35 | }; | |
| 36 | ||
| 21 | 37 | base: File, |
| 22 | 38 | |
| 23 | /// List of all load command headers that are in the file. | |
| 24 | /// We use it to track number and size of all commands needed by the header. | |
| 25 | commands: std.ArrayListUnmanaged(macho.load_command) = std.ArrayListUnmanaged(macho.load_command){}, | |
| 26 | command_file_offset: ?u64 = null, | |
| 39 | /// Table of all load commands | |
| 40 | load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, | |
| 41 | segment_cmd_index: ?u16 = null, | |
| 42 | symtab_cmd_index: ?u16 = null, | |
| 43 | dysymtab_cmd_index: ?u16 = null, | |
| 44 | data_in_code_cmd_index: ?u16 = null, | |
| 27 | 45 | |
| 28 | /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. | |
| 29 | /// Same order as in the file. | |
| 30 | segments: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){}, | |
| 31 | /// Section (headers) *always* follow segment (load commands) directly! | |
| 32 | sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){}, | |
| 46 | /// Table of all sections | |
| 47 | sections: std.ArrayListUnmanaged(macho.section_64) = .{}, | |
| 33 | 48 | |
| 34 | /// Offset (index) into __TEXT segment load command. | |
| 35 | text_segment_offset: ?u64 = null, | |
| 36 | /// Offset (index) into __LINKEDIT segment load command. | |
| 37 | linkedit_segment_offset: ?u664 = null, | |
| 49 | /// __TEXT segment sections | |
| 50 | text_section_index: ?u16 = null, | |
| 51 | cstring_section_index: ?u16 = null, | |
| 52 | const_text_section_index: ?u16 = null, | |
| 53 | stubs_section_index: ?u16 = null, | |
| 54 | stub_helper_section_index: ?u16 = null, | |
| 55 | ||
| 56 | /// __DATA segment sections | |
| 57 | got_section_index: ?u16 = null, | |
| 58 | const_data_section_index: ?u16 = null, | |
| 38 | 59 | |
| 39 | /// Entry point load command | |
| 40 | entry_point_cmd: ?macho.entry_point_command = null, | |
| 41 | 60 | entry_addr: ?u64 = null, |
| 42 | 61 | |
| 43 | /// The first 4GB of process' memory is reserved for the null (__PAGEZERO) segment. | |
| 44 | /// This is also the start address for our binary. | |
| 45 | vm_start_address: u64 = 0x100000000, | |
| 62 | /// Table of all symbols used. | |
| 63 | /// Internally references string table for names (which are optional). | |
| 64 | symbol_table: std.ArrayListUnmanaged(macho.nlist_64) = .{}, | |
| 65 | ||
| 66 | /// Table of symbol names aka the string table. | |
| 67 | string_table: std.ArrayListUnmanaged(u8) = .{}, | |
| 46 | 68 | |
| 47 | seg_table_dirty: bool = false, | |
| 69 | /// Table of symbol vaddr values. The values is the absolute vaddr value. | |
| 70 | /// If the vaddr of the executable __TEXT segment vaddr changes, the entire offset | |
| 71 | /// table needs to be rewritten. | |
| 72 | offset_table: std.ArrayListUnmanaged(u64) = .{}, | |
| 48 | 73 | |
| 49 | 74 | error_flags: File.ErrorFlags = File.ErrorFlags{}, |
| 50 | 75 | |
| 76 | cmd_table_dirty: bool = false, | |
| 77 | ||
| 78 | /// Pointer to the last allocated text block | |
| 79 | last_text_block: ?*TextBlock = null, | |
| 80 | ||
| 51 | 81 | /// `alloc_num / alloc_den` is the factor of padding when allocating. |
| 52 | 82 | const alloc_num = 4; |
| 53 | 83 | const alloc_den = 3; |
| ... | ... | @@ -67,7 +97,23 @@ const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; |
| 67 | 97 | const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; |
| 68 | 98 | |
| 69 | 99 | pub const TextBlock = struct { |
| 70 | pub const empty = TextBlock{}; | |
| 100 | /// Index into the symbol table | |
| 101 | symbol_table_index: ?u32, | |
| 102 | /// Index into offset table | |
| 103 | offset_table_index: ?u32, | |
| 104 | /// Size of this text block | |
| 105 | size: u64, | |
| 106 | /// Points to the previous and next neighbours | |
| 107 | prev: ?*TextBlock, | |
| 108 | next: ?*TextBlock, | |
| 109 | ||
| 110 | pub const empty = TextBlock{ | |
| 111 | .symbol_table_index = null, | |
| 112 | .offset_table_index = null, | |
| 113 | .size = 0, | |
| 114 | .prev = null, | |
| 115 | .next = null, | |
| 116 | }; | |
| 71 | 117 | }; |
| 72 | 118 | |
| 73 | 119 | pub const SrcFn = struct { |
| ... | ... | @@ -117,6 +163,12 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO |
| 117 | 163 | /// Truncates the existing file contents and overwrites the contents. |
| 118 | 164 | /// Returns an error if `file` is not already open with +read +write +seek abilities. |
| 119 | 165 | fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO { |
| 166 | switch (options.output_mode) { | |
| 167 | .Exe => {}, | |
| 168 | .Obj => {}, | |
| 169 | .Lib => return error.TODOImplementWritingLibFiles, | |
| 170 | } | |
| 171 | ||
| 120 | 172 | var self: MachO = .{ |
| 121 | 173 | .base = .{ |
| 122 | 174 | .file = file, |
| ... | ... | @@ -127,104 +179,15 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach |
| 127 | 179 | }; |
| 128 | 180 | errdefer self.deinit(); |
| 129 | 181 | |
| 130 | switch (options.output_mode) { | |
| 131 | .Exe => { | |
| 132 | // The first segment command for executables is always a __PAGEZERO segment. | |
| 133 | const pagezero = .{ | |
| 134 | .cmd = macho.LC_SEGMENT_64, | |
| 135 | .cmdsize = commandSize(@sizeOf(macho.segment_command_64)), | |
| 136 | .segname = makeString("__PAGEZERO"), | |
| 137 | .vmaddr = 0, | |
| 138 | .vmsize = self.vm_start_address, | |
| 139 | .fileoff = 0, | |
| 140 | .filesize = 0, | |
| 141 | .maxprot = macho.VM_PROT_NONE, | |
| 142 | .initprot = macho.VM_PROT_NONE, | |
| 143 | .nsects = 0, | |
| 144 | .flags = 0, | |
| 145 | }; | |
| 146 | try self.commands.append(allocator, .{ | |
| 147 | .cmd = pagezero.cmd, | |
| 148 | .cmdsize = pagezero.cmdsize, | |
| 149 | }); | |
| 150 | try self.segments.append(allocator, pagezero); | |
| 151 | }, | |
| 152 | .Obj => return error.TODOImplementWritingObjFiles, | |
| 153 | .Lib => return error.TODOImplementWritingLibFiles, | |
| 154 | } | |
| 155 | ||
| 156 | 182 | try self.populateMissingMetadata(); |
| 157 | 183 | |
| 158 | 184 | return self; |
| 159 | 185 | } |
| 160 | 186 | |
| 161 | fn writeMachOHeader(self: *MachO) !void { | |
| 162 | var hdr: macho.mach_header_64 = undefined; | |
| 163 | hdr.magic = macho.MH_MAGIC_64; | |
| 164 | ||
| 165 | const CpuInfo = struct { | |
| 166 | cpu_type: macho.cpu_type_t, | |
| 167 | cpu_subtype: macho.cpu_subtype_t, | |
| 168 | }; | |
| 169 | ||
| 170 | const cpu_info: CpuInfo = switch (self.base.options.target.cpu.arch) { | |
| 171 | .aarch64 => .{ | |
| 172 | .cpu_type = macho.CPU_TYPE_ARM64, | |
| 173 | .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL, | |
| 174 | }, | |
| 175 | .x86_64 => .{ | |
| 176 | .cpu_type = macho.CPU_TYPE_X86_64, | |
| 177 | .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL, | |
| 178 | }, | |
| 179 | else => return error.UnsupportedMachOArchitecture, | |
| 180 | }; | |
| 181 | hdr.cputype = cpu_info.cpu_type; | |
| 182 | hdr.cpusubtype = cpu_info.cpu_subtype; | |
| 183 | ||
| 184 | const filetype: u32 = switch (self.base.options.output_mode) { | |
| 185 | .Exe => macho.MH_EXECUTE, | |
| 186 | .Obj => macho.MH_OBJECT, | |
| 187 | .Lib => switch (self.base.options.link_mode) { | |
| 188 | .Static => return error.TODOStaticLibMachOType, | |
| 189 | .Dynamic => macho.MH_DYLIB, | |
| 190 | }, | |
| 191 | }; | |
| 192 | hdr.filetype = filetype; | |
| 193 | ||
| 194 | const ncmds = try math.cast(u32, self.commands.items.len); | |
| 195 | hdr.ncmds = ncmds; | |
| 196 | ||
| 197 | var sizeof_cmds: u32 = 0; | |
| 198 | for (self.commands.items) |cmd| { | |
| 199 | sizeof_cmds += cmd.cmdsize; | |
| 200 | } | |
| 201 | hdr.sizeofcmds = sizeof_cmds; | |
| 202 | ||
| 203 | // TODO should these be set to something else? | |
| 204 | hdr.flags = 0; | |
| 205 | hdr.reserved = 0; | |
| 206 | ||
| 207 | try self.base.file.?.pwriteAll(@ptrCast([*]const u8, &hdr)[0..@sizeOf(macho.mach_header_64)], 0); | |
| 208 | } | |
| 209 | ||
| 210 | 187 | pub fn flush(self: *MachO, module: *Module) !void { |
| 211 | // Save segments first | |
| 212 | { | |
| 213 | const buf = try self.base.allocator.alloc(macho.segment_command_64, self.segments.items.len); | |
| 214 | defer self.base.allocator.free(buf); | |
| 215 | ||
| 216 | self.command_file_offset = @sizeOf(macho.mach_header_64); | |
| 217 | ||
| 218 | for (buf) |*seg, i| { | |
| 219 | seg.* = self.segments.items[i]; | |
| 220 | self.command_file_offset.? += self.segments.items[i].cmdsize; | |
| 221 | } | |
| 222 | ||
| 223 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), @sizeOf(macho.mach_header_64)); | |
| 224 | } | |
| 225 | ||
| 226 | 188 | switch (self.base.options.output_mode) { |
| 227 | 189 | .Exe => { |
| 190 | var last_cmd_offset: usize = @sizeOf(macho.mach_header_64); | |
| 228 | 191 | { |
| 229 | 192 | // Specify path to dynamic linker dyld |
| 230 | 193 | const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)); |
| ... | ... | @@ -235,18 +198,14 @@ pub fn flush(self: *MachO, module: *Module) !void { |
| 235 | 198 | .name = @sizeOf(macho.dylinker_command), |
| 236 | 199 | }, |
| 237 | 200 | }; |
| 238 | try self.commands.append(self.base.allocator, .{ | |
| 239 | .cmd = macho.LC_LOAD_DYLINKER, | |
| 240 | .cmdsize = cmdsize, | |
| 241 | }); | |
| 242 | 201 | |
| 243 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?); | |
| 202 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), last_cmd_offset); | |
| 244 | 203 | |
| 245 | const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command); | |
| 204 | const file_offset = last_cmd_offset + @sizeOf(macho.dylinker_command); | |
| 246 | 205 | try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); |
| 247 | 206 | |
| 248 | 207 | try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); |
| 249 | self.command_file_offset.? += cmdsize; | |
| 208 | last_cmd_offset += cmdsize; | |
| 250 | 209 | } |
| 251 | 210 | |
| 252 | 211 | { |
| ... | ... | @@ -268,21 +227,44 @@ pub fn flush(self: *MachO, module: *Module) !void { |
| 268 | 227 | .dylib = dylib, |
| 269 | 228 | }, |
| 270 | 229 | }; |
| 271 | try self.commands.append(self.base.allocator, .{ | |
| 272 | .cmd = macho.LC_LOAD_DYLIB, | |
| 273 | .cmdsize = cmdsize, | |
| 274 | }); | |
| 275 | 230 | |
| 276 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?); | |
| 231 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), last_cmd_offset); | |
| 277 | 232 | |
| 278 | const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command); | |
| 233 | const file_offset = last_cmd_offset + @sizeOf(macho.dylib_command); | |
| 279 | 234 | try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); |
| 280 | 235 | |
| 281 | 236 | try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); |
| 282 | self.command_file_offset.? += cmdsize; | |
| 237 | last_cmd_offset += cmdsize; | |
| 283 | 238 | } |
| 284 | 239 | }, |
| 285 | .Obj => return error.TODOImplementWritingObjFiles, | |
| 240 | .Obj => { | |
| 241 | { | |
| 242 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 243 | symtab.nsyms = @intCast(u32, self.symbol_table.items.len); | |
| 244 | const allocated_size = self.allocatedSize(symtab.stroff); | |
| 245 | const needed_size = self.string_table.items.len; | |
| 246 | log.debug("allocated_size = 0x{x}, needed_size = 0x{x}\n", .{ allocated_size, needed_size }); | |
| 247 | ||
| 248 | if (needed_size > allocated_size) { | |
| 249 | symtab.strsize = 0; | |
| 250 | symtab.stroff = @intCast(u32, self.findFreeSpace(needed_size, 1)); | |
| 251 | } | |
| 252 | symtab.strsize = @intCast(u32, needed_size); | |
| 253 | ||
| 254 | log.debug("writing string table from 0x{x} to 0x{x}\n", .{ symtab.stroff, symtab.stroff + symtab.strsize }); | |
| 255 | ||
| 256 | try self.base.file.?.pwriteAll(self.string_table.items, symtab.stroff); | |
| 257 | } | |
| 258 | ||
| 259 | var last_cmd_offset: usize = @sizeOf(macho.mach_header_64); | |
| 260 | for (self.load_commands.items) |cmd| { | |
| 261 | const cmd_to_write = [1]@TypeOf(cmd){cmd}; | |
| 262 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(cmd_to_write[0..1]), last_cmd_offset); | |
| 263 | last_cmd_offset += cmd.cmdsize(); | |
| 264 | } | |
| 265 | const off = @sizeOf(macho.mach_header_64) + @sizeOf(macho.segment_command_64); | |
| 266 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.sections.items), off); | |
| 267 | }, | |
| 286 | 268 | .Lib => return error.TODOImplementWritingLibFiles, |
| 287 | 269 | } |
| 288 | 270 | |
| ... | ... | @@ -297,14 +279,110 @@ pub fn flush(self: *MachO, module: *Module) !void { |
| 297 | 279 | } |
| 298 | 280 | |
| 299 | 281 | pub fn deinit(self: *MachO) void { |
| 300 | self.commands.deinit(self.base.allocator); | |
| 301 | self.segments.deinit(self.base.allocator); | |
| 282 | self.offset_table.deinit(self.base.allocator); | |
| 283 | self.string_table.deinit(self.base.allocator); | |
| 284 | self.symbol_table.deinit(self.base.allocator); | |
| 302 | 285 | self.sections.deinit(self.base.allocator); |
| 286 | self.load_commands.deinit(self.base.allocator); | |
| 287 | } | |
| 288 | ||
| 289 | pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { | |
| 290 | if (decl.link.macho.symbol_table_index) |_| return; | |
| 291 | ||
| 292 | try self.symbol_table.ensureCapacity(self.base.allocator, self.symbol_table.items.len + 1); | |
| 293 | try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); | |
| 294 | ||
| 295 | log.debug("allocating symbol index {} for {}\n", .{ self.symbol_table.items.len, decl.name }); | |
| 296 | decl.link.macho.symbol_table_index = @intCast(u32, self.symbol_table.items.len); | |
| 297 | _ = self.symbol_table.addOneAssumeCapacity(); | |
| 298 | ||
| 299 | decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len); | |
| 300 | _ = self.offset_table.addOneAssumeCapacity(); | |
| 301 | ||
| 302 | self.symbol_table.items[decl.link.macho.symbol_table_index.?] = .{ | |
| 303 | .n_strx = 0, | |
| 304 | .n_type = 0, | |
| 305 | .n_sect = 0, | |
| 306 | .n_desc = 0, | |
| 307 | .n_value = 0, | |
| 308 | }; | |
| 309 | self.offset_table.items[decl.link.macho.offset_table_index.?] = 0; | |
| 303 | 310 | } |
| 304 | 311 | |
| 305 | pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {} | |
| 312 | pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { | |
| 313 | const tracy = trace(@src()); | |
| 314 | defer tracy.end(); | |
| 315 | ||
| 316 | var code_buffer = std.ArrayList(u8).init(self.base.allocator); | |
| 317 | defer code_buffer.deinit(); | |
| 306 | 318 | |
| 307 | pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {} | |
| 319 | var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator); | |
| 320 | defer dbg_line_buffer.deinit(); | |
| 321 | ||
| 322 | var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator); | |
| 323 | defer dbg_info_buffer.deinit(); | |
| 324 | ||
| 325 | var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; | |
| 326 | defer { | |
| 327 | var it = dbg_info_type_relocs.iterator(); | |
| 328 | while (it.next()) |entry| { | |
| 329 | entry.value.relocs.deinit(self.base.allocator); | |
| 330 | } | |
| 331 | dbg_info_type_relocs.deinit(self.base.allocator); | |
| 332 | } | |
| 333 | ||
| 334 | const typed_value = decl.typed_value.most_recent.typed_value; | |
| 335 | const res = try codegen.generateSymbol( | |
| 336 | &self.base, | |
| 337 | decl.src(), | |
| 338 | typed_value, | |
| 339 | &code_buffer, | |
| 340 | &dbg_line_buffer, | |
| 341 | &dbg_info_buffer, | |
| 342 | &dbg_info_type_relocs, | |
| 343 | ); | |
| 344 | ||
| 345 | const code = switch (res) { | |
| 346 | .externally_managed => |x| x, | |
| 347 | .appended => code_buffer.items, | |
| 348 | .fail => |em| { | |
| 349 | decl.analysis = .codegen_failure; | |
| 350 | try module.failed_decls.put(module.gpa, decl, em); | |
| 351 | return; | |
| 352 | }, | |
| 353 | }; | |
| 354 | log.debug("generated code {}\n", .{code}); | |
| 355 | ||
| 356 | const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); | |
| 357 | const symbol = &self.symbol_table.items[decl.link.macho.symbol_table_index.?]; | |
| 358 | ||
| 359 | const decl_name = mem.spanZ(decl.name); | |
| 360 | const name_str_index = try self.makeString(decl_name); | |
| 361 | const addr = try self.allocateTextBlock(&decl.link.macho, code.len, required_alignment); | |
| 362 | log.debug("allocated text block for {} at 0x{x}\n", .{ decl_name, addr }); | |
| 363 | log.debug("updated text section {}\n", .{self.sections.items[self.text_section_index.?]}); | |
| 364 | ||
| 365 | symbol.* = .{ | |
| 366 | .n_strx = name_str_index, | |
| 367 | .n_type = macho.N_SECT, | |
| 368 | .n_sect = @intCast(u8, self.text_section_index.?) + 1, | |
| 369 | .n_desc = 0, | |
| 370 | .n_value = addr, | |
| 371 | }; | |
| 372 | self.offset_table.items[decl.link.macho.offset_table_index.?] = addr; | |
| 373 | ||
| 374 | try self.writeSymbol(decl.link.macho.symbol_table_index.?); | |
| 375 | ||
| 376 | const text_section = self.sections.items[self.text_section_index.?]; | |
| 377 | const section_offset = symbol.n_value - text_section.addr; | |
| 378 | const file_offset = text_section.offset + section_offset; | |
| 379 | log.debug("file_offset 0x{x}\n", .{file_offset}); | |
| 380 | try self.base.file.?.pwriteAll(code, file_offset); | |
| 381 | ||
| 382 | // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. | |
| 383 | const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; | |
| 384 | return self.updateDeclExports(module, decl, decl_exports); | |
| 385 | } | |
| 308 | 386 | |
| 309 | 387 | pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {} |
| 310 | 388 | |
| ... | ... | @@ -313,51 +391,191 @@ pub fn updateDeclExports( |
| 313 | 391 | module: *Module, |
| 314 | 392 | decl: *const Module.Decl, |
| 315 | 393 | exports: []const *Module.Export, |
| 316 | ) !void {} | |
| 394 | ) !void { | |
| 395 | const tracy = trace(@src()); | |
| 396 | defer tracy.end(); | |
| 397 | ||
| 398 | if (decl.link.macho.symbol_table_index == null) return; | |
| 399 | ||
| 400 | var decl_sym = self.symbol_table.items[decl.link.macho.symbol_table_index.?]; | |
| 401 | // TODO implement | |
| 402 | if (exports.len == 0) return; | |
| 403 | ||
| 404 | const exp = exports[0]; | |
| 405 | self.entry_addr = decl_sym.n_value; | |
| 406 | decl_sym.n_type |= macho.N_EXT; | |
| 407 | exp.link.sym_index = 0; | |
| 408 | } | |
| 317 | 409 | |
| 318 | 410 | pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} |
| 319 | 411 | |
| 320 | 412 | pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { |
| 321 | @panic("TODO implement getDeclVAddr for MachO"); | |
| 413 | return self.symbol_table.items[decl.link.macho.symbol_table_index.?].n_value; | |
| 322 | 414 | } |
| 323 | 415 | |
| 324 | 416 | pub fn populateMissingMetadata(self: *MachO) !void { |
| 325 | if (self.text_segment_offset == null) { | |
| 326 | self.text_segment_offset = @intCast(u64, self.segments.items.len); | |
| 327 | const file_size = alignSize(u64, self.base.options.program_code_size_hint, 0x1000); | |
| 328 | log.debug("vmsize/filesize = {}", .{file_size}); | |
| 329 | const file_offset = 0; | |
| 330 | const vm_address = self.vm_start_address; // the end of __PAGEZERO segment in VM | |
| 331 | const protection = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE; | |
| 332 | const cmdsize = commandSize(@sizeOf(macho.segment_command_64)); | |
| 333 | const text_segment = .{ | |
| 334 | .cmd = macho.LC_SEGMENT_64, | |
| 335 | .cmdsize = cmdsize, | |
| 336 | .segname = makeString("__TEXT"), | |
| 337 | .vmaddr = vm_address, | |
| 338 | .vmsize = file_size, | |
| 339 | .fileoff = 0, // __TEXT segment *always* starts at 0 file offset | |
| 340 | .filesize = 0, //file_size, | |
| 341 | .maxprot = protection, | |
| 342 | .initprot = protection, | |
| 343 | .nsects = 0, | |
| 344 | .flags = 0, | |
| 345 | }; | |
| 346 | try self.commands.append(self.base.allocator, .{ | |
| 347 | .cmd = macho.LC_SEGMENT_64, | |
| 348 | .cmdsize = cmdsize, | |
| 417 | if (self.segment_cmd_index == null) { | |
| 418 | self.segment_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 419 | try self.load_commands.append(self.base.allocator, .{ | |
| 420 | .Segment = .{ | |
| 421 | .cmd = macho.LC_SEGMENT_64, | |
| 422 | .cmdsize = @sizeOf(macho.segment_command_64), | |
| 423 | .segname = makeStaticString(""), | |
| 424 | .vmaddr = 0, | |
| 425 | .vmsize = 0, | |
| 426 | .fileoff = 0, | |
| 427 | .filesize = 0, | |
| 428 | .maxprot = 0, | |
| 429 | .initprot = 0, | |
| 430 | .nsects = 0, | |
| 431 | .flags = 0, | |
| 432 | }, | |
| 433 | }); | |
| 434 | self.cmd_table_dirty = true; | |
| 435 | } | |
| 436 | if (self.symtab_cmd_index == null) { | |
| 437 | self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); | |
| 438 | try self.load_commands.append(self.base.allocator, .{ | |
| 439 | .Symtab = .{ | |
| 440 | .cmd = macho.LC_SYMTAB, | |
| 441 | .cmdsize = @sizeOf(macho.symtab_command), | |
| 442 | .symoff = 0, | |
| 443 | .nsyms = 0, | |
| 444 | .stroff = 0, | |
| 445 | .strsize = 0, | |
| 446 | }, | |
| 349 | 447 | }); |
| 350 | try self.segments.append(self.base.allocator, text_segment); | |
| 448 | self.cmd_table_dirty = true; | |
| 449 | } | |
| 450 | if (self.text_section_index == null) { | |
| 451 | self.text_section_index = @intCast(u16, self.sections.items.len); | |
| 452 | const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment; | |
| 453 | segment.cmdsize += @sizeOf(macho.section_64); | |
| 454 | segment.nsects += 1; | |
| 455 | ||
| 456 | const file_size = self.base.options.program_code_size_hint; | |
| 457 | const off = @intCast(u32, self.findFreeSpace(file_size, 1)); | |
| 458 | const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; | |
| 459 | ||
| 460 | log.debug("found __text section free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 461 | ||
| 462 | try self.sections.append(self.base.allocator, .{ | |
| 463 | .sectname = makeStaticString("__text"), | |
| 464 | .segname = makeStaticString("__TEXT"), | |
| 465 | .addr = 0, | |
| 466 | .size = file_size, | |
| 467 | .offset = off, | |
| 468 | .@"align" = 0x1000, | |
| 469 | .reloff = 0, | |
| 470 | .nreloc = 0, | |
| 471 | .flags = flags, | |
| 472 | .reserved1 = 0, | |
| 473 | .reserved2 = 0, | |
| 474 | .reserved3 = 0, | |
| 475 | }); | |
| 476 | ||
| 477 | segment.vmsize += file_size; | |
| 478 | segment.filesize += file_size; | |
| 479 | segment.fileoff = off; | |
| 480 | ||
| 481 | log.debug("initial text section {}\n", .{self.sections.items[self.text_section_index.?]}); | |
| 482 | } | |
| 483 | { | |
| 484 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 485 | if (symtab.symoff == 0) { | |
| 486 | const p_align = @sizeOf(macho.nlist_64); | |
| 487 | const nsyms = self.base.options.symbol_count_hint; | |
| 488 | const file_size = p_align * nsyms; | |
| 489 | const off = @intCast(u32, self.findFreeSpace(file_size, p_align)); | |
| 490 | log.debug("found symbol table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 491 | symtab.symoff = off; | |
| 492 | symtab.nsyms = @intCast(u32, nsyms); | |
| 493 | } | |
| 494 | if (symtab.stroff == 0) { | |
| 495 | try self.string_table.append(self.base.allocator, 0); | |
| 496 | const file_size = @intCast(u32, self.string_table.items.len); | |
| 497 | const off = @intCast(u32, self.findFreeSpace(file_size, 1)); | |
| 498 | log.debug("found string table free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); | |
| 499 | symtab.stroff = off; | |
| 500 | symtab.strsize = file_size; | |
| 501 | } | |
| 351 | 502 | } |
| 352 | 503 | } |
| 353 | 504 | |
| 354 | fn makeString(comptime bytes: []const u8) [16]u8 { | |
| 505 | fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { | |
| 506 | const segment = &self.load_commands.items[self.segment_cmd_index.?].Segment; | |
| 507 | const text_section = &self.sections.items[self.text_section_index.?]; | |
| 508 | const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; | |
| 509 | ||
| 510 | var block_placement: ?*TextBlock = null; | |
| 511 | const addr = blk: { | |
| 512 | if (self.last_text_block) |last| { | |
| 513 | const last_symbol = self.symbol_table.items[last.symbol_table_index.?]; | |
| 514 | const ideal_capacity = last.size * alloc_num / alloc_den; | |
| 515 | const ideal_capacity_end_addr = last_symbol.n_value + ideal_capacity; | |
| 516 | const new_start_addr = mem.alignForwardGeneric(u64, ideal_capacity_end_addr, alignment); | |
| 517 | block_placement = last; | |
| 518 | break :blk new_start_addr; | |
| 519 | } else { | |
| 520 | break :blk text_section.addr; | |
| 521 | } | |
| 522 | }; | |
| 523 | log.debug("computed symbol address 0x{x}\n", .{addr}); | |
| 524 | ||
| 525 | const expand_text_section = block_placement == null or block_placement.?.next == null; | |
| 526 | if (expand_text_section) { | |
| 527 | const text_capacity = self.allocatedSize(text_section.offset); | |
| 528 | const needed_size = (addr + new_block_size) - text_section.addr; | |
| 529 | log.debug("text capacity 0x{x}, needed size 0x{x}\n", .{ text_capacity, needed_size }); | |
| 530 | ||
| 531 | if (needed_size > text_capacity) { | |
| 532 | // TODO handle growth | |
| 533 | } | |
| 534 | ||
| 535 | self.last_text_block = text_block; | |
| 536 | text_section.size = needed_size; | |
| 537 | segment.vmsize = needed_size; | |
| 538 | segment.filesize = needed_size; | |
| 539 | if (alignment < text_section.@"align") { | |
| 540 | text_section.@"align" = @intCast(u32, alignment); | |
| 541 | } | |
| 542 | } | |
| 543 | text_block.size = new_block_size; | |
| 544 | ||
| 545 | if (text_block.prev) |prev| { | |
| 546 | prev.next = text_block.next; | |
| 547 | } | |
| 548 | if (text_block.next) |next| { | |
| 549 | next.prev = text_block.prev; | |
| 550 | } | |
| 551 | ||
| 552 | if (block_placement) |big_block| { | |
| 553 | text_block.prev = big_block; | |
| 554 | text_block.next = big_block.next; | |
| 555 | big_block.next = text_block; | |
| 556 | } else { | |
| 557 | text_block.prev = null; | |
| 558 | text_block.next = null; | |
| 559 | } | |
| 560 | ||
| 561 | return addr; | |
| 562 | } | |
| 563 | ||
| 564 | fn makeStaticString(comptime bytes: []const u8) [16]u8 { | |
| 355 | 565 | var buf = [_]u8{0} ** 16; |
| 356 | if (bytes.len > buf.len) @compileError("MachO segment/section name too long"); | |
| 566 | if (bytes.len > buf.len) @compileError("string too long; max 16 bytes"); | |
| 357 | 567 | mem.copy(u8, buf[0..], bytes); |
| 358 | 568 | return buf; |
| 359 | 569 | } |
| 360 | 570 | |
| 571 | fn makeString(self: *MachO, bytes: []const u8) !u32 { | |
| 572 | try self.string_table.ensureCapacity(self.base.allocator, self.string_table.items.len + bytes.len + 1); | |
| 573 | const result = self.string_table.items.len; | |
| 574 | self.string_table.appendSliceAssumeCapacity(bytes); | |
| 575 | self.string_table.appendAssumeCapacity(0); | |
| 576 | return @intCast(u32, result); | |
| 577 | } | |
| 578 | ||
| 361 | 579 | fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int { |
| 362 | 580 | const size = @intCast(Int, min_size); |
| 363 | 581 | if (size % alignment == 0) return size; |
| ... | ... | @@ -370,7 +588,7 @@ fn commandSize(min_size: anytype) u32 { |
| 370 | 588 | return alignSize(u32, min_size, @sizeOf(u64)); |
| 371 | 589 | } |
| 372 | 590 | |
| 373 | fn addPadding(self: *MachO, size: u32, file_offset: u64) !void { | |
| 591 | fn addPadding(self: *MachO, size: u64, file_offset: u64) !void { | |
| 374 | 592 | if (size == 0) return; |
| 375 | 593 | |
| 376 | 594 | const buf = try self.base.allocator.alloc(u8, size); |
| ... | ... | @@ -380,3 +598,151 @@ fn addPadding(self: *MachO, size: u32, file_offset: u64) !void { |
| 380 | 598 | |
| 381 | 599 | try self.base.file.?.pwriteAll(buf, file_offset); |
| 382 | 600 | } |
| 601 | ||
| 602 | fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 { | |
| 603 | const hdr_size: u64 = @sizeOf(macho.mach_header_64); | |
| 604 | if (start < hdr_size) | |
| 605 | return hdr_size; | |
| 606 | ||
| 607 | const end = start + satMul(size, alloc_num) / alloc_den; | |
| 608 | ||
| 609 | { | |
| 610 | const off = @sizeOf(macho.mach_header_64); | |
| 611 | var tight_size: u64 = 0; | |
| 612 | for (self.load_commands.items) |cmd| { | |
| 613 | tight_size += cmd.cmdsize(); | |
| 614 | } | |
| 615 | const increased_size = satMul(tight_size, alloc_num) / alloc_den; | |
| 616 | const test_end = off + increased_size; | |
| 617 | if (end > off and start < test_end) { | |
| 618 | return test_end; | |
| 619 | } | |
| 620 | } | |
| 621 | ||
| 622 | for (self.sections.items) |section| { | |
| 623 | const increased_size = satMul(section.size, alloc_num) / alloc_den; | |
| 624 | const test_end = section.offset + increased_size; | |
| 625 | if (end > section.offset and start < test_end) { | |
| 626 | return test_end; | |
| 627 | } | |
| 628 | } | |
| 629 | ||
| 630 | if (self.symtab_cmd_index) |symtab_index| { | |
| 631 | const symtab = self.load_commands.items[symtab_index].Symtab; | |
| 632 | { | |
| 633 | const tight_size = @sizeOf(macho.nlist_64) * symtab.nsyms; | |
| 634 | const increased_size = satMul(tight_size, alloc_num) / alloc_den; | |
| 635 | const test_end = symtab.symoff + increased_size; | |
| 636 | if (end > symtab.symoff and start < test_end) { | |
| 637 | return test_end; | |
| 638 | } | |
| 639 | } | |
| 640 | { | |
| 641 | const increased_size = satMul(symtab.strsize, alloc_num) / alloc_den; | |
| 642 | const test_end = symtab.stroff + increased_size; | |
| 643 | if (end > symtab.stroff and start < test_end) { | |
| 644 | return test_end; | |
| 645 | } | |
| 646 | } | |
| 647 | } | |
| 648 | ||
| 649 | return null; | |
| 650 | } | |
| 651 | ||
| 652 | fn allocatedSize(self: *MachO, start: u64) u64 { | |
| 653 | if (start == 0) | |
| 654 | return 0; | |
| 655 | var min_pos: u64 = std.math.maxInt(u64); | |
| 656 | { | |
| 657 | const off = @sizeOf(macho.mach_header_64); | |
| 658 | if (off > start and off < min_pos) min_pos = off; | |
| 659 | } | |
| 660 | for (self.sections.items) |section| { | |
| 661 | if (section.offset <= start) continue; | |
| 662 | if (section.offset < min_pos) min_pos = section.offset; | |
| 663 | } | |
| 664 | if (self.symtab_cmd_index) |symtab_index| { | |
| 665 | const symtab = self.load_commands.items[symtab_index].Symtab; | |
| 666 | if (symtab.symoff > start and symtab.symoff < min_pos) min_pos = symtab.symoff; | |
| 667 | if (symtab.stroff > start and symtab.stroff < min_pos) min_pos = symtab.stroff; | |
| 668 | } | |
| 669 | return min_pos - start; | |
| 670 | } | |
| 671 | ||
| 672 | fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u16) u64 { | |
| 673 | var start: u64 = 0; | |
| 674 | while (self.detectAllocCollision(start, object_size)) |item_end| { | |
| 675 | start = mem.alignForwardGeneric(u64, item_end, min_alignment); | |
| 676 | } | |
| 677 | return start; | |
| 678 | } | |
| 679 | ||
| 680 | fn writeSymbol(self: *MachO, index: usize) !void { | |
| 681 | const tracy = trace(@src()); | |
| 682 | defer tracy.end(); | |
| 683 | ||
| 684 | const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; | |
| 685 | var sym = [1]macho.nlist_64{self.symbol_table.items[index]}; | |
| 686 | const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; | |
| 687 | log.debug("writing symbol {} at 0x{x}\n", .{ sym[0], off }); | |
| 688 | try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); | |
| 689 | } | |
| 690 | ||
| 691 | /// Writes Mach-O file header. | |
| 692 | /// Should be invoked last as it needs up-to-date values of ncmds and sizeof_cmds bookkeeping | |
| 693 | /// variables. | |
| 694 | fn writeMachOHeader(self: *MachO) !void { | |
| 695 | var hdr: macho.mach_header_64 = undefined; | |
| 696 | hdr.magic = macho.MH_MAGIC_64; | |
| 697 | ||
| 698 | const CpuInfo = struct { | |
| 699 | cpu_type: macho.cpu_type_t, | |
| 700 | cpu_subtype: macho.cpu_subtype_t, | |
| 701 | }; | |
| 702 | ||
| 703 | const cpu_info: CpuInfo = switch (self.base.options.target.cpu.arch) { | |
| 704 | .aarch64 => .{ | |
| 705 | .cpu_type = macho.CPU_TYPE_ARM64, | |
| 706 | .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL, | |
| 707 | }, | |
| 708 | .x86_64 => .{ | |
| 709 | .cpu_type = macho.CPU_TYPE_X86_64, | |
| 710 | .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL, | |
| 711 | }, | |
| 712 | else => return error.UnsupportedMachOArchitecture, | |
| 713 | }; | |
| 714 | hdr.cputype = cpu_info.cpu_type; | |
| 715 | hdr.cpusubtype = cpu_info.cpu_subtype; | |
| 716 | ||
| 717 | const filetype: u32 = switch (self.base.options.output_mode) { | |
| 718 | .Exe => macho.MH_EXECUTE, | |
| 719 | .Obj => macho.MH_OBJECT, | |
| 720 | .Lib => switch (self.base.options.link_mode) { | |
| 721 | .Static => return error.TODOStaticLibMachOType, | |
| 722 | .Dynamic => macho.MH_DYLIB, | |
| 723 | }, | |
| 724 | }; | |
| 725 | hdr.filetype = filetype; | |
| 726 | hdr.ncmds = @intCast(u32, self.load_commands.items.len); | |
| 727 | ||
| 728 | var sizeofcmds: u32 = 0; | |
| 729 | for (self.load_commands.items) |cmd| { | |
| 730 | sizeofcmds += cmd.cmdsize(); | |
| 731 | } | |
| 732 | ||
| 733 | hdr.sizeofcmds = sizeofcmds; | |
| 734 | ||
| 735 | // TODO should these be set to something else? | |
| 736 | hdr.flags = 0; | |
| 737 | hdr.reserved = 0; | |
| 738 | ||
| 739 | log.debug("writing Mach-O header {}\n", .{hdr}); | |
| 740 | ||
| 741 | try self.base.file.?.pwriteAll(@ptrCast([*]const u8, &hdr)[0..@sizeOf(macho.mach_header_64)], 0); | |
| 742 | } | |
| 743 | ||
| 744 | /// Saturating multiplication | |
| 745 | fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { | |
| 746 | const T = @TypeOf(a, b); | |
| 747 | return std.math.mul(T, a, b) catch std.math.maxInt(T); | |
| 748 | } |
src-self-hosted/liveness.zig+26-15| ... | ... | @@ -15,7 +15,7 @@ pub fn analyze( |
| 15 | 15 | |
| 16 | 16 | var table = std.AutoHashMap(*ir.Inst, void).init(gpa); |
| 17 | 17 | defer table.deinit(); |
| 18 | try table.ensureCapacity(body.instructions.len); | |
| 18 | try table.ensureCapacity(@intCast(u32, body.instructions.len)); | |
| 19 | 19 | try analyzeWithTable(arena, &table, null, body); |
| 20 | 20 | } |
| 21 | 21 | |
| ... | ... | @@ -84,8 +84,11 @@ fn analyzeInst( |
| 84 | 84 | try analyzeWithTable(arena, table, &then_table, inst.then_body); |
| 85 | 85 | |
| 86 | 86 | // Reset the table back to its state from before the branch. |
| 87 | for (then_table.items()) |entry| { | |
| 88 | table.removeAssertDiscard(entry.key); | |
| 87 | { | |
| 88 | var it = then_table.iterator(); | |
| 89 | while (it.next()) |entry| { | |
| 90 | table.removeAssertDiscard(entry.key); | |
| 91 | } | |
| 89 | 92 | } |
| 90 | 93 | |
| 91 | 94 | var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); |
| ... | ... | @@ -97,28 +100,36 @@ fn analyzeInst( |
| 97 | 100 | var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); |
| 98 | 101 | defer else_entry_deaths.deinit(); |
| 99 | 102 | |
| 100 | for (else_table.items()) |entry| { | |
| 101 | const else_death = entry.key; | |
| 102 | if (!then_table.contains(else_death)) { | |
| 103 | try then_entry_deaths.append(else_death); | |
| 103 | { | |
| 104 | var it = else_table.iterator(); | |
| 105 | while (it.next()) |entry| { | |
| 106 | const else_death = entry.key; | |
| 107 | if (!then_table.contains(else_death)) { | |
| 108 | try then_entry_deaths.append(else_death); | |
| 109 | } | |
| 104 | 110 | } |
| 105 | 111 | } |
| 106 | 112 | // This loop is the same, except it's for the then branch, and it additionally |
| 107 | 113 | // has to put its items back into the table to undo the reset. |
| 108 | for (then_table.items()) |entry| { | |
| 109 | const then_death = entry.key; | |
| 110 | if (!else_table.contains(then_death)) { | |
| 111 | try else_entry_deaths.append(then_death); | |
| 114 | { | |
| 115 | var it = then_table.iterator(); | |
| 116 | while (it.next()) |entry| { | |
| 117 | const then_death = entry.key; | |
| 118 | if (!else_table.contains(then_death)) { | |
| 119 | try else_entry_deaths.append(then_death); | |
| 120 | } | |
| 121 | _ = try table.put(then_death, {}); | |
| 112 | 122 | } |
| 113 | _ = try table.put(then_death, {}); | |
| 114 | 123 | } |
| 115 | 124 | // Now we have to correctly populate new_set. |
| 116 | 125 | if (new_set) |ns| { |
| 117 | try ns.ensureCapacity(ns.items().len + then_table.items().len + else_table.items().len); | |
| 118 | for (then_table.items()) |entry| { | |
| 126 | try ns.ensureCapacity(@intCast(u32, ns.count() + then_table.count() + else_table.count())); | |
| 127 | var it = then_table.iterator(); | |
| 128 | while (it.next()) |entry| { | |
| 119 | 129 | _ = ns.putAssumeCapacity(entry.key, {}); |
| 120 | 130 | } |
| 121 | for (else_table.items()) |entry| { | |
| 131 | it = else_table.iterator(); | |
| 132 | while (it.next()) |entry| { | |
| 122 | 133 | _ = ns.putAssumeCapacity(entry.key, {}); |
| 123 | 134 | } |
| 124 | 135 | } |
src-self-hosted/main.zig+2-1| ... | ... | @@ -839,7 +839,8 @@ fn fmtPathFile( |
| 839 | 839 | // As a heuristic, we make enough capacity for the same as the input source. |
| 840 | 840 | try fmt.out_buffer.ensureCapacity(source_code.len); |
| 841 | 841 | fmt.out_buffer.items.len = 0; |
| 842 | const anything_changed = try std.zig.render(fmt.gpa, fmt.out_buffer.writer(), tree); | |
| 842 | const writer = fmt.out_buffer.writer(); | |
| 843 | const anything_changed = try std.zig.render(fmt.gpa, writer, tree); | |
| 843 | 844 | if (!anything_changed) |
| 844 | 845 | return; // Good thing we didn't waste any file system access on this. |
| 845 | 846 |
src-self-hosted/test.zig+11-11| ... | ... | @@ -474,15 +474,15 @@ pub const TestContext = struct { |
| 474 | 474 | var all_errors = try module.getAllErrorsAlloc(); |
| 475 | 475 | defer all_errors.deinit(allocator); |
| 476 | 476 | if (all_errors.list.len != 0) { |
| 477 | std.debug.warn("\nErrors occurred updating the module:\n================\n", .{}); | |
| 477 | std.debug.print("\nErrors occurred updating the module:\n================\n", .{}); | |
| 478 | 478 | for (all_errors.list) |err| { |
| 479 | std.debug.warn(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg }); | |
| 479 | std.debug.print(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg }); | |
| 480 | 480 | } |
| 481 | 481 | if (case.cbe) { |
| 482 | 482 | const C = module.bin_file.cast(link.File.C).?; |
| 483 | std.debug.warn("Generated C: \n===============\n{}\n\n===========\n\n", .{C.main.items}); | |
| 483 | std.debug.print("Generated C: \n===============\n{}\n\n===========\n\n", .{C.main.items}); | |
| 484 | 484 | } |
| 485 | std.debug.warn("Test failed.\n", .{}); | |
| 485 | std.debug.print("Test failed.\n", .{}); | |
| 486 | 486 | std.process.exit(1); |
| 487 | 487 | } |
| 488 | 488 | } |
| ... | ... | @@ -497,12 +497,12 @@ pub const TestContext = struct { |
| 497 | 497 | var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!"); |
| 498 | 498 | |
| 499 | 499 | if (expected_output.len != out.len) { |
| 500 | std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); | |
| 500 | std.debug.print("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); | |
| 501 | 501 | std.process.exit(1); |
| 502 | 502 | } |
| 503 | 503 | for (expected_output) |e, i| { |
| 504 | 504 | if (out[i] != e) { |
| 505 | std.debug.warn("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); | |
| 505 | std.debug.print("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); | |
| 506 | 506 | std.process.exit(1); |
| 507 | 507 | } |
| 508 | 508 | } |
| ... | ... | @@ -526,12 +526,12 @@ pub const TestContext = struct { |
| 526 | 526 | defer test_node.end(); |
| 527 | 527 | |
| 528 | 528 | if (expected_output.len != out_zir.items.len) { |
| 529 | std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); | |
| 529 | std.debug.print("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); | |
| 530 | 530 | std.process.exit(1); |
| 531 | 531 | } |
| 532 | 532 | for (expected_output) |e, i| { |
| 533 | 533 | if (out_zir.items[i] != e) { |
| 534 | std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); | |
| 534 | std.debug.print("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); | |
| 535 | 535 | std.process.exit(1); |
| 536 | 536 | } |
| 537 | 537 | } |
| ... | ... | @@ -554,7 +554,7 @@ pub const TestContext = struct { |
| 554 | 554 | break; |
| 555 | 555 | } |
| 556 | 556 | } else { |
| 557 | std.debug.warn("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); | |
| 557 | std.debug.print("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); | |
| 558 | 558 | std.process.exit(1); |
| 559 | 559 | } |
| 560 | 560 | } |
| ... | ... | @@ -562,7 +562,7 @@ pub const TestContext = struct { |
| 562 | 562 | for (handled_errors) |h, i| { |
| 563 | 563 | if (!h) { |
| 564 | 564 | const er = e[i]; |
| 565 | std.debug.warn("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg }); | |
| 565 | std.debug.print("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg }); | |
| 566 | 566 | std.process.exit(1); |
| 567 | 567 | } |
| 568 | 568 | } |
| ... | ... | @@ -643,7 +643,7 @@ pub const TestContext = struct { |
| 643 | 643 | switch (exec_result.term) { |
| 644 | 644 | .Exited => |code| { |
| 645 | 645 | if (code != 0) { |
| 646 | std.debug.warn("elf file exited with code {}\n", .{code}); | |
| 646 | std.debug.print("elf file exited with code {}\n", .{code}); | |
| 647 | 647 | return error.BinaryBadExitCode; |
| 648 | 648 | } |
| 649 | 649 | }, |
src-self-hosted/translate_c.zig+6-19| ... | ... | @@ -19,23 +19,9 @@ pub const Error = error{OutOfMemory}; |
| 19 | 19 | const TypeError = Error || error{UnsupportedType}; |
| 20 | 20 | const TransError = TypeError || error{UnsupportedTranslation}; |
| 21 | 21 | |
| 22 | const DeclTable = std.HashMap(usize, []const u8, addrHash, addrEql, false); | |
| 22 | const DeclTable = std.AutoArrayHashMap(usize, []const u8); | |
| 23 | 23 | |
| 24 | fn addrHash(x: usize) u32 { | |
| 25 | switch (@typeInfo(usize).Int.bits) { | |
| 26 | 32 => return x, | |
| 27 | // pointers are usually aligned so we ignore the bits that are probably all 0 anyway | |
| 28 | // usually the larger bits of addr space are unused so we just chop em off | |
| 29 | 64 => return @truncate(u32, x >> 4), | |
| 30 | else => @compileError("unreachable"), | |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | fn addrEql(a: usize, b: usize) bool { | |
| 35 | return a == b; | |
| 36 | } | |
| 37 | ||
| 38 | const SymbolTable = std.StringHashMap(*ast.Node); | |
| 24 | const SymbolTable = std.StringArrayHashMap(*ast.Node); | |
| 39 | 25 | const AliasList = std.ArrayList(struct { |
| 40 | 26 | alias: []const u8, |
| 41 | 27 | name: []const u8, |
| ... | ... | @@ -285,7 +271,7 @@ pub const Context = struct { |
| 285 | 271 | /// a list of names that we found by visiting all the top level decls without |
| 286 | 272 | /// translating them. The other maps are updated as we translate; this one is updated |
| 287 | 273 | /// up front in a pre-processing step. |
| 288 | global_names: std.StringHashMap(void), | |
| 274 | global_names: std.StringArrayHashMap(void), | |
| 289 | 275 | |
| 290 | 276 | fn getMangle(c: *Context) u32 { |
| 291 | 277 | c.mangle_count += 1; |
| ... | ... | @@ -380,7 +366,7 @@ pub fn translate( |
| 380 | 366 | .alias_list = AliasList.init(gpa), |
| 381 | 367 | .global_scope = try arena.allocator.create(Scope.Root), |
| 382 | 368 | .clang_context = ZigClangASTUnit_getASTContext(ast_unit).?, |
| 383 | .global_names = std.StringHashMap(void).init(gpa), | |
| 369 | .global_names = std.StringArrayHashMap(void).init(gpa), | |
| 384 | 370 | .token_ids = .{}, |
| 385 | 371 | .token_locs = .{}, |
| 386 | 372 | .errors = .{}, |
| ... | ... | @@ -6424,7 +6410,8 @@ fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto { |
| 6424 | 6410 | } |
| 6425 | 6411 | |
| 6426 | 6412 | fn addMacros(c: *Context) !void { |
| 6427 | for (c.global_scope.macro_table.items()) |kv| { | |
| 6413 | var it = c.global_scope.macro_table.iterator(); | |
| 6414 | while (it.next()) |kv| { | |
| 6428 | 6415 | if (getFnProto(c, kv.value)) |proto_node| { |
| 6429 | 6416 | // If a macro aliases a global variable which is a function pointer, we conclude that |
| 6430 | 6417 | // the macro is intended to represent a function that assumes the function pointer |
src-self-hosted/type.zig+100-22| ... | ... | @@ -163,7 +163,7 @@ pub const Type = extern union { |
| 163 | 163 | // Hot path for common case: |
| 164 | 164 | if (a.castPointer()) |a_payload| { |
| 165 | 165 | if (b.castPointer()) |b_payload| { |
| 166 | return eql(a_payload.pointee_type, b_payload.pointee_type); | |
| 166 | return a.tag() == b.tag() and eql(a_payload.pointee_type, b_payload.pointee_type); | |
| 167 | 167 | } |
| 168 | 168 | } |
| 169 | 169 | const is_slice_a = isSlice(a); |
| ... | ... | @@ -189,10 +189,10 @@ pub const Type = extern union { |
| 189 | 189 | .Array => { |
| 190 | 190 | if (a.arrayLen() != b.arrayLen()) |
| 191 | 191 | return false; |
| 192 | if (a.elemType().eql(b.elemType())) | |
| 192 | if (!a.elemType().eql(b.elemType())) | |
| 193 | 193 | return false; |
| 194 | const sentinel_a = a.arraySentinel(); | |
| 195 | const sentinel_b = b.arraySentinel(); | |
| 194 | const sentinel_a = a.sentinel(); | |
| 195 | const sentinel_b = b.sentinel(); | |
| 196 | 196 | if (sentinel_a) |sa| { |
| 197 | 197 | if (sentinel_b) |sb| { |
| 198 | 198 | return sa.eql(sb); |
| ... | ... | @@ -238,7 +238,7 @@ pub const Type = extern union { |
| 238 | 238 | } |
| 239 | 239 | } |
| 240 | 240 | |
| 241 | pub fn hash(self: Type) u32 { | |
| 241 | pub fn hash(self: Type) u64 { | |
| 242 | 242 | var hasher = std.hash.Wyhash.init(0); |
| 243 | 243 | const zig_type_tag = self.zigTypeTag(); |
| 244 | 244 | std.hash.autoHash(&hasher, zig_type_tag); |
| ... | ... | @@ -303,7 +303,7 @@ pub const Type = extern union { |
| 303 | 303 | // TODO implement more type hashing |
| 304 | 304 | }, |
| 305 | 305 | } |
| 306 | return @truncate(u32, hasher.final()); | |
| 306 | return hasher.final(); | |
| 307 | 307 | } |
| 308 | 308 | |
| 309 | 309 | pub fn copy(self: Type, allocator: *Allocator) error{OutOfMemory}!Type { |
| ... | ... | @@ -501,9 +501,9 @@ pub const Type = extern union { |
| 501 | 501 | .noreturn, |
| 502 | 502 | => return out_stream.writeAll(@tagName(t)), |
| 503 | 503 | |
| 504 | .enum_literal => return out_stream.writeAll("@TypeOf(.EnumLiteral)"), | |
| 505 | .@"null" => return out_stream.writeAll("@TypeOf(null)"), | |
| 506 | .@"undefined" => return out_stream.writeAll("@TypeOf(undefined)"), | |
| 504 | .enum_literal => return out_stream.writeAll("@Type(.EnumLiteral)"), | |
| 505 | .@"null" => return out_stream.writeAll("@Type(.Null)"), | |
| 506 | .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"), | |
| 507 | 507 | |
| 508 | 508 | .@"anyframe" => return out_stream.writeAll("anyframe"), |
| 509 | 509 | .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"), |
| ... | ... | @@ -630,8 +630,8 @@ pub const Type = extern union { |
| 630 | 630 | const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise); |
| 631 | 631 | if (payload.sentinel) |some| switch (payload.size) { |
| 632 | 632 | .One, .C => unreachable, |
| 633 | .Many => try out_stream.writeAll("[*:{}]"), | |
| 634 | .Slice => try out_stream.writeAll("[:{}]"), | |
| 633 | .Many => try out_stream.print("[*:{}]", .{some}), | |
| 634 | .Slice => try out_stream.print("[:{}]", .{some}), | |
| 635 | 635 | } else switch (payload.size) { |
| 636 | 636 | .One => try out_stream.writeAll("*"), |
| 637 | 637 | .Many => try out_stream.writeAll("[*]"), |
| ... | ... | @@ -1341,6 +1341,81 @@ pub const Type = extern union { |
| 1341 | 1341 | }; |
| 1342 | 1342 | } |
| 1343 | 1343 | |
| 1344 | pub fn isAllowzeroPtr(self: Type) bool { | |
| 1345 | return switch (self.tag()) { | |
| 1346 | .u8, | |
| 1347 | .i8, | |
| 1348 | .u16, | |
| 1349 | .i16, | |
| 1350 | .u32, | |
| 1351 | .i32, | |
| 1352 | .u64, | |
| 1353 | .i64, | |
| 1354 | .usize, | |
| 1355 | .isize, | |
| 1356 | .c_short, | |
| 1357 | .c_ushort, | |
| 1358 | .c_int, | |
| 1359 | .c_uint, | |
| 1360 | .c_long, | |
| 1361 | .c_ulong, | |
| 1362 | .c_longlong, | |
| 1363 | .c_ulonglong, | |
| 1364 | .c_longdouble, | |
| 1365 | .f16, | |
| 1366 | .f32, | |
| 1367 | .f64, | |
| 1368 | .f128, | |
| 1369 | .c_void, | |
| 1370 | .bool, | |
| 1371 | .void, | |
| 1372 | .type, | |
| 1373 | .anyerror, | |
| 1374 | .comptime_int, | |
| 1375 | .comptime_float, | |
| 1376 | .noreturn, | |
| 1377 | .@"null", | |
| 1378 | .@"undefined", | |
| 1379 | .array, | |
| 1380 | .array_sentinel, | |
| 1381 | .array_u8, | |
| 1382 | .array_u8_sentinel_0, | |
| 1383 | .fn_noreturn_no_args, | |
| 1384 | .fn_void_no_args, | |
| 1385 | .fn_naked_noreturn_no_args, | |
| 1386 | .fn_ccc_void_no_args, | |
| 1387 | .function, | |
| 1388 | .int_unsigned, | |
| 1389 | .int_signed, | |
| 1390 | .single_mut_pointer, | |
| 1391 | .single_const_pointer, | |
| 1392 | .many_const_pointer, | |
| 1393 | .many_mut_pointer, | |
| 1394 | .c_const_pointer, | |
| 1395 | .c_mut_pointer, | |
| 1396 | .const_slice, | |
| 1397 | .mut_slice, | |
| 1398 | .single_const_pointer_to_comptime_int, | |
| 1399 | .const_slice_u8, | |
| 1400 | .optional, | |
| 1401 | .optional_single_mut_pointer, | |
| 1402 | .optional_single_const_pointer, | |
| 1403 | .enum_literal, | |
| 1404 | .error_union, | |
| 1405 | .@"anyframe", | |
| 1406 | .anyframe_T, | |
| 1407 | .anyerror_void_error_union, | |
| 1408 | .error_set, | |
| 1409 | .error_set_single, | |
| 1410 | => false, | |
| 1411 | ||
| 1412 | .pointer => { | |
| 1413 | const payload = @fieldParentPtr(Payload.Pointer, "base", self.ptr_otherwise); | |
| 1414 | return payload.@"allowzero"; | |
| 1415 | }, | |
| 1416 | }; | |
| 1417 | } | |
| 1418 | ||
| 1344 | 1419 | /// Asserts that the type is an optional |
| 1345 | 1420 | pub fn isPtrLikeOptional(self: Type) bool { |
| 1346 | 1421 | switch (self.tag()) { |
| ... | ... | @@ -1585,8 +1660,8 @@ pub const Type = extern union { |
| 1585 | 1660 | }; |
| 1586 | 1661 | } |
| 1587 | 1662 | |
| 1588 | /// Asserts the type is an array or vector. | |
| 1589 | pub fn arraySentinel(self: Type) ?Value { | |
| 1663 | /// Asserts the type is an array, pointer or vector. | |
| 1664 | pub fn sentinel(self: Type) ?Value { | |
| 1590 | 1665 | return switch (self.tag()) { |
| 1591 | 1666 | .u8, |
| 1592 | 1667 | .i8, |
| ... | ... | @@ -1626,16 +1701,8 @@ pub const Type = extern union { |
| 1626 | 1701 | .fn_naked_noreturn_no_args, |
| 1627 | 1702 | .fn_ccc_void_no_args, |
| 1628 | 1703 | .function, |
| 1629 | .pointer, | |
| 1630 | .single_const_pointer, | |
| 1631 | .single_mut_pointer, | |
| 1632 | .many_const_pointer, | |
| 1633 | .many_mut_pointer, | |
| 1634 | .c_const_pointer, | |
| 1635 | .c_mut_pointer, | |
| 1636 | 1704 | .const_slice, |
| 1637 | 1705 | .mut_slice, |
| 1638 | .single_const_pointer_to_comptime_int, | |
| 1639 | 1706 | .const_slice_u8, |
| 1640 | 1707 | .int_unsigned, |
| 1641 | 1708 | .int_signed, |
| ... | ... | @@ -1651,7 +1718,18 @@ pub const Type = extern union { |
| 1651 | 1718 | .error_set_single, |
| 1652 | 1719 | => unreachable, |
| 1653 | 1720 | |
| 1654 | .array, .array_u8 => return null, | |
| 1721 | .single_const_pointer, | |
| 1722 | .single_mut_pointer, | |
| 1723 | .many_const_pointer, | |
| 1724 | .many_mut_pointer, | |
| 1725 | .c_const_pointer, | |
| 1726 | .c_mut_pointer, | |
| 1727 | .single_const_pointer_to_comptime_int, | |
| 1728 | .array, | |
| 1729 | .array_u8, | |
| 1730 | => return null, | |
| 1731 | ||
| 1732 | .pointer => return self.cast(Payload.Pointer).?.sentinel, | |
| 1655 | 1733 | .array_sentinel => return self.cast(Payload.ArraySentinel).?.sentinel, |
| 1656 | 1734 | .array_u8_sentinel_0 => return Value.initTag(.zero), |
| 1657 | 1735 | }; |
src-self-hosted/value.zig+5-4| ... | ... | @@ -301,15 +301,15 @@ pub const Value = extern union { |
| 301 | 301 | .comptime_int_type => return out_stream.writeAll("comptime_int"), |
| 302 | 302 | .comptime_float_type => return out_stream.writeAll("comptime_float"), |
| 303 | 303 | .noreturn_type => return out_stream.writeAll("noreturn"), |
| 304 | .null_type => return out_stream.writeAll("@TypeOf(null)"), | |
| 305 | .undefined_type => return out_stream.writeAll("@TypeOf(undefined)"), | |
| 304 | .null_type => return out_stream.writeAll("@Type(.Null)"), | |
| 305 | .undefined_type => return out_stream.writeAll("@Type(.Undefined)"), | |
| 306 | 306 | .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"), |
| 307 | 307 | .fn_void_no_args_type => return out_stream.writeAll("fn() void"), |
| 308 | 308 | .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), |
| 309 | 309 | .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"), |
| 310 | 310 | .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), |
| 311 | 311 | .const_slice_u8_type => return out_stream.writeAll("[]const u8"), |
| 312 | .enum_literal_type => return out_stream.writeAll("@TypeOf(.EnumLiteral)"), | |
| 312 | .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"), | |
| 313 | 313 | .anyframe_type => return out_stream.writeAll("anyframe"), |
| 314 | 314 | |
| 315 | 315 | .null_value => return out_stream.writeAll("null"), |
| ... | ... | @@ -358,7 +358,8 @@ pub const Value = extern union { |
| 358 | 358 | .error_set => { |
| 359 | 359 | const error_set = val.cast(Payload.ErrorSet).?; |
| 360 | 360 | try out_stream.writeAll("error{"); |
| 361 | for (error_set.fields.items()) |entry| { | |
| 361 | var it = error_set.fields.iterator(); | |
| 362 | while (it.next()) |entry| { | |
| 362 | 363 | try out_stream.print("{},", .{entry.value}); |
| 363 | 364 | } |
| 364 | 365 | return out_stream.writeAll("}"); |
src-self-hosted/zir.zig+42-5| ... | ... | @@ -78,6 +78,13 @@ pub const Inst = struct { |
| 78 | 78 | bitor, |
| 79 | 79 | /// A labeled block of code, which can return a value. |
| 80 | 80 | block, |
| 81 | /// A block of code, which can return a value. There are no instructions that break out of | |
| 82 | /// this block; it is implied that the final instruction is the result. | |
| 83 | block_flat, | |
| 84 | /// Same as `block` but additionally makes the inner instructions execute at comptime. | |
| 85 | block_comptime, | |
| 86 | /// Same as `block_flat` but additionally makes the inner instructions execute at comptime. | |
| 87 | block_comptime_flat, | |
| 81 | 88 | /// Boolean NOT. See also `bitnot`. |
| 82 | 89 | boolnot, |
| 83 | 90 | /// Return a value from a `Block`. |
| ... | ... | @@ -224,6 +231,10 @@ pub const Inst = struct { |
| 224 | 231 | const_slice_type, |
| 225 | 232 | /// Create a pointer type with attributes |
| 226 | 233 | ptr_type, |
| 234 | /// Slice operation `array_ptr[start..end:sentinel]` | |
| 235 | slice, | |
| 236 | /// Slice operation with just start `lhs[rhs..]` | |
| 237 | slice_start, | |
| 227 | 238 | /// Write a value to a pointer. For loading, see `deref`. |
| 228 | 239 | store, |
| 229 | 240 | /// String Literal. Makes an anonymous Decl and then takes a pointer to it. |
| ... | ... | @@ -336,11 +347,17 @@ pub const Inst = struct { |
| 336 | 347 | .xor, |
| 337 | 348 | .error_union_type, |
| 338 | 349 | .merge_error_sets, |
| 350 | .slice_start, | |
| 339 | 351 | => BinOp, |
| 340 | 352 | |
| 353 | .block, | |
| 354 | .block_flat, | |
| 355 | .block_comptime, | |
| 356 | .block_comptime_flat, | |
| 357 | => Block, | |
| 358 | ||
| 341 | 359 | .arg => Arg, |
| 342 | 360 | .array_type_sentinel => ArrayTypeSentinel, |
| 343 | .block => Block, | |
| 344 | 361 | .@"break" => Break, |
| 345 | 362 | .breakvoid => BreakVoid, |
| 346 | 363 | .call => Call, |
| ... | ... | @@ -368,6 +385,7 @@ pub const Inst = struct { |
| 368 | 385 | .ptr_type => PtrType, |
| 369 | 386 | .enum_literal => EnumLiteral, |
| 370 | 387 | .error_set => ErrorSet, |
| 388 | .slice => Slice, | |
| 371 | 389 | }; |
| 372 | 390 | } |
| 373 | 391 | |
| ... | ... | @@ -392,6 +410,9 @@ pub const Inst = struct { |
| 392 | 410 | .bitcast_result_ptr, |
| 393 | 411 | .bitor, |
| 394 | 412 | .block, |
| 413 | .block_flat, | |
| 414 | .block_comptime, | |
| 415 | .block_comptime_flat, | |
| 395 | 416 | .boolnot, |
| 396 | 417 | .breakpoint, |
| 397 | 418 | .call, |
| ... | ... | @@ -466,6 +487,8 @@ pub const Inst = struct { |
| 466 | 487 | .error_union_type, |
| 467 | 488 | .bitnot, |
| 468 | 489 | .error_set, |
| 490 | .slice, | |
| 491 | .slice_start, | |
| 469 | 492 | => false, |
| 470 | 493 | |
| 471 | 494 | .@"break", |
| ... | ... | @@ -946,6 +969,20 @@ pub const Inst = struct { |
| 946 | 969 | }, |
| 947 | 970 | kw_args: struct {}, |
| 948 | 971 | }; |
| 972 | ||
| 973 | pub const Slice = struct { | |
| 974 | pub const base_tag = Tag.slice; | |
| 975 | base: Inst, | |
| 976 | ||
| 977 | positionals: struct { | |
| 978 | array_ptr: *Inst, | |
| 979 | start: *Inst, | |
| 980 | }, | |
| 981 | kw_args: struct { | |
| 982 | end: ?*Inst = null, | |
| 983 | sentinel: ?*Inst = null, | |
| 984 | }, | |
| 985 | }; | |
| 949 | 986 | }; |
| 950 | 987 | |
| 951 | 988 | pub const ErrorMsg = struct { |
| ... | ... | @@ -1034,7 +1071,7 @@ pub const Module = struct { |
| 1034 | 1071 | defer write.loop_table.deinit(); |
| 1035 | 1072 | |
| 1036 | 1073 | // First, build a map of *Inst to @ or % indexes |
| 1037 | try write.inst_table.ensureCapacity(self.decls.len); | |
| 1074 | try write.inst_table.ensureCapacity(@intCast(u32, self.decls.len)); | |
| 1038 | 1075 | |
| 1039 | 1076 | for (self.decls) |decl, decl_i| { |
| 1040 | 1077 | try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); |
| ... | ... | @@ -1670,7 +1707,7 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { |
| 1670 | 1707 | .arena = std.heap.ArenaAllocator.init(allocator), |
| 1671 | 1708 | .old_module = &old_module, |
| 1672 | 1709 | .next_auto_name = 0, |
| 1673 | .names = std.StringHashMap(void).init(allocator), | |
| 1710 | .names = std.StringArrayHashMap(void).init(allocator), | |
| 1674 | 1711 | .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), |
| 1675 | 1712 | .indent = 0, |
| 1676 | 1713 | .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), |
| ... | ... | @@ -1743,7 +1780,7 @@ const EmitZIR = struct { |
| 1743 | 1780 | arena: std.heap.ArenaAllocator, |
| 1744 | 1781 | old_module: *const IrModule, |
| 1745 | 1782 | decls: std.ArrayListUnmanaged(*Decl), |
| 1746 | names: std.StringHashMap(void), | |
| 1783 | names: std.StringArrayHashMap(void), | |
| 1747 | 1784 | next_auto_name: usize, |
| 1748 | 1785 | primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), |
| 1749 | 1786 | indent: usize, |
| ... | ... | @@ -2559,7 +2596,7 @@ const EmitZIR = struct { |
| 2559 | 2596 | var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() }; |
| 2560 | 2597 | const len = Value.initPayload(&len_pl.base); |
| 2561 | 2598 | |
| 2562 | const inst = if (ty.arraySentinel()) |sentinel| blk: { | |
| 2599 | const inst = if (ty.sentinel()) |sentinel| blk: { | |
| 2563 | 2600 | const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel); |
| 2564 | 2601 | inst.* = .{ |
| 2565 | 2602 | .base = .{ |
src-self-hosted/zir_sema.zig+95-17| ... | ... | @@ -31,7 +31,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! |
| 31 | 31 | .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?), |
| 32 | 32 | .bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?), |
| 33 | 33 | .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?), |
| 34 | .block => return analyzeInstBlock(mod, scope, old_inst.castTag(.block).?), | |
| 34 | .block => return analyzeInstBlock(mod, scope, old_inst.castTag(.block).?, false), | |
| 35 | .block_comptime => return analyzeInstBlock(mod, scope, old_inst.castTag(.block_comptime).?, true), | |
| 36 | .block_flat => return analyzeInstBlockFlat(mod, scope, old_inst.castTag(.block_flat).?, false), | |
| 37 | .block_comptime_flat => return analyzeInstBlockFlat(mod, scope, old_inst.castTag(.block_comptime_flat).?, true), | |
| 35 | 38 | .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?), |
| 36 | 39 | .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?), |
| 37 | 40 | .breakvoid => return analyzeInstBreakVoid(mod, scope, old_inst.castTag(.breakvoid).?), |
| ... | ... | @@ -129,6 +132,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! |
| 129 | 132 | .error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?), |
| 130 | 133 | .anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?), |
| 131 | 134 | .error_set => return analyzeInstErrorSet(mod, scope, old_inst.castTag(.error_set).?), |
| 135 | .slice => return analyzeInstSlice(mod, scope, old_inst.castTag(.slice).?), | |
| 136 | .slice_start => return analyzeInstSliceStart(mod, scope, old_inst.castTag(.slice_start).?), | |
| 132 | 137 | } |
| 133 | 138 | } |
| 134 | 139 | |
| ... | ... | @@ -147,17 +152,16 @@ pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void { |
| 147 | 152 | } |
| 148 | 153 | } |
| 149 | 154 | |
| 150 | pub fn analyzeBodyValueAsType(mod: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type { | |
| 155 | pub fn analyzeBodyValueAsType( | |
| 156 | mod: *Module, | |
| 157 | block_scope: *Scope.Block, | |
| 158 | zir_result_inst: *zir.Inst, | |
| 159 | body: zir.Module.Body, | |
| 160 | ) !Type { | |
| 151 | 161 | try analyzeBody(mod, &block_scope.base, body); |
| 152 | for (block_scope.instructions.items) |inst| { | |
| 153 | if (inst.castTag(.ret)) |ret| { | |
| 154 | const val = try mod.resolveConstValue(&block_scope.base, ret.operand); | |
| 155 | return val.toType(block_scope.base.arena()); | |
| 156 | } else { | |
| 157 | return mod.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); | |
| 158 | } | |
| 159 | } | |
| 160 | unreachable; | |
| 162 | const result_inst = zir_result_inst.analyzed_inst.?; | |
| 163 | const val = try mod.resolveConstValue(&block_scope.base, result_inst); | |
| 164 | return val.toType(block_scope.base.arena()); | |
| 161 | 165 | } |
| 162 | 166 | |
| 163 | 167 | pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { |
| ... | ... | @@ -362,7 +366,7 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError! |
| 362 | 366 | } |
| 363 | 367 | |
| 364 | 368 | fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { |
| 365 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | |
| 369 | const b = try mod.requireFunctionBlock(scope, inst.base.src); | |
| 366 | 370 | const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; |
| 367 | 371 | const ret_type = fn_ty.fnReturnType(); |
| 368 | 372 | return mod.constType(scope, inst.base.src, ret_type); |
| ... | ... | @@ -517,6 +521,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError |
| 517 | 521 | .decl = parent_block.decl, |
| 518 | 522 | .instructions = .{}, |
| 519 | 523 | .arena = parent_block.arena, |
| 524 | .is_comptime = parent_block.is_comptime, | |
| 520 | 525 | }; |
| 521 | 526 | defer child_block.instructions.deinit(mod.gpa); |
| 522 | 527 | |
| ... | ... | @@ -529,7 +534,29 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError |
| 529 | 534 | return &loop_inst.base; |
| 530 | 535 | } |
| 531 | 536 | |
| 532 | fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { | |
| 537 | fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst { | |
| 538 | const parent_block = scope.cast(Scope.Block).?; | |
| 539 | ||
| 540 | var child_block: Scope.Block = .{ | |
| 541 | .parent = parent_block, | |
| 542 | .func = parent_block.func, | |
| 543 | .decl = parent_block.decl, | |
| 544 | .instructions = .{}, | |
| 545 | .arena = parent_block.arena, | |
| 546 | .label = null, | |
| 547 | .is_comptime = parent_block.is_comptime or is_comptime, | |
| 548 | }; | |
| 549 | defer child_block.instructions.deinit(mod.gpa); | |
| 550 | ||
| 551 | try analyzeBody(mod, &child_block.base, inst.positionals.body); | |
| 552 | ||
| 553 | const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items); | |
| 554 | try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); | |
| 555 | ||
| 556 | return copied_instructions[copied_instructions.len - 1]; | |
| 557 | } | |
| 558 | ||
| 559 | fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst { | |
| 533 | 560 | const parent_block = scope.cast(Scope.Block).?; |
| 534 | 561 | |
| 535 | 562 | // Reserve space for a Block instruction so that generated Break instructions can |
| ... | ... | @@ -557,6 +584,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr |
| 557 | 584 | .results = .{}, |
| 558 | 585 | .block_inst = block_inst, |
| 559 | 586 | }), |
| 587 | .is_comptime = is_comptime or parent_block.is_comptime, | |
| 560 | 588 | }; |
| 561 | 589 | const label = &child_block.label.?; |
| 562 | 590 | |
| ... | ... | @@ -569,6 +597,28 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr |
| 569 | 597 | assert(child_block.instructions.items.len != 0); |
| 570 | 598 | assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); |
| 571 | 599 | |
| 600 | if (label.results.items.len == 0) { | |
| 601 | // No need for a block instruction. We can put the new instructions directly into the parent block. | |
| 602 | const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items); | |
| 603 | try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); | |
| 604 | return copied_instructions[copied_instructions.len - 1]; | |
| 605 | } | |
| 606 | if (label.results.items.len == 1) { | |
| 607 | const last_inst_index = child_block.instructions.items.len - 1; | |
| 608 | const last_inst = child_block.instructions.items[last_inst_index]; | |
| 609 | if (last_inst.breakBlock()) |br_block| { | |
| 610 | if (br_block == block_inst) { | |
| 611 | // No need for a block instruction. We can put the new instructions directly into the parent block. | |
| 612 | // Here we omit the break instruction. | |
| 613 | const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]); | |
| 614 | try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); | |
| 615 | return label.results.items[0]; | |
| 616 | } | |
| 617 | } | |
| 618 | } | |
| 619 | // It should be impossible to have the number of results be > 1 in a comptime scope. | |
| 620 | assert(!child_block.is_comptime); // We should have already got a compile error in the condbr condition. | |
| 621 | ||
| 572 | 622 | // Need to set the type and emit the Block instruction. This allows machine code generation |
| 573 | 623 | // to emit a jump instruction to after the block when it encounters the break. |
| 574 | 624 | try parent_block.instructions.append(mod.gpa, &block_inst.base); |
| ... | ... | @@ -595,8 +645,12 @@ fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) |
| 595 | 645 | } |
| 596 | 646 | |
| 597 | 647 | fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { |
| 598 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | |
| 599 | return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt); | |
| 648 | if (scope.cast(Scope.Block)) |b| { | |
| 649 | if (!b.is_comptime) { | |
| 650 | return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt); | |
| 651 | } | |
| 652 | } | |
| 653 | return mod.constVoid(scope, inst.base.src); | |
| 600 | 654 | } |
| 601 | 655 | |
| 602 | 656 | fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { |
| ... | ... | @@ -764,7 +818,7 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In |
| 764 | 818 | .fields = .{}, |
| 765 | 819 | .decl = undefined, // populated below |
| 766 | 820 | }; |
| 767 | try payload.fields.ensureCapacity(&new_decl_arena.allocator, inst.positionals.fields.len); | |
| 821 | try payload.fields.ensureCapacity(&new_decl_arena.allocator, @intCast(u32, inst.positionals.fields.len)); | |
| 768 | 822 | |
| 769 | 823 | for (inst.positionals.fields) |field_name| { |
| 770 | 824 | const entry = try mod.getErrorValue(field_name); |
| ... | ... | @@ -1083,7 +1137,7 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne |
| 1083 | 1137 | const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr); |
| 1084 | 1138 | const uncasted_index = try resolveInst(mod, scope, inst.positionals.index); |
| 1085 | 1139 | const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index); |
| 1086 | ||
| 1140 | ||
| 1087 | 1141 | const elem_ty = switch (array_ptr.ty.zigTypeTag()) { |
| 1088 | 1142 | .Pointer => array_ptr.ty.elemType(), |
| 1089 | 1143 | else => return mod.fail(scope, inst.positionals.array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}), |
| ... | ... | @@ -1120,6 +1174,22 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne |
| 1120 | 1174 | return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); |
| 1121 | 1175 | } |
| 1122 | 1176 | |
| 1177 | fn analyzeInstSlice(mod: *Module, scope: *Scope, inst: *zir.Inst.Slice) InnerError!*Inst { | |
| 1178 | const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr); | |
| 1179 | const start = try resolveInst(mod, scope, inst.positionals.start); | |
| 1180 | const end = if (inst.kw_args.end) |end| try resolveInst(mod, scope, end) else null; | |
| 1181 | const sentinel = if (inst.kw_args.sentinel) |sentinel| try resolveInst(mod, scope, sentinel) else null; | |
| 1182 | ||
| 1183 | return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, end, sentinel); | |
| 1184 | } | |
| 1185 | ||
| 1186 | fn analyzeInstSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | |
| 1187 | const array_ptr = try resolveInst(mod, scope, inst.positionals.lhs); | |
| 1188 | const start = try resolveInst(mod, scope, inst.positionals.rhs); | |
| 1189 | ||
| 1190 | return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); | |
| 1191 | } | |
| 1192 | ||
| 1123 | 1193 | fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { |
| 1124 | 1194 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{}); |
| 1125 | 1195 | } |
| ... | ... | @@ -1187,6 +1257,12 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn |
| 1187 | 1257 | |
| 1188 | 1258 | if (casted_lhs.value()) |lhs_val| { |
| 1189 | 1259 | if (casted_rhs.value()) |rhs_val| { |
| 1260 | if (lhs_val.isUndef() or rhs_val.isUndef()) { | |
| 1261 | return mod.constInst(scope, inst.base.src, .{ | |
| 1262 | .ty = resolved_type, | |
| 1263 | .val = Value.initTag(.undef), | |
| 1264 | }); | |
| 1265 | } | |
| 1190 | 1266 | return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val); |
| 1191 | 1267 | } |
| 1192 | 1268 | } |
| ... | ... | @@ -1376,6 +1452,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE |
| 1376 | 1452 | .decl = parent_block.decl, |
| 1377 | 1453 | .instructions = .{}, |
| 1378 | 1454 | .arena = parent_block.arena, |
| 1455 | .is_comptime = parent_block.is_comptime, | |
| 1379 | 1456 | }; |
| 1380 | 1457 | defer true_block.instructions.deinit(mod.gpa); |
| 1381 | 1458 | try analyzeBody(mod, &true_block.base, inst.positionals.then_body); |
| ... | ... | @@ -1386,6 +1463,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE |
| 1386 | 1463 | .decl = parent_block.decl, |
| 1387 | 1464 | .instructions = .{}, |
| 1388 | 1465 | .arena = parent_block.arena, |
| 1466 | .is_comptime = parent_block.is_comptime, | |
| 1389 | 1467 | }; |
| 1390 | 1468 | defer false_block.instructions.deinit(mod.gpa); |
| 1391 | 1469 | try analyzeBody(mod, &false_block.base, inst.positionals.else_body); |
src/ir.cpp+12-1| ... | ... | @@ -15342,9 +15342,14 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr, |
| 15342 | 15342 | ZigType *array_type = actual_type->data.pointer.child_type; |
| 15343 | 15343 | bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0 |
| 15344 | 15344 | || !actual_type->data.pointer.is_const); |
| 15345 | ||
| 15345 | 15346 | if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, |
| 15346 | 15347 | array_type->data.array.child_type, source_node, |
| 15347 | !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk) | |
| 15348 | !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk && | |
| 15349 | (slice_ptr_type->data.pointer.sentinel == nullptr || | |
| 15350 | (array_type->data.array.sentinel != nullptr && | |
| 15351 | const_values_equal(ira->codegen, array_type->data.array.sentinel, | |
| 15352 | slice_ptr_type->data.pointer.sentinel)))) | |
| 15348 | 15353 | { |
| 15349 | 15354 | // If the pointers both have ABI align, it works. |
| 15350 | 15355 | // Or if the array length is 0, alignment doesn't matter. |
| ... | ... | @@ -25684,6 +25689,10 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy |
| 25684 | 25689 | } |
| 25685 | 25690 | set_optional_payload(inner_fields[2], struct_field->init_val); |
| 25686 | 25691 | |
| 25692 | inner_fields[3]->special = ConstValSpecialStatic; | |
| 25693 | inner_fields[3]->type = ira->codegen->builtin_types.entry_bool; | |
| 25694 | inner_fields[3]->data.x_bool = struct_field->is_comptime; | |
| 25695 | ||
| 25687 | 25696 | ZigValue *name = create_const_str_lit(ira->codegen, struct_field->name)->data.x_ptr.data.ref.pointee; |
| 25688 | 25697 | init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(struct_field->name), true); |
| 25689 | 25698 | |
| ... | ... | @@ -26292,6 +26301,8 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI |
| 26292 | 26301 | buf_ptr(&field->type_entry->name), buf_ptr(&field->type_entry->name))); |
| 26293 | 26302 | return ira->codegen->invalid_inst_gen->value->type; |
| 26294 | 26303 | } |
| 26304 | if ((err = get_const_field_bool(ira, source_instr->source_node, field_value, "is_comptime", 3, &field->is_comptime))) | |
| 26305 | return ira->codegen->invalid_inst_gen->value->type; | |
| 26295 | 26306 | } |
| 26296 | 26307 | |
| 26297 | 26308 | return entry; |
test/compile_errors.zig+8| ... | ... | @@ -2,6 +2,14 @@ const tests = @import("tests.zig"); |
| 2 | 2 | const std = @import("std"); |
| 3 | 3 | |
| 4 | 4 | pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 5 | cases.add("slice sentinel mismatch", | |
| 6 | \\export fn entry() void { | |
| 7 | \\ const y: [:1]const u8 = &[_:2]u8{ 1, 2 }; | |
| 8 | \\} | |
| 9 | , &[_][]const u8{ | |
| 10 | "tmp.zig:2:37: error: expected type '[:1]const u8', found '*const [2:2]u8'", | |
| 11 | }); | |
| 12 | ||
| 5 | 13 | cases.add("@Type with undefined", |
| 6 | 14 | \\comptime { |
| 7 | 15 | \\ _ = @Type(.{ .Array = .{ .len = 0, .child = u8, .sentinel = undefined } }); |
test/stage1/behavior/type_info.zig+6| ... | ... | @@ -418,3 +418,9 @@ test "Struct.is_tuple" { |
| 418 | 418 | expect(@typeInfo(@TypeOf(.{0})).Struct.is_tuple); |
| 419 | 419 | expect(!@typeInfo(@TypeOf(.{ .a = 0 })).Struct.is_tuple); |
| 420 | 420 | } |
| 421 | ||
| 422 | test "StructField.is_comptime" { | |
| 423 | const info = @typeInfo(struct { x: u8 = 3, comptime y: u32 = 5 }).Struct; | |
| 424 | expect(!info.fields[0].is_comptime); | |
| 425 | expect(info.fields[1].is_comptime); | |
| 426 | } |
test/stage2/test.zig+16-7| ... | ... | @@ -274,7 +274,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 274 | 274 | } |
| 275 | 275 | |
| 276 | 276 | { |
| 277 | var case = ctx.exe("substracting numbers at runtime", linux_x64); | |
| 277 | var case = ctx.exe("subtracting numbers at runtime", linux_x64); | |
| 278 | 278 | case.addCompareOutput( |
| 279 | 279 | \\export fn _start() noreturn { |
| 280 | 280 | \\ sub(7, 4); |
| ... | ... | @@ -967,10 +967,19 @@ pub fn addCases(ctx: *TestContext) !void { |
| 967 | 967 | \\fn entry() void {} |
| 968 | 968 | , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); |
| 969 | 969 | |
| 970 | ctx.compileError("extern variable has no type", linux_x64, | |
| 971 | \\comptime { | |
| 972 | \\ _ = foo; | |
| 973 | \\} | |
| 974 | \\extern var foo; | |
| 975 | , &[_][]const u8{":4:1: error: unable to infer variable type"}); | |
| 970 | { | |
| 971 | var case = ctx.obj("extern variable has no type", linux_x64); | |
| 972 | case.addError( | |
| 973 | \\comptime { | |
| 974 | \\ _ = foo; | |
| 975 | \\} | |
| 976 | \\extern var foo: i32; | |
| 977 | , &[_][]const u8{":2:9: error: unable to resolve comptime value"}); | |
| 978 | case.addError( | |
| 979 | \\export fn entry() void { | |
| 980 | \\ _ = foo; | |
| 981 | \\} | |
| 982 | \\extern var foo; | |
| 983 | , &[_][]const u8{":4:1: error: unable to infer variable type"}); | |
| 984 | } | |
| 976 | 985 | } |