| 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,6 +25,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB) |
| 25 | clang-cpp | 25 | clang-cpp |
| 26 | PATHS | 26 | PATHS |
| 27 | ${CLANG_LIBDIRS} | 27 | ${CLANG_LIBDIRS} |
| 28 | /usr/lib/llvm/11/lib | ||
| 29 | /usr/lib/llvm/11/lib64 | ||
| 28 | /usr/lib/llvm-11/lib | 30 | /usr/lib/llvm-11/lib |
| 29 | /usr/local/llvm110/lib | 31 | /usr/local/llvm110/lib |
| 30 | /usr/local/llvm11/lib | 32 | /usr/local/llvm11/lib |
cmake/Findllvm.cmake+2| ... | @@ -26,6 +26,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB) | ... | @@ -26,6 +26,8 @@ if(ZIG_PREFER_CLANG_CPP_DYLIB) |
| 26 | LLVM | 26 | LLVM |
| 27 | PATHS | 27 | PATHS |
| 28 | ${LLVM_LIBDIRS} | 28 | ${LLVM_LIBDIRS} |
| 29 | /usr/lib/llvm/11/lib | ||
| 30 | /usr/lib/llvm/11/lib64 | ||
| 29 | /usr/lib/llvm-11/lib | 31 | /usr/lib/llvm-11/lib |
| 30 | /usr/local/llvm11/lib | 32 | /usr/local/llvm11/lib |
| 31 | /usr/local/llvm110/lib | 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,7 +20,8 @@ pub const BufSet = struct { |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | pub fn deinit(self: *BufSet) void { | 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 | self.free(entry.key); | 25 | self.free(entry.key); |
| 25 | } | 26 | } |
| 26 | self.hash_map.deinit(); | 27 | self.hash_map.deinit(); |
lib/std/builtin.zig+1| ... | @@ -261,6 +261,7 @@ pub const TypeInfo = union(enum) { | ... | @@ -261,6 +261,7 @@ pub const TypeInfo = union(enum) { |
| 261 | name: []const u8, | 261 | name: []const u8, |
| 262 | field_type: type, | 262 | field_type: type, |
| 263 | default_value: anytype, | 263 | default_value: anytype, |
| 264 | is_comptime: bool, | ||
| 264 | }; | 265 | }; |
| 265 | 266 | ||
| 266 | /// This data structure is used by the Zig language code generation and | 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,3 +330,8 @@ pub const FILE = @Type(.Opaque); |
| 330 | pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*c_void; | 330 | pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*c_void; |
| 331 | pub extern "c" fn dlclose(handle: *c_void) c_int; | 331 | pub extern "c" fn dlclose(handle: *c_void) c_int; |
| 332 | pub extern "c" fn dlsym(handle: ?*c_void, symbol: [*:0]const u8) ?*c_void; | 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,10 +44,10 @@ pub const ChildProcess = struct { |
| 44 | stderr_behavior: StdIo, | 44 | stderr_behavior: StdIo, |
| 45 | 45 | ||
| 46 | /// Set to change the user id when spawning the child process. | 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 | /// Set to change the group id when spawning the child process. | 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 | /// Set to change the current working directory when spawning the child process. | 52 | /// Set to change the current working directory when spawning the child process. |
| 53 | cwd: ?[]const u8, | 53 | cwd: ?[]const u8, |
lib/std/fmt.zig+17| ... | @@ -66,6 +66,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool { | ... | @@ -66,6 +66,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool { |
| 66 | /// - output numeric value in hexadecimal notation | 66 | /// - output numeric value in hexadecimal notation |
| 67 | /// - `s`: print a pointer-to-many as a c-string, use zero-termination | 67 | /// - `s`: print a pointer-to-many as a c-string, use zero-termination |
| 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. | 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 | /// - `e`: output floating point value in scientific notation | 70 | /// - `e`: output floating point value in scientific notation |
| 70 | /// - `d`: output numeric value in decimal notation | 71 | /// - `d`: output numeric value in decimal notation |
| 71 | /// - `b`: output integer value in binary notation | 72 | /// - `b`: output integer value in binary notation |
| ... | @@ -599,6 +600,16 @@ pub fn formatText( | ... | @@ -599,6 +600,16 @@ pub fn formatText( |
| 599 | try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, writer); | 600 | try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, writer); |
| 600 | } | 601 | } |
| 601 | return; | 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 | } else { | 613 | } else { |
| 603 | @compileError("Unknown format string: '" ++ fmt ++ "'"); | 614 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| 604 | } | 615 | } |
| ... | @@ -1319,6 +1330,12 @@ test "slice" { | ... | @@ -1319,6 +1330,12 @@ test "slice" { |
| 1319 | try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); | 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 | test "pointer" { | 1339 | test "pointer" { |
| 1323 | { | 1340 | { |
| 1324 | const value = @intToPtr(*align(1) i32, 0xdeadbeef); | 1341 | const value = @intToPtr(*align(1) i32, 0xdeadbeef); |
lib/std/fmt/parse_float.zig+4-1| ... | @@ -37,7 +37,9 @@ | ... | @@ -37,7 +37,9 @@ |
| 37 | const std = @import("../std.zig"); | 37 | const std = @import("../std.zig"); |
| 38 | const ascii = std.ascii; | 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 | const f64_plus_zero: u64 = 0x0000000000000000; | 44 | const f64_plus_zero: u64 = 0x0000000000000000; |
| 43 | const f64_minus_zero: u64 = 0x8000000000000000; | 45 | const f64_minus_zero: u64 = 0x8000000000000000; |
| ... | @@ -409,6 +411,7 @@ test "fmt.parseFloat" { | ... | @@ -409,6 +411,7 @@ test "fmt.parseFloat" { |
| 409 | expect(approxEq(T, try parseFloat(T, "123142.1"), 123142.1, epsilon)); | 411 | expect(approxEq(T, try parseFloat(T, "123142.1"), 123142.1, epsilon)); |
| 410 | expect(approxEq(T, try parseFloat(T, "-123142.1124"), @as(T, -123142.1124), epsilon)); | 412 | expect(approxEq(T, try parseFloat(T, "-123142.1124"), @as(T, -123142.1124), epsilon)); |
| 411 | expect(approxEq(T, try parseFloat(T, "0.7062146892655368"), @as(T, 0.7062146892655368), epsilon)); | 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,91 +4,94 @@ |
| 4 | // The MIT license requires this copyright notice to be included in all copies | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | // and substantial portions of the software. | 5 | // and substantial portions of the software. |
| 6 | const std = @import("std.zig"); | 6 | const std = @import("std.zig"); |
| 7 | const debug = std.debug; | 7 | const builtin = @import("builtin"); |
| 8 | const assert = debug.assert; | 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 | const math = std.math; | 12 | const math = std.math; |
| 11 | const mem = std.mem; | 13 | const mem = std.mem; |
| 12 | const meta = std.meta; | 14 | const meta = std.meta; |
| 13 | const trait = meta.trait; | 15 | const trait = meta.trait; |
| 14 | const autoHash = std.hash.autoHash; | ||
| 15 | const Wyhash = std.hash.Wyhash; | ||
| 16 | const Allocator = mem.Allocator; | 16 | const Allocator = mem.Allocator; |
| 17 | const builtin = @import("builtin"); | 17 | const Wyhash = std.hash.Wyhash; |
| 18 | const hash_map = @This(); | 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 | pub fn AutoHashMap(comptime K: type, comptime V: type) type { | 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 | pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type { | 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 | /// Builtin hashmap for strings as keys. | 49 | /// Builtin hashmap for strings as keys. |
| 29 | pub fn StringHashMap(comptime V: type) type { | 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 | pub fn StringHashMapUnmanaged(comptime V: type) type { | 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 | pub fn eqlString(a: []const u8, b: []const u8) bool { | 58 | pub fn eqlString(a: []const u8, b: []const u8) bool { |
| 38 | return mem.eql(u8, a, b); | 59 | return mem.eql(u8, a, b); |
| 39 | } | 60 | } |
| 40 | 61 | ||
| 41 | pub fn hashString(s: []const u8) u32 { | 62 | pub fn hashString(s: []const u8) u64 { |
| 42 | return @truncate(u32, std.hash.Wyhash.hash(0, s)); | 63 | return std.hash.Wyhash.hash(0, s); |
| 43 | } | 64 | } |
| 44 | 65 | ||
| 45 | /// Insertion order is preserved. | 66 | pub const DefaultMaxLoadPercentage = 80; |
| 46 | /// Deletions perform a "swap removal" on the entries list. | 67 | |
| 47 | /// Modifying the hash map while iterating is allowed, however one must understand | 68 | /// General purpose hash table. |
| 48 | /// the (well defined) behavior when mixing insertions and deletions with iteration. | 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 | /// For a hash map that can be initialized directly that does not store an Allocator | 72 | /// For a hash map that can be initialized directly that does not store an Allocator |
| 50 | /// field, see `HashMapUnmanaged`. | 73 | /// field, see `HashMapUnmanaged`. |
| 51 | /// When `store_hash` is `false`, this data structure is biased towards cheap `eql` | 74 | /// If iterating over the table entries is a strong usecase and needs to be fast, |
| 52 | /// functions. It does not store each item's hash in the table. Setting `store_hash` | 75 | /// prefer the alternative `std.ArrayHashMap`. |
| 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 | pub fn HashMap( | 76 | pub fn HashMap( |
| 56 | comptime K: type, | 77 | comptime K: type, |
| 57 | comptime V: type, | 78 | comptime V: type, |
| 58 | comptime hash: fn (key: K) u32, | 79 | comptime hashFn: fn (key: K) u64, |
| 59 | comptime eql: fn (a: K, b: K) bool, | 80 | comptime eqlFn: fn (a: K, b: K) bool, |
| 60 | comptime store_hash: bool, | 81 | comptime MaxLoadPercentage: u64, |
| 61 | ) type { | 82 | ) type { |
| 62 | return struct { | 83 | return struct { |
| 63 | unmanaged: Unmanaged, | 84 | unmanaged: Unmanaged, |
| 64 | allocator: *Allocator, | 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 | pub const Entry = Unmanaged.Entry; | 88 | pub const Entry = Unmanaged.Entry; |
| 68 | pub const Hash = Unmanaged.Hash; | 89 | pub const Hash = Unmanaged.Hash; |
| 90 | pub const Iterator = Unmanaged.Iterator; | ||
| 91 | pub const Size = Unmanaged.Size; | ||
| 69 | pub const GetOrPutResult = Unmanaged.GetOrPutResult; | 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 | const Self = @This(); | 94 | const Self = @This(); |
| 91 | const Index = Unmanaged.Index; | ||
| 92 | 95 | ||
| 93 | pub fn init(allocator: *Allocator) Self { | 96 | pub fn init(allocator: *Allocator) Self { |
| 94 | return .{ | 97 | return .{ |
| ... | @@ -110,17 +113,12 @@ pub fn HashMap( | ... | @@ -110,17 +113,12 @@ pub fn HashMap( |
| 110 | return self.unmanaged.clearAndFree(self.allocator); | 113 | return self.unmanaged.clearAndFree(self.allocator); |
| 111 | } | 114 | } |
| 112 | 115 | ||
| 113 | /// Deprecated. Use `items().len`. | ||
| 114 | pub fn count(self: Self) usize { | 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 | pub fn iterator(self: *const Self) Iterator { | 120 | pub fn iterator(self: *const Self) Iterator { |
| 120 | return Iterator{ | 121 | return self.unmanaged.iterator(); |
| 121 | .hm = self, | ||
| 122 | .index = 0, | ||
| 123 | }; | ||
| 124 | } | 122 | } |
| 125 | 123 | ||
| 126 | /// If key exists this function cannot fail. | 124 | /// If key exists this function cannot fail. |
| ... | @@ -150,13 +148,13 @@ pub fn HashMap( | ... | @@ -150,13 +148,13 @@ pub fn HashMap( |
| 150 | 148 | ||
| 151 | /// Increases capacity, guaranteeing that insertions up until the | 149 | /// Increases capacity, guaranteeing that insertions up until the |
| 152 | /// `expected_count` will not cause an allocation, and therefore cannot fail. | 150 | /// `expected_count` will not cause an allocation, and therefore cannot fail. |
| 153 | pub fn ensureCapacity(self: *Self, new_capacity: usize) !void { | 151 | pub fn ensureCapacity(self: *Self, expected_count: Size) !void { |
| 154 | return self.unmanaged.ensureCapacity(self.allocator, new_capacity); | 152 | return self.unmanaged.ensureCapacity(self.allocator, expected_count); |
| 155 | } | 153 | } |
| 156 | 154 | ||
| 157 | /// Returns the number of total elements which may be present before it is | 155 | /// Returns the number of total elements which may be present before it is |
| 158 | /// no longer guaranteed that no allocations will be performed. | 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 | return self.unmanaged.capacity(); | 158 | return self.unmanaged.capacity(); |
| 161 | } | 159 | } |
| 162 | 160 | ||
| ... | @@ -197,18 +195,14 @@ pub fn HashMap( | ... | @@ -197,18 +195,14 @@ pub fn HashMap( |
| 197 | return self.unmanaged.fetchPutAssumeCapacity(key, value); | 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 | pub fn get(self: Self, key: K) ?V { | 198 | pub fn get(self: Self, key: K) ?V { |
| 209 | return self.unmanaged.get(key); | 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 | pub fn contains(self: Self, key: K) bool { | 206 | pub fn contains(self: Self, key: K) bool { |
| 213 | return self.unmanaged.contains(key); | 207 | return self.unmanaged.contains(key); |
| 214 | } | 208 | } |
| ... | @@ -225,10 +219,6 @@ pub fn HashMap( | ... | @@ -225,10 +219,6 @@ pub fn HashMap( |
| 225 | return self.unmanaged.removeAssertDiscard(key); | 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 | pub fn clone(self: Self) !Self { | 222 | pub fn clone(self: Self) !Self { |
| 233 | var other = try self.unmanaged.clone(self.allocator); | 223 | var other = try self.unmanaged.clone(self.allocator); |
| 234 | return other.promote(self.allocator); | 224 | return other.promote(self.allocator); |
| ... | @@ -236,63 +226,152 @@ pub fn HashMap( | ... | @@ -236,63 +226,152 @@ pub fn HashMap( |
| 236 | }; | 226 | }; |
| 237 | } | 227 | } |
| 238 | 228 | ||
| 239 | /// General purpose hash table. | 229 | /// A HashMap based on open addressing and linear probing. |
| 240 | /// Insertion order is preserved. | 230 | /// A lookup or modification typically occurs only 2 cache misses. |
| 241 | /// Deletions perform a "swap removal" on the entries list. | 231 | /// No order is guaranteed and any modification invalidates live iterators. |
| 242 | /// Modifying the hash map while iterating is allowed, however one must understand | 232 | /// It achieves good performance with quite high load factors (by default, |
| 243 | /// the (well defined) behavior when mixing insertions and deletions with iteration. | 233 | /// grow is triggered at 80% full) and only one byte of overhead per element. |
| 244 | /// This type does not store an Allocator field - the Allocator must be passed in | 234 | /// The struct itself is only 16 bytes for a small footprint. This comes at |
| 245 | /// with each function call that requires it. See `HashMap` for a type that stores | 235 | /// the price of handling size with u32, which should be reasonnable enough |
| 246 | /// an Allocator field for convenience. | 236 | /// for almost all uses. |
| 247 | /// Can be initialized directly using the default field values. | 237 | /// Deletions are achieved with tombstones. |
| 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. | ||
| 256 | pub fn HashMapUnmanaged( | 238 | pub fn HashMapUnmanaged( |
| 257 | comptime K: type, | 239 | comptime K: type, |
| 258 | comptime V: type, | 240 | comptime V: type, |
| 259 | comptime hash: fn (key: K) u32, | 241 | hashFn: fn (key: K) u64, |
| 260 | comptime eql: fn (a: K, b: K) bool, | 242 | eqlFn: fn (a: K, b: K) bool, |
| 261 | comptime store_hash: bool, | 243 | comptime MaxLoadPercentage: u64, |
| 262 | ) type { | 244 | ) type { |
| 245 | comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100); | ||
| 246 | |||
| 263 | return struct { | 247 | return struct { |
| 264 | /// It is permitted to access this field directly. | 248 | const Self = @This(); |
| 265 | entries: std.ArrayListUnmanaged(Entry) = .{}, | 249 | |
| 266 | 250 | // This is actually a midway pointer to the single buffer containing | |
| 267 | /// When entries length is less than `linear_scan_max`, this remains `null`. | 251 | // a `Header` field, the `Metadata`s and `Entry`s. |
| 268 | /// Once entries length grows big enough, this field is allocated. There is | 252 | // At `-@sizeOf(Header)` is the Header field. |
| 269 | /// an IndexHeader followed by an array of Index(I) structs, where I is defined | 253 | // At `sizeOf(Metadata) * capacity + offset`, which is pointed to by |
| 270 | /// by how many total indexes there are. | 254 | // self.header().entries, is the array of entries. |
| 271 | index_header: ?*IndexHeader = null, | 255 | // This means that the hashmap only holds one live allocation, to |
| 272 | 256 | // reduce memory fragmentation and struct size. | |
| 273 | /// Modifying the key is illegal behavior. | 257 | /// Pointer to the metadata. |
| 274 | /// Modifying the value is allowed. | 258 | metadata: ?[*]Metadata = null, |
| 275 | /// Entry pointers become invalid whenever this HashMap is modified, | 259 | |
| 276 | /// unless `ensureCapacity` was previously used. | 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 | pub const Entry = struct { | 280 | pub const Entry = struct { |
| 278 | /// This field is `void` if `store_hash` is `false`. | ||
| 279 | hash: Hash, | ||
| 280 | key: K, | 281 | key: K, |
| 281 | value: V, | 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 | pub const GetOrPutResult = struct { | 369 | pub const GetOrPutResult = struct { |
| 287 | entry: *Entry, | 370 | entry: *Entry, |
| 288 | found_existing: bool, | 371 | found_existing: bool, |
| 289 | }; | 372 | }; |
| 290 | 373 | ||
| 291 | pub const Managed = HashMap(K, V, hash, eql, store_hash); | 374 | pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage); |
| 292 | |||
| 293 | const Self = @This(); | ||
| 294 | |||
| 295 | const linear_scan_max = 8; | ||
| 296 | 375 | ||
| 297 | pub fn promote(self: Self, allocator: *Allocator) Managed { | 376 | pub fn promote(self: Self, allocator: *Allocator) Managed { |
| 298 | return .{ | 377 | return .{ |
| ... | @@ -301,167 +380,156 @@ pub fn HashMapUnmanaged( | ... | @@ -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 | pub fn deinit(self: *Self, allocator: *Allocator) void { | 391 | pub fn deinit(self: *Self, allocator: *Allocator) void { |
| 305 | self.entries.deinit(allocator); | 392 | self.deallocate(allocator); |
| 306 | if (self.index_header) |header| { | ||
| 307 | header.free(allocator); | ||
| 308 | } | ||
| 309 | self.* = undefined; | 393 | self.* = undefined; |
| 310 | } | 394 | } |
| 311 | 395 | ||
| 312 | pub fn clearRetainingCapacity(self: *Self) void { | 396 | fn deallocate(self: *Self, allocator: *Allocator) void { |
| 313 | self.entries.items.len = 0; | 397 | if (self.metadata == null) return; |
| 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 | } | ||
| 324 | 398 | ||
| 325 | pub fn clearAndFree(self: *Self, allocator: *Allocator) void { | 399 | const cap = self.capacity(); |
| 326 | self.entries.shrink(allocator, 0); | 400 | const meta_size = @sizeOf(Header) + cap * @sizeOf(Metadata); |
| 327 | if (self.index_header) |header| { | 401 | |
| 328 | header.free(allocator); | 402 | const alignment = @alignOf(Entry) - 1; |
| 329 | self.index_header = null; | 403 | const entries_size = @as(usize, cap) * @sizeOf(Entry) + alignment; |
| 330 | } | 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. | 416 | fn capacityForSize(size: Size) Size { |
| 334 | /// If there is an existing item with `key`, then the result | 417 | var new_cap = @truncate(u32, (@as(u64, size) * 100) / MaxLoadPercentage + 1); |
| 335 | /// `Entry` pointer points to it, and found_existing is true. | 418 | new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable; |
| 336 | /// Otherwise, puts a new item with undefined value, and | 419 | return new_cap; |
| 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); | ||
| 348 | } | 420 | } |
| 349 | 421 | ||
| 350 | /// If there is an existing item with `key`, then the result | 422 | pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_size: Size) !void { |
| 351 | /// `Entry` pointer points to it, and found_existing is true. | 423 | if (new_size > self.size) |
| 352 | /// Otherwise, puts a new item with undefined value, and | 424 | try self.growIfNeeded(allocator, new_size - self.size); |
| 353 | /// the `Entry` pointer points to it. Caller should then initialize | 425 | } |
| 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 | }; | ||
| 380 | 426 | ||
| 381 | switch (header.capacityIndexType()) { | 427 | pub fn clearRetainingCapacity(self: *Self) void { |
| 382 | .u8 => return self.getOrPutInternal(key, header, u8), | 428 | if (self.metadata) |_| { |
| 383 | .u16 => return self.getOrPutInternal(key, header, u16), | 429 | self.initMetadatas(); |
| 384 | .u32 => return self.getOrPutInternal(key, header, u32), | 430 | self.size = 0; |
| 385 | .usize => return self.getOrPutInternal(key, header, usize), | 431 | self.available = 0; |
| 386 | } | 432 | } |
| 387 | } | 433 | } |
| 388 | 434 | ||
| 389 | pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry { | 435 | pub fn clearAndFree(self: *Self, allocator: *Allocator) void { |
| 390 | const res = try self.getOrPut(allocator, key); | 436 | self.deallocate(allocator); |
| 391 | if (!res.found_existing) | 437 | self.size = 0; |
| 392 | res.entry.value = value; | 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 | 445 | fn header(self: *const Self) *Header { |
| 398 | /// `expected_count` will not cause an allocation, and therefore cannot fail. | 446 | return @ptrCast(*Header, @ptrCast([*]Header, self.metadata.?) - 1); |
| 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 | } | ||
| 424 | } | 447 | } |
| 425 | 448 | ||
| 426 | /// Returns the number of total elements which may be present before it is | 449 | fn entries(self: *const Self) [*]Entry { |
| 427 | /// no longer guaranteed that no allocations will be performed. | 450 | return self.header().entries; |
| 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); | ||
| 433 | } | 451 | } |
| 434 | 452 | ||
| 435 | /// Clobbers any existing data. To detect if a put would clobber | 453 | pub fn capacity(self: *const Self) Size { |
| 436 | /// existing data, see `getOrPut`. | 454 | if (self.metadata == null) return 0; |
| 437 | pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void { | 455 | |
| 438 | const result = try self.getOrPut(allocator, key); | 456 | return self.header().capacity; |
| 439 | result.entry.value = value; | ||
| 440 | } | 457 | } |
| 441 | 458 | ||
| 442 | /// Inserts a key-value pair into the hash map, asserting that no previous | 459 | pub fn iterator(self: *const Self) Iterator { |
| 443 | /// entry with the same key is already present | 460 | return .{ .hm = self }; |
| 461 | } | ||
| 462 | |||
| 463 | /// Insert an entry in the map. Assumes it is not already present. | ||
| 444 | pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void { | 464 | pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void { |
| 445 | const result = try self.getOrPut(allocator, key); | 465 | assert(!self.contains(key)); |
| 446 | assert(!result.found_existing); | 466 | try self.growIfNeeded(allocator, 1); |
| 447 | result.entry.value = value; | 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 | pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { | 471 | pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { |
| 454 | const result = self.getOrPutAssumeCapacity(key); | 472 | const hash = hashFn(key); |
| 455 | result.entry.value = value; | 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. | 508 | /// Insert an entry in the map. Assumes it is not already present, |
| 459 | /// Asserts that it does not clobber any existing data. | 509 | /// and that no allocation is needed. |
| 460 | /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`. | ||
| 461 | pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { | 510 | pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { |
| 462 | const result = self.getOrPutAssumeCapacity(key); | 511 | assert(!self.contains(key)); |
| 463 | assert(!result.found_existing); | 512 | |
| 464 | result.entry.value = value; | 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 | /// Inserts a new `Entry` into the hash map, returning the previous one, if any. | 535 | /// Inserts a new `Entry` into the hash map, returning the previous one, if any. |
| ... | @@ -488,400 +556,622 @@ pub fn HashMapUnmanaged( | ... | @@ -488,400 +556,622 @@ pub fn HashMapUnmanaged( |
| 488 | } | 556 | } |
| 489 | 557 | ||
| 490 | pub fn getEntry(self: Self, key: K) ?*Entry { | 558 | pub fn getEntry(self: Self, key: K) ?*Entry { |
| 491 | const index = self.getIndex(key) orelse return null; | 559 | if (self.size == 0) { |
| 492 | return &self.entries.items[index]; | 560 | return null; |
| 493 | } | 561 | } |
| 494 | 562 | ||
| 495 | pub fn getIndex(self: Self, key: K) ?usize { | 563 | const hash = hashFn(key); |
| 496 | const header = self.index_header orelse { | 564 | const mask = self.capacity() - 1; |
| 497 | // Linear scan. | 565 | const fingerprint = Metadata.takeFingerprint(hash); |
| 498 | const h = if (store_hash) hash(key) else {}; | 566 | var idx = @truncate(usize, hash & mask); |
| 499 | for (self.entries.items) |*item, i| { | 567 | |
| 500 | if (item.hash == h and eql(key, item.key)) { | 568 | var metadata = self.metadata.? + idx; |
| 501 | return i; | 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; | 576 | idx = (idx + 1) & mask; |
| 505 | }; | 577 | metadata = self.metadata.? + idx; |
| 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), | ||
| 511 | } | 578 | } |
| 512 | } | ||
| 513 | 579 | ||
| 514 | pub fn get(self: Self, key: K) ?V { | 580 | return null; |
| 515 | return if (self.getEntry(key)) |entry| entry.value else null; | ||
| 516 | } | 581 | } |
| 517 | 582 | ||
| 518 | pub fn contains(self: Self, key: K) bool { | 583 | /// Insert an entry if the associated key is not already present, otherwise update preexisting value. |
| 519 | return self.getEntry(key) != null; | 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 | 590 | /// Get an optional pointer to the value associated with key, if present. |
| 523 | /// the hash map, and then returned from this function. | 591 | pub fn get(self: Self, key: K) ?V { |
| 524 | pub fn remove(self: *Self, key: K) ?Entry { | 592 | if (self.size == 0) { |
| 525 | const header = self.index_header orelse { | 593 | return null; |
| 526 | // Linear scan. | 594 | } |
| 527 | const h = if (store_hash) hash(key) else {}; | 595 | |
| 528 | for (self.entries.items) |item, i| { | 596 | const hash = hashFn(key); |
| 529 | if (item.hash == h and eql(key, item.key)) { | 597 | const mask = self.capacity() - 1; |
| 530 | return self.entries.swapRemove(i); | 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; | 609 | idx = (idx + 1) & mask; |
| 534 | }; | 610 | metadata = self.metadata.? + idx; |
| 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), | ||
| 540 | } | 611 | } |
| 541 | } | ||
| 542 | 612 | ||
| 543 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, | 613 | return null; |
| 544 | /// and discards it. | ||
| 545 | pub fn removeAssertDiscard(self: *Self, key: K) void { | ||
| 546 | assert(self.remove(key) != null); | ||
| 547 | } | 614 | } |
| 548 | 615 | ||
| 549 | pub fn items(self: Self) []Entry { | 616 | pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { |
| 550 | return self.entries.items; | 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 { | 622 | pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { |
| 554 | var other: Self = .{}; | 623 | const hash = hashFn(key); |
| 555 | try other.entries.appendSlice(allocator, self.entries.items); | 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| { | 640 | idx = (idx + 1) & mask; |
| 558 | const new_header = try IndexHeader.alloc(allocator, header.indexes_len); | 641 | metadata = self.metadata.? + idx; |
| 559 | other.insertAllEntriesIntoNewHeader(new_header); | ||
| 560 | other.index_header = new_header; | ||
| 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 { | 661 | pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry { |
| 566 | const indexes = header.indexes(I); | 662 | const res = try self.getOrPut(allocator, key); |
| 567 | const h = hash(key); | 663 | if (!res.found_existing) res.entry.value = value; |
| 568 | const start_index = header.constrainIndex(h); | 664 | return res.entry; |
| 569 | var roll_over: usize = 0; | 665 | } |
| 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 | } | ||
| 588 | 666 | ||
| 589 | // Now we have to shift over the following indexes. | 667 | /// Return true if there is a value associated with key in the map. |
| 590 | roll_over += 1; | 668 | pub fn contains(self: *const Self, key: K) bool { |
| 591 | while (roll_over < header.indexes_len) : (roll_over += 1) { | 669 | return self.get(key) != null; |
| 592 | const next_index_index = header.constrainIndex(start_index + roll_over); | 670 | } |
| 593 | const next_index = &indexes[next_index_index]; | 671 | |
| 594 | if (next_index.isEmpty() or next_index.distance_from_start_index == 0) { | 672 | /// If there is an `Entry` with a matching key, it is deleted from |
| 595 | index.setEmpty(); | 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 | return removed_entry; | 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 | return null; | 698 | return null; |
| 605 | } | 699 | } |
| 606 | 700 | ||
| 607 | fn updateEntryIndex( | 701 | /// Asserts there is an `Entry` with matching key, deletes it from the hash map, |
| 608 | self: *Self, | 702 | /// and discards it. |
| 609 | header: *IndexHeader, | 703 | pub fn removeAssertDiscard(self: *Self, key: K) void { |
| 610 | old_entry_index: usize, | 704 | assert(self.contains(key)); |
| 611 | new_entry_index: usize, | 705 | |
| 612 | comptime I: type, | 706 | const hash = hashFn(key); |
| 613 | indexes: []Index(I), | 707 | const mask = self.capacity() - 1; |
| 614 | ) void { | 708 | const fingerprint = Metadata.takeFingerprint(hash); |
| 615 | const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key); | 709 | var idx = @truncate(usize, hash & mask); |
| 616 | const start_index = header.constrainIndex(h); | 710 | |
| 617 | var roll_over: usize = 0; | 711 | var metadata = self.metadata.? + idx; |
| 618 | while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { | 712 | while (metadata[0].isUsed() or metadata[0].isTombstone()) { |
| 619 | const index_index = header.constrainIndex(start_index + roll_over); | 713 | if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) { |
| 620 | const index = &indexes[index_index]; | 714 | const entry = &self.entries()[idx]; |
| 621 | if (index.entry_index == old_entry_index) { | 715 | if (eqlFn(entry.key, key)) { |
| 622 | index.entry_index = @intCast(I, new_entry_index); | 716 | metadata[0].remove(); |
| 623 | return; | 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 | unreachable; | 726 | unreachable; |
| 627 | } | 727 | } |
| 628 | 728 | ||
| 629 | /// Must ensureCapacity before calling this. | 729 | fn initMetadatas(self: *Self) void { |
| 630 | fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult { | 730 | @memset(@ptrCast([*]u8, self.metadata.?), 0, @sizeOf(Metadata) * self.capacity()); |
| 631 | const indexes = header.indexes(I); | 731 | } |
| 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 | } | ||
| 659 | 732 | ||
| 660 | // This pointer survives the following append because we call | 733 | // This counts the number of occupied slots, used + tombstones, which is |
| 661 | // entries.ensureCapacity before getOrPutInternal. | 734 | // what has to stay under the MaxLoadPercentage of capacity. |
| 662 | const entry = &self.entries.items[index.entry_index]; | 735 | fn load(self: *const Self) Size { |
| 663 | const hash_match = if (store_hash) h == entry.hash else true; | 736 | const max_load = (self.capacity() * MaxLoadPercentage) / 100; |
| 664 | if (hash_match and eql(key, entry.key)) { | 737 | assert(max_load >= self.available); |
| 665 | return .{ | 738 | return @truncate(Size, max_load - self.available); |
| 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; | ||
| 725 | } | 739 | } |
| 726 | 740 | ||
| 727 | fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize { | 741 | fn growIfNeeded(self: *Self, allocator: *Allocator, new_count: Size) !void { |
| 728 | const indexes = header.indexes(I); | 742 | if (new_count > self.available) { |
| 729 | const h = hash(key); | 743 | try self.grow(allocator, capacityForSize(self.load() + new_count)); |
| 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; | ||
| 742 | } | 744 | } |
| 743 | return null; | ||
| 744 | } | 745 | } |
| 745 | 746 | ||
| 746 | fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void { | 747 | pub fn clone(self: Self, allocator: *Allocator) !Self { |
| 747 | switch (header.capacityIndexType()) { | 748 | var other = Self{}; |
| 748 | .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8), | 749 | if (self.size == 0) |
| 749 | .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16), | 750 | return other; |
| 750 | .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32), | 751 | |
| 751 | .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize), | 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 { | 772 | fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void { |
| 756 | const indexes = header.indexes(I); | 773 | const new_cap = std.math.max(new_capacity, MinimalCapacity); |
| 757 | entry_loop: for (self.entries.items) |entry, i| { | 774 | assert(new_cap > self.capacity()); |
| 758 | const h = if (store_hash) entry.hash else hash(entry.key); | 775 | assert(std.math.isPowerOfTwo(new_cap)); |
| 759 | const start_index = header.constrainIndex(h); | 776 | |
| 760 | var entry_index = i; | 777 | var map = Self{}; |
| 761 | var roll_over: usize = 0; | 778 | defer map.deinit(allocator); |
| 762 | var distance_from_start_index: usize = 0; | 779 | try map.allocate(allocator, new_cap); |
| 763 | while (roll_over < header.indexes_len) : ({ | 780 | map.initMetadatas(); |
| 764 | roll_over += 1; | 781 | map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100); |
| 765 | distance_from_start_index += 1; | 782 | |
| 766 | }) { | 783 | if (self.size != 0) { |
| 767 | const index_index = header.constrainIndex(start_index + roll_over); | 784 | const old_capacity = self.capacity(); |
| 768 | const next_index = indexes[index_index]; | 785 | var i: Size = 0; |
| 769 | if (next_index.isEmpty()) { | 786 | var metadata = self.metadata.?; |
| 770 | header.maybeBumpMax(distance_from_start_index); | 787 | var entr = self.entries(); |
| 771 | indexes[index_index] = .{ | 788 | while (i < old_capacity) : (i += 1) { |
| 772 | .distance_from_start_index = @intCast(I, distance_from_start_index), | 789 | if (metadata[i].isUsed()) { |
| 773 | .entry_index = @intCast(I, entry_index), | 790 | const entry = &entr[i]; |
| 774 | }; | 791 | map.putAssumeCapacityNoClobber(entry.key, entry.value); |
| 775 | continue :entry_loop; | 792 | if (map.size == self.size) |
| 776 | } | 793 | break; |
| 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; | ||
| 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 { | 862 | try map.ensureCapacity(20); |
| 796 | if (indexes_len < math.maxInt(u8)) | 863 | const initial_capacity = map.capacity(); |
| 797 | return .u8; | 864 | testing.expect(initial_capacity >= 20); |
| 798 | if (indexes_len < math.maxInt(u16)) | 865 | var i: i32 = 0; |
| 799 | return .u16; | 866 | while (i < 20) : (i += 1) { |
| 800 | if (indexes_len < math.maxInt(u32)) | 867 | testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null); |
| 801 | return .u32; | 868 | } |
| 802 | return .usize; | 869 | // shouldn't resize from putAssumeCapacity |
| 870 | testing.expect(initial_capacity == map.capacity()); | ||
| 803 | } | 871 | } |
| 804 | 872 | ||
| 805 | fn capacityIndexSize(indexes_len: usize) usize { | 873 | test "std.hash_map ensureCapacity with tombstones" { |
| 806 | switch (capacityIndexType(indexes_len)) { | 874 | var map = AutoHashMap(i32, i32).init(std.testing.allocator); |
| 807 | .u8 => return @sizeOf(Index(u8)), | 875 | defer map.deinit(); |
| 808 | .u16 => return @sizeOf(Index(u16)), | 876 | |
| 809 | .u32 => return @sizeOf(Index(u32)), | 877 | var i: i32 = 0; |
| 810 | .usize => return @sizeOf(Index(usize)), | 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 { | 886 | test "std.hash_map clearRetainingCapacity" { |
| 815 | return extern struct { | 887 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); |
| 816 | entry_index: I, | 888 | defer map.deinit(); |
| 817 | distance_from_start_index: I, | 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{ | 896 | const cap = map.capacity(); |
| 822 | .entry_index = math.maxInt(I), | 897 | expect(cap > 0); |
| 823 | .distance_from_start_index = undefined, | 898 | |
| 824 | }; | 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 { | 910 | const growTo = 12456; |
| 827 | return idx.entry_index == math.maxInt(I); | 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 { | 997 | i = 0; |
| 831 | idx.entry_index = math.maxInt(I); | 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` | 1007 | test "std.hash_map reverse removes" { |
| 837 | /// and the array length are determined by `indexes_len`. | 1008 | var map = AutoHashMap(u32, u32).init(std.testing.allocator); |
| 838 | const IndexHeader = struct { | 1009 | defer map.deinit(); |
| 839 | max_distance_from_start_index: usize, | ||
| 840 | indexes_len: usize, | ||
| 841 | 1010 | ||
| 842 | fn constrainIndex(header: IndexHeader, i: usize) usize { | 1011 | var i: u32 = 0; |
| 843 | // This is an optimization for modulo of power of two integers; | 1012 | while (i < 16) : (i += 1) { |
| 844 | // it requires `indexes_len` to always be a power of two. | 1013 | try map.putNoClobber(i, i); |
| 845 | return i & (header.indexes_len - 1); | ||
| 846 | } | 1014 | } |
| 847 | 1015 | ||
| 848 | fn indexes(header: *IndexHeader, comptime I: type) []Index(I) { | 1016 | i = 16; |
| 849 | const start = @ptrCast([*]Index(I), @ptrCast([*]u8, header) + @sizeOf(IndexHeader)); | 1017 | while (i > 0) : (i -= 1) { |
| 850 | return start[0..header.indexes_len]; | 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 { | 1026 | expectEqual(map.count(), 0); |
| 854 | return hash_map.capacityIndexType(header.indexes_len); | 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 { | 1038 | _ = map.remove(7); |
| 858 | if (distance_from_start_index > header.max_distance_from_start_index) { | 1039 | _ = map.remove(15); |
| 859 | header.max_distance_from_start_index = distance_from_start_index; | 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 { | 1056 | try map.put(15, 15); |
| 864 | const index_size = hash_map.capacityIndexSize(len); | 1057 | try map.put(13, 13); |
| 865 | const nbytes = @sizeOf(IndexHeader) + index_size * len; | 1058 | try map.put(14, 14); |
| 866 | const bytes = try allocator.allocAdvanced(u8, @alignOf(IndexHeader), nbytes, .exact); | 1059 | try map.put(7, 7); |
| 867 | @memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader)); | 1060 | i = 0; |
| 868 | const result = @ptrCast(*IndexHeader, bytes.ptr); | 1061 | while (i < 16) : (i += 1) { |
| 869 | result.* = .{ | 1062 | expectEqual(map.get(i).?, i); |
| 870 | .max_distance_from_start_index = 0, | 1063 | } |
| 871 | .indexes_len = len, | 1064 | } |
| 872 | }; | 1065 | |
| 873 | return result; | 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 { | 1160 | i = 0; |
| 877 | const index_size = hash_map.capacityIndexSize(header.indexes_len); | 1161 | while (i < 20) : (i += 1) { |
| 878 | const ptr = @ptrCast([*]u8, header); | 1162 | var n = try map.getOrPutValue(i, 1); |
| 879 | const slice = ptr[0 .. @sizeOf(IndexHeader) + header.indexes_len * index_size]; | ||
| 880 | allocator.free(slice); | ||
| 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 | var map = AutoHashMap(i32, i32).init(std.testing.allocator); | 1175 | var map = AutoHashMap(i32, i32).init(std.testing.allocator); |
| 886 | defer map.deinit(); | 1176 | defer map.deinit(); |
| 887 | 1177 | ||
| ... | @@ -925,85 +1215,10 @@ test "basic hash map usage" { | ... | @@ -925,85 +1215,10 @@ test "basic hash map usage" { |
| 925 | map.removeAssertDiscard(3); | 1215 | map.removeAssertDiscard(3); |
| 926 | } | 1216 | } |
| 927 | 1217 | ||
| 928 | test "iterator hash map" { | 1218 | test "std.hash_map clone" { |
| 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" { | ||
| 1003 | var original = AutoHashMap(i32, i32).init(std.testing.allocator); | 1219 | var original = AutoHashMap(i32, i32).init(std.testing.allocator); |
| 1004 | defer original.deinit(); | 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 | var i: u8 = 0; | 1222 | var i: u8 = 0; |
| 1008 | while (i < 10) : (i += 1) { | 1223 | while (i < 10) : (i += 1) { |
| 1009 | try original.putNoClobber(i, i * 10); | 1224 | try original.putNoClobber(i, i * 10); |
| ... | @@ -1017,69 +1232,3 @@ test "clone" { | ... | @@ -1017,69 +1232,3 @@ test "clone" { |
| 1017 | testing.expect(copy.get(i).? == i * 10); | 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,7 +325,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 325 | break; | 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 | log.err("Memory leak detected: {}", .{large_alloc.value.getStackTrace()}); | 330 | log.err("Memory leak detected: {}", .{large_alloc.value.getStackTrace()}); |
| 330 | leaks = true; | 331 | leaks = true; |
| 331 | } | 332 | } |
| ... | @@ -584,7 +585,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { | ... | @@ -584,7 +585,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 584 | if (new_aligned_size > largest_bucket_object_size) { | 585 | if (new_aligned_size > largest_bucket_object_size) { |
| 585 | try self.large_allocations.ensureCapacity( | 586 | try self.large_allocations.ensureCapacity( |
| 586 | self.backing_allocator, | 587 | self.backing_allocator, |
| 587 | self.large_allocations.entries.items.len + 1, | 588 | self.large_allocations.count() + 1, |
| 588 | ); | 589 | ); |
| 589 | 590 | ||
| 590 | const slice = try self.backing_allocator.allocFn(self.backing_allocator, len, ptr_align, len_align, ret_addr); | 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,9 +123,9 @@ pub const Headers = struct { |
| 123 | 123 | ||
| 124 | pub fn deinit(self: *Self) void { | 124 | pub fn deinit(self: *Self) void { |
| 125 | { | 125 | { |
| 126 | for (self.index.items()) |*entry| { | 126 | var it = self.index.iterator(); |
| 127 | const dex = &entry.value; | 127 | while (it.next()) |entry| { |
| 128 | dex.deinit(self.allocator); | 128 | entry.value.deinit(self.allocator); |
| 129 | self.allocator.free(entry.key); | 129 | self.allocator.free(entry.key); |
| 130 | } | 130 | } |
| 131 | self.index.deinit(self.allocator); | 131 | self.index.deinit(self.allocator); |
| ... | @@ -333,7 +333,8 @@ pub const Headers = struct { | ... | @@ -333,7 +333,8 @@ pub const Headers = struct { |
| 333 | 333 | ||
| 334 | fn rebuildIndex(self: *Self) void { | 334 | fn rebuildIndex(self: *Self) void { |
| 335 | // clear out the indexes | 335 | // clear out the indexes |
| 336 | for (self.index.items()) |*entry| { | 336 | var it = self.index.iterator(); |
| 337 | while (it.next()) |entry| { | ||
| 337 | entry.value.shrinkRetainingCapacity(0); | 338 | entry.value.shrinkRetainingCapacity(0); |
| 338 | } | 339 | } |
| 339 | // fill up indexes again; we know capacity is fine from before | 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,6 +169,15 @@ pub const BitOutStream = BitWriter; |
| 169 | /// Deprecated: use `bitWriter` | 169 | /// Deprecated: use `bitWriter` |
| 170 | pub const bitOutStream = bitWriter; | 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 | pub const Packing = @import("io/serialization.zig").Packing; | 181 | pub const Packing = @import("io/serialization.zig").Packing; |
| 173 | 182 | ||
| 174 | pub const Serializer = @import("io/serialization.zig").Serializer; | 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,34 +705,34 @@ pub fn Vector(comptime len: u32, comptime child: type) type { |
| 705 | pub fn cast(comptime DestType: type, target: anytype) DestType { | 705 | pub fn cast(comptime DestType: type, target: anytype) DestType { |
| 706 | const TargetType = @TypeOf(target); | 706 | const TargetType = @TypeOf(target); |
| 707 | switch (@typeInfo(DestType)) { | 707 | switch (@typeInfo(DestType)) { |
| 708 | .Pointer => { | 708 | .Pointer => |dest_ptr| { |
| 709 | switch (@typeInfo(TargetType)) { | 709 | switch (@typeInfo(TargetType)) { |
| 710 | .Int, .ComptimeInt => { | 710 | .Int, .ComptimeInt => { |
| 711 | return @intToPtr(DestType, target); | 711 | return @intToPtr(DestType, target); |
| 712 | }, | 712 | }, |
| 713 | .Pointer => |ptr| { | 713 | .Pointer => |ptr| { |
| 714 | return @ptrCast(DestType, @alignCast(ptr.alignment, target)); | 714 | return @ptrCast(DestType, @alignCast(dest_ptr.alignment, target)); |
| 715 | }, | 715 | }, |
| 716 | .Optional => |opt| { | 716 | .Optional => |opt| { |
| 717 | if (@typeInfo(opt.child) == .Pointer) { | 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 | else => {}, | 721 | else => {}, |
| 722 | } | 722 | } |
| 723 | }, | 723 | }, |
| 724 | .Optional => |opt| { | 724 | .Optional => |dest_opt| { |
| 725 | if (@typeInfo(opt.child) == .Pointer) { | 725 | if (@typeInfo(dest_opt.child) == .Pointer) { |
| 726 | switch (@typeInfo(TargetType)) { | 726 | switch (@typeInfo(TargetType)) { |
| 727 | .Int, .ComptimeInt => { | 727 | .Int, .ComptimeInt => { |
| 728 | return @intToPtr(DestType, target); | 728 | return @intToPtr(DestType, target); |
| 729 | }, | 729 | }, |
| 730 | .Pointer => |ptr| { | 730 | .Pointer => { |
| 731 | return @ptrCast(DestType, @alignCast(ptr.alignment, target)); | 731 | return @ptrCast(DestType, @alignCast(@alignOf(dest_opt.child.Child), target)); |
| 732 | }, | 732 | }, |
| 733 | .Optional => |target_opt| { | 733 | .Optional => |target_opt| { |
| 734 | if (@typeInfo(target_opt.child) == .Pointer) { | 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 | else => {}, | 738 | else => {}, |
lib/std/meta/trailer_flags.zig+1| ... | @@ -46,6 +46,7 @@ pub fn TrailerFlags(comptime Fields: type) type { | ... | @@ -46,6 +46,7 @@ pub fn TrailerFlags(comptime Fields: type) type { |
| 46 | ??struct_field.field_type, | 46 | ??struct_field.field_type, |
| 47 | @as(?struct_field.field_type, null), | 47 | @as(?struct_field.field_type, null), |
| 48 | ), | 48 | ), |
| 49 | .is_comptime = false, | ||
| 49 | }; | 50 | }; |
| 50 | } | 51 | } |
| 51 | break :blk @Type(.{ | 52 | break :blk @Type(.{ |
lib/std/net.zig+4-1| ... | @@ -1164,7 +1164,7 @@ fn linuxLookupNameFromDnsSearch( | ... | @@ -1164,7 +1164,7 @@ fn linuxLookupNameFromDnsSearch( |
| 1164 | } | 1164 | } |
| 1165 | 1165 | ||
| 1166 | const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, ".")) | 1166 | const search = if (rc.search.isNull() or dots >= rc.ndots or mem.endsWith(u8, name, ".")) |
| 1167 | &[_]u8{} | 1167 | "" |
| 1168 | else | 1168 | else |
| 1169 | rc.search.span(); | 1169 | rc.search.span(); |
| 1170 | 1170 | ||
| ... | @@ -1641,6 +1641,9 @@ pub const StreamServer = struct { | ... | @@ -1641,6 +1641,9 @@ pub const StreamServer = struct { |
| 1641 | /// by the socket buffer limits, not by the system memory. | 1641 | /// by the socket buffer limits, not by the system memory. |
| 1642 | SystemResources, | 1642 | SystemResources, |
| 1643 | 1643 | ||
| 1644 | /// Socket is not listening for new connections. | ||
| 1645 | SocketNotListening, | ||
| 1646 | |||
| 1644 | ProtocolFailure, | 1647 | ProtocolFailure, |
| 1645 | 1648 | ||
| 1646 | /// Firewall rules forbid connection. | 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,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{ | 2515 | pub const SetEidError = error{ |
| 2516 | ResourceLimitReached, | ||
| 2517 | InvalidUserId, | 2516 | InvalidUserId, |
| 2518 | PermissionDenied, | 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 | switch (errno(system.setuid(uid))) { | 2523 | switch (errno(system.setuid(uid))) { |
| 2523 | 0 => return, | 2524 | 0 => return, |
| 2524 | EAGAIN => return error.ResourceLimitReached, | 2525 | EAGAIN => return error.ResourceLimitReached, |
| ... | @@ -2528,7 +2529,16 @@ pub fn setuid(uid: u32) SetIdError!void { | ... | @@ -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 | switch (errno(system.setreuid(ruid, euid))) { | 2542 | switch (errno(system.setreuid(ruid, euid))) { |
| 2533 | 0 => return, | 2543 | 0 => return, |
| 2534 | EAGAIN => return error.ResourceLimitReached, | 2544 | EAGAIN => return error.ResourceLimitReached, |
| ... | @@ -2538,7 +2548,7 @@ pub fn setreuid(ruid: u32, euid: u32) SetIdError!void { | ... | @@ -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 | switch (errno(system.setgid(gid))) { | 2552 | switch (errno(system.setgid(gid))) { |
| 2543 | 0 => return, | 2553 | 0 => return, |
| 2544 | EAGAIN => return error.ResourceLimitReached, | 2554 | EAGAIN => return error.ResourceLimitReached, |
| ... | @@ -2548,7 +2558,16 @@ pub fn setgid(gid: u32) SetIdError!void { | ... | @@ -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 | switch (errno(system.setregid(rgid, egid))) { | 2571 | switch (errno(system.setregid(rgid, egid))) { |
| 2553 | 0 => return, | 2572 | 0 => return, |
| 2554 | EAGAIN => return error.ResourceLimitReached, | 2573 | EAGAIN => return error.ResourceLimitReached, |
| ... | @@ -2802,6 +2821,9 @@ pub const AcceptError = error{ | ... | @@ -2802,6 +2821,9 @@ pub const AcceptError = error{ |
| 2802 | /// by the socket buffer limits, not by the system memory. | 2821 | /// by the socket buffer limits, not by the system memory. |
| 2803 | SystemResources, | 2822 | SystemResources, |
| 2804 | 2823 | ||
| 2824 | /// Socket is not listening for new connections. | ||
| 2825 | SocketNotListening, | ||
| 2826 | |||
| 2805 | ProtocolFailure, | 2827 | ProtocolFailure, |
| 2806 | 2828 | ||
| 2807 | /// Firewall rules forbid connection. | 2829 | /// Firewall rules forbid connection. |
| ... | @@ -2870,7 +2892,7 @@ pub fn accept( | ... | @@ -2870,7 +2892,7 @@ pub fn accept( |
| 2870 | EBADF => unreachable, // always a race condition | 2892 | EBADF => unreachable, // always a race condition |
| 2871 | ECONNABORTED => return error.ConnectionAborted, | 2893 | ECONNABORTED => return error.ConnectionAborted, |
| 2872 | EFAULT => unreachable, | 2894 | EFAULT => unreachable, |
| 2873 | EINVAL => unreachable, | 2895 | EINVAL => return error.SocketNotListening, |
| 2874 | ENOTSOCK => unreachable, | 2896 | ENOTSOCK => unreachable, |
| 2875 | EMFILE => return error.ProcessFdQuotaExceeded, | 2897 | EMFILE => return error.ProcessFdQuotaExceeded, |
| 2876 | ENFILE => return error.SystemFdQuotaExceeded, | 2898 | ENFILE => return error.SystemFdQuotaExceeded, |
| ... | @@ -5328,3 +5350,71 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t { | ... | @@ -5328,3 +5350,71 @@ pub fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) !fd_t { |
| 5328 | else => |err| return std.os.unexpectedErrno(err), | 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,9 +7,13 @@ const std = @import("../../std.zig"); |
| 7 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 8 | const maxInt = std.math.maxInt; | 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 | pub const fd_t = c_int; | 12 | pub const fd_t = c_int; |
| 11 | pub const pid_t = c_int; | 13 | pub const pid_t = c_int; |
| 12 | pub const mode_t = c_uint; | 14 | pub const mode_t = c_uint; |
| 15 | pub const uid_t = u32; | ||
| 16 | pub const gid_t = u32; | ||
| 13 | 17 | ||
| 14 | pub const in_port_t = u16; | 18 | pub const in_port_t = u16; |
| 15 | pub const sa_family_t = u8; | 19 | pub const sa_family_t = u8; |
| ... | @@ -79,8 +83,8 @@ pub const Stat = extern struct { | ... | @@ -79,8 +83,8 @@ pub const Stat = extern struct { |
| 79 | mode: u16, | 83 | mode: u16, |
| 80 | nlink: u16, | 84 | nlink: u16, |
| 81 | ino: ino_t, | 85 | ino: ino_t, |
| 82 | uid: u32, | 86 | uid: uid_t, |
| 83 | gid: u32, | 87 | gid: gid_t, |
| 84 | rdev: i32, | 88 | rdev: i32, |
| 85 | atimesec: isize, | 89 | atimesec: isize, |
| 86 | atimensec: isize, | 90 | atimensec: isize, |
lib/std/os/bits/dragonfly.zig+10-3| ... | @@ -9,10 +9,17 @@ const maxInt = std.math.maxInt; | ... | @@ -9,10 +9,17 @@ const maxInt = std.math.maxInt; |
| 9 | pub fn S_ISCHR(m: u32) bool { | 9 | pub fn S_ISCHR(m: u32) bool { |
| 10 | return m & S_IFMT == S_IFCHR; | 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 | pub const fd_t = c_int; | 17 | pub const fd_t = c_int; |
| 13 | pub const pid_t = c_int; | 18 | pub const pid_t = c_int; |
| 14 | pub const off_t = c_long; | 19 | pub const off_t = c_long; |
| 15 | pub const mode_t = c_uint; | 20 | pub const mode_t = c_uint; |
| 21 | pub const uid_t = u32; | ||
| 22 | pub const gid_t = u32; | ||
| 16 | 23 | ||
| 17 | pub const ENOTSUP = EOPNOTSUPP; | 24 | pub const ENOTSUP = EOPNOTSUPP; |
| 18 | pub const EWOULDBLOCK = EAGAIN; | 25 | pub const EWOULDBLOCK = EAGAIN; |
| ... | @@ -151,8 +158,8 @@ pub const Stat = extern struct { | ... | @@ -151,8 +158,8 @@ pub const Stat = extern struct { |
| 151 | dev: c_uint, | 158 | dev: c_uint, |
| 152 | mode: c_ushort, | 159 | mode: c_ushort, |
| 153 | padding1: u16, | 160 | padding1: u16, |
| 154 | uid: c_uint, | 161 | uid: uid_t, |
| 155 | gid: c_uint, | 162 | gid: gid_t, |
| 156 | rdev: c_uint, | 163 | rdev: c_uint, |
| 157 | atim: timespec, | 164 | atim: timespec, |
| 158 | mtim: timespec, | 165 | mtim: timespec, |
| ... | @@ -511,7 +518,7 @@ pub const siginfo_t = extern struct { | ... | @@ -511,7 +518,7 @@ pub const siginfo_t = extern struct { |
| 511 | si_errno: c_int, | 518 | si_errno: c_int, |
| 512 | si_code: c_int, | 519 | si_code: c_int, |
| 513 | si_pid: c_int, | 520 | si_pid: c_int, |
| 514 | si_uid: c_uint, | 521 | si_uid: uid_t, |
| 515 | si_status: c_int, | 522 | si_status: c_int, |
| 516 | si_addr: ?*c_void, | 523 | si_addr: ?*c_void, |
| 517 | si_value: union_sigval, | 524 | si_value: union_sigval, |
lib/std/os/bits/freebsd.zig+6-2| ... | @@ -6,8 +6,12 @@ | ... | @@ -6,8 +6,12 @@ |
| 6 | const std = @import("../../std.zig"); | 6 | const std = @import("../../std.zig"); |
| 7 | const maxInt = std.math.maxInt; | 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 | pub const fd_t = c_int; | 11 | pub const fd_t = c_int; |
| 10 | pub const pid_t = c_int; | 12 | pub const pid_t = c_int; |
| 13 | pub const uid_t = u32; | ||
| 14 | pub const gid_t = u32; | ||
| 11 | pub const mode_t = c_uint; | 15 | pub const mode_t = c_uint; |
| 12 | 16 | ||
| 13 | pub const socklen_t = u32; | 17 | pub const socklen_t = u32; |
| ... | @@ -128,8 +132,8 @@ pub const Stat = extern struct { | ... | @@ -128,8 +132,8 @@ pub const Stat = extern struct { |
| 128 | 132 | ||
| 129 | mode: u16, | 133 | mode: u16, |
| 130 | __pad0: u16, | 134 | __pad0: u16, |
| 131 | uid: u32, | 135 | uid: uid_t, |
| 132 | gid: u32, | 136 | gid: gid_t, |
| 133 | __pad1: u32, | 137 | __pad1: u32, |
| 134 | rdev: u64, | 138 | rdev: u64, |
| 135 | 139 |
lib/std/os/bits/linux.zig+4-4| ... | @@ -29,7 +29,7 @@ const is_mips = builtin.arch.isMIPS(); | ... | @@ -29,7 +29,7 @@ const is_mips = builtin.arch.isMIPS(); |
| 29 | 29 | ||
| 30 | pub const pid_t = i32; | 30 | pub const pid_t = i32; |
| 31 | pub const fd_t = i32; | 31 | pub const fd_t = i32; |
| 32 | pub const uid_t = i32; | 32 | pub const uid_t = u32; |
| 33 | pub const gid_t = u32; | 33 | pub const gid_t = u32; |
| 34 | pub const clock_t = isize; | 34 | pub const clock_t = isize; |
| 35 | 35 | ||
| ... | @@ -853,7 +853,7 @@ pub const signalfd_siginfo = extern struct { | ... | @@ -853,7 +853,7 @@ pub const signalfd_siginfo = extern struct { |
| 853 | errno: i32, | 853 | errno: i32, |
| 854 | code: i32, | 854 | code: i32, |
| 855 | pid: u32, | 855 | pid: u32, |
| 856 | uid: u32, | 856 | uid: uid_t, |
| 857 | fd: i32, | 857 | fd: i32, |
| 858 | tid: u32, | 858 | tid: u32, |
| 859 | band: u32, | 859 | band: u32, |
| ... | @@ -1491,10 +1491,10 @@ pub const Statx = extern struct { | ... | @@ -1491,10 +1491,10 @@ pub const Statx = extern struct { |
| 1491 | nlink: u32, | 1491 | nlink: u32, |
| 1492 | 1492 | ||
| 1493 | /// User ID of owner | 1493 | /// User ID of owner |
| 1494 | uid: u32, | 1494 | uid: uid_t, |
| 1495 | 1495 | ||
| 1496 | /// Group ID of owner | 1496 | /// Group ID of owner |
| 1497 | gid: u32, | 1497 | gid: gid_t, |
| 1498 | 1498 | ||
| 1499 | /// File type and mode | 1499 | /// File type and mode |
| 1500 | mode: u16, | 1500 | mode: u16, |
lib/std/os/bits/linux/x86_64.zig+3-2| ... | @@ -7,6 +7,7 @@ | ... | @@ -7,6 +7,7 @@ |
| 7 | const std = @import("../../../std.zig"); | 7 | const std = @import("../../../std.zig"); |
| 8 | const pid_t = linux.pid_t; | 8 | const pid_t = linux.pid_t; |
| 9 | const uid_t = linux.uid_t; | 9 | const uid_t = linux.uid_t; |
| 10 | const gid_t = linux.gid_t; | ||
| 10 | const clock_t = linux.clock_t; | 11 | const clock_t = linux.clock_t; |
| 11 | const stack_t = linux.stack_t; | 12 | const stack_t = linux.stack_t; |
| 12 | const sigset_t = linux.sigset_t; | 13 | const sigset_t = linux.sigset_t; |
| ... | @@ -523,8 +524,8 @@ pub const Stat = extern struct { | ... | @@ -523,8 +524,8 @@ pub const Stat = extern struct { |
| 523 | nlink: usize, | 524 | nlink: usize, |
| 524 | 525 | ||
| 525 | mode: u32, | 526 | mode: u32, |
| 526 | uid: u32, | 527 | uid: uid_t, |
| 527 | gid: u32, | 528 | gid: gid_t, |
| 528 | __pad0: u32, | 529 | __pad0: u32, |
| 529 | rdev: u64, | 530 | rdev: u64, |
| 530 | size: off_t, | 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,7 +655,7 @@ pub fn nanosleep(req: *const timespec, rem: ?*timespec) usize { |
| 655 | return syscall2(.nanosleep, @ptrToInt(req), @ptrToInt(rem)); | 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 | if (@hasField(SYS, "setuid32")) { | 659 | if (@hasField(SYS, "setuid32")) { |
| 660 | return syscall1(.setuid32, uid); | 660 | return syscall1(.setuid32, uid); |
| 661 | } else { | 661 | } else { |
| ... | @@ -663,7 +663,7 @@ pub fn setuid(uid: u32) usize { | ... | @@ -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 | if (@hasField(SYS, "setgid32")) { | 667 | if (@hasField(SYS, "setgid32")) { |
| 668 | return syscall1(.setgid32, gid); | 668 | return syscall1(.setgid32, gid); |
| 669 | } else { | 669 | } else { |
| ... | @@ -671,7 +671,7 @@ pub fn setgid(gid: u32) usize { | ... | @@ -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 | if (@hasField(SYS, "setreuid32")) { | 675 | if (@hasField(SYS, "setreuid32")) { |
| 676 | return syscall2(.setreuid32, ruid, euid); | 676 | return syscall2(.setreuid32, ruid, euid); |
| 677 | } else { | 677 | } else { |
| ... | @@ -679,7 +679,7 @@ pub fn setreuid(ruid: u32, euid: u32) usize { | ... | @@ -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 | if (@hasField(SYS, "setregid32")) { | 683 | if (@hasField(SYS, "setregid32")) { |
| 684 | return syscall2(.setregid32, rgid, egid); | 684 | return syscall2(.setregid32, rgid, egid); |
| 685 | } else { | 685 | } else { |
| ... | @@ -687,47 +687,61 @@ pub fn setregid(rgid: u32, egid: u32) usize { | ... | @@ -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 | if (@hasField(SYS, "getuid32")) { | 691 | if (@hasField(SYS, "getuid32")) { |
| 692 | return @as(u32, syscall0(.getuid32)); | 692 | return @as(uid_t, syscall0(.getuid32)); |
| 693 | } else { | 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 | if (@hasField(SYS, "getgid32")) { | 699 | if (@hasField(SYS, "getgid32")) { |
| 700 | return @as(u32, syscall0(.getgid32)); | 700 | return @as(gid_t, syscall0(.getgid32)); |
| 701 | } else { | 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 | if (@hasField(SYS, "geteuid32")) { | 707 | if (@hasField(SYS, "geteuid32")) { |
| 708 | return @as(u32, syscall0(.geteuid32)); | 708 | return @as(uid_t, syscall0(.geteuid32)); |
| 709 | } else { | 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 | if (@hasField(SYS, "getegid32")) { | 715 | if (@hasField(SYS, "getegid32")) { |
| 716 | return @as(u32, syscall0(.getegid32)); | 716 | return @as(gid_t, syscall0(.getegid32)); |
| 717 | } else { | 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 { | 722 | pub fn seteuid(euid: uid_t) usize { |
| 723 | return setreuid(std.math.maxInt(u32), euid); | 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 { | 733 | pub fn setegid(egid: gid_t) usize { |
| 727 | return setregid(std.math.maxInt(u32), egid); | 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 | if (@hasField(SYS, "getresuid32")) { | 745 | if (@hasField(SYS, "getresuid32")) { |
| 732 | return syscall3(.getresuid32, @ptrToInt(ruid), @ptrToInt(euid), @ptrToInt(suid)); | 746 | return syscall3(.getresuid32, @ptrToInt(ruid), @ptrToInt(euid), @ptrToInt(suid)); |
| 733 | } else { | 747 | } else { |
| ... | @@ -735,7 +749,7 @@ pub fn getresuid(ruid: *u32, euid: *u32, suid: *u32) usize { | ... | @@ -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 | if (@hasField(SYS, "getresgid32")) { | 753 | if (@hasField(SYS, "getresgid32")) { |
| 740 | return syscall3(.getresgid32, @ptrToInt(rgid), @ptrToInt(egid), @ptrToInt(sgid)); | 754 | return syscall3(.getresgid32, @ptrToInt(rgid), @ptrToInt(egid), @ptrToInt(sgid)); |
| 741 | } else { | 755 | } else { |
| ... | @@ -743,7 +757,7 @@ pub fn getresgid(rgid: *u32, egid: *u32, sgid: *u32) usize { | ... | @@ -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 | if (@hasField(SYS, "setresuid32")) { | 761 | if (@hasField(SYS, "setresuid32")) { |
| 748 | return syscall3(.setresuid32, ruid, euid, suid); | 762 | return syscall3(.setresuid32, ruid, euid, suid); |
| 749 | } else { | 763 | } else { |
| ... | @@ -751,7 +765,7 @@ pub fn setresuid(ruid: u32, euid: u32, suid: u32) usize { | ... | @@ -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 | if (@hasField(SYS, "setresgid32")) { | 769 | if (@hasField(SYS, "setresgid32")) { |
| 756 | return syscall3(.setresgid32, rgid, egid, sgid); | 770 | return syscall3(.setresgid32, rgid, egid, sgid); |
| 757 | } else { | 771 | } else { |
| ... | @@ -759,7 +773,7 @@ pub fn setresgid(rgid: u32, egid: u32, sgid: u32) usize { | ... | @@ -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 | if (@hasField(SYS, "getgroups32")) { | 777 | if (@hasField(SYS, "getgroups32")) { |
| 764 | return syscall2(.getgroups32, size, @ptrToInt(list)); | 778 | return syscall2(.getgroups32, size, @ptrToInt(list)); |
| 765 | } else { | 779 | } else { |
| ... | @@ -767,7 +781,7 @@ pub fn getgroups(size: usize, list: *u32) usize { | ... | @@ -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 | if (@hasField(SYS, "setgroups32")) { | 785 | if (@hasField(SYS, "setgroups32")) { |
| 772 | return syscall2(.setgroups32, size, @ptrToInt(list)); | 786 | return syscall2(.setgroups32, size, @ptrToInt(list)); |
| 773 | } else { | 787 | } else { |
| ... | @@ -1226,6 +1240,22 @@ pub fn bpf(cmd: BPF.Cmd, attr: *BPF.Attr, size: u32) usize { | ... | @@ -1226,6 +1240,22 @@ pub fn bpf(cmd: BPF.Cmd, attr: *BPF.Attr, size: u32) usize { |
| 1226 | return syscall3(.bpf, @enumToInt(cmd), @ptrToInt(attr), size); | 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 | test "" { | 1259 | test "" { |
| 1230 | if (builtin.os.tag == .linux) { | 1260 | if (builtin.os.tag == .linux) { |
| 1231 | _ = @import("linux/test.zig"); | 1261 | _ = @import("linux/test.zig"); |
lib/std/os/test.zig+36| ... | @@ -555,3 +555,39 @@ test "signalfd" { | ... | @@ -555,3 +555,39 @@ test "signalfd" { |
| 555 | return error.SkipZigTest; | 555 | return error.SkipZigTest; |
| 556 | _ = std.os.signalfd; | 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,3 +287,5 @@ pub extern "kernel32" fn K32GetWsChangesEx(hProcess: HANDLE, lpWatchInfoEx: PPSA |
| 287 | pub extern "kernel32" fn K32InitializeProcessForWsWatch(hProcess: HANDLE) callconv(.Stdcall) BOOL; | 287 | pub extern "kernel32" fn K32InitializeProcessForWsWatch(hProcess: HANDLE) callconv(.Stdcall) BOOL; |
| 288 | pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL; | 288 | pub extern "kernel32" fn K32QueryWorkingSet(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL; |
| 289 | pub extern "kernel32" fn K32QueryWorkingSetEx(hProcess: HANDLE, pv: PVOID, cb: DWORD) callconv(.Stdcall) BOOL; | 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,8 +578,8 @@ fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []cons |
| 578 | } | 578 | } |
| 579 | 579 | ||
| 580 | pub const UserInfo = struct { | 580 | pub const UserInfo = struct { |
| 581 | uid: u32, | 581 | uid: os.uid_t, |
| 582 | gid: u32, | 582 | gid: os.gid_t, |
| 583 | }; | 583 | }; |
| 584 | 584 | ||
| 585 | /// POSIX function which gets a uid from username. | 585 | /// POSIX function which gets a uid from username. |
| ... | @@ -607,8 +607,8 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo { | ... | @@ -607,8 +607,8 @@ pub fn posixGetUserInfo(name: []const u8) !UserInfo { |
| 607 | var buf: [std.mem.page_size]u8 = undefined; | 607 | var buf: [std.mem.page_size]u8 = undefined; |
| 608 | var name_index: usize = 0; | 608 | var name_index: usize = 0; |
| 609 | var state = State.Start; | 609 | var state = State.Start; |
| 610 | var uid: u32 = 0; | 610 | var uid: os.uid_t = 0; |
| 611 | var gid: u32 = 0; | 611 | var gid: os.gid_t = 0; |
| 612 | 612 | ||
| 613 | while (true) { | 613 | while (true) { |
| 614 | const amt_read = try reader.read(buf[0..]); | 614 | const amt_read = try reader.read(buf[0..]); |
lib/std/progress.zig+3-3| ... | @@ -197,7 +197,7 @@ pub const Progress = struct { | ... | @@ -197,7 +197,7 @@ pub const Progress = struct { |
| 197 | var maybe_node: ?*Node = &self.root; | 197 | var maybe_node: ?*Node = &self.root; |
| 198 | while (maybe_node) |node| { | 198 | while (maybe_node) |node| { |
| 199 | if (need_ellipse) { | 199 | if (need_ellipse) { |
| 200 | self.bufWrite(&end, "...", .{}); | 200 | self.bufWrite(&end, "... ", .{}); |
| 201 | } | 201 | } |
| 202 | need_ellipse = false; | 202 | need_ellipse = false; |
| 203 | if (node.name.len != 0 or node.estimated_total_items != null) { | 203 | if (node.name.len != 0 or node.estimated_total_items != null) { |
| ... | @@ -218,7 +218,7 @@ pub const Progress = struct { | ... | @@ -218,7 +218,7 @@ pub const Progress = struct { |
| 218 | maybe_node = node.recently_updated_child; | 218 | maybe_node = node.recently_updated_child; |
| 219 | } | 219 | } |
| 220 | if (need_ellipse) { | 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,7 +253,7 @@ pub const Progress = struct { |
| 253 | const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11; | 253 | const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11; |
| 254 | const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end; | 254 | const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end; |
| 255 | if (end.* > max_end) { | 255 | if (end.* > max_end) { |
| 256 | const suffix = "..."; | 256 | const suffix = "... "; |
| 257 | self.columns_written = self.columns_written - (end.* - max_end) + suffix.len; | 257 | self.columns_written = self.columns_written - (end.* - max_end) + suffix.len; |
| 258 | std.mem.copy(u8, self.output_buffer[max_end..], suffix); | 258 | std.mem.copy(u8, self.output_buffer[max_end..], suffix); |
| 259 | end.* = max_end + suffix.len; | 259 | end.* = max_end + suffix.len; |
lib/std/special/test_runner.zig+1-1| ... | @@ -40,7 +40,7 @@ pub fn main() anyerror!void { | ... | @@ -40,7 +40,7 @@ pub fn main() anyerror!void { |
| 40 | test_node.activate(); | 40 | test_node.activate(); |
| 41 | progress.refresh(); | 41 | progress.refresh(); |
| 42 | if (progress.terminal == null) { | 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 | const result = if (test_fn.async_frame_size) |size| switch (io_mode) { | 45 | const result = if (test_fn.async_frame_size) |size| switch (io_mode) { |
| 46 | .evented => blk: { | 46 | .evented => blk: { |
lib/std/std.zig+7| ... | @@ -3,11 +3,15 @@ | ... | @@ -3,11 +3,15 @@ |
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | 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 | 4 | // The MIT license requires this copyright notice to be included in all copies |
| 5 | // and substantial portions of the software. | 5 | // and substantial portions of the software. |
| 6 | pub const ArrayHashMap = array_hash_map.ArrayHashMap; | ||
| 7 | pub const ArrayHashMapUnmanaged = array_hash_map.ArrayHashMapUnmanaged; | ||
| 6 | pub const ArrayList = @import("array_list.zig").ArrayList; | 8 | pub const ArrayList = @import("array_list.zig").ArrayList; |
| 7 | pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned; | 9 | pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned; |
| 8 | pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged; | 10 | pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged; |
| 9 | pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; | 11 | pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; |
| 10 | pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged; | 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 | pub const AutoHashMap = hash_map.AutoHashMap; | 15 | pub const AutoHashMap = hash_map.AutoHashMap; |
| 12 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; | 16 | pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; |
| 13 | pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; | 17 | pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; |
| ... | @@ -32,10 +36,13 @@ pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; | ... | @@ -32,10 +36,13 @@ pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; |
| 32 | pub const SpinLock = @import("spinlock.zig").SpinLock; | 36 | pub const SpinLock = @import("spinlock.zig").SpinLock; |
| 33 | pub const StringHashMap = hash_map.StringHashMap; | 37 | pub const StringHashMap = hash_map.StringHashMap; |
| 34 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; | 38 | pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; |
| 39 | pub const StringArrayHashMap = array_hash_map.StringArrayHashMap; | ||
| 40 | pub const StringArrayHashMapUnmanaged = array_hash_map.StringArrayHashMapUnmanaged; | ||
| 35 | pub const TailQueue = @import("linked_list.zig").TailQueue; | 41 | pub const TailQueue = @import("linked_list.zig").TailQueue; |
| 36 | pub const Target = @import("target.zig").Target; | 42 | pub const Target = @import("target.zig").Target; |
| 37 | pub const Thread = @import("thread.zig").Thread; | 43 | pub const Thread = @import("thread.zig").Thread; |
| 38 | 44 | ||
| 45 | pub const array_hash_map = @import("array_hash_map.zig"); | ||
| 39 | pub const atomic = @import("atomic.zig"); | 46 | pub const atomic = @import("atomic.zig"); |
| 40 | pub const base64 = @import("base64.zig"); | 47 | pub const base64 = @import("base64.zig"); |
| 41 | pub const build = @import("build.zig"); | 48 | pub const build = @import("build.zig"); |
lib/std/target.zig+1-1| ... | @@ -101,7 +101,7 @@ pub const Target = struct { | ... | @@ -101,7 +101,7 @@ pub const Target = struct { |
| 101 | 101 | ||
| 102 | /// Latest Windows version that the Zig Standard Library is aware of | 102 | /// Latest Windows version that the Zig Standard Library is aware of |
| 103 | pub const latest = WindowsVersion.win10_20h1; | 103 | pub const latest = WindowsVersion.win10_20h1; |
| 104 | 104 | ||
| 105 | pub const Range = struct { | 105 | pub const Range = struct { |
| 106 | min: WindowsVersion, | 106 | min: WindowsVersion, |
| 107 | max: WindowsVersion, | 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,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 | test "zig fmt: C pointers" { | 629 | test "zig fmt: C pointers" { |
| 619 | try testCanonical( | 630 | try testCanonical( |
| 620 | \\const Ptr = [*c]i32; | 631 | \\const Ptr = [*c]i32; |
| ... | @@ -885,6 +896,28 @@ test "zig fmt: 2nd arg multiline string" { | ... | @@ -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 | test "zig fmt: if condition wraps" { | 921 | test "zig fmt: if condition wraps" { |
| 889 | try testTransform( | 922 | try testTransform( |
| 890 | \\comptime { | 923 | \\comptime { |
| ... | @@ -915,6 +948,11 @@ test "zig fmt: if condition wraps" { | ... | @@ -915,6 +948,11 @@ test "zig fmt: if condition wraps" { |
| 915 | \\ var a = if (a) |*f| x: { | 948 | \\ var a = if (a) |*f| x: { |
| 916 | \\ break :x &a.b; | 949 | \\ break :x &a.b; |
| 917 | \\ } else |err| err; | 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 | \\comptime { | 958 | \\comptime { |
| ... | @@ -951,6 +989,35 @@ test "zig fmt: if condition wraps" { | ... | @@ -951,6 +989,35 @@ test "zig fmt: if condition wraps" { |
| 951 | \\ var a = if (a) |*f| x: { | 989 | \\ var a = if (a) |*f| x: { |
| 952 | \\ break :x &a.b; | 990 | \\ break :x &a.b; |
| 953 | \\ } else |err| err; | 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,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 | test "zig fmt: same-line doc comment on variable declaration" { | 1059 | test "zig fmt: same-line doc comment on variable declaration" { |
| 981 | try testTransform( | 1060 | try testTransform( |
| 982 | \\pub const MAP_ANONYMOUS = 0x1000; /// allocated from memory, swap space | 1061 | \\pub const MAP_ANONYMOUS = 0x1000; /// allocated from memory, swap space |
| ... | @@ -1228,7 +1307,7 @@ test "zig fmt: array literal with hint" { | ... | @@ -1228,7 +1307,7 @@ test "zig fmt: array literal with hint" { |
| 1228 | \\const a = []u8{ | 1307 | \\const a = []u8{ |
| 1229 | \\ 1, 2, | 1308 | \\ 1, 2, |
| 1230 | \\ 3, // | 1309 | \\ 3, // |
| 1231 | \\ 4, | 1310 | \\ 4, |
| 1232 | \\ 5, 6, | 1311 | \\ 5, 6, |
| 1233 | \\ 7, | 1312 | \\ 7, |
| 1234 | \\}; | 1313 | \\}; |
| ... | @@ -1293,7 +1372,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" { | ... | @@ -1293,7 +1372,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" { |
| 1293 | \\ \\ZIG_C_HEADER_FILES {} | 1372 | \\ \\ZIG_C_HEADER_FILES {} |
| 1294 | \\ \\ZIG_DIA_GUIDS_LIB {} | 1373 | \\ \\ZIG_DIA_GUIDS_LIB {} |
| 1295 | \\ \\ | 1374 | \\ \\ |
| 1296 | \\ , | 1375 | \\ , |
| 1297 | \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR), | 1376 | \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR), |
| 1298 | \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER), | 1377 | \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER), |
| 1299 | \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB), | 1378 | \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB), |
| ... | @@ -2885,20 +2964,20 @@ test "zig fmt: multiline string in array" { | ... | @@ -2885,20 +2964,20 @@ test "zig fmt: multiline string in array" { |
| 2885 | try testCanonical( | 2964 | try testCanonical( |
| 2886 | \\const Foo = [][]const u8{ | 2965 | \\const Foo = [][]const u8{ |
| 2887 | \\ \\aaa | 2966 | \\ \\aaa |
| 2888 | \\, | 2967 | \\ , |
| 2889 | \\ \\bbb | 2968 | \\ \\bbb |
| 2890 | \\}; | 2969 | \\}; |
| 2891 | \\ | 2970 | \\ |
| 2892 | \\fn bar() void { | 2971 | \\fn bar() void { |
| 2893 | \\ const Foo = [][]const u8{ | 2972 | \\ const Foo = [][]const u8{ |
| 2894 | \\ \\aaa | 2973 | \\ \\aaa |
| 2895 | \\ , | 2974 | \\ , |
| 2896 | \\ \\bbb | 2975 | \\ \\bbb |
| 2897 | \\ }; | 2976 | \\ }; |
| 2898 | \\ const Bar = [][]const u8{ // comment here | 2977 | \\ const Bar = [][]const u8{ // comment here |
| 2899 | \\ \\aaa | 2978 | \\ \\aaa |
| 2900 | \\ \\ | 2979 | \\ \\ |
| 2901 | \\ , // and another comment can go here | 2980 | \\ , // and another comment can go here |
| 2902 | \\ \\bbb | 2981 | \\ \\bbb |
| 2903 | \\ }; | 2982 | \\ }; |
| 2904 | \\} | 2983 | \\} |
| ... | @@ -3214,6 +3293,34 @@ test "zig fmt: C var args" { | ... | @@ -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 | const std = @import("std"); | 3324 | const std = @import("std"); |
| 3218 | const mem = std.mem; | 3325 | const mem = std.mem; |
| 3219 | const warn = std.debug.warn; | 3326 | const warn = std.debug.warn; |
| ... | @@ -3256,7 +3363,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b | ... | @@ -3256,7 +3363,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b |
| 3256 | var buffer = std.ArrayList(u8).init(allocator); | 3363 | var buffer = std.ArrayList(u8).init(allocator); |
| 3257 | errdefer buffer.deinit(); | 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 | return buffer.toOwnedSlice(); | 3368 | return buffer.toOwnedSlice(); |
| 3261 | } | 3369 | } |
| 3262 | fn testTransform(source: []const u8, expected_source: []const u8) !void { | 3370 | fn testTransform(source: []const u8, expected_source: []const u8) !void { |
lib/std/zig/render.zig+763-912| ... | @@ -6,10 +6,12 @@ | ... | @@ -6,10 +6,12 @@ |
| 6 | const std = @import("../std.zig"); | 6 | const std = @import("../std.zig"); |
| 7 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 8 | const mem = std.mem; | 8 | const mem = std.mem; |
| 9 | const meta = std.meta; | ||
| 9 | const ast = std.zig.ast; | 10 | const ast = std.zig.ast; |
| 10 | const Token = std.zig.Token; | 11 | const Token = std.zig.Token; |
| 11 | 12 | ||
| 12 | const indent_delta = 4; | 13 | const indent_delta = 4; |
| 14 | const asm_indent_delta = 2; | ||
| 13 | 15 | ||
| 14 | pub const Error = error{ | 16 | pub const Error = error{ |
| 15 | /// Ran out of memory allocating call stack frames to complete rendering. | 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,70 +23,32 @@ pub fn render(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree) (@Typ |
| 21 | // cannot render an invalid tree | 23 | // cannot render an invalid tree |
| 22 | std.debug.assert(tree.errors.len == 0); | 24 | std.debug.assert(tree.errors.len == 0); |
| 23 | 25 | ||
| 24 | // make a passthrough stream that checks whether something changed | 26 | var change_detection_stream = std.io.changeDetectionStream(tree.source, stream); |
| 25 | const MyStream = struct { | 27 | var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, change_detection_stream.writer()); |
| 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 | }; | ||
| 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) { | 31 | return change_detection_stream.changeDetected(); |
| 64 | my_stream.anything_changed = true; | ||
| 65 | } | ||
| 66 | |||
| 67 | return my_stream.anything_changed; | ||
| 68 | } | 32 | } |
| 69 | 33 | ||
| 70 | fn renderRoot( | 34 | fn renderRoot( |
| 71 | allocator: *mem.Allocator, | 35 | allocator: *mem.Allocator, |
| 72 | stream: anytype, | 36 | ais: anytype, |
| 73 | tree: *ast.Tree, | 37 | tree: *ast.Tree, |
| 74 | ) (@TypeOf(stream).Error || Error)!void { | 38 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 39 | |||
| 75 | // render all the line comments at the beginning of the file | 40 | // render all the line comments at the beginning of the file |
| 76 | for (tree.token_ids) |token_id, i| { | 41 | for (tree.token_ids) |token_id, i| { |
| 77 | if (token_id != .LineComment) break; | 42 | if (token_id != .LineComment) break; |
| 78 | const token_loc = tree.token_locs[i]; | 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 | const next_token = tree.token_locs[i + 1]; | 45 | const next_token = tree.token_locs[i + 1]; |
| 81 | const loc = tree.tokenLocationLoc(token_loc.end, next_token); | 46 | const loc = tree.tokenLocationLoc(token_loc.end, next_token); |
| 82 | if (loc.line >= 2) { | 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 | var decl_i: ast.NodeIndex = 0; | 52 | var decl_i: ast.NodeIndex = 0; |
| 89 | const root_decls = tree.root_node.decls(); | 53 | const root_decls = tree.root_node.decls(); |
| 90 | 54 | ||
| ... | @@ -145,7 +109,7 @@ fn renderRoot( | ... | @@ -145,7 +109,7 @@ fn renderRoot( |
| 145 | // If there's no next reformatted `decl`, just copy the | 109 | // If there's no next reformatted `decl`, just copy the |
| 146 | // remaining input tokens and bail out. | 110 | // remaining input tokens and bail out. |
| 147 | const start = tree.token_locs[copy_start_token_index].start; | 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 | return; | 113 | return; |
| 150 | } | 114 | } |
| 151 | decl = root_decls[decl_i]; | 115 | decl = root_decls[decl_i]; |
| ... | @@ -186,26 +150,25 @@ fn renderRoot( | ... | @@ -186,26 +150,25 @@ fn renderRoot( |
| 186 | 150 | ||
| 187 | const start = tree.token_locs[copy_start_token_index].start; | 151 | const start = tree.token_locs[copy_start_token_index].start; |
| 188 | const end = tree.token_locs[copy_end_token_index].start; | 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 | decl_i += 1; | 157 | decl_i += 1; |
| 194 | if (decl_i >= root_decls.len) return; | 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 { | 163 | fn renderExtraNewline(tree: *ast.Tree, ais: anytype, node: *ast.Node) @TypeOf(ais.*).Error!void { |
| 200 | return renderExtraNewlineToken(tree, stream, start_col, node.firstToken()); | 164 | return renderExtraNewlineToken(tree, ais, node.firstToken()); |
| 201 | } | 165 | } |
| 202 | 166 | ||
| 203 | fn renderExtraNewlineToken( | 167 | fn renderExtraNewlineToken( |
| 204 | tree: *ast.Tree, | 168 | tree: *ast.Tree, |
| 205 | stream: anytype, | 169 | ais: anytype, |
| 206 | start_col: *usize, | ||
| 207 | first_token: ast.TokenIndex, | 170 | first_token: ast.TokenIndex, |
| 208 | ) @TypeOf(stream).Error!void { | 171 | ) @TypeOf(ais.*).Error!void { |
| 209 | var prev_token = first_token; | 172 | var prev_token = first_token; |
| 210 | if (prev_token == 0) return; | 173 | if (prev_token == 0) return; |
| 211 | var newline_threshold: usize = 2; | 174 | var newline_threshold: usize = 2; |
| ... | @@ -218,28 +181,27 @@ fn renderExtraNewlineToken( | ... | @@ -218,28 +181,27 @@ fn renderExtraNewlineToken( |
| 218 | const prev_token_end = tree.token_locs[prev_token - 1].end; | 181 | const prev_token_end = tree.token_locs[prev_token - 1].end; |
| 219 | const loc = tree.tokenLocation(prev_token_end, first_token); | 182 | const loc = tree.tokenLocation(prev_token_end, first_token); |
| 220 | if (loc.line >= newline_threshold) { | 183 | if (loc.line >= newline_threshold) { |
| 221 | try stream.writeByte('\n'); | 184 | try ais.insertNewline(); |
| 222 | start_col.* = 0; | ||
| 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 { | 188 | fn renderTopLevelDecl(allocator: *mem.Allocator, ais: anytype, tree: *ast.Tree, decl: *ast.Node) (@TypeOf(ais.*).Error || Error)!void { |
| 227 | try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Newline); | 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 | switch (decl.tag) { | 193 | switch (decl.tag) { |
| 232 | .FnProto => { | 194 | .FnProto => { |
| 233 | const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); | 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 | if (fn_proto.getBodyNode()) |body_node| { | 199 | if (fn_proto.getBodyNode()) |body_node| { |
| 238 | try renderExpression(allocator, stream, tree, indent, start_col, decl, .Space); | 200 | try renderExpression(allocator, ais, tree, decl, .Space); |
| 239 | try renderExpression(allocator, stream, tree, indent, start_col, body_node, space); | 201 | try renderExpression(allocator, ais, tree, body_node, space); |
| 240 | } else { | 202 | } else { |
| 241 | try renderExpression(allocator, stream, tree, indent, start_col, decl, .None); | 203 | try renderExpression(allocator, ais, tree, decl, .None); |
| 242 | try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, start_col, space); | 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,35 +209,35 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr |
| 247 | const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl); | 209 | const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl); |
| 248 | 210 | ||
| 249 | if (use_decl.visib_token) |visib_token| { | 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 | 214 | try renderToken(tree, ais, use_decl.use_token, .Space); // usingnamespace |
| 253 | try renderExpression(allocator, stream, tree, indent, start_col, use_decl.expr, .None); | 215 | try renderExpression(allocator, ais, tree, use_decl.expr, .None); |
| 254 | try renderToken(tree, stream, use_decl.semicolon_token, indent, start_col, space); // ; | 216 | try renderToken(tree, ais, use_decl.semicolon_token, space); // ; |
| 255 | }, | 217 | }, |
| 256 | 218 | ||
| 257 | .VarDecl => { | 219 | .VarDecl => { |
| 258 | const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl); | 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); | 222 | try renderDocComments(tree, ais, var_decl, var_decl.getDocComments()); |
| 261 | try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl); | 223 | try renderVarDecl(allocator, ais, tree, var_decl); |
| 262 | }, | 224 | }, |
| 263 | 225 | ||
| 264 | .TestDecl => { | 226 | .TestDecl => { |
| 265 | const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl); | 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); | 229 | try renderDocComments(tree, ais, test_decl, test_decl.doc_comments); |
| 268 | try renderToken(tree, stream, test_decl.test_token, indent, start_col, .Space); | 230 | try renderToken(tree, ais, test_decl.test_token, .Space); |
| 269 | try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, .Space); | 231 | try renderExpression(allocator, ais, tree, test_decl.name, .Space); |
| 270 | try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, space); | 232 | try renderExpression(allocator, ais, tree, test_decl.body_node, space); |
| 271 | }, | 233 | }, |
| 272 | 234 | ||
| 273 | .ContainerField => { | 235 | .ContainerField => { |
| 274 | const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl); | 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 | if (field.comptime_token) |t| { | 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 | const src_has_trailing_comma = blk: { | 243 | const src_has_trailing_comma = blk: { |
| ... | @@ -288,68 +250,67 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr | ... | @@ -288,68 +250,67 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr |
| 288 | const last_token_space: Space = if (src_has_trailing_comma) .None else space; | 250 | const last_token_space: Space = if (src_has_trailing_comma) .None else space; |
| 289 | 251 | ||
| 290 | if (field.type_expr == null and field.value_expr == null) { | 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 | } else if (field.type_expr != null and field.value_expr == null) { | 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 | 255 | try renderToken(tree, ais, field.name_token, .None); // name |
| 294 | try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // : | 256 | try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // : |
| 295 | 257 | ||
| 296 | if (field.align_expr) |align_value_expr| { | 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 | const lparen_token = tree.prevToken(align_value_expr.firstToken()); | 260 | const lparen_token = tree.prevToken(align_value_expr.firstToken()); |
| 299 | const align_kw = tree.prevToken(lparen_token); | 261 | const align_kw = tree.prevToken(lparen_token); |
| 300 | const rparen_token = tree.nextToken(align_value_expr.lastToken()); | 262 | const rparen_token = tree.nextToken(align_value_expr.lastToken()); |
| 301 | try renderToken(tree, stream, align_kw, indent, start_col, .None); // align | 263 | try renderToken(tree, ais, align_kw, .None); // align |
| 302 | try renderToken(tree, stream, lparen_token, indent, start_col, .None); // ( | 264 | try renderToken(tree, ais, lparen_token, .None); // ( |
| 303 | try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment | 265 | try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment |
| 304 | try renderToken(tree, stream, rparen_token, indent, start_col, last_token_space); // ) | 266 | try renderToken(tree, ais, rparen_token, last_token_space); // ) |
| 305 | } else { | 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 | } else if (field.type_expr == null and field.value_expr != null) { | 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 | 271 | try renderToken(tree, ais, field.name_token, .Space); // name |
| 310 | try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // = | 272 | try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // = |
| 311 | try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value | 273 | try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value |
| 312 | } else { | 274 | } else { |
| 313 | try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name | 275 | try renderToken(tree, ais, field.name_token, .None); // name |
| 314 | try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // : | 276 | try renderToken(tree, ais, tree.nextToken(field.name_token), .Space); // : |
| 315 | 277 | ||
| 316 | if (field.align_expr) |align_value_expr| { | 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 | const lparen_token = tree.prevToken(align_value_expr.firstToken()); | 280 | const lparen_token = tree.prevToken(align_value_expr.firstToken()); |
| 319 | const align_kw = tree.prevToken(lparen_token); | 281 | const align_kw = tree.prevToken(lparen_token); |
| 320 | const rparen_token = tree.nextToken(align_value_expr.lastToken()); | 282 | const rparen_token = tree.nextToken(align_value_expr.lastToken()); |
| 321 | try renderToken(tree, stream, align_kw, indent, start_col, .None); // align | 283 | try renderToken(tree, ais, align_kw, .None); // align |
| 322 | try renderToken(tree, stream, lparen_token, indent, start_col, .None); // ( | 284 | try renderToken(tree, ais, lparen_token, .None); // ( |
| 323 | try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment | 285 | try renderExpression(allocator, ais, tree, align_value_expr, .None); // alignment |
| 324 | try renderToken(tree, stream, rparen_token, indent, start_col, .Space); // ) | 286 | try renderToken(tree, ais, rparen_token, .Space); // ) |
| 325 | } else { | 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); // = | 290 | try renderToken(tree, ais, tree.prevToken(field.value_expr.?.firstToken()), .Space); // = |
| 329 | try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value | 291 | try renderExpression(allocator, ais, tree, field.value_expr.?, last_token_space); // value |
| 330 | } | 292 | } |
| 331 | 293 | ||
| 332 | if (src_has_trailing_comma) { | 294 | if (src_has_trailing_comma) { |
| 333 | const comma = tree.nextToken(field.lastToken()); | 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 | .Comptime => { | 300 | .Comptime => { |
| 339 | assert(!decl.requireSemiColon()); | 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 | .DocComment => { | 305 | .DocComment => { |
| 344 | const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl); | 306 | const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl); |
| 345 | const kind = tree.token_ids[comment.first_line]; | 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 | var tok_i = comment.first_line + 1; | 309 | var tok_i = comment.first_line + 1; |
| 348 | while (true) : (tok_i += 1) { | 310 | while (true) : (tok_i += 1) { |
| 349 | const tok_id = tree.token_ids[tok_i]; | 311 | const tok_id = tree.token_ids[tok_i]; |
| 350 | if (tok_id == kind) { | 312 | if (tok_id == kind) { |
| 351 | try stream.writeByteNTimes(' ', indent); | 313 | try renderToken(tree, ais, tok_i, .Newline); |
| 352 | try renderToken(tree, stream, tok_i, indent, start_col, .Newline); | ||
| 353 | } else if (tok_id == .LineComment) { | 314 | } else if (tok_id == .LineComment) { |
| 354 | continue; | 315 | continue; |
| 355 | } else { | 316 | } else { |
| ... | @@ -363,13 +324,11 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr | ... | @@ -363,13 +324,11 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr |
| 363 | 324 | ||
| 364 | fn renderExpression( | 325 | fn renderExpression( |
| 365 | allocator: *mem.Allocator, | 326 | allocator: *mem.Allocator, |
| 366 | stream: anytype, | 327 | ais: anytype, |
| 367 | tree: *ast.Tree, | 328 | tree: *ast.Tree, |
| 368 | indent: usize, | ||
| 369 | start_col: *usize, | ||
| 370 | base: *ast.Node, | 329 | base: *ast.Node, |
| 371 | space: Space, | 330 | space: Space, |
| 372 | ) (@TypeOf(stream).Error || Error)!void { | 331 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 373 | switch (base.tag) { | 332 | switch (base.tag) { |
| 374 | .Identifier, | 333 | .Identifier, |
| 375 | .IntegerLiteral, | 334 | .IntegerLiteral, |
| ... | @@ -383,18 +342,18 @@ fn renderExpression( | ... | @@ -383,18 +342,18 @@ fn renderExpression( |
| 383 | .UndefinedLiteral, | 342 | .UndefinedLiteral, |
| 384 | => { | 343 | => { |
| 385 | const casted_node = base.cast(ast.Node.OneToken).?; | 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 | .AnyType => { | 348 | .AnyType => { |
| 390 | const any_type = base.castTag(.AnyType).?; | 349 | const any_type = base.castTag(.AnyType).?; |
| 391 | if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) { | 350 | if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) { |
| 392 | // TODO remove in next release cycle | 351 | // TODO remove in next release cycle |
| 393 | try stream.writeAll("anytype"); | 352 | try ais.writer().writeAll("anytype"); |
| 394 | if (space == .Comma) try stream.writeAll(",\n"); | 353 | if (space == .Comma) try ais.writer().writeAll(",\n"); |
| 395 | return; | 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 | .Block, .LabeledBlock => { | 359 | .Block, .LabeledBlock => { |
| ... | @@ -424,65 +383,65 @@ fn renderExpression( | ... | @@ -424,65 +383,65 @@ fn renderExpression( |
| 424 | }; | 383 | }; |
| 425 | 384 | ||
| 426 | if (block.label) |label| { | 385 | if (block.label) |label| { |
| 427 | try renderToken(tree, stream, label, indent, start_col, Space.None); | 386 | try renderToken(tree, ais, label, Space.None); |
| 428 | try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); | 387 | try renderToken(tree, ais, tree.nextToken(label), Space.Space); |
| 429 | } | 388 | } |
| 430 | 389 | ||
| 431 | if (block.statements.len == 0) { | 390 | if (block.statements.len == 0) { |
| 432 | try renderToken(tree, stream, block.lbrace, indent + indent_delta, start_col, Space.None); | 391 | ais.pushIndentNextLine(); |
| 433 | return renderToken(tree, stream, block.rbrace, indent, start_col, space); | 392 | defer ais.popIndent(); |
| 393 | try renderToken(tree, ais, block.lbrace, Space.None); | ||
| 434 | } else { | 394 | } else { |
| 435 | const block_indent = indent + indent_delta; | 395 | ais.pushIndentNextLine(); |
| 436 | try renderToken(tree, stream, block.lbrace, block_indent, start_col, Space.Newline); | 396 | defer ais.popIndent(); |
| 397 | |||
| 398 | try renderToken(tree, ais, block.lbrace, Space.Newline); | ||
| 437 | 399 | ||
| 438 | for (block.statements) |statement, i| { | 400 | for (block.statements) |statement, i| { |
| 439 | try stream.writeByteNTimes(' ', block_indent); | 401 | try renderStatement(allocator, ais, tree, statement); |
| 440 | try renderStatement(allocator, stream, tree, block_indent, start_col, statement); | ||
| 441 | 402 | ||
| 442 | if (i + 1 < block.statements.len) { | 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 | .Defer => { | 411 | .Defer => { |
| 453 | const defer_node = @fieldParentPtr(ast.Node.Defer, "base", base); | 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 | if (defer_node.payload) |payload| { | 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 | .Comptime => { | 420 | .Comptime => { |
| 462 | const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", base); | 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); | 423 | try renderToken(tree, ais, comptime_node.comptime_token, Space.Space); |
| 465 | return renderExpression(allocator, stream, tree, indent, start_col, comptime_node.expr, space); | 424 | return renderExpression(allocator, ais, tree, comptime_node.expr, space); |
| 466 | }, | 425 | }, |
| 467 | .Nosuspend => { | 426 | .Nosuspend => { |
| 468 | const nosuspend_node = @fieldParentPtr(ast.Node.Nosuspend, "base", base); | 427 | const nosuspend_node = @fieldParentPtr(ast.Node.Nosuspend, "base", base); |
| 469 | if (mem.eql(u8, tree.tokenSlice(nosuspend_node.nosuspend_token), "noasync")) { | 428 | if (mem.eql(u8, tree.tokenSlice(nosuspend_node.nosuspend_token), "noasync")) { |
| 470 | // TODO: remove this | 429 | // TODO: remove this |
| 471 | try stream.writeAll("nosuspend "); | 430 | try ais.writer().writeAll("nosuspend "); |
| 472 | } else { | 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 | .Suspend => { | 437 | .Suspend => { |
| 479 | const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base); | 438 | const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base); |
| 480 | 439 | ||
| 481 | if (suspend_node.body) |body| { | 440 | if (suspend_node.body) |body| { |
| 482 | try renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, Space.Space); | 441 | try renderToken(tree, ais, suspend_node.suspend_token, Space.Space); |
| 483 | return renderExpression(allocator, stream, tree, indent, start_col, body, space); | 442 | return renderExpression(allocator, ais, tree, body, space); |
| 484 | } else { | 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,26 +449,21 @@ fn renderExpression( |
| 490 | const infix_op_node = @fieldParentPtr(ast.Node.Catch, "base", base); | 449 | const infix_op_node = @fieldParentPtr(ast.Node.Catch, "base", base); |
| 491 | 450 | ||
| 492 | const op_space = Space.Space; | 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 | const after_op_space = blk: { | 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)); | 455 | const same_line = tree.tokensOnSameLine(infix_op_node.op_token, tree.nextToken(infix_op_node.op_token)); |
| 497 | break :blk if (loc.line == 0) op_space else Space.Newline; | 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); | 459 | try renderToken(tree, ais, infix_op_node.op_token, 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 | } | ||
| 507 | 460 | ||
| 508 | if (infix_op_node.payload) |payload| { | 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 | .Add, | 469 | .Add, |
| ... | @@ -561,22 +515,16 @@ fn renderExpression( | ... | @@ -561,22 +515,16 @@ fn renderExpression( |
| 561 | .Period, .ErrorUnion, .Range => Space.None, | 515 | .Period, .ErrorUnion, .Range => Space.None, |
| 562 | else => Space.Space, | 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 | const after_op_space = blk: { | 520 | const after_op_space = blk: { |
| 567 | const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token)); | 521 | const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token)); |
| 568 | break :blk if (loc.line == 0) op_space else Space.Newline; | 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); | 525 | try renderToken(tree, ais, infix_op_node.op_token, after_op_space); |
| 572 | if (after_op_space == Space.Newline and | 526 | ais.pushIndentOneShot(); |
| 573 | tree.token_ids[tree.nextToken(infix_op_node.op_token)] != .MultilineStringLiteralLine) | 527 | return renderExpression(allocator, ais, tree, infix_op_node.rhs, space); |
| 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); | ||
| 580 | }, | 528 | }, |
| 581 | 529 | ||
| 582 | .BitNot, | 530 | .BitNot, |
| ... | @@ -587,8 +535,8 @@ fn renderExpression( | ... | @@ -587,8 +535,8 @@ fn renderExpression( |
| 587 | .AddressOf, | 535 | .AddressOf, |
| 588 | => { | 536 | => { |
| 589 | const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); | 537 | const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); |
| 590 | try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.None); | 538 | try renderToken(tree, ais, casted_node.op_token, Space.None); |
| 591 | return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space); | 539 | return renderExpression(allocator, ais, tree, casted_node.rhs, space); |
| 592 | }, | 540 | }, |
| 593 | 541 | ||
| 594 | .Try, | 542 | .Try, |
| ... | @@ -596,18 +544,16 @@ fn renderExpression( | ... | @@ -596,18 +544,16 @@ fn renderExpression( |
| 596 | .Await, | 544 | .Await, |
| 597 | => { | 545 | => { |
| 598 | const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); | 546 | const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); |
| 599 | try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.Space); | 547 | try renderToken(tree, ais, casted_node.op_token, Space.Space); |
| 600 | return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space); | 548 | return renderExpression(allocator, ais, tree, casted_node.rhs, space); |
| 601 | }, | 549 | }, |
| 602 | 550 | ||
| 603 | .ArrayType => { | 551 | .ArrayType => { |
| 604 | const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base); | 552 | const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base); |
| 605 | return renderArrayType( | 553 | return renderArrayType( |
| 606 | allocator, | 554 | allocator, |
| 607 | stream, | 555 | ais, |
| 608 | tree, | 556 | tree, |
| 609 | indent, | ||
| 610 | start_col, | ||
| 611 | array_type.op_token, | 557 | array_type.op_token, |
| 612 | array_type.rhs, | 558 | array_type.rhs, |
| 613 | array_type.len_expr, | 559 | array_type.len_expr, |
| ... | @@ -619,10 +565,8 @@ fn renderExpression( | ... | @@ -619,10 +565,8 @@ fn renderExpression( |
| 619 | const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base); | 565 | const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base); |
| 620 | return renderArrayType( | 566 | return renderArrayType( |
| 621 | allocator, | 567 | allocator, |
| 622 | stream, | 568 | ais, |
| 623 | tree, | 569 | tree, |
| 624 | indent, | ||
| 625 | start_col, | ||
| 626 | array_type.op_token, | 570 | array_type.op_token, |
| 627 | array_type.rhs, | 571 | array_type.rhs, |
| 628 | array_type.len_expr, | 572 | array_type.len_expr, |
| ... | @@ -635,111 +579,111 @@ fn renderExpression( | ... | @@ -635,111 +579,111 @@ fn renderExpression( |
| 635 | const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base); | 579 | const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base); |
| 636 | const op_tok_id = tree.token_ids[ptr_type.op_token]; | 580 | const op_tok_id = tree.token_ids[ptr_type.op_token]; |
| 637 | switch (op_tok_id) { | 581 | switch (op_tok_id) { |
| 638 | .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'), | 582 | .Asterisk, .AsteriskAsterisk => try ais.writer().writeByte('*'), |
| 639 | .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier) | 583 | .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier) |
| 640 | try stream.writeAll("[*c") | 584 | try ais.writer().writeAll("[*c") |
| 641 | else | 585 | else |
| 642 | try stream.writeAll("[*"), | 586 | try ais.writer().writeAll("[*"), |
| 643 | else => unreachable, | 587 | else => unreachable, |
| 644 | } | 588 | } |
| 645 | if (ptr_type.ptr_info.sentinel) |sentinel| { | 589 | if (ptr_type.ptr_info.sentinel) |sentinel| { |
| 646 | const colon_token = tree.prevToken(sentinel.firstToken()); | 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 | const sentinel_space = switch (op_tok_id) { | 592 | const sentinel_space = switch (op_tok_id) { |
| 649 | .LBracket => Space.None, | 593 | .LBracket => Space.None, |
| 650 | else => Space.Space, | 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 | switch (op_tok_id) { | 598 | switch (op_tok_id) { |
| 655 | .Asterisk, .AsteriskAsterisk => {}, | 599 | .Asterisk, .AsteriskAsterisk => {}, |
| 656 | .LBracket => try stream.writeByte(']'), | 600 | .LBracket => try ais.writer().writeByte(']'), |
| 657 | else => unreachable, | 601 | else => unreachable, |
| 658 | } | 602 | } |
| 659 | if (ptr_type.ptr_info.allowzero_token) |allowzero_token| { | 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 | if (ptr_type.ptr_info.align_info) |align_info| { | 606 | if (ptr_type.ptr_info.align_info) |align_info| { |
| 663 | const lparen_token = tree.prevToken(align_info.node.firstToken()); | 607 | const lparen_token = tree.prevToken(align_info.node.firstToken()); |
| 664 | const align_token = tree.prevToken(lparen_token); | 608 | const align_token = tree.prevToken(lparen_token); |
| 665 | 609 | ||
| 666 | try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align | 610 | try renderToken(tree, ais, align_token, Space.None); // align |
| 667 | try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( | 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 | if (align_info.bit_range) |bit_range| { | 615 | if (align_info.bit_range) |bit_range| { |
| 672 | const colon1 = tree.prevToken(bit_range.start.firstToken()); | 616 | const colon1 = tree.prevToken(bit_range.start.firstToken()); |
| 673 | const colon2 = tree.prevToken(bit_range.end.firstToken()); | 617 | const colon2 = tree.prevToken(bit_range.end.firstToken()); |
| 674 | 618 | ||
| 675 | try renderToken(tree, stream, colon1, indent, start_col, Space.None); // : | 619 | try renderToken(tree, ais, colon1, Space.None); // : |
| 676 | try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None); | 620 | try renderExpression(allocator, ais, tree, bit_range.start, Space.None); |
| 677 | try renderToken(tree, stream, colon2, indent, start_col, Space.None); // : | 621 | try renderToken(tree, ais, colon2, Space.None); // : |
| 678 | try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None); | 622 | try renderExpression(allocator, ais, tree, bit_range.end, Space.None); |
| 679 | 623 | ||
| 680 | const rparen_token = tree.nextToken(bit_range.end.lastToken()); | 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 | } else { | 626 | } else { |
| 683 | const rparen_token = tree.nextToken(align_info.node.lastToken()); | 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 | if (ptr_type.ptr_info.const_token) |const_token| { | 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 | if (ptr_type.ptr_info.volatile_token) |volatile_token| { | 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 | .SliceType => { | 640 | .SliceType => { |
| 697 | const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base); | 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 | if (slice_type.ptr_info.sentinel) |sentinel| { | 643 | if (slice_type.ptr_info.sentinel) |sentinel| { |
| 700 | const colon_token = tree.prevToken(sentinel.firstToken()); | 644 | const colon_token = tree.prevToken(sentinel.firstToken()); |
| 701 | try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : | 645 | try renderToken(tree, ais, colon_token, Space.None); // : |
| 702 | try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); | 646 | try renderExpression(allocator, ais, tree, sentinel, Space.None); |
| 703 | try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ] | 647 | try renderToken(tree, ais, tree.nextToken(sentinel.lastToken()), Space.None); // ] |
| 704 | } else { | 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 | if (slice_type.ptr_info.allowzero_token) |allowzero_token| { | 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 | if (slice_type.ptr_info.align_info) |align_info| { | 655 | if (slice_type.ptr_info.align_info) |align_info| { |
| 712 | const lparen_token = tree.prevToken(align_info.node.firstToken()); | 656 | const lparen_token = tree.prevToken(align_info.node.firstToken()); |
| 713 | const align_token = tree.prevToken(lparen_token); | 657 | const align_token = tree.prevToken(lparen_token); |
| 714 | 658 | ||
| 715 | try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align | 659 | try renderToken(tree, ais, align_token, Space.None); // align |
| 716 | try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( | 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 | if (align_info.bit_range) |bit_range| { | 664 | if (align_info.bit_range) |bit_range| { |
| 721 | const colon1 = tree.prevToken(bit_range.start.firstToken()); | 665 | const colon1 = tree.prevToken(bit_range.start.firstToken()); |
| 722 | const colon2 = tree.prevToken(bit_range.end.firstToken()); | 666 | const colon2 = tree.prevToken(bit_range.end.firstToken()); |
| 723 | 667 | ||
| 724 | try renderToken(tree, stream, colon1, indent, start_col, Space.None); // : | 668 | try renderToken(tree, ais, colon1, Space.None); // : |
| 725 | try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None); | 669 | try renderExpression(allocator, ais, tree, bit_range.start, Space.None); |
| 726 | try renderToken(tree, stream, colon2, indent, start_col, Space.None); // : | 670 | try renderToken(tree, ais, colon2, Space.None); // : |
| 727 | try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None); | 671 | try renderExpression(allocator, ais, tree, bit_range.end, Space.None); |
| 728 | 672 | ||
| 729 | const rparen_token = tree.nextToken(bit_range.end.lastToken()); | 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 | } else { | 675 | } else { |
| 732 | const rparen_token = tree.nextToken(align_info.node.lastToken()); | 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 | if (slice_type.ptr_info.const_token) |const_token| { | 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 | if (slice_type.ptr_info.volatile_token) |volatile_token| { | 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 | .ArrayInitializer, .ArrayInitializerDot => { | 689 | .ArrayInitializer, .ArrayInitializerDot => { |
| ... | @@ -768,27 +712,33 @@ fn renderExpression( | ... | @@ -768,27 +712,33 @@ fn renderExpression( |
| 768 | 712 | ||
| 769 | if (exprs.len == 0) { | 713 | if (exprs.len == 0) { |
| 770 | switch (lhs) { | 714 | switch (lhs) { |
| 771 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | 715 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), |
| 772 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, 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 | const expr = exprs[0]; | 728 | const expr = exprs[0]; |
| 729 | |||
| 780 | switch (lhs) { | 730 | switch (lhs) { |
| 781 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | 731 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), |
| 782 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, 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); | 734 | try renderToken(tree, ais, lbrace, Space.None); |
| 785 | try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None); | 735 | try renderExpression(allocator, ais, tree, expr, Space.None); |
| 786 | return renderToken(tree, stream, rtoken, indent, start_col, space); | 736 | return renderToken(tree, ais, rtoken, space); |
| 787 | } | 737 | } |
| 788 | 738 | ||
| 789 | switch (lhs) { | 739 | switch (lhs) { |
| 790 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | 740 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), |
| 791 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None), | 741 | .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), |
| 792 | } | 742 | } |
| 793 | 743 | ||
| 794 | // scan to find row size | 744 | // scan to find row size |
| ... | @@ -830,79 +780,70 @@ fn renderExpression( | ... | @@ -830,79 +780,70 @@ fn renderExpression( |
| 830 | var expr_widths = widths[0 .. widths.len - row_size]; | 780 | var expr_widths = widths[0 .. widths.len - row_size]; |
| 831 | var column_widths = widths[widths.len - row_size ..]; | 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 | var counting_stream = std.io.countingOutStream(std.io.null_out_stream); | 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 | for (exprs) |expr, i| { | 787 | for (exprs) |expr, i| { |
| 837 | counting_stream.bytes_written = 0; | 788 | counting_stream.bytes_written = 0; |
| 838 | var dummy_col: usize = 0; | 789 | try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); |
| 839 | try renderExpression(allocator, counting_stream.outStream(), tree, indent, &dummy_col, expr, Space.None); | ||
| 840 | const width = @intCast(usize, counting_stream.bytes_written); | 790 | const width = @intCast(usize, counting_stream.bytes_written); |
| 841 | const col = i % row_size; | 791 | const col = i % row_size; |
| 842 | column_widths[col] = std.math.max(column_widths[col], width); | 792 | column_widths[col] = std.math.max(column_widths[col], width); |
| 843 | expr_widths[i] = width; | 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) { | 801 | var col: usize = 1; |
| 849 | try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline); | 802 | for (exprs) |expr, i| { |
| 850 | try stream.writeByteNTimes(' ', new_indent); | 803 | if (i + 1 < exprs.len) { |
| 851 | } else { | 804 | const next_expr = exprs[i + 1]; |
| 852 | new_indent -= indent_delta; | 805 | try renderExpression(allocator, ais, tree, expr, Space.None); |
| 853 | try renderToken(tree, stream, lbrace, new_indent, start_col, Space.None); | ||
| 854 | } | ||
| 855 | 806 | ||
| 856 | var col: usize = 1; | 807 | const comma = tree.nextToken(expr.*.lastToken()); |
| 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); | ||
| 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) { | 812 | const padding = column_widths[i % row_size] - expr_widths[i]; |
| 865 | try renderToken(tree, stream, comma, new_indent, start_col, Space.Space); // , | 813 | try ais.writer().writeByteNTimes(' ', padding); |
| 866 | 814 | ||
| 867 | const padding = column_widths[i % row_size] - expr_widths[i]; | 815 | col += 1; |
| 868 | try stream.writeByteNTimes(' ', padding); | 816 | continue; |
| 817 | } | ||
| 818 | col = 1; | ||
| 869 | 819 | ||
| 870 | col += 1; | 820 | if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) { |
| 871 | continue; | 821 | try renderToken(tree, ais, comma, Space.Newline); // , |
| 872 | } | 822 | } else { |
| 873 | col = 1; | 823 | try renderToken(tree, ais, comma, Space.None); // , |
| 824 | } | ||
| 874 | 825 | ||
| 875 | if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) { | 826 | try renderExtraNewline(tree, ais, next_expr); |
| 876 | try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // , | ||
| 877 | } else { | 827 | } else { |
| 878 | try renderToken(tree, stream, comma, new_indent, start_col, Space.None); // , | 828 | try renderExpression(allocator, ais, tree, expr, Space.Comma); // , |
| 879 | } | ||
| 880 | |||
| 881 | try renderExtraNewline(tree, stream, start_col, next_expr); | ||
| 882 | if (next_expr.tag != .MultilineStringLiteral) { | ||
| 883 | try stream.writeByteNTimes(' ', new_indent); | ||
| 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) { | 832 | return renderToken(tree, ais, rtoken, space); |
| 890 | try stream.writeByteNTimes(' ', indent); | ||
| 891 | } | ||
| 892 | return renderToken(tree, stream, rtoken, indent, start_col, space); | ||
| 893 | } else { | 833 | } else { |
| 894 | try renderToken(tree, stream, lbrace, indent, start_col, Space.Space); | 834 | try renderToken(tree, ais, lbrace, Space.Space); |
| 895 | for (exprs) |expr, i| { | 835 | for (exprs) |expr, i| { |
| 896 | if (i + 1 < exprs.len) { | 836 | if (i + 1 < exprs.len) { |
| 897 | try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None); | 837 | const next_expr = exprs[i + 1]; |
| 898 | const comma = tree.nextToken(expr.lastToken()); | 838 | try renderExpression(allocator, ais, tree, expr, Space.None); |
| 899 | try renderToken(tree, stream, comma, indent, start_col, Space.Space); // , | 839 | const comma = tree.nextToken(expr.*.lastToken()); |
| 840 | try renderToken(tree, ais, comma, Space.Space); // , | ||
| 900 | } else { | 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,11 +873,17 @@ fn renderExpression( |
| 932 | 873 | ||
| 933 | if (field_inits.len == 0) { | 874 | if (field_inits.len == 0) { |
| 934 | switch (lhs) { | 875 | switch (lhs) { |
| 935 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | 876 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), |
| 936 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, 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); | 879 | |
| 939 | return renderToken(tree, stream, rtoken, indent, start_col, space); | 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 | const src_has_trailing_comma = blk: { | 889 | const src_has_trailing_comma = blk: { |
| ... | @@ -952,9 +899,10 @@ fn renderExpression( | ... | @@ -952,9 +899,10 @@ fn renderExpression( |
| 952 | const expr_outputs_one_line = blk: { | 899 | const expr_outputs_one_line = blk: { |
| 953 | // render field expressions until a LF is found | 900 | // render field expressions until a LF is found |
| 954 | for (field_inits) |field_init| { | 901 | for (field_inits) |field_init| { |
| 955 | var find_stream = FindByteOutStream.init('\n'); | 902 | var find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream); |
| 956 | var dummy_col: usize = 0; | 903 | var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, find_stream.writer()); |
| 957 | try renderExpression(allocator, find_stream.outStream(), tree, 0, &dummy_col, field_init, Space.None); | 904 | |
| 905 | try renderExpression(allocator, &auto_indenting_stream, tree, field_init, Space.None); | ||
| 958 | if (find_stream.byte_found) break :blk false; | 906 | if (find_stream.byte_found) break :blk false; |
| 959 | } | 907 | } |
| 960 | break :blk true; | 908 | break :blk true; |
| ... | @@ -967,7 +915,6 @@ fn renderExpression( | ... | @@ -967,7 +915,6 @@ fn renderExpression( |
| 967 | .StructInitializer, | 915 | .StructInitializer, |
| 968 | .StructInitializerDot, | 916 | .StructInitializerDot, |
| 969 | => break :blk, | 917 | => break :blk, |
| 970 | |||
| 971 | else => {}, | 918 | else => {}, |
| 972 | } | 919 | } |
| 973 | 920 | ||
| ... | @@ -977,76 +924,78 @@ fn renderExpression( | ... | @@ -977,76 +924,78 @@ fn renderExpression( |
| 977 | } | 924 | } |
| 978 | 925 | ||
| 979 | switch (lhs) { | 926 | switch (lhs) { |
| 980 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | 927 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), |
| 981 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, 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); | 930 | try renderToken(tree, ais, lbrace, Space.Space); |
| 984 | try renderExpression(allocator, stream, tree, indent, start_col, &field_init.base, Space.Space); | 931 | try renderExpression(allocator, ais, tree, &field_init.base, Space.Space); |
| 985 | return renderToken(tree, stream, rtoken, indent, start_col, space); | 932 | return renderToken(tree, ais, rtoken, space); |
| 986 | } | 933 | } |
| 987 | 934 | ||
| 988 | if (!src_has_trailing_comma and src_same_line and expr_outputs_one_line) { | 935 | if (!src_has_trailing_comma and src_same_line and expr_outputs_one_line) { |
| 989 | // render all on one line, no trailing comma | 936 | // render all on one line, no trailing comma |
| 990 | switch (lhs) { | 937 | switch (lhs) { |
| 991 | .dot => |dot| try renderToken(tree, stream, dot, indent, start_col, Space.None), | 938 | .dot => |dot| try renderToken(tree, ais, dot, Space.None), |
| 992 | .node => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, 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 | for (field_inits) |field_init, i| { | 943 | for (field_inits) |field_init, i| { |
| 997 | if (i + 1 < field_inits.len) { | 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 | const comma = tree.nextToken(field_init.lastToken()); | 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 | } else { | 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) { | 963 | ais.pushIndentNextLine(); |
| 1013 | .dot => |dot| try renderToken(tree, stream, dot, new_indent, start_col, Space.None), | 964 | defer ais.popIndent(); |
| 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); | ||
| 1017 | 965 | ||
| 1018 | for (field_inits) |field_init, i| { | 966 | try renderToken(tree, ais, lbrace, Space.Newline); |
| 1019 | try stream.writeByteNTimes(' ', new_indent); | ||
| 1020 | 967 | ||
| 1021 | if (i + 1 < field_inits.len) { | 968 | for (field_inits) |field_init, i| { |
| 1022 | try renderExpression(allocator, stream, tree, new_indent, start_col, field_init, Space.None); | 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()); | 973 | const comma = tree.nextToken(field_init.lastToken()); |
| 1025 | try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); | 974 | try renderToken(tree, ais, comma, Space.Newline); |
| 1026 | 975 | ||
| 1027 | try renderExtraNewline(tree, stream, start_col, field_inits[i + 1]); | 976 | try renderExtraNewline(tree, ais, next_field_init); |
| 1028 | } else { | 977 | } else { |
| 1029 | try renderExpression(allocator, stream, tree, new_indent, start_col, field_init, Space.Comma); | 978 | try renderExpression(allocator, ais, tree, field_init, Space.Comma); |
| 979 | } | ||
| 1030 | } | 980 | } |
| 1031 | } | 981 | } |
| 1032 | 982 | ||
| 1033 | try stream.writeByteNTimes(' ', indent); | 983 | return renderToken(tree, ais, rtoken, space); |
| 1034 | return renderToken(tree, stream, rtoken, indent, start_col, space); | ||
| 1035 | }, | 984 | }, |
| 1036 | 985 | ||
| 1037 | .Call => { | 986 | .Call => { |
| 1038 | const call = @fieldParentPtr(ast.Node.Call, "base", base); | 987 | const call = @fieldParentPtr(ast.Node.Call, "base", base); |
| 1039 | if (call.async_token) |async_token| { | 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 | const lparen = tree.nextToken(call.lhs.lastToken()); | 994 | const lparen = tree.nextToken(call.lhs.lastToken()); |
| 1046 | 995 | ||
| 1047 | if (call.params_len == 0) { | 996 | if (call.params_len == 0) { |
| 1048 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); | 997 | try renderToken(tree, ais, lparen, Space.None); |
| 1049 | return renderToken(tree, stream, call.rtoken, indent, start_col, space); | 998 | return renderToken(tree, ais, call.rtoken, space); |
| 1050 | } | 999 | } |
| 1051 | 1000 | ||
| 1052 | const src_has_trailing_comma = blk: { | 1001 | const src_has_trailing_comma = blk: { |
| ... | @@ -1055,43 +1004,41 @@ fn renderExpression( | ... | @@ -1055,43 +1004,41 @@ fn renderExpression( |
| 1055 | }; | 1004 | }; |
| 1056 | 1005 | ||
| 1057 | if (src_has_trailing_comma) { | 1006 | if (src_has_trailing_comma) { |
| 1058 | const new_indent = indent + indent_delta; | 1007 | try renderToken(tree, ais, lparen, Space.Newline); |
| 1059 | try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); | ||
| 1060 | 1008 | ||
| 1061 | const params = call.params(); | 1009 | const params = call.params(); |
| 1062 | for (params) |param_node, i| { | 1010 | for (params) |param_node, i| { |
| 1063 | const param_node_new_indent = if (param_node.tag == .MultilineStringLiteral) blk: { | 1011 | ais.pushIndent(); |
| 1064 | break :blk indent; | 1012 | defer ais.popIndent(); |
| 1065 | } else blk: { | ||
| 1066 | try stream.writeByteNTimes(' ', new_indent); | ||
| 1067 | break :blk new_indent; | ||
| 1068 | }; | ||
| 1069 | 1013 | ||
| 1070 | if (i + 1 < params.len) { | 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 | const comma = tree.nextToken(param_node.lastToken()); | 1017 | const comma = tree.nextToken(param_node.lastToken()); |
| 1073 | try renderToken(tree, stream, comma, new_indent, start_col, Space.Newline); // , | 1018 | try renderToken(tree, ais, comma, Space.Newline); // , |
| 1074 | try renderExtraNewline(tree, stream, start_col, params[i + 1]); | 1019 | try renderExtraNewline(tree, ais, next_node); |
| 1075 | } else { | 1020 | } else { |
| 1076 | try renderExpression(allocator, stream, tree, param_node_new_indent, start_col, param_node, Space.Comma); | 1021 | try renderExpression(allocator, ais, tree, param_node, Space.Comma); |
| 1077 | try stream.writeByteNTimes(' ', indent); | ||
| 1078 | return renderToken(tree, stream, call.rtoken, indent, start_col, space); | ||
| 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 | const params = call.params(); | 1029 | const params = call.params(); |
| 1086 | for (params) |param_node, i| { | 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 | if (i + 1 < params.len) { | 1035 | if (i + 1 < params.len) { |
| 1036 | const next_param = params[i + 1]; | ||
| 1090 | const comma = tree.nextToken(param_node.lastToken()); | 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 | .ArrayAccess => { | 1044 | .ArrayAccess => { |
| ... | @@ -1100,26 +1047,25 @@ fn renderExpression( | ... | @@ -1100,26 +1047,25 @@ fn renderExpression( |
| 1100 | const lbracket = tree.nextToken(suffix_op.lhs.lastToken()); | 1047 | const lbracket = tree.nextToken(suffix_op.lhs.lastToken()); |
| 1101 | const rbracket = tree.nextToken(suffix_op.index_expr.lastToken()); | 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); | 1050 | try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); |
| 1104 | try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [ | 1051 | try renderToken(tree, ais, lbracket, Space.None); // [ |
| 1105 | 1052 | ||
| 1106 | const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; | 1053 | const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; |
| 1107 | const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; | 1054 | const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; |
| 1108 | const new_indent = if (ends_with_comment) indent + indent_delta else indent; | 1055 | { |
| 1109 | const new_space = if (ends_with_comment) Space.Newline else Space.None; | 1056 | 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); | 1057 | |
| 1111 | if (starts_with_comment) { | 1058 | ais.pushIndent(); |
| 1112 | try stream.writeByte('\n'); | 1059 | defer ais.popIndent(); |
| 1113 | } | 1060 | try renderExpression(allocator, ais, tree, suffix_op.index_expr, new_space); |
| 1114 | if (ends_with_comment or starts_with_comment) { | ||
| 1115 | try stream.writeByteNTimes(' ', indent); | ||
| 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 | .Slice => { | 1066 | .Slice => { |
| 1120 | const suffix_op = base.castTag(.Slice).?; | 1067 | const suffix_op = base.castTag(.Slice).?; |
| 1121 | 1068 | try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); | |
| 1122 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None); | ||
| 1123 | 1069 | ||
| 1124 | const lbracket = tree.prevToken(suffix_op.start.firstToken()); | 1070 | const lbracket = tree.prevToken(suffix_op.start.firstToken()); |
| 1125 | const dotdot = tree.nextToken(suffix_op.start.lastToken()); | 1071 | const dotdot = tree.nextToken(suffix_op.start.lastToken()); |
| ... | @@ -1129,32 +1075,33 @@ fn renderExpression( | ... | @@ -1129,32 +1075,33 @@ fn renderExpression( |
| 1129 | const after_start_space = if (after_start_space_bool) Space.Space else Space.None; | 1075 | const after_start_space = if (after_start_space_bool) Space.Space else Space.None; |
| 1130 | const after_op_space = if (suffix_op.end != null) after_start_space else Space.None; | 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); // [ | 1078 | try renderToken(tree, ais, lbracket, Space.None); // [ |
| 1133 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.start, after_start_space); | 1079 | try renderExpression(allocator, ais, tree, suffix_op.start, after_start_space); |
| 1134 | try renderToken(tree, stream, dotdot, indent, start_col, after_op_space); // .. | 1080 | try renderToken(tree, ais, dotdot, after_op_space); // .. |
| 1135 | if (suffix_op.end) |end| { | 1081 | if (suffix_op.end) |end| { |
| 1136 | const after_end_space = if (suffix_op.sentinel != null) Space.Space else Space.None; | 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 | if (suffix_op.sentinel) |sentinel| { | 1085 | if (suffix_op.sentinel) |sentinel| { |
| 1140 | const colon = tree.prevToken(sentinel.firstToken()); | 1086 | const colon = tree.prevToken(sentinel.firstToken()); |
| 1141 | try renderToken(tree, stream, colon, indent, start_col, Space.None); // : | 1087 | try renderToken(tree, ais, colon, Space.None); // : |
| 1142 | try renderExpression(allocator, stream, tree, indent, start_col, sentinel, 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 | .Deref => { | 1093 | .Deref => { |
| 1147 | const suffix_op = base.castTag(.Deref).?; | 1094 | const suffix_op = base.castTag(.Deref).?; |
| 1148 | 1095 | ||
| 1149 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None); | 1096 | try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); |
| 1150 | return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // .* | 1097 | return renderToken(tree, ais, suffix_op.rtoken, space); // .* |
| 1151 | }, | 1098 | }, |
| 1152 | .UnwrapOptional => { | 1099 | .UnwrapOptional => { |
| 1153 | const suffix_op = base.castTag(.UnwrapOptional).?; | 1100 | const suffix_op = base.castTag(.UnwrapOptional).?; |
| 1154 | 1101 | ||
| 1155 | try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None); | 1102 | try renderExpression(allocator, ais, tree, suffix_op.lhs, Space.None); |
| 1156 | try renderToken(tree, stream, tree.prevToken(suffix_op.rtoken), indent, start_col, Space.None); // . | 1103 | try renderToken(tree, ais, tree.prevToken(suffix_op.rtoken), Space.None); // . |
| 1157 | return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ? | 1104 | return renderToken(tree, ais, suffix_op.rtoken, space); // ? |
| 1158 | }, | 1105 | }, |
| 1159 | 1106 | ||
| 1160 | .Break => { | 1107 | .Break => { |
| ... | @@ -1163,145 +1110,152 @@ fn renderExpression( | ... | @@ -1163,145 +1110,152 @@ fn renderExpression( |
| 1163 | const maybe_label = flow_expr.getLabel(); | 1110 | const maybe_label = flow_expr.getLabel(); |
| 1164 | 1111 | ||
| 1165 | if (maybe_label == null and maybe_rhs == null) { | 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 | if (maybe_label) |label| { | 1117 | if (maybe_label) |label| { |
| 1171 | const colon = tree.nextToken(flow_expr.ltoken); | 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 | if (maybe_rhs == null) { | 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 | .Continue => { | 1129 | .Continue => { |
| 1183 | const flow_expr = base.castTag(.Continue).?; | 1130 | const flow_expr = base.castTag(.Continue).?; |
| 1184 | if (flow_expr.getLabel()) |label| { | 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 | const colon = tree.nextToken(flow_expr.ltoken); | 1133 | const colon = tree.nextToken(flow_expr.ltoken); |
| 1187 | try renderToken(tree, stream, colon, indent, start_col, Space.None); // : | 1134 | try renderToken(tree, ais, colon, Space.None); // : |
| 1188 | return renderToken(tree, stream, label, indent, start_col, space); // label | 1135 | return renderToken(tree, ais, label, space); // label |
| 1189 | } else { | 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 | .Return => { | 1141 | .Return => { |
| 1195 | const flow_expr = base.castTag(.Return).?; | 1142 | const flow_expr = base.castTag(.Return).?; |
| 1196 | if (flow_expr.getRHS()) |rhs| { | 1143 | if (flow_expr.getRHS()) |rhs| { |
| 1197 | try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); | 1144 | try renderToken(tree, ais, flow_expr.ltoken, Space.Space); |
| 1198 | return renderExpression(allocator, stream, tree, indent, start_col, rhs, space); | 1145 | return renderExpression(allocator, ais, tree, rhs, space); |
| 1199 | } else { | 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 | .Payload => { | 1151 | .Payload => { |
| 1205 | const payload = @fieldParentPtr(ast.Node.Payload, "base", base); | 1152 | const payload = @fieldParentPtr(ast.Node.Payload, "base", base); |
| 1206 | 1153 | ||
| 1207 | try renderToken(tree, stream, payload.lpipe, indent, start_col, Space.None); | 1154 | try renderToken(tree, ais, payload.lpipe, Space.None); |
| 1208 | try renderExpression(allocator, stream, tree, indent, start_col, payload.error_symbol, Space.None); | 1155 | try renderExpression(allocator, ais, tree, payload.error_symbol, Space.None); |
| 1209 | return renderToken(tree, stream, payload.rpipe, indent, start_col, space); | 1156 | return renderToken(tree, ais, payload.rpipe, space); |
| 1210 | }, | 1157 | }, |
| 1211 | 1158 | ||
| 1212 | .PointerPayload => { | 1159 | .PointerPayload => { |
| 1213 | const payload = @fieldParentPtr(ast.Node.PointerPayload, "base", base); | 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 | if (payload.ptr_token) |ptr_token| { | 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); | 1166 | try renderExpression(allocator, ais, tree, payload.value_symbol, Space.None); |
| 1220 | return renderToken(tree, stream, payload.rpipe, indent, start_col, space); | 1167 | return renderToken(tree, ais, payload.rpipe, space); |
| 1221 | }, | 1168 | }, |
| 1222 | 1169 | ||
| 1223 | .PointerIndexPayload => { | 1170 | .PointerIndexPayload => { |
| 1224 | const payload = @fieldParentPtr(ast.Node.PointerIndexPayload, "base", base); | 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 | if (payload.ptr_token) |ptr_token| { | 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 | if (payload.index_symbol) |index_symbol| { | 1179 | if (payload.index_symbol) |index_symbol| { |
| 1233 | const comma = tree.nextToken(payload.value_symbol.lastToken()); | 1180 | const comma = tree.nextToken(payload.value_symbol.lastToken()); |
| 1234 | 1181 | ||
| 1235 | try renderToken(tree, stream, comma, indent, start_col, Space.Space); | 1182 | try renderToken(tree, ais, comma, Space.Space); |
| 1236 | try renderExpression(allocator, stream, tree, indent, start_col, index_symbol, Space.None); | 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 | .GroupedExpression => { | 1189 | .GroupedExpression => { |
| 1243 | const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", base); | 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); | 1192 | try renderToken(tree, ais, grouped_expr.lparen, Space.None); |
| 1246 | try renderExpression(allocator, stream, tree, indent, start_col, grouped_expr.expr, Space.None); | 1193 | { |
| 1247 | return renderToken(tree, stream, grouped_expr.rparen, indent, start_col, space); | 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 | .FieldInitializer => { | 1200 | .FieldInitializer => { |
| 1251 | const field_init = @fieldParentPtr(ast.Node.FieldInitializer, "base", base); | 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); // . | 1203 | try renderToken(tree, ais, field_init.period_token, Space.None); // . |
| 1254 | try renderToken(tree, stream, field_init.name_token, indent, start_col, Space.Space); // name | 1204 | try renderToken(tree, ais, field_init.name_token, Space.Space); // name |
| 1255 | try renderToken(tree, stream, tree.nextToken(field_init.name_token), indent, start_col, Space.Space); // = | 1205 | try renderToken(tree, ais, tree.nextToken(field_init.name_token), Space.Space); // = |
| 1256 | return renderExpression(allocator, stream, tree, indent, start_col, field_init.expr, space); | 1206 | return renderExpression(allocator, ais, tree, field_init.expr, space); |
| 1257 | }, | 1207 | }, |
| 1258 | 1208 | ||
| 1259 | .ContainerDecl => { | 1209 | .ContainerDecl => { |
| 1260 | const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base); | 1210 | const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base); |
| 1261 | 1211 | ||
| 1262 | if (container_decl.layout_token) |layout_token| { | 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 | switch (container_decl.init_arg_expr) { | 1216 | switch (container_decl.init_arg_expr) { |
| 1267 | .None => { | 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 | .Enum => |enum_tag_type| { | 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 | const lparen = tree.nextToken(container_decl.kind_token); | 1223 | const lparen = tree.nextToken(container_decl.kind_token); |
| 1274 | const enum_token = tree.nextToken(lparen); | 1224 | const enum_token = tree.nextToken(lparen); |
| 1275 | 1225 | ||
| 1276 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | 1226 | try renderToken(tree, ais, lparen, Space.None); // ( |
| 1277 | try renderToken(tree, stream, enum_token, indent, start_col, Space.None); // enum | 1227 | try renderToken(tree, ais, enum_token, Space.None); // enum |
| 1278 | 1228 | ||
| 1279 | if (enum_tag_type) |expr| { | 1229 | if (enum_tag_type) |expr| { |
| 1280 | try renderToken(tree, stream, tree.nextToken(enum_token), indent, start_col, Space.None); // ( | 1230 | try renderToken(tree, ais, tree.nextToken(enum_token), Space.None); // ( |
| 1281 | try renderExpression(allocator, stream, tree, indent, start_col, expr, Space.None); | 1231 | try renderExpression(allocator, ais, tree, expr, Space.None); |
| 1282 | 1232 | ||
| 1283 | const rparen = tree.nextToken(expr.lastToken()); | 1233 | const rparen = tree.nextToken(expr.lastToken()); |
| 1284 | try renderToken(tree, stream, rparen, indent, start_col, Space.None); // ) | 1234 | try renderToken(tree, ais, rparen, Space.None); // ) |
| 1285 | try renderToken(tree, stream, tree.nextToken(rparen), indent, start_col, Space.Space); // ) | 1235 | try renderToken(tree, ais, tree.nextToken(rparen), Space.Space); // ) |
| 1286 | } else { | 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 | .Type => |type_expr| { | 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 | const lparen = tree.nextToken(container_decl.kind_token); | 1243 | const lparen = tree.nextToken(container_decl.kind_token); |
| 1294 | const rparen = tree.nextToken(type_expr.lastToken()); | 1244 | const rparen = tree.nextToken(type_expr.lastToken()); |
| 1295 | 1245 | ||
| 1296 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | 1246 | try renderToken(tree, ais, lparen, Space.None); // ( |
| 1297 | try renderExpression(allocator, stream, tree, indent, start_col, type_expr, Space.None); | 1247 | try renderExpression(allocator, ais, tree, type_expr, Space.None); |
| 1298 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | 1248 | try renderToken(tree, ais, rparen, Space.Space); // ) |
| 1299 | }, | 1249 | }, |
| 1300 | } | 1250 | } |
| 1301 | 1251 | ||
| 1302 | if (container_decl.fields_and_decls_len == 0) { | 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); // { | 1253 | { |
| 1304 | return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // } | 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 | const src_has_trailing_comma = blk: { | 1261 | const src_has_trailing_comma = blk: { |
| ... | @@ -1332,43 +1286,39 @@ fn renderExpression( | ... | @@ -1332,43 +1286,39 @@ fn renderExpression( |
| 1332 | 1286 | ||
| 1333 | if (src_has_trailing_comma or !src_has_only_fields) { | 1287 | if (src_has_trailing_comma or !src_has_only_fields) { |
| 1334 | // One declaration per line | 1288 | // One declaration per line |
| 1335 | const new_indent = indent + indent_delta; | 1289 | ais.pushIndentNextLine(); |
| 1336 | try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, .Newline); // { | 1290 | defer ais.popIndent(); |
| 1291 | try renderToken(tree, ais, container_decl.lbrace_token, .Newline); // { | ||
| 1337 | 1292 | ||
| 1338 | for (fields_and_decls) |decl, i| { | 1293 | for (fields_and_decls) |decl, i| { |
| 1339 | try stream.writeByteNTimes(' ', new_indent); | 1294 | try renderContainerDecl(allocator, ais, tree, decl, .Newline); |
| 1340 | try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl, .Newline); | ||
| 1341 | 1295 | ||
| 1342 | if (i + 1 < fields_and_decls.len) { | 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 | } else if (src_has_newline) { | 1300 | } else if (src_has_newline) { |
| 1349 | // All the declarations on the same line, but place the items on | 1301 | // All the declarations on the same line, but place the items on |
| 1350 | // their own line | 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; | 1305 | ais.pushIndent(); |
| 1354 | try stream.writeByteNTimes(' ', new_indent); | 1306 | defer ais.popIndent(); |
| 1355 | 1307 | ||
| 1356 | for (fields_and_decls) |decl, i| { | 1308 | for (fields_and_decls) |decl, i| { |
| 1357 | const space_after_decl: Space = if (i + 1 >= fields_and_decls.len) .Newline else .Space; | 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 | } else { | 1312 | } else { |
| 1363 | // All the declarations on the same line | 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 | for (fields_and_decls) |decl| { | 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 | .ErrorSetDecl => { | 1324 | .ErrorSetDecl => { |
| ... | @@ -1377,9 +1327,9 @@ fn renderExpression( | ... | @@ -1377,9 +1327,9 @@ fn renderExpression( |
| 1377 | const lbrace = tree.nextToken(err_set_decl.error_token); | 1327 | const lbrace = tree.nextToken(err_set_decl.error_token); |
| 1378 | 1328 | ||
| 1379 | if (err_set_decl.decls_len == 0) { | 1329 | if (err_set_decl.decls_len == 0) { |
| 1380 | try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); | 1330 | try renderToken(tree, ais, err_set_decl.error_token, Space.None); |
| 1381 | try renderToken(tree, stream, lbrace, indent, start_col, Space.None); | 1331 | try renderToken(tree, ais, lbrace, Space.None); |
| 1382 | return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); | 1332 | return renderToken(tree, ais, err_set_decl.rbrace_token, space); |
| 1383 | } | 1333 | } |
| 1384 | 1334 | ||
| 1385 | if (err_set_decl.decls_len == 1) blk: { | 1335 | if (err_set_decl.decls_len == 1) blk: { |
| ... | @@ -1393,13 +1343,13 @@ fn renderExpression( | ... | @@ -1393,13 +1343,13 @@ fn renderExpression( |
| 1393 | break :blk; | 1343 | break :blk; |
| 1394 | } | 1344 | } |
| 1395 | 1345 | ||
| 1396 | try renderToken(tree, stream, err_set_decl.error_token, indent, start_col, Space.None); // error | 1346 | try renderToken(tree, ais, err_set_decl.error_token, Space.None); // error |
| 1397 | try renderToken(tree, stream, lbrace, indent, start_col, Space.None); // { | 1347 | try renderToken(tree, ais, lbrace, Space.None); // { |
| 1398 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.None); | 1348 | try renderExpression(allocator, ais, tree, node, Space.None); |
| 1399 | return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // } | 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 | const src_has_trailing_comma = blk: { | 1354 | const src_has_trailing_comma = blk: { |
| 1405 | const maybe_comma = tree.prevToken(err_set_decl.rbrace_token); | 1355 | const maybe_comma = tree.prevToken(err_set_decl.rbrace_token); |
| ... | @@ -1407,72 +1357,66 @@ fn renderExpression( | ... | @@ -1407,72 +1357,66 @@ fn renderExpression( |
| 1407 | }; | 1357 | }; |
| 1408 | 1358 | ||
| 1409 | if (src_has_trailing_comma) { | 1359 | if (src_has_trailing_comma) { |
| 1410 | try renderToken(tree, stream, lbrace, indent, start_col, Space.Newline); // { | 1360 | { |
| 1411 | const new_indent = indent + indent_delta; | 1361 | ais.pushIndent(); |
| 1412 | 1362 | defer ais.popIndent(); | |
| 1413 | const decls = err_set_decl.decls(); | 1363 | |
| 1414 | for (decls) |node, i| { | 1364 | try renderToken(tree, ais, lbrace, Space.Newline); // { |
| 1415 | try stream.writeByteNTimes(' ', new_indent); | 1365 | const decls = err_set_decl.decls(); |
| 1416 | 1366 | for (decls) |node, i| { | |
| 1417 | if (i + 1 < decls.len) { | 1367 | if (i + 1 < decls.len) { |
| 1418 | try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.None); | 1368 | try renderExpression(allocator, ais, tree, node, Space.None); |
| 1419 | try renderToken(tree, stream, tree.nextToken(node.lastToken()), new_indent, start_col, Space.Newline); // , | 1369 | try renderToken(tree, ais, tree.nextToken(node.lastToken()), Space.Newline); // , |
| 1420 | 1370 | ||
| 1421 | try renderExtraNewline(tree, stream, start_col, decls[i + 1]); | 1371 | try renderExtraNewline(tree, ais, decls[i + 1]); |
| 1422 | } else { | 1372 | } else { |
| 1423 | try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.Comma); | 1373 | try renderExpression(allocator, ais, tree, node, Space.Comma); |
| 1374 | } | ||
| 1424 | } | 1375 | } |
| 1425 | } | 1376 | } |
| 1426 | 1377 | ||
| 1427 | try stream.writeByteNTimes(' ', indent); | 1378 | return renderToken(tree, ais, err_set_decl.rbrace_token, space); // } |
| 1428 | return renderToken(tree, stream, err_set_decl.rbrace_token, indent, start_col, space); // } | ||
| 1429 | } else { | 1379 | } else { |
| 1430 | try renderToken(tree, stream, lbrace, indent, start_col, Space.Space); // { | 1380 | try renderToken(tree, ais, lbrace, Space.Space); // { |
| 1431 | 1381 | ||
| 1432 | const decls = err_set_decl.decls(); | 1382 | const decls = err_set_decl.decls(); |
| 1433 | for (decls) |node, i| { | 1383 | for (decls) |node, i| { |
| 1434 | if (i + 1 < decls.len) { | 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 | const comma_token = tree.nextToken(node.lastToken()); | 1387 | const comma_token = tree.nextToken(node.lastToken()); |
| 1438 | assert(tree.token_ids[comma_token] == .Comma); | 1388 | assert(tree.token_ids[comma_token] == .Comma); |
| 1439 | try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // , | 1389 | try renderToken(tree, ais, comma_token, Space.Space); // , |
| 1440 | try renderExtraNewline(tree, stream, start_col, decls[i + 1]); | 1390 | try renderExtraNewline(tree, ais, decls[i + 1]); |
| 1441 | } else { | 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 | .ErrorTag => { | 1400 | .ErrorTag => { |
| 1451 | const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base); | 1401 | const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base); |
| 1452 | 1402 | ||
| 1453 | try renderDocComments(tree, stream, tag, tag.doc_comments, indent, start_col); | 1403 | try renderDocComments(tree, ais, tag, tag.doc_comments); |
| 1454 | return renderToken(tree, stream, tag.name_token, indent, start_col, space); // name | 1404 | return renderToken(tree, ais, tag.name_token, space); // name |
| 1455 | }, | 1405 | }, |
| 1456 | 1406 | ||
| 1457 | .MultilineStringLiteral => { | 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 | const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base); | 1408 | const multiline_str_literal = @fieldParentPtr(ast.Node.MultilineStringLiteral, "base", base); |
| 1461 | 1409 | ||
| 1462 | var skip_first_indent = true; | 1410 | { |
| 1463 | if (tree.token_ids[multiline_str_literal.firstToken() - 1] != .LineComment) { | 1411 | const locked_indents = ais.lockOneShotIndent(); |
| 1464 | try stream.print("\n", .{}); | 1412 | defer { |
| 1465 | skip_first_indent = false; | 1413 | var i: u8 = 0; |
| 1466 | } | 1414 | while (i < locked_indents) : (i += 1) ais.popIndent(); |
| 1467 | |||
| 1468 | for (multiline_str_literal.lines()) |t| { | ||
| 1469 | if (!skip_first_indent) { | ||
| 1470 | try stream.writeByteNTimes(' ', indent + indent_delta); | ||
| 1471 | } | 1415 | } |
| 1472 | try renderToken(tree, stream, t, indent, start_col, Space.None); | 1416 | try ais.maybeInsertNewline(); |
| 1473 | skip_first_indent = false; | 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 | .BuiltinCall => { | 1422 | .BuiltinCall => { |
| ... | @@ -1480,9 +1424,9 @@ fn renderExpression( | ... | @@ -1480,9 +1424,9 @@ fn renderExpression( |
| 1480 | 1424 | ||
| 1481 | // TODO remove after 0.7.0 release | 1425 | // TODO remove after 0.7.0 release |
| 1482 | if (mem.eql(u8, tree.tokenSlice(builtin_call.builtin_token), "@OpaqueType")) | 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 | const src_params_trailing_comma = blk: { | 1431 | const src_params_trailing_comma = blk: { |
| 1488 | if (builtin_call.params_len < 2) break :blk false; | 1432 | if (builtin_call.params_len < 2) break :blk false; |
| ... | @@ -1494,31 +1438,30 @@ fn renderExpression( | ... | @@ -1494,31 +1438,30 @@ fn renderExpression( |
| 1494 | const lparen = tree.nextToken(builtin_call.builtin_token); | 1438 | const lparen = tree.nextToken(builtin_call.builtin_token); |
| 1495 | 1439 | ||
| 1496 | if (!src_params_trailing_comma) { | 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 | // render all on one line, no trailing comma | 1443 | // render all on one line, no trailing comma |
| 1500 | const params = builtin_call.params(); | 1444 | const params = builtin_call.params(); |
| 1501 | for (params) |param_node, i| { | 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 | if (i + 1 < params.len) { | 1448 | if (i + 1 < params.len) { |
| 1505 | const comma_token = tree.nextToken(param_node.lastToken()); | 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 | } else { | 1453 | } else { |
| 1510 | // one param per line | 1454 | // one param per line |
| 1511 | const new_indent = indent + indent_delta; | 1455 | ais.pushIndent(); |
| 1512 | try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); // ( | 1456 | defer ais.popIndent(); |
| 1457 | try renderToken(tree, ais, lparen, Space.Newline); // ( | ||
| 1513 | 1458 | ||
| 1514 | for (builtin_call.params()) |param_node| { | 1459 | for (builtin_call.params()) |param_node| { |
| 1515 | try stream.writeByteNTimes(' ', new_indent); | 1460 | try renderExpression(allocator, ais, tree, param_node, Space.Comma); |
| 1516 | try renderExpression(allocator, stream, tree, indent, start_col, 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 | .FnProto => { | 1467 | .FnProto => { |
| ... | @@ -1528,24 +1471,24 @@ fn renderExpression( | ... | @@ -1528,24 +1471,24 @@ fn renderExpression( |
| 1528 | const visib_token = tree.token_ids[visib_token_index]; | 1471 | const visib_token = tree.token_ids[visib_token_index]; |
| 1529 | assert(visib_token == .Keyword_pub or visib_token == .Keyword_export); | 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 | if (fn_proto.getExternExportInlineToken()) |extern_export_inline_token| { | 1477 | if (fn_proto.getExternExportInlineToken()) |extern_export_inline_token| { |
| 1535 | if (fn_proto.getIsExternPrototype() == null) | 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 | if (fn_proto.getLibName()) |lib_name| { | 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 | const lparen = if (fn_proto.getNameToken()) |name_token| blk: { | 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 | 1487 | try renderToken(tree, ais, fn_proto.fn_token, Space.Space); // fn |
| 1545 | try renderToken(tree, stream, name_token, indent, start_col, Space.None); // name | 1488 | try renderToken(tree, ais, name_token, Space.None); // name |
| 1546 | break :blk tree.nextToken(name_token); | 1489 | break :blk tree.nextToken(name_token); |
| 1547 | } else blk: { | 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 | break :blk tree.nextToken(fn_proto.fn_token); | 1492 | break :blk tree.nextToken(fn_proto.fn_token); |
| 1550 | }; | 1493 | }; |
| 1551 | assert(tree.token_ids[lparen] == .LParen); | 1494 | assert(tree.token_ids[lparen] == .LParen); |
| ... | @@ -1572,47 +1515,45 @@ fn renderExpression( | ... | @@ -1572,47 +1515,45 @@ fn renderExpression( |
| 1572 | }; | 1515 | }; |
| 1573 | 1516 | ||
| 1574 | if (!src_params_trailing_comma) { | 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 | // render all on one line, no trailing comma | 1520 | // render all on one line, no trailing comma |
| 1578 | for (fn_proto.params()) |param_decl, i| { | 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 | if (i + 1 < fn_proto.params_len or fn_proto.getVarArgsToken() != null) { | 1524 | if (i + 1 < fn_proto.params_len or fn_proto.getVarArgsToken() != null) { |
| 1582 | const comma = tree.nextToken(param_decl.lastToken()); | 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 | if (fn_proto.getVarArgsToken()) |var_args_token| { | 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 | } else { | 1532 | } else { |
| 1590 | // one param per line | 1533 | // one param per line |
| 1591 | const new_indent = indent + indent_delta; | 1534 | ais.pushIndent(); |
| 1592 | try renderToken(tree, stream, lparen, new_indent, start_col, Space.Newline); // ( | 1535 | defer ais.popIndent(); |
| 1536 | try renderToken(tree, ais, lparen, Space.Newline); // ( | ||
| 1593 | 1537 | ||
| 1594 | for (fn_proto.params()) |param_decl| { | 1538 | for (fn_proto.params()) |param_decl| { |
| 1595 | try stream.writeByteNTimes(' ', new_indent); | 1539 | try renderParamDecl(allocator, ais, tree, param_decl, Space.Comma); |
| 1596 | try renderParamDecl(allocator, stream, tree, new_indent, start_col, param_decl, Space.Comma); | ||
| 1597 | } | 1540 | } |
| 1598 | if (fn_proto.getVarArgsToken()) |var_args_token| { | 1541 | if (fn_proto.getVarArgsToken()) |var_args_token| { |
| 1599 | try stream.writeByteNTimes(' ', new_indent); | 1542 | try renderToken(tree, ais, var_args_token, Space.Comma); |
| 1600 | try renderToken(tree, stream, var_args_token, new_indent, start_col, 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 | if (fn_proto.getAlignExpr()) |align_expr| { | 1548 | if (fn_proto.getAlignExpr()) |align_expr| { |
| 1608 | const align_rparen = tree.nextToken(align_expr.lastToken()); | 1549 | const align_rparen = tree.nextToken(align_expr.lastToken()); |
| 1609 | const align_lparen = tree.prevToken(align_expr.firstToken()); | 1550 | const align_lparen = tree.prevToken(align_expr.firstToken()); |
| 1610 | const align_kw = tree.prevToken(align_lparen); | 1551 | const align_kw = tree.prevToken(align_lparen); |
| 1611 | 1552 | ||
| 1612 | try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align | 1553 | try renderToken(tree, ais, align_kw, Space.None); // align |
| 1613 | try renderToken(tree, stream, align_lparen, indent, start_col, Space.None); // ( | 1554 | try renderToken(tree, ais, align_lparen, Space.None); // ( |
| 1614 | try renderExpression(allocator, stream, tree, indent, start_col, align_expr, Space.None); | 1555 | try renderExpression(allocator, ais, tree, align_expr, Space.None); |
| 1615 | try renderToken(tree, stream, align_rparen, indent, start_col, Space.Space); // ) | 1556 | try renderToken(tree, ais, align_rparen, Space.Space); // ) |
| 1616 | } | 1557 | } |
| 1617 | 1558 | ||
| 1618 | if (fn_proto.getSectionExpr()) |section_expr| { | 1559 | if (fn_proto.getSectionExpr()) |section_expr| { |
| ... | @@ -1620,10 +1561,10 @@ fn renderExpression( | ... | @@ -1620,10 +1561,10 @@ fn renderExpression( |
| 1620 | const section_lparen = tree.prevToken(section_expr.firstToken()); | 1561 | const section_lparen = tree.prevToken(section_expr.firstToken()); |
| 1621 | const section_kw = tree.prevToken(section_lparen); | 1562 | const section_kw = tree.prevToken(section_lparen); |
| 1622 | 1563 | ||
| 1623 | try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // section | 1564 | try renderToken(tree, ais, section_kw, Space.None); // section |
| 1624 | try renderToken(tree, stream, section_lparen, indent, start_col, Space.None); // ( | 1565 | try renderToken(tree, ais, section_lparen, Space.None); // ( |
| 1625 | try renderExpression(allocator, stream, tree, indent, start_col, section_expr, Space.None); | 1566 | try renderExpression(allocator, ais, tree, section_expr, Space.None); |
| 1626 | try renderToken(tree, stream, section_rparen, indent, start_col, Space.Space); // ) | 1567 | try renderToken(tree, ais, section_rparen, Space.Space); // ) |
| 1627 | } | 1568 | } |
| 1628 | 1569 | ||
| 1629 | if (fn_proto.getCallconvExpr()) |callconv_expr| { | 1570 | if (fn_proto.getCallconvExpr()) |callconv_expr| { |
| ... | @@ -1631,23 +1572,23 @@ fn renderExpression( | ... | @@ -1631,23 +1572,23 @@ fn renderExpression( |
| 1631 | const callconv_lparen = tree.prevToken(callconv_expr.firstToken()); | 1572 | const callconv_lparen = tree.prevToken(callconv_expr.firstToken()); |
| 1632 | const callconv_kw = tree.prevToken(callconv_lparen); | 1573 | const callconv_kw = tree.prevToken(callconv_lparen); |
| 1633 | 1574 | ||
| 1634 | try renderToken(tree, stream, callconv_kw, indent, start_col, Space.None); // callconv | 1575 | try renderToken(tree, ais, callconv_kw, Space.None); // callconv |
| 1635 | try renderToken(tree, stream, callconv_lparen, indent, start_col, Space.None); // ( | 1576 | try renderToken(tree, ais, callconv_lparen, Space.None); // ( |
| 1636 | try renderExpression(allocator, stream, tree, indent, start_col, callconv_expr, Space.None); | 1577 | try renderExpression(allocator, ais, tree, callconv_expr, Space.None); |
| 1637 | try renderToken(tree, stream, callconv_rparen, indent, start_col, Space.Space); // ) | 1578 | try renderToken(tree, ais, callconv_rparen, Space.Space); // ) |
| 1638 | } else if (fn_proto.getIsExternPrototype() != null) { | 1579 | } else if (fn_proto.getIsExternPrototype() != null) { |
| 1639 | try stream.writeAll("callconv(.C) "); | 1580 | try ais.writer().writeAll("callconv(.C) "); |
| 1640 | } else if (fn_proto.getIsAsync() != null) { | 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 | switch (fn_proto.return_type) { | 1585 | switch (fn_proto.return_type) { |
| 1645 | .Explicit => |node| { | 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 | .InferErrorSet => |node| { | 1589 | .InferErrorSet => |node| { |
| 1649 | try renderToken(tree, stream, tree.prevToken(node.firstToken()), indent, start_col, Space.None); // ! | 1590 | try renderToken(tree, ais, tree.prevToken(node.firstToken()), Space.None); // ! |
| 1650 | return renderExpression(allocator, stream, tree, indent, start_col, node, space); | 1591 | return renderExpression(allocator, ais, tree, node, space); |
| 1651 | }, | 1592 | }, |
| 1652 | .Invalid => unreachable, | 1593 | .Invalid => unreachable, |
| 1653 | } | 1594 | } |
| ... | @@ -1657,11 +1598,11 @@ fn renderExpression( | ... | @@ -1657,11 +1598,11 @@ fn renderExpression( |
| 1657 | const anyframe_type = @fieldParentPtr(ast.Node.AnyFrameType, "base", base); | 1598 | const anyframe_type = @fieldParentPtr(ast.Node.AnyFrameType, "base", base); |
| 1658 | 1599 | ||
| 1659 | if (anyframe_type.result) |result| { | 1600 | if (anyframe_type.result) |result| { |
| 1660 | try renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, Space.None); // anyframe | 1601 | try renderToken(tree, ais, anyframe_type.anyframe_token, Space.None); // anyframe |
| 1661 | try renderToken(tree, stream, result.arrow_token, indent, start_col, Space.None); // -> | 1602 | try renderToken(tree, ais, result.arrow_token, Space.None); // -> |
| 1662 | return renderExpression(allocator, stream, tree, indent, start_col, result.return_type, space); | 1603 | return renderExpression(allocator, ais, tree, result.return_type, space); |
| 1663 | } else { | 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,38 +1611,38 @@ fn renderExpression( |
| 1670 | .Switch => { | 1611 | .Switch => { |
| 1671 | const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base); | 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 | 1614 | try renderToken(tree, ais, switch_node.switch_token, Space.Space); // switch |
| 1674 | try renderToken(tree, stream, tree.nextToken(switch_node.switch_token), indent, start_col, Space.None); // ( | 1615 | try renderToken(tree, ais, tree.nextToken(switch_node.switch_token), Space.None); // ( |
| 1675 | 1616 | ||
| 1676 | const rparen = tree.nextToken(switch_node.expr.lastToken()); | 1617 | const rparen = tree.nextToken(switch_node.expr.lastToken()); |
| 1677 | const lbrace = tree.nextToken(rparen); | 1618 | const lbrace = tree.nextToken(rparen); |
| 1678 | 1619 | ||
| 1679 | if (switch_node.cases_len == 0) { | 1620 | if (switch_node.cases_len == 0) { |
| 1680 | try renderExpression(allocator, stream, tree, indent, start_col, switch_node.expr, Space.None); | 1621 | try renderExpression(allocator, ais, tree, switch_node.expr, Space.None); |
| 1681 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | 1622 | try renderToken(tree, ais, rparen, Space.Space); // ) |
| 1682 | try renderToken(tree, stream, lbrace, indent, start_col, Space.None); // { | 1623 | try renderToken(tree, ais, lbrace, Space.None); // { |
| 1683 | return renderToken(tree, stream, switch_node.rbrace, indent, start_col, space); // } | 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); | 1627 | try renderExpression(allocator, ais, tree, switch_node.expr, Space.None); |
| 1687 | 1628 | try renderToken(tree, ais, rparen, Space.Space); // ) | |
| 1688 | const new_indent = indent + indent_delta; | ||
| 1689 | 1629 | ||
| 1690 | try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) | 1630 | { |
| 1691 | try renderToken(tree, stream, lbrace, new_indent, start_col, Space.Newline); // { | 1631 | ais.pushIndentNextLine(); |
| 1632 | defer ais.popIndent(); | ||
| 1633 | try renderToken(tree, ais, lbrace, Space.Newline); // { | ||
| 1692 | 1634 | ||
| 1693 | const cases = switch_node.cases(); | 1635 | const cases = switch_node.cases(); |
| 1694 | for (cases) |node, i| { | 1636 | for (cases) |node, i| { |
| 1695 | try stream.writeByteNTimes(' ', new_indent); | 1637 | try renderExpression(allocator, ais, tree, node, Space.Comma); |
| 1696 | try renderExpression(allocator, stream, tree, new_indent, start_col, node, Space.Comma); | ||
| 1697 | 1638 | ||
| 1698 | if (i + 1 < cases.len) { | 1639 | if (i + 1 < cases.len) { |
| 1699 | try renderExtraNewline(tree, stream, start_col, cases[i + 1]); | 1640 | try renderExtraNewline(tree, ais, cases[i + 1]); |
| 1641 | } | ||
| 1700 | } | 1642 | } |
| 1701 | } | 1643 | } |
| 1702 | 1644 | ||
| 1703 | try stream.writeByteNTimes(' ', indent); | 1645 | return renderToken(tree, ais, switch_node.rbrace, space); // } |
| 1704 | return renderToken(tree, stream, switch_node.rbrace, indent, start_col, space); // } | ||
| 1705 | }, | 1646 | }, |
| 1706 | 1647 | ||
| 1707 | .SwitchCase => { | 1648 | .SwitchCase => { |
| ... | @@ -1718,43 +1659,41 @@ fn renderExpression( | ... | @@ -1718,43 +1659,41 @@ fn renderExpression( |
| 1718 | const items = switch_case.items(); | 1659 | const items = switch_case.items(); |
| 1719 | for (items) |node, i| { | 1660 | for (items) |node, i| { |
| 1720 | if (i + 1 < items.len) { | 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 | const comma_token = tree.nextToken(node.lastToken()); | 1664 | const comma_token = tree.nextToken(node.lastToken()); |
| 1724 | try renderToken(tree, stream, comma_token, indent, start_col, Space.Space); // , | 1665 | try renderToken(tree, ais, comma_token, Space.Space); // , |
| 1725 | try renderExtraNewline(tree, stream, start_col, items[i + 1]); | 1666 | try renderExtraNewline(tree, ais, items[i + 1]); |
| 1726 | } else { | 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 | } else { | 1671 | } else { |
| 1731 | const items = switch_case.items(); | 1672 | const items = switch_case.items(); |
| 1732 | for (items) |node, i| { | 1673 | for (items) |node, i| { |
| 1733 | if (i + 1 < items.len) { | 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 | const comma_token = tree.nextToken(node.lastToken()); | 1677 | const comma_token = tree.nextToken(node.lastToken()); |
| 1737 | try renderToken(tree, stream, comma_token, indent, start_col, Space.Newline); // , | 1678 | try renderToken(tree, ais, comma_token, Space.Newline); // , |
| 1738 | try renderExtraNewline(tree, stream, start_col, items[i + 1]); | 1679 | try renderExtraNewline(tree, ais, items[i + 1]); |
| 1739 | try stream.writeByteNTimes(' ', indent); | ||
| 1740 | } else { | 1680 | } else { |
| 1741 | try renderExpression(allocator, stream, tree, indent, start_col, node, Space.Comma); | 1681 | try renderExpression(allocator, ais, tree, node, Space.Comma); |
| 1742 | try stream.writeByteNTimes(' ', indent); | ||
| 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 | if (switch_case.payload) |payload| { | 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 | .SwitchElse => { | 1694 | .SwitchElse => { |
| 1756 | const switch_else = @fieldParentPtr(ast.Node.SwitchElse, "base", base); | 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 | .Else => { | 1698 | .Else => { |
| 1760 | const else_node = @fieldParentPtr(ast.Node.Else, "base", base); | 1699 | const else_node = @fieldParentPtr(ast.Node.Else, "base", base); |
| ... | @@ -1763,37 +1702,37 @@ fn renderExpression( | ... | @@ -1763,37 +1702,37 @@ fn renderExpression( |
| 1763 | const same_line = body_is_block or tree.tokensOnSameLine(else_node.else_token, else_node.body.lastToken()); | 1702 | const same_line = body_is_block or tree.tokensOnSameLine(else_node.else_token, else_node.body.lastToken()); |
| 1764 | 1703 | ||
| 1765 | const after_else_space = if (same_line or else_node.payload != null) Space.Space else Space.Newline; | 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 | if (else_node.payload) |payload| { | 1707 | if (else_node.payload) |payload| { |
| 1769 | const payload_space = if (same_line) Space.Space else Space.Newline; | 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 | if (same_line) { | 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 | .While => { | 1721 | .While => { |
| 1783 | const while_node = @fieldParentPtr(ast.Node.While, "base", base); | 1722 | const while_node = @fieldParentPtr(ast.Node.While, "base", base); |
| 1784 | 1723 | ||
| 1785 | if (while_node.label) |label| { | 1724 | if (while_node.label) |label| { |
| 1786 | try renderToken(tree, stream, label, indent, start_col, Space.None); // label | 1725 | try renderToken(tree, ais, label, Space.None); // label |
| 1787 | try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); // : | 1726 | try renderToken(tree, ais, tree.nextToken(label), Space.Space); // : |
| 1788 | } | 1727 | } |
| 1789 | 1728 | ||
| 1790 | if (while_node.inline_token) |inline_token| { | 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 | 1733 | try renderToken(tree, ais, while_node.while_token, Space.Space); // while |
| 1795 | try renderToken(tree, stream, tree.nextToken(while_node.while_token), indent, start_col, Space.None); // ( | 1734 | try renderToken(tree, ais, tree.nextToken(while_node.while_token), Space.None); // ( |
| 1796 | try renderExpression(allocator, stream, tree, indent, start_col, while_node.condition, Space.None); | 1735 | try renderExpression(allocator, ais, tree, while_node.condition, Space.None); |
| 1797 | 1736 | ||
| 1798 | const cond_rparen = tree.nextToken(while_node.condition.lastToken()); | 1737 | const cond_rparen = tree.nextToken(while_node.condition.lastToken()); |
| 1799 | 1738 | ||
| ... | @@ -1815,12 +1754,12 @@ fn renderExpression( | ... | @@ -1815,12 +1754,12 @@ fn renderExpression( |
| 1815 | 1754 | ||
| 1816 | { | 1755 | { |
| 1817 | const rparen_space = if (while_node.payload != null or while_node.continue_expr != null) Space.Space else block_start_space; | 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 | if (while_node.payload) |payload| { | 1760 | if (while_node.payload) |payload| { |
| 1822 | const payload_space = if (while_node.continue_expr != null) Space.Space else block_start_space; | 1761 | const payload_space = Space.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); | 1762 | try renderExpression(allocator, ais, tree, payload, payload_space); |
| 1824 | } | 1763 | } |
| 1825 | 1764 | ||
| 1826 | if (while_node.continue_expr) |continue_expr| { | 1765 | if (while_node.continue_expr) |continue_expr| { |
| ... | @@ -1828,29 +1767,22 @@ fn renderExpression( | ... | @@ -1828,29 +1767,22 @@ fn renderExpression( |
| 1828 | const lparen = tree.prevToken(continue_expr.firstToken()); | 1767 | const lparen = tree.prevToken(continue_expr.firstToken()); |
| 1829 | const colon = tree.prevToken(lparen); | 1768 | const colon = tree.prevToken(lparen); |
| 1830 | 1769 | ||
| 1831 | try renderToken(tree, stream, colon, indent, start_col, Space.Space); // : | 1770 | try renderToken(tree, ais, colon, Space.Space); // : |
| 1832 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | 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; | 1778 | { |
| 1840 | if (block_start_space == Space.Newline) { | 1779 | if (!body_is_block) ais.pushIndent(); |
| 1841 | new_indent += indent_delta; | 1780 | defer if (!body_is_block) ais.popIndent(); |
| 1842 | try stream.writeByteNTimes(' ', new_indent); | 1781 | try renderExpression(allocator, ais, tree, while_node.body, after_body_space); |
| 1843 | start_col.* = new_indent; | ||
| 1844 | } | 1782 | } |
| 1845 | 1783 | ||
| 1846 | try renderExpression(allocator, stream, tree, indent, start_col, while_node.body, after_body_space); | ||
| 1847 | |||
| 1848 | if (while_node.@"else") |@"else"| { | 1784 | if (while_node.@"else") |@"else"| { |
| 1849 | if (after_body_space == Space.Newline) { | 1785 | return renderExpression(allocator, ais, tree, &@"else".base, space); |
| 1850 | try stream.writeByteNTimes(' ', indent); | ||
| 1851 | start_col.* = indent; | ||
| 1852 | } | ||
| 1853 | return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space); | ||
| 1854 | } | 1786 | } |
| 1855 | }, | 1787 | }, |
| 1856 | 1788 | ||
| ... | @@ -1858,17 +1790,17 @@ fn renderExpression( | ... | @@ -1858,17 +1790,17 @@ fn renderExpression( |
| 1858 | const for_node = @fieldParentPtr(ast.Node.For, "base", base); | 1790 | const for_node = @fieldParentPtr(ast.Node.For, "base", base); |
| 1859 | 1791 | ||
| 1860 | if (for_node.label) |label| { | 1792 | if (for_node.label) |label| { |
| 1861 | try renderToken(tree, stream, label, indent, start_col, Space.None); // label | 1793 | try renderToken(tree, ais, label, Space.None); // label |
| 1862 | try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); // : | 1794 | try renderToken(tree, ais, tree.nextToken(label), Space.Space); // : |
| 1863 | } | 1795 | } |
| 1864 | 1796 | ||
| 1865 | if (for_node.inline_token) |inline_token| { | 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 | 1801 | try renderToken(tree, ais, for_node.for_token, Space.Space); // for |
| 1870 | try renderToken(tree, stream, tree.nextToken(for_node.for_token), indent, start_col, Space.None); // ( | 1802 | try renderToken(tree, ais, tree.nextToken(for_node.for_token), Space.None); // ( |
| 1871 | try renderExpression(allocator, stream, tree, indent, start_col, for_node.array_expr, Space.None); | 1803 | try renderExpression(allocator, ais, tree, for_node.array_expr, Space.None); |
| 1872 | 1804 | ||
| 1873 | const rparen = tree.nextToken(for_node.array_expr.lastToken()); | 1805 | const rparen = tree.nextToken(for_node.array_expr.lastToken()); |
| 1874 | 1806 | ||
| ... | @@ -1876,10 +1808,10 @@ fn renderExpression( | ... | @@ -1876,10 +1808,10 @@ fn renderExpression( |
| 1876 | const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken()); | 1808 | const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken()); |
| 1877 | const body_on_same_line = body_is_block or src_one_line_to_body; | 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 | const space_after_payload = if (body_on_same_line) Space.Space else Space.Newline; | 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 | const space_after_body = blk: { | 1816 | const space_after_body = blk: { |
| 1885 | if (for_node.@"else") |@"else"| { | 1817 | if (for_node.@"else") |@"else"| { |
| ... | @@ -1894,13 +1826,14 @@ fn renderExpression( | ... | @@ -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; | 1829 | { |
| 1898 | if (!body_on_same_line) try stream.writeByteNTimes(' ', body_indent); | 1830 | if (!body_on_same_line) ais.pushIndent(); |
| 1899 | try renderExpression(allocator, stream, tree, body_indent, start_col, for_node.body, space_after_body); // { body } | 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 | if (for_node.@"else") |@"else"| { | 1835 | if (for_node.@"else") |@"else"| { |
| 1902 | if (space_after_body == Space.Newline) try stream.writeByteNTimes(' ', indent); | 1836 | return renderExpression(allocator, ais, tree, &@"else".base, space); // else |
| 1903 | return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space); // else | ||
| 1904 | } | 1837 | } |
| 1905 | }, | 1838 | }, |
| 1906 | 1839 | ||
| ... | @@ -1910,29 +1843,29 @@ fn renderExpression( | ... | @@ -1910,29 +1843,29 @@ fn renderExpression( |
| 1910 | const lparen = tree.nextToken(if_node.if_token); | 1843 | const lparen = tree.nextToken(if_node.if_token); |
| 1911 | const rparen = tree.nextToken(if_node.condition.lastToken()); | 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 | 1846 | try renderToken(tree, ais, if_node.if_token, Space.Space); // if |
| 1914 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | 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 | const body_is_if_block = if_node.body.tag == .If; | 1851 | const body_is_if_block = if_node.body.tag == .If; |
| 1919 | const body_is_block = nodeIsBlock(if_node.body); | 1852 | const body_is_block = nodeIsBlock(if_node.body); |
| 1920 | 1853 | ||
| 1921 | if (body_is_if_block) { | 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 | } else if (body_is_block) { | 1856 | } else if (body_is_block) { |
| 1924 | const after_rparen_space = if (if_node.payload == null) Space.BlockStart else Space.Space; | 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 | if (if_node.payload) |payload| { | 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 | if (if_node.@"else") |@"else"| { | 1864 | if (if_node.@"else") |@"else"| { |
| 1932 | try renderExpression(allocator, stream, tree, indent, start_col, if_node.body, Space.SpaceOrOutdent); | 1865 | try renderExpression(allocator, ais, tree, if_node.body, Space.SpaceOrOutdent); |
| 1933 | return renderExpression(allocator, stream, tree, indent, start_col, &@"else".base, space); | 1866 | return renderExpression(allocator, ais, tree, &@"else".base, space); |
| 1934 | } else { | 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,186 +1873,184 @@ fn renderExpression( |
| 1940 | 1873 | ||
| 1941 | if (src_has_newline) { | 1874 | if (src_has_newline) { |
| 1942 | const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space; | 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 | if (if_node.payload) |payload| { | 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 | if (if_node.@"else") |@"else"| { | 1882 | if (if_node.@"else") |@"else"| { |
| 1953 | const else_is_block = nodeIsBlock(@"else".body); | 1883 | const else_is_block = nodeIsBlock(@"else".body); |
| 1954 | try renderExpression(allocator, stream, tree, new_indent, start_col, if_node.body, Space.Newline); | 1884 | |
| 1955 | try stream.writeByteNTimes(' ', indent); | 1885 | { |
| 1886 | ais.pushIndent(); | ||
| 1887 | defer ais.popIndent(); | ||
| 1888 | try renderExpression(allocator, ais, tree, if_node.body, Space.Newline); | ||
| 1889 | } | ||
| 1956 | 1890 | ||
| 1957 | if (else_is_block) { | 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 | if (@"else".payload) |payload| { | 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 | } else { | 1899 | } else { |
| 1966 | const after_else_space = if (@"else".payload == null) Space.Newline else Space.Space; | 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 | if (@"else".payload) |payload| { | 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 | } else { | 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 | if (if_node.payload) |payload| { | 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 | if (if_node.@"else") |@"else"| { | 1926 | if (if_node.@"else") |@"else"| { |
| 1988 | try renderExpression(allocator, stream, tree, indent, start_col, if_node.body, Space.Space); | 1927 | try renderExpression(allocator, ais, tree, if_node.body, Space.Space); |
| 1989 | try renderToken(tree, stream, @"else".else_token, indent, start_col, Space.Space); | 1928 | try renderToken(tree, ais, @"else".else_token, Space.Space); |
| 1990 | 1929 | ||
| 1991 | if (@"else".payload) |payload| { | 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 | } else { | 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 | .Asm => { | 1940 | .Asm => { |
| 2002 | const asm_node = @fieldParentPtr(ast.Node.Asm, "base", base); | 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 | if (asm_node.volatile_token) |volatile_token| { | 1945 | if (asm_node.volatile_token) |volatile_token| { |
| 2007 | try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile | 1946 | try renderToken(tree, ais, volatile_token, Space.Space); // volatile |
| 2008 | try renderToken(tree, stream, tree.nextToken(volatile_token), indent, start_col, Space.None); // ( | 1947 | try renderToken(tree, ais, tree.nextToken(volatile_token), Space.None); // ( |
| 2009 | } else { | 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) { | 1952 | asmblk: { |
| 2014 | try renderExpression(allocator, stream, tree, indent, start_col, asm_node.template, Space.None); | 1953 | ais.pushIndent(); |
| 2015 | return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); | 1954 | defer ais.popIndent(); |
| 2016 | } | ||
| 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) { | 1963 | ais.setIndentDelta(asm_indent_delta); |
| 2023 | // After rendering a multiline string literal the cursor is | 1964 | defer ais.setIndentDelta(indent_delta); |
| 2024 | // already offset by indent | ||
| 2025 | try stream.writeByteNTimes(' ', indent_delta); | ||
| 2026 | } else { | ||
| 2027 | try stream.writeByteNTimes(' ', indent_once); | ||
| 2028 | } | ||
| 2029 | 1965 | ||
| 2030 | const colon1 = tree.nextToken(asm_node.template.lastToken()); | 1966 | const colon1 = tree.nextToken(asm_node.template.lastToken()); |
| 2031 | const indent_extra = indent_once + 2; | ||
| 2032 | 1967 | ||
| 2033 | const colon2 = if (asm_node.outputs.len == 0) blk: { | 1968 | const colon2 = if (asm_node.outputs.len == 0) blk: { |
| 2034 | try renderToken(tree, stream, colon1, indent, start_col, Space.Newline); // : | 1969 | try renderToken(tree, ais, colon1, Space.Newline); // : |
| 2035 | try stream.writeByteNTimes(' ', indent_once); | ||
| 2036 | 1970 | ||
| 2037 | break :blk tree.nextToken(colon1); | 1971 | break :blk tree.nextToken(colon1); |
| 2038 | } else blk: { | 1972 | } else blk: { |
| 2039 | try renderToken(tree, stream, colon1, indent, start_col, Space.Space); // : | 1973 | try renderToken(tree, ais, colon1, 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 | }; | ||
| 2067 | 1974 | ||
| 2068 | const colon3 = if (asm_node.inputs.len == 0) blk: { | 1975 | ais.pushIndent(); |
| 2069 | try renderToken(tree, stream, colon2, indent, start_col, Space.Newline); // : | 1976 | defer ais.popIndent(); |
| 2070 | try stream.writeByteNTimes(' ', indent_once); | ||
| 2071 | 1977 | ||
| 2072 | break :blk tree.nextToken(colon2); | 1978 | for (asm_node.outputs) |*asm_output, i| { |
| 2073 | } else blk: { | 1979 | if (i + 1 < asm_node.outputs.len) { |
| 2074 | try renderToken(tree, stream, colon2, indent, start_col, Space.Space); // : | 1980 | const next_asm_output = asm_node.outputs[i + 1]; |
| 2075 | 1981 | try renderAsmOutput(allocator, ais, tree, asm_output, Space.None); | |
| 2076 | for (asm_node.inputs) |*asm_input, i| { | 1982 | |
| 2077 | if (i + 1 < asm_node.inputs.len) { | 1983 | const comma = tree.prevToken(next_asm_output.firstToken()); |
| 2078 | const next_asm_input = &asm_node.inputs[i + 1]; | 1984 | try renderToken(tree, ais, comma, Space.Newline); // , |
| 2079 | try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.None); | 1985 | try renderExtraNewlineToken(tree, ais, next_asm_output.firstToken()); |
| 2080 | 1986 | } else if (asm_node.inputs.len == 0 and asm_node.clobbers.len == 0) { | |
| 2081 | const comma = tree.prevToken(next_asm_input.firstToken()); | 1987 | try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline); |
| 2082 | try renderToken(tree, stream, comma, indent_extra, start_col, Space.Newline); // , | 1988 | break :asmblk; |
| 2083 | try renderExtraNewlineToken(tree, stream, start_col, next_asm_input.firstToken()); | 1989 | } else { |
| 2084 | 1990 | try renderAsmOutput(allocator, ais, tree, asm_output, Space.Newline); | |
| 2085 | try stream.writeByteNTimes(' ', indent_extra); | 1991 | const comma_or_colon = tree.nextToken(asm_output.lastToken()); |
| 2086 | } else if (asm_node.clobbers.len == 0) { | 1992 | break :blk switch (tree.token_ids[comma_or_colon]) { |
| 2087 | try renderAsmInput(allocator, stream, tree, indent_extra, start_col, asm_input, Space.Newline); | 1993 | .Comma => tree.nextToken(comma_or_colon), |
| 2088 | try stream.writeByteNTimes(' ', indent); | 1994 | else => comma_or_colon, |
| 2089 | return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); // ) | 1995 | }; |
| 2090 | } else { | 1996 | } |
| 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 | }; | ||
| 2098 | } | 1997 | } |
| 2099 | } | 1998 | unreachable; |
| 2100 | unreachable; | 1999 | }; |
| 2101 | }; | ||
| 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| { | 2031 | try renderToken(tree, ais, colon3, Space.Space); // : |
| 2106 | if (i + 1 >= asm_node.clobbers.len) { | 2032 | ais.pushIndent(); |
| 2107 | try renderExpression(allocator, stream, tree, indent_extra, start_col, clobber_node, Space.Newline); | 2033 | defer ais.popIndent(); |
| 2108 | try stream.writeByteNTimes(' ', indent); | 2034 | for (asm_node.clobbers) |clobber_node, i| { |
| 2109 | return renderToken(tree, stream, asm_node.rparen, indent, start_col, space); | 2035 | if (i + 1 >= asm_node.clobbers.len) { |
| 2110 | } else { | 2036 | try renderExpression(allocator, ais, tree, clobber_node, Space.Newline); |
| 2111 | try renderExpression(allocator, stream, tree, indent_extra, start_col, clobber_node, Space.None); | 2037 | break :asmblk; |
| 2112 | const comma = tree.nextToken(clobber_node.lastToken()); | 2038 | } else { |
| 2113 | try renderToken(tree, stream, comma, indent_once, start_col, Space.Space); // , | 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 | .EnumLiteral => { | 2049 | .EnumLiteral => { |
| 2119 | const enum_literal = @fieldParentPtr(ast.Node.EnumLiteral, "base", base); | 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); // . | 2052 | try renderToken(tree, ais, enum_literal.dot, Space.None); // . |
| 2122 | return renderToken(tree, stream, enum_literal.name, indent, start_col, space); // name | 2053 | return renderToken(tree, ais, enum_literal.name, space); // name |
| 2123 | }, | 2054 | }, |
| 2124 | 2055 | ||
| 2125 | .ContainerField, | 2056 | .ContainerField, |
| ... | @@ -2133,118 +2064,115 @@ fn renderExpression( | ... | @@ -2133,118 +2064,115 @@ fn renderExpression( |
| 2133 | 2064 | ||
| 2134 | fn renderArrayType( | 2065 | fn renderArrayType( |
| 2135 | allocator: *mem.Allocator, | 2066 | allocator: *mem.Allocator, |
| 2136 | stream: anytype, | 2067 | ais: anytype, |
| 2137 | tree: *ast.Tree, | 2068 | tree: *ast.Tree, |
| 2138 | indent: usize, | ||
| 2139 | start_col: *usize, | ||
| 2140 | lbracket: ast.TokenIndex, | 2069 | lbracket: ast.TokenIndex, |
| 2141 | rhs: *ast.Node, | 2070 | rhs: *ast.Node, |
| 2142 | len_expr: *ast.Node, | 2071 | len_expr: *ast.Node, |
| 2143 | opt_sentinel: ?*ast.Node, | 2072 | opt_sentinel: ?*ast.Node, |
| 2144 | space: Space, | 2073 | space: Space, |
| 2145 | ) (@TypeOf(stream).Error || Error)!void { | 2074 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2146 | const rbracket = tree.nextToken(if (opt_sentinel) |sentinel| | 2075 | const rbracket = tree.nextToken(if (opt_sentinel) |sentinel| |
| 2147 | sentinel.lastToken() | 2076 | sentinel.lastToken() |
| 2148 | else | 2077 | else |
| 2149 | len_expr.lastToken()); | 2078 | len_expr.lastToken()); |
| 2150 | 2079 | ||
| 2151 | try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [ | ||
| 2152 | |||
| 2153 | const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; | 2080 | const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; |
| 2154 | const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; | 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 | const new_space = if (ends_with_comment) Space.Newline else Space.None; | 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); | 2083 | { |
| 2158 | if (starts_with_comment) { | 2084 | const do_indent = (starts_with_comment or ends_with_comment); |
| 2159 | try stream.writeByte('\n'); | 2085 | if (do_indent) ais.pushIndent(); |
| 2160 | } | 2086 | defer if (do_indent) ais.popIndent(); |
| 2161 | if (ends_with_comment or starts_with_comment) { | 2087 | |
| 2162 | try stream.writeByteNTimes(' ', indent); | 2088 | try renderToken(tree, ais, lbracket, Space.None); // [ |
| 2163 | } | 2089 | try renderExpression(allocator, ais, tree, len_expr, new_space); |
| 2164 | if (opt_sentinel) |sentinel| { | 2090 | |
| 2165 | const colon_token = tree.prevToken(sentinel.firstToken()); | 2091 | if (starts_with_comment) { |
| 2166 | try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : | 2092 | try ais.maybeInsertNewline(); |
| 2167 | try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); | 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 | fn renderAsmOutput( | 2108 | fn renderAsmOutput( |
| 2175 | allocator: *mem.Allocator, | 2109 | allocator: *mem.Allocator, |
| 2176 | stream: anytype, | 2110 | ais: anytype, |
| 2177 | tree: *ast.Tree, | 2111 | tree: *ast.Tree, |
| 2178 | indent: usize, | ||
| 2179 | start_col: *usize, | ||
| 2180 | asm_output: *const ast.Node.Asm.Output, | 2112 | asm_output: *const ast.Node.Asm.Output, |
| 2181 | space: Space, | 2113 | space: Space, |
| 2182 | ) (@TypeOf(stream).Error || Error)!void { | 2114 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2183 | try stream.writeAll("["); | 2115 | try ais.writer().writeAll("["); |
| 2184 | try renderExpression(allocator, stream, tree, indent, start_col, asm_output.symbolic_name, Space.None); | 2116 | try renderExpression(allocator, ais, tree, asm_output.symbolic_name, Space.None); |
| 2185 | try stream.writeAll("] "); | 2117 | try ais.writer().writeAll("] "); |
| 2186 | try renderExpression(allocator, stream, tree, indent, start_col, asm_output.constraint, Space.None); | 2118 | try renderExpression(allocator, ais, tree, asm_output.constraint, Space.None); |
| 2187 | try stream.writeAll(" ("); | 2119 | try ais.writer().writeAll(" ("); |
| 2188 | 2120 | ||
| 2189 | switch (asm_output.kind) { | 2121 | switch (asm_output.kind) { |
| 2190 | ast.Node.Asm.Output.Kind.Variable => |variable_name| { | 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 | ast.Node.Asm.Output.Kind.Return => |return_type| { | 2125 | ast.Node.Asm.Output.Kind.Return => |return_type| { |
| 2194 | try stream.writeAll("-> "); | 2126 | try ais.writer().writeAll("-> "); |
| 2195 | try renderExpression(allocator, stream, tree, indent, start_col, return_type, Space.None); | 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 | fn renderAsmInput( | 2134 | fn renderAsmInput( |
| 2203 | allocator: *mem.Allocator, | 2135 | allocator: *mem.Allocator, |
| 2204 | stream: anytype, | 2136 | ais: anytype, |
| 2205 | tree: *ast.Tree, | 2137 | tree: *ast.Tree, |
| 2206 | indent: usize, | ||
| 2207 | start_col: *usize, | ||
| 2208 | asm_input: *const ast.Node.Asm.Input, | 2138 | asm_input: *const ast.Node.Asm.Input, |
| 2209 | space: Space, | 2139 | space: Space, |
| 2210 | ) (@TypeOf(stream).Error || Error)!void { | 2140 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2211 | try stream.writeAll("["); | 2141 | try ais.writer().writeAll("["); |
| 2212 | try renderExpression(allocator, stream, tree, indent, start_col, asm_input.symbolic_name, Space.None); | 2142 | try renderExpression(allocator, ais, tree, asm_input.symbolic_name, Space.None); |
| 2213 | try stream.writeAll("] "); | 2143 | try ais.writer().writeAll("] "); |
| 2214 | try renderExpression(allocator, stream, tree, indent, start_col, asm_input.constraint, Space.None); | 2144 | try renderExpression(allocator, ais, tree, asm_input.constraint, Space.None); |
| 2215 | try stream.writeAll(" ("); | 2145 | try ais.writer().writeAll(" ("); |
| 2216 | try renderExpression(allocator, stream, tree, indent, start_col, asm_input.expr, Space.None); | 2146 | try renderExpression(allocator, ais, tree, asm_input.expr, Space.None); |
| 2217 | return renderToken(tree, stream, asm_input.lastToken(), indent, start_col, space); // ) | 2147 | return renderToken(tree, ais, asm_input.lastToken(), space); // ) |
| 2218 | } | 2148 | } |
| 2219 | 2149 | ||
| 2220 | fn renderVarDecl( | 2150 | fn renderVarDecl( |
| 2221 | allocator: *mem.Allocator, | 2151 | allocator: *mem.Allocator, |
| 2222 | stream: anytype, | 2152 | ais: anytype, |
| 2223 | tree: *ast.Tree, | 2153 | tree: *ast.Tree, |
| 2224 | indent: usize, | ||
| 2225 | start_col: *usize, | ||
| 2226 | var_decl: *ast.Node.VarDecl, | 2154 | var_decl: *ast.Node.VarDecl, |
| 2227 | ) (@TypeOf(stream).Error || Error)!void { | 2155 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2228 | if (var_decl.getVisibToken()) |visib_token| { | 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 | if (var_decl.getExternExportToken()) |extern_export_token| { | 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 | if (var_decl.getLibName()) |lib_name| { | 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 | if (var_decl.getComptimeToken()) |comptime_token| { | 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 | if (var_decl.getThreadLocalToken()) |thread_local_token| { | 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 | const name_space = if (var_decl.getTypeNode() == null and | 2177 | const name_space = if (var_decl.getTypeNode() == null and |
| 2250 | (var_decl.getAlignNode() != null or | 2178 | (var_decl.getAlignNode() != null or |
| ... | @@ -2253,95 +2181,92 @@ fn renderVarDecl( | ... | @@ -2253,95 +2181,92 @@ fn renderVarDecl( |
| 2253 | Space.Space | 2181 | Space.Space |
| 2254 | else | 2182 | else |
| 2255 | Space.None; | 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 | if (var_decl.getTypeNode()) |type_node| { | 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 | const s = if (var_decl.getAlignNode() != null or | 2188 | const s = if (var_decl.getAlignNode() != null or |
| 2261 | var_decl.getSectionNode() != null or | 2189 | var_decl.getSectionNode() != null or |
| 2262 | var_decl.getInitNode() != null) Space.Space else Space.None; | 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 | if (var_decl.getAlignNode()) |align_node| { | 2194 | if (var_decl.getAlignNode()) |align_node| { |
| 2267 | const lparen = tree.prevToken(align_node.firstToken()); | 2195 | const lparen = tree.prevToken(align_node.firstToken()); |
| 2268 | const align_kw = tree.prevToken(lparen); | 2196 | const align_kw = tree.prevToken(lparen); |
| 2269 | const rparen = tree.nextToken(align_node.lastToken()); | 2197 | const rparen = tree.nextToken(align_node.lastToken()); |
| 2270 | try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align | 2198 | try renderToken(tree, ais, align_kw, Space.None); // align |
| 2271 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | 2199 | try renderToken(tree, ais, lparen, Space.None); // ( |
| 2272 | try renderExpression(allocator, stream, tree, indent, start_col, align_node, Space.None); | 2200 | try renderExpression(allocator, ais, tree, align_node, Space.None); |
| 2273 | const s = if (var_decl.getSectionNode() != null or var_decl.getInitNode() != null) Space.Space else Space.None; | 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 | if (var_decl.getSectionNode()) |section_node| { | 2205 | if (var_decl.getSectionNode()) |section_node| { |
| 2278 | const lparen = tree.prevToken(section_node.firstToken()); | 2206 | const lparen = tree.prevToken(section_node.firstToken()); |
| 2279 | const section_kw = tree.prevToken(lparen); | 2207 | const section_kw = tree.prevToken(lparen); |
| 2280 | const rparen = tree.nextToken(section_node.lastToken()); | 2208 | const rparen = tree.nextToken(section_node.lastToken()); |
| 2281 | try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // linksection | 2209 | try renderToken(tree, ais, section_kw, Space.None); // linksection |
| 2282 | try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( | 2210 | try renderToken(tree, ais, lparen, Space.None); // ( |
| 2283 | try renderExpression(allocator, stream, tree, indent, start_col, section_node, Space.None); | 2211 | try renderExpression(allocator, ais, tree, section_node, Space.None); |
| 2284 | const s = if (var_decl.getInitNode() != null) Space.Space else Space.None; | 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 | if (var_decl.getInitNode()) |init_node| { | 2216 | if (var_decl.getInitNode()) |init_node| { |
| 2289 | const s = if (init_node.tag == .MultilineStringLiteral) Space.None else Space.Space; | 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); // = | 2218 | try renderToken(tree, ais, var_decl.getEqToken().?, s); // = |
| 2291 | try renderExpression(allocator, stream, tree, indent, start_col, init_node, Space.None); | 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 | fn renderParamDecl( | 2226 | fn renderParamDecl( |
| 2298 | allocator: *mem.Allocator, | 2227 | allocator: *mem.Allocator, |
| 2299 | stream: anytype, | 2228 | ais: anytype, |
| 2300 | tree: *ast.Tree, | 2229 | tree: *ast.Tree, |
| 2301 | indent: usize, | ||
| 2302 | start_col: *usize, | ||
| 2303 | param_decl: ast.Node.FnProto.ParamDecl, | 2230 | param_decl: ast.Node.FnProto.ParamDecl, |
| 2304 | space: Space, | 2231 | space: Space, |
| 2305 | ) (@TypeOf(stream).Error || Error)!void { | 2232 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2306 | try renderDocComments(tree, stream, param_decl, param_decl.doc_comments, indent, start_col); | 2233 | try renderDocComments(tree, ais, param_decl, param_decl.doc_comments); |
| 2307 | 2234 | ||
| 2308 | if (param_decl.comptime_token) |comptime_token| { | 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 | if (param_decl.noalias_token) |noalias_token| { | 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 | if (param_decl.name_token) |name_token| { | 2241 | if (param_decl.name_token) |name_token| { |
| 2315 | try renderToken(tree, stream, name_token, indent, start_col, Space.None); | 2242 | try renderToken(tree, ais, name_token, Space.None); |
| 2316 | try renderToken(tree, stream, tree.nextToken(name_token), indent, start_col, Space.Space); // : | 2243 | try renderToken(tree, ais, tree.nextToken(name_token), Space.Space); // : |
| 2317 | } | 2244 | } |
| 2318 | switch (param_decl.param_type) { | 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 | fn renderStatement( | 2250 | fn renderStatement( |
| 2324 | allocator: *mem.Allocator, | 2251 | allocator: *mem.Allocator, |
| 2325 | stream: anytype, | 2252 | ais: anytype, |
| 2326 | tree: *ast.Tree, | 2253 | tree: *ast.Tree, |
| 2327 | indent: usize, | ||
| 2328 | start_col: *usize, | ||
| 2329 | base: *ast.Node, | 2254 | base: *ast.Node, |
| 2330 | ) (@TypeOf(stream).Error || Error)!void { | 2255 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2331 | switch (base.tag) { | 2256 | switch (base.tag) { |
| 2332 | .VarDecl => { | 2257 | .VarDecl => { |
| 2333 | const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base); | 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 | else => { | 2261 | else => { |
| 2337 | if (base.requireSemiColon()) { | 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 | const semicolon_index = tree.nextToken(base.lastToken()); | 2265 | const semicolon_index = tree.nextToken(base.lastToken()); |
| 2341 | assert(tree.token_ids[semicolon_index] == .Semicolon); | 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 | } else { | 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,24 +2285,19 @@ const Space = enum { |
| 2360 | 2285 | ||
| 2361 | fn renderTokenOffset( | 2286 | fn renderTokenOffset( |
| 2362 | tree: *ast.Tree, | 2287 | tree: *ast.Tree, |
| 2363 | stream: anytype, | 2288 | ais: anytype, |
| 2364 | token_index: ast.TokenIndex, | 2289 | token_index: ast.TokenIndex, |
| 2365 | indent: usize, | ||
| 2366 | start_col: *usize, | ||
| 2367 | space: Space, | 2290 | space: Space, |
| 2368 | token_skip_bytes: usize, | 2291 | token_skip_bytes: usize, |
| 2369 | ) (@TypeOf(stream).Error || Error)!void { | 2292 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2370 | if (space == Space.BlockStart) { | 2293 | if (space == Space.BlockStart) { |
| 2371 | if (start_col.* < indent + indent_delta) | 2294 | // If placing the lbrace on the current line would cause an uggly gap then put the lbrace on the next line |
| 2372 | return renderToken(tree, stream, token_index, indent, start_col, Space.Space); | 2295 | const new_space = if (ais.isLineOverIndented()) Space.Newline else Space.Space; |
| 2373 | try renderToken(tree, stream, token_index, indent, start_col, Space.Newline); | 2296 | return renderToken(tree, ais, token_index, new_space); |
| 2374 | try stream.writeByteNTimes(' ', indent); | ||
| 2375 | start_col.* = indent; | ||
| 2376 | return; | ||
| 2377 | } | 2297 | } |
| 2378 | 2298 | ||
| 2379 | var token_loc = tree.token_locs[token_index]; | 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 | if (space == Space.NoComment) | 2302 | if (space == Space.NoComment) |
| 2383 | return; | 2303 | return; |
| ... | @@ -2386,20 +2306,20 @@ fn renderTokenOffset( | ... | @@ -2386,20 +2306,20 @@ fn renderTokenOffset( |
| 2386 | var next_token_loc = tree.token_locs[token_index + 1]; | 2306 | var next_token_loc = tree.token_locs[token_index + 1]; |
| 2387 | 2307 | ||
| 2388 | if (space == Space.Comma) switch (next_token_id) { | 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 | .LineComment => { | 2310 | .LineComment => { |
| 2391 | try stream.writeAll(", "); | 2311 | try ais.writer().writeAll(", "); |
| 2392 | return renderToken(tree, stream, token_index + 1, indent, start_col, Space.Newline); | 2312 | return renderToken(tree, ais, token_index + 1, Space.Newline); |
| 2393 | }, | 2313 | }, |
| 2394 | else => { | 2314 | else => { |
| 2395 | if (token_index + 2 < tree.token_ids.len and | 2315 | if (token_index + 2 < tree.token_ids.len and |
| 2396 | tree.token_ids[token_index + 2] == .MultilineStringLiteralLine) | 2316 | tree.token_ids[token_index + 2] == .MultilineStringLiteralLine) |
| 2397 | { | 2317 | { |
| 2398 | try stream.writeAll(","); | 2318 | try ais.writer().writeAll(","); |
| 2399 | return; | 2319 | return; |
| 2400 | } else { | 2320 | } else { |
| 2401 | try stream.writeAll(",\n"); | 2321 | try ais.writer().writeAll(","); |
| 2402 | start_col.* = 0; | 2322 | try ais.insertNewline(); |
| 2403 | return; | 2323 | return; |
| 2404 | } | 2324 | } |
| 2405 | }, | 2325 | }, |
| ... | @@ -2423,15 +2343,14 @@ fn renderTokenOffset( | ... | @@ -2423,15 +2343,14 @@ fn renderTokenOffset( |
| 2423 | if (next_token_id == .MultilineStringLiteralLine) { | 2343 | if (next_token_id == .MultilineStringLiteralLine) { |
| 2424 | return; | 2344 | return; |
| 2425 | } else { | 2345 | } else { |
| 2426 | try stream.writeAll("\n"); | 2346 | try ais.insertNewline(); |
| 2427 | start_col.* = 0; | ||
| 2428 | return; | 2347 | return; |
| 2429 | } | 2348 | } |
| 2430 | }, | 2349 | }, |
| 2431 | Space.Space, Space.SpaceOrOutdent => { | 2350 | Space.Space, Space.SpaceOrOutdent => { |
| 2432 | if (next_token_id == .MultilineStringLiteralLine) | 2351 | if (next_token_id == .MultilineStringLiteralLine) |
| 2433 | return; | 2352 | return; |
| 2434 | try stream.writeByte(' '); | 2353 | try ais.writer().writeByte(' '); |
| 2435 | return; | 2354 | return; |
| 2436 | }, | 2355 | }, |
| 2437 | Space.NoComment, Space.Comma, Space.BlockStart => unreachable, | 2356 | Space.NoComment, Space.Comma, Space.BlockStart => unreachable, |
| ... | @@ -2448,8 +2367,7 @@ fn renderTokenOffset( | ... | @@ -2448,8 +2367,7 @@ fn renderTokenOffset( |
| 2448 | next_token_id = tree.token_ids[token_index + offset]; | 2367 | next_token_id = tree.token_ids[token_index + offset]; |
| 2449 | next_token_loc = tree.token_locs[token_index + offset]; | 2368 | next_token_loc = tree.token_locs[token_index + offset]; |
| 2450 | if (next_token_id != .LineComment) { | 2369 | if (next_token_id != .LineComment) { |
| 2451 | try stream.writeByte('\n'); | 2370 | try ais.insertNewline(); |
| 2452 | start_col.* = 0; | ||
| 2453 | return; | 2371 | return; |
| 2454 | } | 2372 | } |
| 2455 | }, | 2373 | }, |
| ... | @@ -2462,7 +2380,7 @@ fn renderTokenOffset( | ... | @@ -2462,7 +2380,7 @@ fn renderTokenOffset( |
| 2462 | 2380 | ||
| 2463 | var loc = tree.tokenLocationLoc(token_loc.end, next_token_loc); | 2381 | var loc = tree.tokenLocationLoc(token_loc.end, next_token_loc); |
| 2464 | if (loc.line == 0) { | 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 | offset = 2; | 2384 | offset = 2; |
| 2467 | token_loc = next_token_loc; | 2385 | token_loc = next_token_loc; |
| 2468 | next_token_loc = tree.token_locs[token_index + offset]; | 2386 | next_token_loc = tree.token_locs[token_index + offset]; |
| ... | @@ -2470,26 +2388,16 @@ fn renderTokenOffset( | ... | @@ -2470,26 +2388,16 @@ fn renderTokenOffset( |
| 2470 | if (next_token_id != .LineComment) { | 2388 | if (next_token_id != .LineComment) { |
| 2471 | switch (space) { | 2389 | switch (space) { |
| 2472 | Space.None, Space.Space => { | 2390 | Space.None, Space.Space => { |
| 2473 | try stream.writeByte('\n'); | 2391 | try ais.insertNewline(); |
| 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; | ||
| 2481 | }, | 2392 | }, |
| 2482 | Space.SpaceOrOutdent => { | 2393 | Space.SpaceOrOutdent => { |
| 2483 | try stream.writeByte('\n'); | 2394 | try ais.insertNewline(); |
| 2484 | try stream.writeByteNTimes(' ', indent); | ||
| 2485 | start_col.* = indent; | ||
| 2486 | }, | 2395 | }, |
| 2487 | Space.Newline => { | 2396 | Space.Newline => { |
| 2488 | if (next_token_id == .MultilineStringLiteralLine) { | 2397 | if (next_token_id == .MultilineStringLiteralLine) { |
| 2489 | return; | 2398 | return; |
| 2490 | } else { | 2399 | } else { |
| 2491 | try stream.writeAll("\n"); | 2400 | try ais.insertNewline(); |
| 2492 | start_col.* = 0; | ||
| 2493 | return; | 2401 | return; |
| 2494 | } | 2402 | } |
| 2495 | }, | 2403 | }, |
| ... | @@ -2505,10 +2413,9 @@ fn renderTokenOffset( | ... | @@ -2505,10 +2413,9 @@ fn renderTokenOffset( |
| 2505 | // translate-c doesn't generate correct newlines | 2413 | // translate-c doesn't generate correct newlines |
| 2506 | // in generated code (loc.line == 0) so treat that case | 2414 | // in generated code (loc.line == 0) so treat that case |
| 2507 | // as though there was meant to be a newline between the tokens | 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); | 2416 | var newline_count = if (loc.line <= 1) @as(u8, 1) else @as(u8, 2); |
| 2509 | try stream.writeByteNTimes('\n', newline_count); | 2417 | while (newline_count > 0) : (newline_count -= 1) try ais.insertNewline(); |
| 2510 | try stream.writeByteNTimes(' ', indent); | 2418 | try ais.writer().writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")); |
| 2511 | try stream.writeAll(mem.trimRight(u8, tree.tokenSliceLoc(next_token_loc), " ")); | ||
| 2512 | 2419 | ||
| 2513 | offset += 1; | 2420 | offset += 1; |
| 2514 | token_loc = next_token_loc; | 2421 | token_loc = next_token_loc; |
| ... | @@ -2520,32 +2427,15 @@ fn renderTokenOffset( | ... | @@ -2520,32 +2427,15 @@ fn renderTokenOffset( |
| 2520 | if (next_token_id == .MultilineStringLiteralLine) { | 2427 | if (next_token_id == .MultilineStringLiteralLine) { |
| 2521 | return; | 2428 | return; |
| 2522 | } else { | 2429 | } else { |
| 2523 | try stream.writeAll("\n"); | 2430 | try ais.insertNewline(); |
| 2524 | start_col.* = 0; | ||
| 2525 | return; | 2431 | return; |
| 2526 | } | 2432 | } |
| 2527 | }, | 2433 | }, |
| 2528 | Space.None, Space.Space => { | 2434 | Space.None, Space.Space => { |
| 2529 | try stream.writeByte('\n'); | 2435 | try ais.insertNewline(); |
| 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; | ||
| 2544 | }, | 2436 | }, |
| 2545 | Space.SpaceOrOutdent => { | 2437 | Space.SpaceOrOutdent => { |
| 2546 | try stream.writeByte('\n'); | 2438 | try ais.insertNewline(); |
| 2547 | try stream.writeByteNTimes(' ', indent); | ||
| 2548 | start_col.* = indent; | ||
| 2549 | }, | 2439 | }, |
| 2550 | Space.NoNewline => {}, | 2440 | Space.NoNewline => {}, |
| 2551 | Space.NoComment, Space.Comma, Space.BlockStart => unreachable, | 2441 | Space.NoComment, Space.Comma, Space.BlockStart => unreachable, |
| ... | @@ -2558,46 +2448,38 @@ fn renderTokenOffset( | ... | @@ -2558,46 +2448,38 @@ fn renderTokenOffset( |
| 2558 | 2448 | ||
| 2559 | fn renderToken( | 2449 | fn renderToken( |
| 2560 | tree: *ast.Tree, | 2450 | tree: *ast.Tree, |
| 2561 | stream: anytype, | 2451 | ais: anytype, |
| 2562 | token_index: ast.TokenIndex, | 2452 | token_index: ast.TokenIndex, |
| 2563 | indent: usize, | ||
| 2564 | start_col: *usize, | ||
| 2565 | space: Space, | 2453 | space: Space, |
| 2566 | ) (@TypeOf(stream).Error || Error)!void { | 2454 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2567 | return renderTokenOffset(tree, stream, token_index, indent, start_col, space, 0); | 2455 | return renderTokenOffset(tree, ais, token_index, space, 0); |
| 2568 | } | 2456 | } |
| 2569 | 2457 | ||
| 2570 | fn renderDocComments( | 2458 | fn renderDocComments( |
| 2571 | tree: *ast.Tree, | 2459 | tree: *ast.Tree, |
| 2572 | stream: anytype, | 2460 | ais: anytype, |
| 2573 | node: anytype, | 2461 | node: anytype, |
| 2574 | doc_comments: ?*ast.Node.DocComment, | 2462 | doc_comments: ?*ast.Node.DocComment, |
| 2575 | indent: usize, | 2463 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2576 | start_col: *usize, | ||
| 2577 | ) (@TypeOf(stream).Error || Error)!void { | ||
| 2578 | const comment = doc_comments orelse return; | 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 | fn renderDocCommentsToken( | 2468 | fn renderDocCommentsToken( |
| 2583 | tree: *ast.Tree, | 2469 | tree: *ast.Tree, |
| 2584 | stream: anytype, | 2470 | ais: anytype, |
| 2585 | comment: *ast.Node.DocComment, | 2471 | comment: *ast.Node.DocComment, |
| 2586 | first_token: ast.TokenIndex, | 2472 | first_token: ast.TokenIndex, |
| 2587 | indent: usize, | 2473 | ) (@TypeOf(ais.*).Error || Error)!void { |
| 2588 | start_col: *usize, | ||
| 2589 | ) (@TypeOf(stream).Error || Error)!void { | ||
| 2590 | var tok_i = comment.first_line; | 2474 | var tok_i = comment.first_line; |
| 2591 | while (true) : (tok_i += 1) { | 2475 | while (true) : (tok_i += 1) { |
| 2592 | switch (tree.token_ids[tok_i]) { | 2476 | switch (tree.token_ids[tok_i]) { |
| 2593 | .DocComment, .ContainerDocComment => { | 2477 | .DocComment, .ContainerDocComment => { |
| 2594 | if (comment.first_line < first_token) { | 2478 | if (comment.first_line < first_token) { |
| 2595 | try renderToken(tree, stream, tok_i, indent, start_col, Space.Newline); | 2479 | try renderToken(tree, ais, tok_i, Space.Newline); |
| 2596 | try stream.writeByteNTimes(' ', indent); | ||
| 2597 | } else { | 2480 | } else { |
| 2598 | try renderToken(tree, stream, tok_i, indent, start_col, Space.NoComment); | 2481 | try renderToken(tree, ais, tok_i, Space.NoComment); |
| 2599 | try stream.writeAll("\n"); | 2482 | try ais.insertNewline(); |
| 2600 | try stream.writeByteNTimes(' ', indent); | ||
| 2601 | } | 2483 | } |
| 2602 | }, | 2484 | }, |
| 2603 | .LineComment => continue, | 2485 | .LineComment => continue, |
| ... | @@ -2669,41 +2551,10 @@ fn nodeCausesSliceOpSpace(base: *ast.Node) bool { | ... | @@ -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. | 2554 | fn copyFixingWhitespace(ais: anytype, slice: []const u8) @TypeOf(ais.*).Error!void { |
| 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 { | ||
| 2704 | for (slice) |byte| switch (byte) { | 2555 | for (slice) |byte| switch (byte) { |
| 2705 | '\t' => try stream.writeAll(" "), | 2556 | '\t' => try ais.writer().writeAll(" "), |
| 2706 | '\r' => {}, | 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,6 +1175,7 @@ pub const Tokenizer = struct { |
| 1175 | }, | 1175 | }, |
| 1176 | .num_dot_dec => switch (c) { | 1176 | .num_dot_dec => switch (c) { |
| 1177 | '.' => { | 1177 | '.' => { |
| 1178 | result.id = .IntegerLiteral; | ||
| 1178 | self.index -= 1; | 1179 | self.index -= 1; |
| 1179 | state = .start; | 1180 | state = .start; |
| 1180 | break; | 1181 | break; |
| ... | @@ -1183,7 +1184,6 @@ pub const Tokenizer = struct { | ... | @@ -1183,7 +1184,6 @@ pub const Tokenizer = struct { |
| 1183 | state = .float_exponent_unsigned; | 1184 | state = .float_exponent_unsigned; |
| 1184 | }, | 1185 | }, |
| 1185 | '0'...'9' => { | 1186 | '0'...'9' => { |
| 1186 | result.id = .FloatLiteral; | ||
| 1187 | state = .float_fraction_dec; | 1187 | state = .float_fraction_dec; |
| 1188 | }, | 1188 | }, |
| 1189 | else => { | 1189 | else => { |
| ... | @@ -1769,6 +1769,7 @@ test "tokenizer - number literals decimal" { | ... | @@ -1769,6 +1769,7 @@ test "tokenizer - number literals decimal" { |
| 1769 | testTokenize("7", &[_]Token.Id{.IntegerLiteral}); | 1769 | testTokenize("7", &[_]Token.Id{.IntegerLiteral}); |
| 1770 | testTokenize("8", &[_]Token.Id{.IntegerLiteral}); | 1770 | testTokenize("8", &[_]Token.Id{.IntegerLiteral}); |
| 1771 | testTokenize("9", &[_]Token.Id{.IntegerLiteral}); | 1771 | testTokenize("9", &[_]Token.Id{.IntegerLiteral}); |
| 1772 | testTokenize("1..", &[_]Token.Id{ .IntegerLiteral, .Ellipsis2 }); | ||
| 1772 | testTokenize("0a", &[_]Token.Id{ .Invalid, .Identifier }); | 1773 | testTokenize("0a", &[_]Token.Id{ .Invalid, .Identifier }); |
| 1773 | testTokenize("9b", &[_]Token.Id{ .Invalid, .Identifier }); | 1774 | testTokenize("9b", &[_]Token.Id{ .Invalid, .Identifier }); |
| 1774 | testTokenize("1z", &[_]Token.Id{ .Invalid, .Identifier }); | 1775 | testTokenize("1z", &[_]Token.Id{ .Invalid, .Identifier }); |
src-self-hosted/Module.zig+242-130| ... | @@ -36,17 +36,17 @@ bin_file_path: []const u8, | ... | @@ -36,17 +36,17 @@ bin_file_path: []const u8, |
| 36 | /// It's rare for a decl to be exported, so we save memory by having a sparse map of | 36 | /// It's rare for a decl to be exported, so we save memory by having a sparse map of |
| 37 | /// Decl pointers to details about them being exported. | 37 | /// Decl pointers to details about them being exported. |
| 38 | /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. | 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 | /// We track which export is associated with the given symbol name for quick | 40 | /// We track which export is associated with the given symbol name for quick |
| 41 | /// detection of symbol collisions. | 41 | /// detection of symbol collisions. |
| 42 | symbol_exports: std.StringHashMapUnmanaged(*Export) = .{}, | 42 | symbol_exports: std.StringArrayHashMapUnmanaged(*Export) = .{}, |
| 43 | /// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl | 43 | /// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl |
| 44 | /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that | 44 | /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that |
| 45 | /// is performing the export of another Decl. | 45 | /// is performing the export of another Decl. |
| 46 | /// This table owns the Export memory. | 46 | /// This table owns the Export memory. |
| 47 | export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, | 47 | export_owners: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{}, |
| 48 | /// Maps fully qualified namespaced names to the Decl struct for them. | 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 | link_error_flags: link.File.ErrorFlags = .{}, | 51 | link_error_flags: link.File.ErrorFlags = .{}, |
| 52 | 52 | ||
| ... | @@ -57,13 +57,13 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), | ... | @@ -57,13 +57,13 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), |
| 57 | /// The ErrorMsg memory is owned by the decl, using Module's allocator. | 57 | /// The ErrorMsg memory is owned by the decl, using Module's allocator. |
| 58 | /// Note that a Decl can succeed but the Fn it represents can fail. In this case, | 58 | /// Note that a Decl can succeed but the Fn it represents can fail. In this case, |
| 59 | /// a Decl can have a failed_decls entry but have analysis status of success. | 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 | /// Using a map here for consistency with the other fields here. | 61 | /// Using a map here for consistency with the other fields here. |
| 62 | /// The ErrorMsg memory is owned by the `Scope`, using Module's allocator. | 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 | /// Using a map here for consistency with the other fields here. | 64 | /// Using a map here for consistency with the other fields here. |
| 65 | /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. | 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 | /// Incrementing integer used to compare against the corresponding Decl | 68 | /// Incrementing integer used to compare against the corresponding Decl |
| 69 | /// field to determine whether a Decl's status applies to an ongoing update, or a | 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,7 +125,7 @@ pub const Decl = struct { |
| 125 | /// mapping them to an address in the output file. | 125 | /// mapping them to an address in the output file. |
| 126 | /// Memory owned by this decl, using Module's allocator. | 126 | /// Memory owned by this decl, using Module's allocator. |
| 127 | name: [*:0]const u8, | 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 | /// Reference to externally owned memory. | 129 | /// Reference to externally owned memory. |
| 130 | scope: *Scope, | 130 | scope: *Scope, |
| 131 | /// The AST Node decl index or ZIR Inst index that contains this declaration. | 131 | /// The AST Node decl index or ZIR Inst index that contains this declaration. |
| ... | @@ -201,9 +201,9 @@ pub const Decl = struct { | ... | @@ -201,9 +201,9 @@ pub const Decl = struct { |
| 201 | /// typed_value may need to be regenerated. | 201 | /// typed_value may need to be regenerated. |
| 202 | dependencies: DepsTable = .{}, | 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 | /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` | 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 | pub fn destroy(self: *Decl, gpa: *Allocator) void { | 208 | pub fn destroy(self: *Decl, gpa: *Allocator) void { |
| 209 | gpa.free(mem.spanZ(self.name)); | 209 | gpa.free(mem.spanZ(self.name)); |
| ... | @@ -217,9 +217,10 @@ pub const Decl = struct { | ... | @@ -217,9 +217,10 @@ pub const Decl = struct { |
| 217 | 217 | ||
| 218 | pub fn src(self: Decl) usize { | 218 | pub fn src(self: Decl) usize { |
| 219 | switch (self.scope.tag) { | 219 | switch (self.scope.tag) { |
| 220 | .file => { | 220 | .container => { |
| 221 | const file = @fieldParentPtr(Scope.File, "base", self.scope); | 221 | const container = @fieldParentPtr(Scope.Container, "base", self.scope); |
| 222 | const tree = file.contents.tree; | 222 | const tree = container.file_scope.contents.tree; |
| 223 | // TODO Container should have it's own decls() | ||
| 223 | const decl_node = tree.root_node.decls()[self.src_index]; | 224 | const decl_node = tree.root_node.decls()[self.src_index]; |
| 224 | return tree.token_locs[decl_node.firstToken()].start; | 225 | return tree.token_locs[decl_node.firstToken()].start; |
| 225 | }, | 226 | }, |
| ... | @@ -229,7 +230,7 @@ pub const Decl = struct { | ... | @@ -229,7 +230,7 @@ pub const Decl = struct { |
| 229 | const src_decl = module.decls[self.src_index]; | 230 | const src_decl = module.decls[self.src_index]; |
| 230 | return src_decl.inst.src; | 231 | return src_decl.inst.src; |
| 231 | }, | 232 | }, |
| 232 | .block => unreachable, | 233 | .file, .block => unreachable, |
| 233 | .gen_zir => unreachable, | 234 | .gen_zir => unreachable, |
| 234 | .local_val => unreachable, | 235 | .local_val => unreachable, |
| 235 | .local_ptr => unreachable, | 236 | .local_ptr => unreachable, |
| ... | @@ -359,6 +360,7 @@ pub const Scope = struct { | ... | @@ -359,6 +360,7 @@ pub const Scope = struct { |
| 359 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena, | 360 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena, |
| 360 | .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, | 361 | .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, |
| 361 | .file => unreachable, | 362 | .file => unreachable, |
| 363 | .container => unreachable, | ||
| 362 | } | 364 | } |
| 363 | } | 365 | } |
| 364 | 366 | ||
| ... | @@ -368,15 +370,16 @@ pub const Scope = struct { | ... | @@ -368,15 +370,16 @@ pub const Scope = struct { |
| 368 | return switch (self.tag) { | 370 | return switch (self.tag) { |
| 369 | .block => self.cast(Block).?.decl, | 371 | .block => self.cast(Block).?.decl, |
| 370 | .gen_zir => self.cast(GenZIR).?.decl, | 372 | .gen_zir => self.cast(GenZIR).?.decl, |
| 371 | .local_val => return self.cast(LocalVal).?.gen_zir.decl, | 373 | .local_val => self.cast(LocalVal).?.gen_zir.decl, |
| 372 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl, | 374 | .local_ptr => self.cast(LocalPtr).?.gen_zir.decl, |
| 373 | .decl => self.cast(DeclAnalysis).?.decl, | 375 | .decl => self.cast(DeclAnalysis).?.decl, |
| 374 | .zir_module => null, | 376 | .zir_module => null, |
| 375 | .file => null, | 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 | /// returns it. | 383 | /// returns it. |
| 381 | pub fn namespace(self: *Scope) *Scope { | 384 | pub fn namespace(self: *Scope) *Scope { |
| 382 | switch (self.tag) { | 385 | switch (self.tag) { |
| ... | @@ -385,7 +388,8 @@ pub const Scope = struct { | ... | @@ -385,7 +388,8 @@ pub const Scope = struct { |
| 385 | .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope, | 388 | .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope, |
| 386 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope, | 389 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope, |
| 387 | .decl => return self.cast(DeclAnalysis).?.decl.scope, | 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,8 +403,9 @@ pub const Scope = struct { |
| 399 | .local_val => unreachable, | 403 | .local_val => unreachable, |
| 400 | .local_ptr => unreachable, | 404 | .local_ptr => unreachable, |
| 401 | .decl => unreachable, | 405 | .decl => unreachable, |
| 406 | .file => unreachable, | ||
| 402 | .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name), | 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,11 +414,12 @@ pub const Scope = struct { |
| 409 | switch (self.tag) { | 414 | switch (self.tag) { |
| 410 | .file => return self.cast(File).?.contents.tree, | 415 | .file => return self.cast(File).?.contents.tree, |
| 411 | .zir_module => unreachable, | 416 | .zir_module => unreachable, |
| 412 | .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree, | 417 | .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(Container).?.file_scope.contents.tree, |
| 413 | .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree, | 418 | .block => return self.cast(Block).?.decl.scope.cast(Container).?.file_scope.contents.tree, |
| 414 | .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(File).?.contents.tree, | 419 | .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(Container).?.file_scope.contents.tree, |
| 415 | .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(File).?.contents.tree, | 420 | .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree, |
| 416 | .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(File).?.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,13 +433,15 @@ pub const Scope = struct { |
| 427 | .decl => unreachable, | 433 | .decl => unreachable, |
| 428 | .zir_module => unreachable, | 434 | .zir_module => unreachable, |
| 429 | .file => unreachable, | 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 | /// returns the sub_file_path field. | 441 | /// returns the sub_file_path field. |
| 435 | pub fn subFilePath(base: *Scope) []const u8 { | 442 | pub fn subFilePath(base: *Scope) []const u8 { |
| 436 | switch (base.tag) { | 443 | switch (base.tag) { |
| 444 | .container => return @fieldParentPtr(Container, "base", base).file_scope.sub_file_path, | ||
| 437 | .file => return @fieldParentPtr(File, "base", base).sub_file_path, | 445 | .file => return @fieldParentPtr(File, "base", base).sub_file_path, |
| 438 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, | 446 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, |
| 439 | .block => unreachable, | 447 | .block => unreachable, |
| ... | @@ -453,11 +461,13 @@ pub const Scope = struct { | ... | @@ -453,11 +461,13 @@ pub const Scope = struct { |
| 453 | .local_val => unreachable, | 461 | .local_val => unreachable, |
| 454 | .local_ptr => unreachable, | 462 | .local_ptr => unreachable, |
| 455 | .decl => unreachable, | 463 | .decl => unreachable, |
| 464 | .container => unreachable, | ||
| 456 | } | 465 | } |
| 457 | } | 466 | } |
| 458 | 467 | ||
| 459 | pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 { | 468 | pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 { |
| 460 | switch (base.tag) { | 469 | switch (base.tag) { |
| 470 | .container => return @fieldParentPtr(Container, "base", base).file_scope.getSource(module), | ||
| 461 | .file => return @fieldParentPtr(File, "base", base).getSource(module), | 471 | .file => return @fieldParentPtr(File, "base", base).getSource(module), |
| 462 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), | 472 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), |
| 463 | .gen_zir => unreachable, | 473 | .gen_zir => unreachable, |
| ... | @@ -471,8 +481,9 @@ pub const Scope = struct { | ... | @@ -471,8 +481,9 @@ pub const Scope = struct { |
| 471 | /// Asserts the scope is a namespace Scope and removes the Decl from the namespace. | 481 | /// Asserts the scope is a namespace Scope and removes the Decl from the namespace. |
| 472 | pub fn removeDecl(base: *Scope, child: *Decl) void { | 482 | pub fn removeDecl(base: *Scope, child: *Decl) void { |
| 473 | switch (base.tag) { | 483 | switch (base.tag) { |
| 474 | .file => return @fieldParentPtr(File, "base", base).removeDecl(child), | 484 | .container => return @fieldParentPtr(Container, "base", base).removeDecl(child), |
| 475 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child), | 485 | .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child), |
| 486 | .file => unreachable, | ||
| 476 | .block => unreachable, | 487 | .block => unreachable, |
| 477 | .gen_zir => unreachable, | 488 | .gen_zir => unreachable, |
| 478 | .local_val => unreachable, | 489 | .local_val => unreachable, |
| ... | @@ -499,6 +510,7 @@ pub const Scope = struct { | ... | @@ -499,6 +510,7 @@ pub const Scope = struct { |
| 499 | .local_val => unreachable, | 510 | .local_val => unreachable, |
| 500 | .local_ptr => unreachable, | 511 | .local_ptr => unreachable, |
| 501 | .decl => unreachable, | 512 | .decl => unreachable, |
| 513 | .container => unreachable, | ||
| 502 | } | 514 | } |
| 503 | } | 515 | } |
| 504 | 516 | ||
| ... | @@ -515,6 +527,8 @@ pub const Scope = struct { | ... | @@ -515,6 +527,8 @@ pub const Scope = struct { |
| 515 | zir_module, | 527 | zir_module, |
| 516 | /// .zig source code. | 528 | /// .zig source code. |
| 517 | file, | 529 | file, |
| 530 | /// struct, enum or union, every .file contains one of these. | ||
| 531 | container, | ||
| 518 | block, | 532 | block, |
| 519 | decl, | 533 | decl, |
| 520 | gen_zir, | 534 | gen_zir, |
| ... | @@ -522,6 +536,33 @@ pub const Scope = struct { | ... | @@ -522,6 +536,33 @@ pub const Scope = struct { |
| 522 | local_ptr, | 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 | pub const File = struct { | 566 | pub const File = struct { |
| 526 | pub const base_tag: Tag = .file; | 567 | pub const base_tag: Tag = .file; |
| 527 | base: Scope = Scope{ .tag = base_tag }, | 568 | base: Scope = Scope{ .tag = base_tag }, |
| ... | @@ -544,8 +585,7 @@ pub const Scope = struct { | ... | @@ -544,8 +585,7 @@ pub const Scope = struct { |
| 544 | loaded_success, | 585 | loaded_success, |
| 545 | }, | 586 | }, |
| 546 | 587 | ||
| 547 | /// Direct children of the file. | 588 | root_container: Container, |
| 548 | decls: ArrayListUnmanaged(*Decl), | ||
| 549 | 589 | ||
| 550 | pub fn unload(self: *File, gpa: *Allocator) void { | 590 | pub fn unload(self: *File, gpa: *Allocator) void { |
| 551 | switch (self.status) { | 591 | switch (self.status) { |
| ... | @@ -569,20 +609,11 @@ pub const Scope = struct { | ... | @@ -569,20 +609,11 @@ pub const Scope = struct { |
| 569 | } | 609 | } |
| 570 | 610 | ||
| 571 | pub fn deinit(self: *File, gpa: *Allocator) void { | 611 | pub fn deinit(self: *File, gpa: *Allocator) void { |
| 572 | self.decls.deinit(gpa); | 612 | self.root_container.deinit(gpa); |
| 573 | self.unload(gpa); | 613 | self.unload(gpa); |
| 574 | self.* = undefined; | 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 | pub fn dumpSrc(self: *File, src: usize) void { | 617 | pub fn dumpSrc(self: *File, src: usize) void { |
| 587 | const loc = std.zig.findLineColumn(self.source.bytes, src); | 618 | const loc = std.zig.findLineColumn(self.source.bytes, src); |
| 588 | std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); | 619 | std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); |
| ... | @@ -604,11 +635,6 @@ pub const Scope = struct { | ... | @@ -604,11 +635,6 @@ pub const Scope = struct { |
| 604 | .bytes => |bytes| return bytes, | 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 | pub const ZIRModule = struct { | 640 | pub const ZIRModule = struct { |
| ... | @@ -725,6 +751,7 @@ pub const Scope = struct { | ... | @@ -725,6 +751,7 @@ pub const Scope = struct { |
| 725 | /// Points to the arena allocator of DeclAnalysis | 751 | /// Points to the arena allocator of DeclAnalysis |
| 726 | arena: *Allocator, | 752 | arena: *Allocator, |
| 727 | label: ?Label = null, | 753 | label: ?Label = null, |
| 754 | is_comptime: bool, | ||
| 728 | 755 | ||
| 729 | pub const Label = struct { | 756 | pub const Label = struct { |
| 730 | zir_block: *zir.Inst.Block, | 757 | zir_block: *zir.Inst.Block, |
| ... | @@ -860,7 +887,10 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { | ... | @@ -860,7 +887,10 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { |
| 860 | .source = .{ .unloaded = {} }, | 887 | .source = .{ .unloaded = {} }, |
| 861 | .contents = .{ .not_available = {} }, | 888 | .contents = .{ .not_available = {} }, |
| 862 | .status = .never_loaded, | 889 | .status = .never_loaded, |
| 863 | .decls = .{}, | 890 | .root_container = .{ |
| 891 | .file_scope = root_scope, | ||
| 892 | .decls = .{}, | ||
| 893 | }, | ||
| 864 | }; | 894 | }; |
| 865 | break :blk &root_scope.base; | 895 | break :blk &root_scope.base; |
| 866 | } else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) { | 896 | } else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) { |
| ... | @@ -932,7 +962,8 @@ pub fn deinit(self: *Module) void { | ... | @@ -932,7 +962,8 @@ pub fn deinit(self: *Module) void { |
| 932 | self.symbol_exports.deinit(gpa); | 962 | self.symbol_exports.deinit(gpa); |
| 933 | self.root_scope.destroy(gpa); | 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 | gpa.free(entry.key); | 967 | gpa.free(entry.key); |
| 937 | } | 968 | } |
| 938 | self.global_error_set.deinit(gpa); | 969 | self.global_error_set.deinit(gpa); |
| ... | @@ -967,7 +998,7 @@ pub fn update(self: *Module) !void { | ... | @@ -967,7 +998,7 @@ pub fn update(self: *Module) !void { |
| 967 | // to force a refresh we unload now. | 998 | // to force a refresh we unload now. |
| 968 | if (self.root_scope.cast(Scope.File)) |zig_file| { | 999 | if (self.root_scope.cast(Scope.File)) |zig_file| { |
| 969 | zig_file.unload(self.gpa); | 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 | error.AnalysisFail => { | 1002 | error.AnalysisFail => { |
| 972 | assert(self.totalErrorCount() != 0); | 1003 | assert(self.totalErrorCount() != 0); |
| 973 | }, | 1004 | }, |
| ... | @@ -1235,8 +1266,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1235,8 +1266,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1235 | const tracy = trace(@src()); | 1266 | const tracy = trace(@src()); |
| 1236 | defer tracy.end(); | 1267 | defer tracy.end(); |
| 1237 | 1268 | ||
| 1238 | const file_scope = decl.scope.cast(Scope.File).?; | 1269 | const container_scope = decl.scope.cast(Scope.Container).?; |
| 1239 | const tree = try self.getAstTree(file_scope); | 1270 | const tree = try self.getAstTree(container_scope); |
| 1240 | const ast_node = tree.root_node.decls()[decl.src_index]; | 1271 | const ast_node = tree.root_node.decls()[decl.src_index]; |
| 1241 | switch (ast_node.tag) { | 1272 | switch (ast_node.tag) { |
| 1242 | .FnProto => { | 1273 | .FnProto => { |
| ... | @@ -1307,7 +1338,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1307,7 +1338,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1307 | .return_type = return_type_inst, | 1338 | .return_type = return_type_inst, |
| 1308 | .param_types = param_types, | 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 | // We need the memory for the Type to go into the arena for the Decl | 1342 | // We need the memory for the Type to go into the arena for the Decl |
| 1313 | var decl_arena = std.heap.ArenaAllocator.init(self.gpa); | 1343 | var decl_arena = std.heap.ArenaAllocator.init(self.gpa); |
| ... | @@ -1320,10 +1350,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1320,10 +1350,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1320 | .decl = decl, | 1350 | .decl = decl, |
| 1321 | .instructions = .{}, | 1351 | .instructions = .{}, |
| 1322 | .arena = &decl_arena.allocator, | 1352 | .arena = &decl_arena.allocator, |
| 1353 | .is_comptime = false, | ||
| 1323 | }; | 1354 | }; |
| 1324 | defer block_scope.instructions.deinit(self.gpa); | 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 | .instructions = fn_type_scope.instructions.items, | 1358 | .instructions = fn_type_scope.instructions.items, |
| 1328 | }); | 1359 | }); |
| 1329 | const new_func = try decl_arena.allocator.create(Fn); | 1360 | const new_func = try decl_arena.allocator.create(Fn); |
| ... | @@ -1457,6 +1488,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1457,6 +1488,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1457 | .decl = decl, | 1488 | .decl = decl, |
| 1458 | .instructions = .{}, | 1489 | .instructions = .{}, |
| 1459 | .arena = &decl_arena.allocator, | 1490 | .arena = &decl_arena.allocator, |
| 1491 | .is_comptime = true, | ||
| 1460 | }; | 1492 | }; |
| 1461 | defer block_scope.instructions.deinit(self.gpa); | 1493 | defer block_scope.instructions.deinit(self.gpa); |
| 1462 | 1494 | ||
| ... | @@ -1489,35 +1521,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1489,35 +1521,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1489 | return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{}); | 1521 | return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{}); |
| 1490 | } | 1522 | } |
| 1491 | 1523 | ||
| 1492 | const explicit_type = blk: { | 1524 | const var_info: struct { ty: Type, val: ?Value } = if (var_decl.getInitNode()) |init_node| vi: { |
| 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: { | ||
| 1521 | var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa); | 1525 | var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa); |
| 1522 | defer gen_scope_arena.deinit(); | 1526 | defer gen_scope_arena.deinit(); |
| 1523 | var gen_scope: Scope.GenZIR = .{ | 1527 | var gen_scope: Scope.GenZIR = .{ |
| ... | @@ -1526,11 +1530,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1526,11 +1530,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1526 | .parent = decl.scope, | 1530 | .parent = decl.scope, |
| 1527 | }; | 1531 | }; |
| 1528 | defer gen_scope.instructions.deinit(self.gpa); | 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 | 1534 | const init_result_loc: astgen.ResultLoc = if (var_decl.getTypeNode()) |type_node| rl: { |
| 1532 | const init_inst = try astgen.expr(self, &gen_scope.base, .none, init_node); | 1535 | const src = tree.token_locs[type_node.firstToken()].start; |
| 1533 | _ = try astgen.addZIRUnOp(self, &gen_scope.base, src, .@"return", init_inst); | 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 | var inner_block: Scope.Block = .{ | 1547 | var inner_block: Scope.Block = .{ |
| 1536 | .parent = null, | 1548 | .parent = null, |
| ... | @@ -1538,42 +1550,58 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1538,42 +1550,58 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1538 | .decl = decl, | 1550 | .decl = decl, |
| 1539 | .instructions = .{}, | 1551 | .instructions = .{}, |
| 1540 | .arena = &gen_scope_arena.allocator, | 1552 | .arena = &gen_scope_arena.allocator, |
| 1553 | .is_comptime = true, | ||
| 1541 | }; | 1554 | }; |
| 1542 | defer inner_block.instructions.deinit(self.gpa); | 1555 | defer inner_block.instructions.deinit(self.gpa); |
| 1543 | try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items }); | 1556 | try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items }); |
| 1544 | 1557 | ||
| 1545 | for (inner_block.instructions.items) |inst| { | 1558 | // The result location guarantees the type coercion. |
| 1546 | if (inst.castTag(.ret)) |ret| { | 1559 | const analyzed_init_inst = init_inst.analyzed_inst.?; |
| 1547 | const coerced = if (explicit_type) |some| | 1560 | // The is_comptime in the Scope.Block guarantees the result is comptime-known. |
| 1548 | try self.coerce(&inner_block.base, some, ret.operand) | 1561 | const val = analyzed_init_inst.value().?; |
| 1549 | else | 1562 | |
| 1550 | ret.operand; | 1563 | const ty = try analyzed_init_inst.ty.copy(block_scope.arena); |
| 1551 | const val = coerced.value() orelse | 1564 | break :vi .{ |
| 1552 | return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); | 1565 | .ty = ty, |
| 1553 | 1566 | .val = try val.copy(block_scope.arena), | |
| 1554 | var_type = explicit_type orelse try ret.operand.ty.copy(block_scope.arena); | 1567 | }; |
| 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; | ||
| 1561 | } else if (!is_extern) { | 1568 | } else if (!is_extern) { |
| 1562 | return self.failTok(&block_scope.base, var_decl.firstToken(), "variables must be initialized", .{}); | 1569 | return self.failTok(&block_scope.base, var_decl.firstToken(), "variables must be initialized", .{}); |
| 1563 | } else if (explicit_type) |some| blk: { | 1570 | } else if (var_decl.getTypeNode()) |type_node| vi: { |
| 1564 | var_type = some; | 1571 | // Temporary arena for the zir instructions. |
| 1565 | break :blk null; | 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 | } else { | 1594 | } else { |
| 1567 | return self.failTok(&block_scope.base, var_decl.firstToken(), "unable to infer variable type", .{}); | 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)) { | 1598 | if (is_mutable and !var_info.ty.isValidVarType(is_extern)) { |
| 1571 | return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_type}); | 1599 | return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_info.ty}); |
| 1572 | } | 1600 | } |
| 1573 | 1601 | ||
| 1574 | var type_changed = true; | 1602 | var type_changed = true; |
| 1575 | if (decl.typedValueManaged()) |tvm| { | 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 | tvm.deinit(self.gpa); | 1606 | tvm.deinit(self.gpa); |
| 1579 | } | 1607 | } |
| ... | @@ -1582,7 +1610,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1582,7 +1610,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1582 | const var_payload = try decl_arena.allocator.create(Value.Payload.Variable); | 1610 | const var_payload = try decl_arena.allocator.create(Value.Payload.Variable); |
| 1583 | new_variable.* = .{ | 1611 | new_variable.* = .{ |
| 1584 | .owner_decl = decl, | 1612 | .owner_decl = decl, |
| 1585 | .init = value orelse undefined, | 1613 | .init = var_info.val orelse undefined, |
| 1586 | .is_extern = is_extern, | 1614 | .is_extern = is_extern, |
| 1587 | .is_mutable = is_mutable, | 1615 | .is_mutable = is_mutable, |
| 1588 | .is_threadlocal = is_threadlocal, | 1616 | .is_threadlocal = is_threadlocal, |
| ... | @@ -1593,7 +1621,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1593,7 +1621,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1593 | decl.typed_value = .{ | 1621 | decl.typed_value = .{ |
| 1594 | .most_recent = .{ | 1622 | .most_recent = .{ |
| 1595 | .typed_value = .{ | 1623 | .typed_value = .{ |
| 1596 | .ty = var_type, | 1624 | .ty = var_info.ty, |
| 1597 | .val = Value.initPayload(&var_payload.base), | 1625 | .val = Value.initPayload(&var_payload.base), |
| 1598 | }, | 1626 | }, |
| 1599 | .arena = decl_arena_state, | 1627 | .arena = decl_arena_state, |
| ... | @@ -1628,8 +1656,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1628,8 +1656,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1628 | }; | 1656 | }; |
| 1629 | defer gen_scope.instructions.deinit(self.gpa); | 1657 | defer gen_scope.instructions.deinit(self.gpa); |
| 1630 | 1658 | ||
| 1631 | // TODO comptime scope here | 1659 | _ = try astgen.comptimeExpr(self, &gen_scope.base, .none, comptime_decl.expr); |
| 1632 | _ = try astgen.expr(self, &gen_scope.base, .none, comptime_decl.expr); | ||
| 1633 | 1660 | ||
| 1634 | var block_scope: Scope.Block = .{ | 1661 | var block_scope: Scope.Block = .{ |
| 1635 | .parent = null, | 1662 | .parent = null, |
| ... | @@ -1637,6 +1664,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1637,6 +1664,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1637 | .decl = decl, | 1664 | .decl = decl, |
| 1638 | .instructions = .{}, | 1665 | .instructions = .{}, |
| 1639 | .arena = &analysis_arena.allocator, | 1666 | .arena = &analysis_arena.allocator, |
| 1667 | .is_comptime = true, | ||
| 1640 | }; | 1668 | }; |
| 1641 | defer block_scope.instructions.deinit(self.gpa); | 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,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 | const tracy = trace(@src()); | 1731 | const tracy = trace(@src()); |
| 1704 | defer tracy.end(); | 1732 | defer tracy.end(); |
| 1705 | 1733 | ||
| 1734 | const root_scope = container_scope.file_scope; | ||
| 1735 | |||
| 1706 | switch (root_scope.status) { | 1736 | switch (root_scope.status) { |
| 1707 | .never_loaded, .unloaded_success => { | 1737 | .never_loaded, .unloaded_success => { |
| 1708 | try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); | 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,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 | const tracy = trace(@src()); | 1778 | const tracy = trace(@src()); |
| 1749 | defer tracy.end(); | 1779 | defer tracy.end(); |
| 1750 | 1780 | ||
| 1751 | // We may be analyzing it for the first time, or this may be | 1781 | // We may be analyzing it for the first time, or this may be |
| 1752 | // an incremental update. This code handles both cases. | 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 | const decls = tree.root_node.decls(); | 1784 | const decls = tree.root_node.decls(); |
| 1755 | 1785 | ||
| 1756 | try self.work_queue.ensureUnusedCapacity(decls.len); | 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 | // Keep track of the decls that we expect to see in this file so that | 1789 | // Keep track of the decls that we expect to see in this file so that |
| 1760 | // we know which ones have been deleted. | 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 | defer deleted_decls.deinit(); | 1792 | defer deleted_decls.deinit(); |
| 1763 | try deleted_decls.ensureCapacity(root_scope.decls.items.len); | 1793 | try deleted_decls.ensureCapacity(container_scope.decls.items().len); |
| 1764 | for (root_scope.decls.items) |file_decl| { | 1794 | for (container_scope.decls.items()) |entry| { |
| 1765 | deleted_decls.putAssumeCapacityNoClobber(file_decl, {}); | 1795 | deleted_decls.putAssumeCapacityNoClobber(entry.key, {}); |
| 1766 | } | 1796 | } |
| 1767 | 1797 | ||
| 1768 | for (decls) |src_decl, decl_i| { | 1798 | for (decls) |src_decl, decl_i| { |
| ... | @@ -1774,7 +1804,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { | ... | @@ -1774,7 +1804,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1774 | 1804 | ||
| 1775 | const name_loc = tree.token_locs[name_tok]; | 1805 | const name_loc = tree.token_locs[name_tok]; |
| 1776 | const name = tree.tokenSliceLoc(name_loc); | 1806 | const name = tree.tokenSliceLoc(name_loc); |
| 1777 | const name_hash = root_scope.fullyQualifiedNameHash(name); | 1807 | const name_hash = container_scope.fullyQualifiedNameHash(name); |
| 1778 | const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); | 1808 | const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); |
| 1779 | if (self.decl_table.get(name_hash)) |decl| { | 1809 | if (self.decl_table.get(name_hash)) |decl| { |
| 1780 | // Update the AST Node index of the decl, even if its contents are unchanged, it may | 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,8 +1832,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1802 | } | 1832 | } |
| 1803 | } | 1833 | } |
| 1804 | } else { | 1834 | } else { |
| 1805 | const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); | 1835 | const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); |
| 1806 | root_scope.decls.appendAssumeCapacity(new_decl); | 1836 | container_scope.decls.putAssumeCapacity(new_decl, {}); |
| 1807 | if (fn_proto.getExternExportInlineToken()) |maybe_export_token| { | 1837 | if (fn_proto.getExternExportInlineToken()) |maybe_export_token| { |
| 1808 | if (tree.token_ids[maybe_export_token] == .Keyword_export) { | 1838 | if (tree.token_ids[maybe_export_token] == .Keyword_export) { |
| 1809 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); | 1839 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); |
| ... | @@ -1813,7 +1843,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { | ... | @@ -1813,7 +1843,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1813 | } else if (src_decl.castTag(.VarDecl)) |var_decl| { | 1843 | } else if (src_decl.castTag(.VarDecl)) |var_decl| { |
| 1814 | const name_loc = tree.token_locs[var_decl.name_token]; | 1844 | const name_loc = tree.token_locs[var_decl.name_token]; |
| 1815 | const name = tree.tokenSliceLoc(name_loc); | 1845 | const name = tree.tokenSliceLoc(name_loc); |
| 1816 | const name_hash = root_scope.fullyQualifiedNameHash(name); | 1846 | const name_hash = container_scope.fullyQualifiedNameHash(name); |
| 1817 | const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); | 1847 | const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); |
| 1818 | if (self.decl_table.get(name_hash)) |decl| { | 1848 | if (self.decl_table.get(name_hash)) |decl| { |
| 1819 | // Update the AST Node index of the decl, even if its contents are unchanged, it may | 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,8 +1859,8 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1829 | decl.contents_hash = contents_hash; | 1859 | decl.contents_hash = contents_hash; |
| 1830 | } | 1860 | } |
| 1831 | } else { | 1861 | } else { |
| 1832 | const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); | 1862 | const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); |
| 1833 | root_scope.decls.appendAssumeCapacity(new_decl); | 1863 | container_scope.decls.putAssumeCapacity(new_decl, {}); |
| 1834 | if (var_decl.getExternExportToken()) |maybe_export_token| { | 1864 | if (var_decl.getExternExportToken()) |maybe_export_token| { |
| 1835 | if (tree.token_ids[maybe_export_token] == .Keyword_export) { | 1865 | if (tree.token_ids[maybe_export_token] == .Keyword_export) { |
| 1836 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); | 1866 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); |
| ... | @@ -1842,11 +1872,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { | ... | @@ -1842,11 +1872,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { |
| 1842 | const name = try std.fmt.allocPrint(self.gpa, "__comptime_{}", .{name_index}); | 1872 | const name = try std.fmt.allocPrint(self.gpa, "__comptime_{}", .{name_index}); |
| 1843 | defer self.gpa.free(name); | 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 | const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); | 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); | 1878 | const new_decl = try self.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash); |
| 1849 | root_scope.decls.appendAssumeCapacity(new_decl); | 1879 | container_scope.decls.putAssumeCapacity(new_decl, {}); |
| 1850 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); | 1880 | self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); |
| 1851 | } else if (src_decl.castTag(.ContainerField)) |container_field| { | 1881 | } else if (src_decl.castTag(.ContainerField)) |container_field| { |
| 1852 | log.err("TODO: analyze container field", .{}); | 1882 | log.err("TODO: analyze container field", .{}); |
| ... | @@ -1879,7 +1909,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { | ... | @@ -1879,7 +1909,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { |
| 1879 | 1909 | ||
| 1880 | // Keep track of the decls that we expect to see in this file so that | 1910 | // Keep track of the decls that we expect to see in this file so that |
| 1881 | // we know which ones have been deleted. | 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 | defer deleted_decls.deinit(); | 1913 | defer deleted_decls.deinit(); |
| 1884 | try deleted_decls.ensureCapacity(self.decl_table.items().len); | 1914 | try deleted_decls.ensureCapacity(self.decl_table.items().len); |
| 1885 | for (self.decl_table.items()) |entry| { | 1915 | for (self.decl_table.items()) |entry| { |
| ... | @@ -2007,6 +2037,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { | ... | @@ -2007,6 +2037,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { |
| 2007 | .decl = decl, | 2037 | .decl = decl, |
| 2008 | .instructions = .{}, | 2038 | .instructions = .{}, |
| 2009 | .arena = &arena.allocator, | 2039 | .arena = &arena.allocator, |
| 2040 | .is_comptime = false, | ||
| 2010 | }; | 2041 | }; |
| 2011 | defer inner_block.instructions.deinit(self.gpa); | 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,16 +2119,23 @@ pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanage |
| 2088 | errdefer self.global_error_set.removeAssertDiscard(name); | 2119 | errdefer self.global_error_set.removeAssertDiscard(name); |
| 2089 | 2120 | ||
| 2090 | gop.entry.key = try self.gpa.dupe(u8, name); | 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 | return gop.entry.*; | 2123 | return gop.entry.*; |
| 2093 | } | 2124 | } |
| 2094 | 2125 | ||
| 2095 | /// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites. | 2126 | pub fn requireFunctionBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { |
| 2096 | pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { | ||
| 2097 | return scope.cast(Scope.Block) orelse | 2127 | return scope.cast(Scope.Block) orelse |
| 2098 | return self.fail(scope, src, "instruction illegal outside function body", .{}); | 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 | pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value { | 2139 | pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value { |
| 2102 | return (try self.resolveDefinedValue(scope, base)) orelse | 2140 | return (try self.resolveDefinedValue(scope, base)) orelse |
| 2103 | return self.fail(scope, base.src, "unable to resolve comptime value", .{}); | 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,6 +2622,72 @@ pub fn analyzeIsErr(self: *Module, scope: *Scope, src: usize, operand: *Inst) In |
| 2584 | return self.fail(scope, src, "TODO implement analysis of iserr", .{}); | 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 | /// Asserts that lhs and rhs types are both numeric. | 2691 | /// Asserts that lhs and rhs types are both numeric. |
| 2588 | pub fn cmpNumeric( | 2692 | pub fn cmpNumeric( |
| 2589 | self: *Module, | 2693 | self: *Module, |
| ... | @@ -2794,6 +2898,12 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty | ... | @@ -2794,6 +2898,12 @@ pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Ty |
| 2794 | prev_inst = next_inst; | 2898 | prev_inst = next_inst; |
| 2795 | continue; | 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 | if (prev_inst.ty.isInt() and | 2907 | if (prev_inst.ty.isInt() and |
| 2798 | next_inst.ty.isInt() and | 2908 | next_inst.ty.isInt() and |
| 2799 | prev_inst.ty.isSignedInt() == next_inst.ty.isSignedInt()) | 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,6 +3155,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err |
| 3045 | self.failed_files.putAssumeCapacityNoClobber(scope, err_msg); | 3155 | self.failed_files.putAssumeCapacityNoClobber(scope, err_msg); |
| 3046 | }, | 3156 | }, |
| 3047 | .file => unreachable, | 3157 | .file => unreachable, |
| 3158 | .container => unreachable, | ||
| 3048 | } | 3159 | } |
| 3049 | return error.AnalysisFail; | 3160 | return error.AnalysisFail; |
| 3050 | } | 3161 | } |
| ... | @@ -3432,6 +3543,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic | ... | @@ -3432,6 +3543,7 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic |
| 3432 | .decl = parent_block.decl, | 3543 | .decl = parent_block.decl, |
| 3433 | .instructions = .{}, | 3544 | .instructions = .{}, |
| 3434 | .arena = parent_block.arena, | 3545 | .arena = parent_block.arena, |
| 3546 | .is_comptime = parent_block.is_comptime, | ||
| 3435 | }; | 3547 | }; |
| 3436 | defer fail_block.instructions.deinit(mod.gpa); | 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,7 +258,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 258 | .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), | 258 | .OptionalType => return rlWrap(mod, scope, rl, try optionalType(mod, scope, node.castTag(.OptionalType).?)), |
| 259 | .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?), | 259 | .UnwrapOptional => return unwrapOptional(mod, scope, rl, node.castTag(.UnwrapOptional).?), |
| 260 | .Block => return rlWrapVoid(mod, scope, rl, node, try blockExpr(mod, scope, node.castTag(.Block).?)), | 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 | .Break => return rlWrap(mod, scope, rl, try breakExpr(mod, scope, node.castTag(.Break).?)), | 262 | .Break => return rlWrap(mod, scope, rl, try breakExpr(mod, scope, node.castTag(.Break).?)), |
| 263 | .PtrType => return rlWrap(mod, scope, rl, try ptrType(mod, scope, node.castTag(.PtrType).?)), | 263 | .PtrType => return rlWrap(mod, scope, rl, try ptrType(mod, scope, node.castTag(.PtrType).?)), |
| 264 | .GroupedExpression => return expr(mod, scope, rl, node.castTag(.GroupedExpression).?.expr), | 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,15 +275,16 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 275 | .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)), | 275 | .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)), |
| 276 | .For => return forExpr(mod, scope, rl, node.castTag(.For).?), | 276 | .For => return forExpr(mod, scope, rl, node.castTag(.For).?), |
| 277 | .ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?), | 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 | .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), | 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 | .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), | 283 | .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), |
| 281 | .Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}), | 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 | .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}), | 285 | .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}), |
| 284 | .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}), | 286 | .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}), |
| 285 | .Try => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}), | 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 | .ArrayInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializer", .{}), | 288 | .ArrayInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializer", .{}), |
| 288 | .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}), | 289 | .ArrayInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .ArrayInitializerDot", .{}), |
| 289 | .StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}), | 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,11 +295,46 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr |
| 294 | .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}), | 295 | .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}), |
| 295 | .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}), | 296 | .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}), |
| 296 | .ContainerDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ContainerDecl", .{}), | 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 | .Nosuspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Nosuspend", .{}), | 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 | fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { | 338 | fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { |
| 303 | const tree = parent_scope.tree(); | 339 | const tree = parent_scope.tree(); |
| 304 | const src = tree.token_locs[node.ltoken].start; | 340 | const src = tree.token_locs[node.ltoken].start; |
| ... | @@ -360,10 +396,13 @@ fn labeledBlockExpr( | ... | @@ -360,10 +396,13 @@ fn labeledBlockExpr( |
| 360 | parent_scope: *Scope, | 396 | parent_scope: *Scope, |
| 361 | rl: ResultLoc, | 397 | rl: ResultLoc, |
| 362 | block_node: *ast.Node.LabeledBlock, | 398 | block_node: *ast.Node.LabeledBlock, |
| 399 | zir_tag: zir.Inst.Tag, | ||
| 363 | ) InnerError!*zir.Inst { | 400 | ) InnerError!*zir.Inst { |
| 364 | const tracy = trace(@src()); | 401 | const tracy = trace(@src()); |
| 365 | defer tracy.end(); | 402 | defer tracy.end(); |
| 366 | 403 | ||
| 404 | assert(zir_tag == .block or zir_tag == .block_comptime); | ||
| 405 | |||
| 367 | const tree = parent_scope.tree(); | 406 | const tree = parent_scope.tree(); |
| 368 | const src = tree.token_locs[block_node.lbrace].start; | 407 | const src = tree.token_locs[block_node.lbrace].start; |
| 369 | 408 | ||
| ... | @@ -373,7 +412,7 @@ fn labeledBlockExpr( | ... | @@ -373,7 +412,7 @@ fn labeledBlockExpr( |
| 373 | const block_inst = try gen_zir.arena.create(zir.Inst.Block); | 412 | const block_inst = try gen_zir.arena.create(zir.Inst.Block); |
| 374 | block_inst.* = .{ | 413 | block_inst.* = .{ |
| 375 | .base = .{ | 414 | .base = .{ |
| 376 | .tag = .block, | 415 | .tag = zir_tag, |
| 377 | .src = src, | 416 | .src = src, |
| 378 | }, | 417 | }, |
| 379 | .positionals = .{ | 418 | .positionals = .{ |
| ... | @@ -751,13 +790,31 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!* | ... | @@ -751,13 +790,31 @@ fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!* |
| 751 | } | 790 | } |
| 752 | 791 | ||
| 753 | fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) InnerError!*zir.Inst { | 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 | const tree = scope.tree(); | 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); | 814 | const operand_ptr = try expr(mod, scope, .ref, lhs); |
| 758 | // TODO we could avoid an unnecessary copy if .iserr took a pointer | 815 | // TODO we could avoid an unnecessary copy if .iserr, .isnull took a pointer |
| 759 | const err_union = try addZIRUnOp(mod, scope, src, .deref, err_union_ptr); | 816 | const err_union = try addZIRUnOp(mod, scope, src, .deref, operand_ptr); |
| 760 | const cond = try addZIRUnOp(mod, scope, src, .iserr, err_union); | 817 | const cond = try addZIRUnOp(mod, scope, src, cond_op, err_union); |
| 761 | 818 | ||
| 762 | var block_scope: Scope.GenZIR = .{ | 819 | var block_scope: Scope.GenZIR = .{ |
| 763 | .parent = scope, | 820 | .parent = scope, |
| ... | @@ -773,7 +830,7 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) | ... | @@ -773,7 +830,7 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) |
| 773 | .else_body = undefined, // populated below | 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 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), | 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,55 +843,55 @@ fn catchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Catch) |
| 786 | .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, | 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 | .parent = scope, | 847 | .parent = scope, |
| 791 | .decl = block_scope.decl, | 848 | .decl = block_scope.decl, |
| 792 | .arena = block_scope.arena, | 849 | .arena = block_scope.arena, |
| 793 | .instructions = .{}, | 850 | .instructions = .{}, |
| 794 | }; | 851 | }; |
| 795 | defer err_scope.instructions.deinit(mod.gpa); | 852 | defer then_scope.instructions.deinit(mod.gpa); |
| 796 | 853 | ||
| 797 | var err_val_scope: Scope.LocalVal = undefined; | 854 | var err_val_scope: Scope.LocalVal = undefined; |
| 798 | const err_sub_scope = blk: { | 855 | const then_sub_scope = blk: { |
| 799 | const payload = node.payload orelse | 856 | const payload = payload_node orelse |
| 800 | break :blk &err_scope.base; | 857 | break :blk &then_scope.base; |
| 801 | 858 | ||
| 802 | const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken()); | 859 | const err_name = tree.tokenSlice(payload.castTag(.Payload).?.error_symbol.firstToken()); |
| 803 | if (mem.eql(u8, err_name, "_")) | 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 | err_val_scope = .{ | 864 | err_val_scope = .{ |
| 808 | .parent = &err_scope.base, | 865 | .parent = &then_scope.base, |
| 809 | .gen_zir = &err_scope, | 866 | .gen_zir = &then_scope, |
| 810 | .name = err_name, | 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 | break :blk &err_val_scope.base; | 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 | .block = block, | 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 | .parent = scope, | 879 | .parent = scope, |
| 823 | .decl = block_scope.decl, | 880 | .decl = block_scope.decl, |
| 824 | .arena = block_scope.arena, | 881 | .arena = block_scope.arena, |
| 825 | .instructions = .{}, | 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); | 886 | const unwrapped_payload = try addZIRUnOp(mod, &else_scope.base, src, unwrap_op, operand_ptr); |
| 830 | _ = try addZIRInst(mod, &not_err_scope.base, src, zir.Inst.Break, .{ | 887 | _ = try addZIRInst(mod, &else_scope.base, src, zir.Inst.Break, .{ |
| 831 | .block = block, | 888 | .block = block, |
| 832 | .operand = unwrapped_payload, | 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) }; | 892 | condbr.positionals.then_body = .{ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items) }; |
| 836 | condbr.positionals.else_body = .{ .instructions = try not_err_scope.arena.dupe(*zir.Inst, not_err_scope.instructions.items) }; | 893 | condbr.positionals.else_body = .{ .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items) }; |
| 837 | return rlWrap(mod, scope, rl, &block.base); | 894 | return rlWrapPtr(mod, scope, rl, &block.base); |
| 838 | } | 895 | } |
| 839 | 896 | ||
| 840 | /// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating. | 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,6 +951,36 @@ fn arrayAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Array |
| 894 | return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{})); | 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 | fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { | 984 | fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { |
| 898 | const tree = scope.tree(); | 985 | const tree = scope.tree(); |
| 899 | const src = tree.token_locs[node.rtoken].start; | 986 | const src = tree.token_locs[node.rtoken].start; |
| ... | @@ -946,7 +1033,7 @@ fn boolBinOp( | ... | @@ -946,7 +1033,7 @@ fn boolBinOp( |
| 946 | .else_body = undefined, // populated below | 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 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), | 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,7 +1182,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1095 | .else_body = undefined, // populated below | 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 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), | 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,7 +1305,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W |
| 1218 | .then_body = undefined, // populated below | 1305 | .then_body = undefined, // populated below |
| 1219 | .else_body = undefined, // populated below | 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 | .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items), | 1309 | .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items), |
| 1223 | }); | 1310 | }); |
| 1224 | // TODO avoid emitting the continue expr when there | 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,7 +1318,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W |
| 1231 | const loop = try addZIRInstLoop(mod, &expr_scope.base, while_src, .{ | 1318 | const loop = try addZIRInstLoop(mod, &expr_scope.base, while_src, .{ |
| 1232 | .instructions = try expr_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items), | 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 | .instructions = try expr_scope.arena.dupe(*zir.Inst, expr_scope.instructions.items), | 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,7 +1452,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) |
| 1365 | .then_body = undefined, // populated below | 1452 | .then_body = undefined, // populated below |
| 1366 | .else_body = undefined, // populated below | 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 | .instructions = try loop_scope.arena.dupe(*zir.Inst, cond_scope.instructions.items), | 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,7 +1469,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) |
| 1382 | const loop = try addZIRInstLoop(mod, &for_scope.base, for_src, .{ | 1469 | const loop = try addZIRInstLoop(mod, &for_scope.base, for_src, .{ |
| 1383 | .instructions = try for_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items), | 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 | .instructions = try for_scope.arena.dupe(*zir.Inst, for_scope.instructions.items), | 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,6 +2347,30 @@ pub fn addZIRBinOp( |
| 2260 | return &inst.base; | 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 | pub fn addZIRInst( | 2374 | pub fn addZIRInst( |
| 2264 | mod: *Module, | 2375 | mod: *Module, |
| 2265 | scope: *Scope, | 2376 | scope: *Scope, |
| ... | @@ -2278,12 +2389,6 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ | ... | @@ -2278,12 +2389,6 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ |
| 2278 | return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); | 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 | /// TODO The existence of this function is a workaround for a bug in stage1. | 2392 | /// TODO The existence of this function is a workaround for a bug in stage1. |
| 2288 | pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop { | 2393 | pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop { |
| 2289 | const P = std.meta.fieldInfo(zir.Inst.Loop, "positionals").field_type; | 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,7 +132,7 @@ pub fn generateSymbol( |
| 132 | .Array => { | 132 | .Array => { |
| 133 | // TODO populate .debug_info for the array | 133 | // TODO populate .debug_info for the array |
| 134 | if (typed_value.val.cast(Value.Payload.Bytes)) |payload| { | 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 | try code.ensureCapacity(code.items.len + payload.data.len + 1); | 136 | try code.ensureCapacity(code.items.len + payload.data.len + 1); |
| 137 | code.appendSliceAssumeCapacity(payload.data); | 137 | code.appendSliceAssumeCapacity(payload.data); |
| 138 | const prev_len = code.items.len; | 138 | const prev_len = code.items.len; |
| ... | @@ -359,7 +359,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -359,7 +359,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 359 | }; | 359 | }; |
| 360 | 360 | ||
| 361 | const Branch = struct { | 361 | const Branch = struct { |
| 362 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, | 362 | inst_table: std.AutoArrayHashMapUnmanaged(*ir.Inst, MCValue) = .{}, |
| 363 | 363 | ||
| 364 | fn deinit(self: *Branch, gpa: *Allocator) void { | 364 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 365 | self.inst_table.deinit(gpa); | 365 | self.inst_table.deinit(gpa); |
| ... | @@ -436,8 +436,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -436,8 +436,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 436 | try branch_stack.append(.{}); | 436 | try branch_stack.append(.{}); |
| 437 | 437 | ||
| 438 | const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: { | 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| { | 439 | if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| { |
| 440 | const tree = scope_file.contents.tree; | 440 | const tree = container_scope.file_scope.contents.tree; |
| 441 | const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; | 441 | const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; |
| 442 | const block = fn_proto.getBodyNode().?.castTag(.Block).?; | 442 | const block = fn_proto.getBodyNode().?.castTag(.Block).?; |
| 443 | const lbrace_src = tree.token_locs[block.lbrace].start; | 443 | const lbrace_src = tree.token_locs[block.lbrace].start; |
| ... | @@ -750,7 +750,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -750,7 +750,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 750 | const ptr_bits = arch.ptrBitWidth(); | 750 | const ptr_bits = arch.ptrBitWidth(); |
| 751 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); | 751 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 752 | if (abi_size <= ptr_bytes) { | 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 | if (self.allocReg(inst)) |reg| { | 754 | if (self.allocReg(inst)) |reg| { |
| 755 | return MCValue{ .register = registerAlias(reg, abi_size) }; | 755 | return MCValue{ .register = registerAlias(reg, abi_size) }; |
| 756 | } | 756 | } |
| ... | @@ -788,7 +788,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -788,7 +788,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 788 | /// `reg_owner` is the instruction that gets associated with the register in the register table. | 788 | /// `reg_owner` is the instruction that gets associated with the register in the register table. |
| 789 | /// This can have a side effect of spilling instructions to the stack to free up a register. | 789 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| 790 | fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue { | 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 | const reg = self.allocReg(reg_owner) orelse b: { | 793 | const reg = self.allocReg(reg_owner) orelse b: { |
| 794 | // We'll take over the first register. Move the instruction that was previously | 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,7 +1247,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1247 | if (inst.base.isUnused()) | 1247 | if (inst.base.isUnused()) |
| 1248 | return MCValue.dead; | 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 | const result = self.args[self.arg_index]; | 1252 | const result = self.args[self.arg_index]; |
| 1253 | self.arg_index += 1; | 1253 | self.arg_index += 1; |
| ... | @@ -1443,7 +1443,57 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -1443,7 +1443,57 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1443 | } | 1443 | } |
| 1444 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { | 1444 | } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { |
| 1445 | switch (arch) { | 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 | .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}), | 1497 | .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}), |
| 1448 | else => unreachable, | 1498 | else => unreachable, |
| 1449 | } | 1499 | } |
| ... | @@ -2486,6 +2536,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { | ... | @@ -2486,6 +2536,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2486 | const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; | 2536 | const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; |
| 2487 | const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; | 2537 | const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; |
| 2488 | return MCValue{ .memory = got_addr }; | 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 | } else { | 2544 | } else { |
| 2490 | return self.fail(src, "TODO codegen non-ELF const Decl pointer", .{}); | 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,7 +85,7 @@ fn genArray(file: *C, decl: *Decl) !void { |
| 85 | const name = try map(file.base.allocator, mem.span(decl.name)); | 85 | const name = try map(file.base.allocator, mem.span(decl.name)); |
| 86 | defer file.base.allocator.free(name); | 86 | defer file.base.allocator.free(name); |
| 87 | if (tv.val.cast(Value.Payload.Bytes)) |payload| | 87 | if (tv.val.cast(Value.Payload.Bytes)) |payload| |
| 88 | if (tv.ty.arraySentinel()) |sentinel| | 88 | if (tv.ty.sentinel()) |sentinel| |
| 89 | if (sentinel.toUnsignedInt() == 0) | 89 | if (sentinel.toUnsignedInt() == 0) |
| 90 | try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) | 90 | try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) |
| 91 | else | 91 | else |
| ... | @@ -110,7 +110,8 @@ const Context = struct { | ... | @@ -110,7 +110,8 @@ const Context = struct { |
| 110 | } | 110 | } |
| 111 | 111 | ||
| 112 | fn deinit(self: *Context) void { | 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 | self.file.base.allocator.free(kv.value); | 115 | self.file.base.allocator.free(kv.value); |
| 115 | } | 116 | } |
| 116 | self.inst_map.deinit(); | 117 | self.inst_map.deinit(); |
src-self-hosted/ir.zig+9-1| ... | @@ -189,7 +189,7 @@ pub const Inst = struct { | ... | @@ -189,7 +189,7 @@ pub const Inst = struct { |
| 189 | } | 189 | } |
| 190 | 190 | ||
| 191 | pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator { | 191 | pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator { |
| 192 | return switch (self.base.tag) { | 192 | return switch (base.tag) { |
| 193 | .cmp_lt => .lt, | 193 | .cmp_lt => .lt, |
| 194 | .cmp_lte => .lte, | 194 | .cmp_lte => .lte, |
| 195 | .cmp_eq => .eq, | 195 | .cmp_eq => .eq, |
| ... | @@ -220,6 +220,14 @@ pub const Inst = struct { | ... | @@ -220,6 +220,14 @@ pub const Inst = struct { |
| 220 | unreachable; | 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 | pub const NoOp = struct { | 231 | pub const NoOp = struct { |
| 224 | base: Inst, | 232 | base: Inst, |
| 225 | 233 |
src-self-hosted/link.zig+1-1| ... | @@ -47,7 +47,7 @@ pub const File = struct { | ... | @@ -47,7 +47,7 @@ pub const File = struct { |
| 47 | }; | 47 | }; |
| 48 | 48 | ||
| 49 | /// For DWARF .debug_info. | 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 | /// For DWARF .debug_info. | 52 | /// For DWARF .debug_info. |
| 53 | pub const DbgInfoTypeReloc = struct { | 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,7 +1629,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1629 | 1629 | ||
| 1630 | var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; | 1630 | var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; |
| 1631 | defer { | 1631 | defer { |
| 1632 | for (dbg_info_type_relocs.items()) |*entry| { | 1632 | var it = dbg_info_type_relocs.iterator(); |
| 1633 | while (it.next()) |entry| { | ||
| 1633 | entry.value.relocs.deinit(self.base.allocator); | 1634 | entry.value.relocs.deinit(self.base.allocator); |
| 1634 | } | 1635 | } |
| 1635 | dbg_info_type_relocs.deinit(self.base.allocator); | 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,8 +1656,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1655 | try dbg_line_buffer.ensureCapacity(26); | 1656 | try dbg_line_buffer.ensureCapacity(26); |
| 1656 | 1657 | ||
| 1657 | const line_off: u28 = blk: { | 1658 | const line_off: u28 = blk: { |
| 1658 | if (decl.scope.cast(Module.Scope.File)) |scope_file| { | 1659 | if (decl.scope.cast(Module.Scope.Container)) |container_scope| { |
| 1659 | const tree = scope_file.contents.tree; | 1660 | const tree = container_scope.file_scope.contents.tree; |
| 1660 | const file_ast_decls = tree.root_node.decls(); | 1661 | const file_ast_decls = tree.root_node.decls(); |
| 1661 | // TODO Look into improving the performance here by adding a token-index-to-line | 1662 | // TODO Look into improving the performance here by adding a token-index-to-line |
| 1662 | // lookup table. Currently this involves scanning over the source code for newlines. | 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,7 +1918,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1917 | // Now we emit the .debug_info types of the Decl. These will count towards the size of | 1918 | // Now we emit the .debug_info types of the Decl. These will count towards the size of |
| 1918 | // the buffer, so we have to do it before computing the offset, and we can't perform the actual | 1919 | // the buffer, so we have to do it before computing the offset, and we can't perform the actual |
| 1919 | // relocations yet. | 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 | entry.value.off = @intCast(u32, dbg_info_buffer.items.len); | 1923 | entry.value.off = @intCast(u32, dbg_info_buffer.items.len); |
| 1922 | try self.addDbgInfoType(entry.key, &dbg_info_buffer); | 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,7 +1927,8 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { |
| 1925 | try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len)); | 1927 | try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len)); |
| 1926 | 1928 | ||
| 1927 | // Now that we have the offset assigned we can finally perform type relocations. | 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 | for (entry.value.relocs.items) |off| { | 1932 | for (entry.value.relocs.items) |off| { |
| 1930 | mem.writeInt( | 1933 | mem.writeInt( |
| 1931 | u32, | 1934 | u32, |
| ... | @@ -2154,8 +2157,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec | ... | @@ -2154,8 +2157,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec |
| 2154 | const tracy = trace(@src()); | 2157 | const tracy = trace(@src()); |
| 2155 | defer tracy.end(); | 2158 | defer tracy.end(); |
| 2156 | 2159 | ||
| 2157 | const scope_file = decl.scope.cast(Module.Scope.File).?; | 2160 | const container_scope = decl.scope.cast(Module.Scope.Container).?; |
| 2158 | const tree = scope_file.contents.tree; | 2161 | const tree = container_scope.file_scope.contents.tree; |
| 2159 | const file_ast_decls = tree.root_node.decls(); | 2162 | const file_ast_decls = tree.root_node.decls(); |
| 2160 | // TODO Look into improving the performance here by adding a token-index-to-line | 2163 | // TODO Look into improving the performance here by adding a token-index-to-line |
| 2161 | // lookup table. Currently this involves scanning over the source code for newlines. | 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,36 +18,66 @@ const File = link.File; |
| 18 | 18 | ||
| 19 | pub const base_tag: File.Tag = File.Tag.macho; | 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 | base: File, | 37 | base: File, |
| 22 | 38 | ||
| 23 | /// List of all load command headers that are in the file. | 39 | /// Table of all load commands |
| 24 | /// We use it to track number and size of all commands needed by the header. | 40 | load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, |
| 25 | commands: std.ArrayListUnmanaged(macho.load_command) = std.ArrayListUnmanaged(macho.load_command){}, | 41 | segment_cmd_index: ?u16 = null, |
| 26 | command_file_offset: ?u64 = 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. | 46 | /// Table of all sections |
| 29 | /// Same order as in the file. | 47 | sections: std.ArrayListUnmanaged(macho.section_64) = .{}, |
| 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){}, | ||
| 33 | 48 | ||
| 34 | /// Offset (index) into __TEXT segment load command. | 49 | /// __TEXT segment sections |
| 35 | text_segment_offset: ?u64 = null, | 50 | text_section_index: ?u16 = null, |
| 36 | /// Offset (index) into __LINKEDIT segment load command. | 51 | cstring_section_index: ?u16 = null, |
| 37 | linkedit_segment_offset: ?u664 = 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 | entry_addr: ?u64 = null, | 60 | entry_addr: ?u64 = null, |
| 42 | 61 | ||
| 43 | /// The first 4GB of process' memory is reserved for the null (__PAGEZERO) segment. | 62 | /// Table of all symbols used. |
| 44 | /// This is also the start address for our binary. | 63 | /// Internally references string table for names (which are optional). |
| 45 | vm_start_address: u64 = 0x100000000, | 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 | error_flags: File.ErrorFlags = File.ErrorFlags{}, | 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 | /// `alloc_num / alloc_den` is the factor of padding when allocating. | 81 | /// `alloc_num / alloc_den` is the factor of padding when allocating. |
| 52 | const alloc_num = 4; | 82 | const alloc_num = 4; |
| 53 | const alloc_den = 3; | 83 | const alloc_den = 3; |
| ... | @@ -67,7 +97,23 @@ const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; | ... | @@ -67,7 +97,23 @@ const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; |
| 67 | const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; | 97 | const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; |
| 68 | 98 | ||
| 69 | pub const TextBlock = struct { | 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 | pub const SrcFn = struct { | 119 | pub const SrcFn = struct { |
| ... | @@ -117,6 +163,12 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO | ... | @@ -117,6 +163,12 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO |
| 117 | /// Truncates the existing file contents and overwrites the contents. | 163 | /// Truncates the existing file contents and overwrites the contents. |
| 118 | /// Returns an error if `file` is not already open with +read +write +seek abilities. | 164 | /// Returns an error if `file` is not already open with +read +write +seek abilities. |
| 119 | fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO { | 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 | var self: MachO = .{ | 172 | var self: MachO = .{ |
| 121 | .base = .{ | 173 | .base = .{ |
| 122 | .file = file, | 174 | .file = file, |
| ... | @@ -127,104 +179,15 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach | ... | @@ -127,104 +179,15 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach |
| 127 | }; | 179 | }; |
| 128 | errdefer self.deinit(); | 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 | try self.populateMissingMetadata(); | 182 | try self.populateMissingMetadata(); |
| 157 | 183 | ||
| 158 | return self; | 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 | pub fn flush(self: *MachO, module: *Module) !void { | 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 | switch (self.base.options.output_mode) { | 188 | switch (self.base.options.output_mode) { |
| 227 | .Exe => { | 189 | .Exe => { |
| 190 | var last_cmd_offset: usize = @sizeOf(macho.mach_header_64); | ||
| 228 | { | 191 | { |
| 229 | // Specify path to dynamic linker dyld | 192 | // Specify path to dynamic linker dyld |
| 230 | const cmdsize = commandSize(@sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH)); | 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,18 +198,14 @@ pub fn flush(self: *MachO, module: *Module) !void { |
| 235 | .name = @sizeOf(macho.dylinker_command), | 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 | try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); | 205 | try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); |
| 247 | 206 | ||
| 248 | try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); | 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,21 +227,44 @@ pub fn flush(self: *MachO, module: *Module) !void { |
| 268 | .dylib = dylib, | 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 | try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); | 234 | try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); |
| 280 | 235 | ||
| 281 | try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); | 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 | .Lib => return error.TODOImplementWritingLibFiles, | 268 | .Lib => return error.TODOImplementWritingLibFiles, |
| 287 | } | 269 | } |
| 288 | 270 | ||
| ... | @@ -297,14 +279,110 @@ pub fn flush(self: *MachO, module: *Module) !void { | ... | @@ -297,14 +279,110 @@ pub fn flush(self: *MachO, module: *Module) !void { |
| 297 | } | 279 | } |
| 298 | 280 | ||
| 299 | pub fn deinit(self: *MachO) void { | 281 | pub fn deinit(self: *MachO) void { |
| 300 | self.commands.deinit(self.base.allocator); | 282 | self.offset_table.deinit(self.base.allocator); |
| 301 | self.segments.deinit(self.base.allocator); | 283 | self.string_table.deinit(self.base.allocator); |
| 284 | self.symbol_table.deinit(self.base.allocator); | ||
| 302 | self.sections.deinit(self.base.allocator); | 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 | pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {} | 387 | pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {} |
| 310 | 388 | ||
| ... | @@ -313,51 +391,191 @@ pub fn updateDeclExports( | ... | @@ -313,51 +391,191 @@ pub fn updateDeclExports( |
| 313 | module: *Module, | 391 | module: *Module, |
| 314 | decl: *const Module.Decl, | 392 | decl: *const Module.Decl, |
| 315 | exports: []const *Module.Export, | 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 | pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} | 410 | pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} |
| 319 | 411 | ||
| 320 | pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { | 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 | pub fn populateMissingMetadata(self: *MachO) !void { | 416 | pub fn populateMissingMetadata(self: *MachO) !void { |
| 325 | if (self.text_segment_offset == null) { | 417 | if (self.segment_cmd_index == null) { |
| 326 | self.text_segment_offset = @intCast(u64, self.segments.items.len); | 418 | self.segment_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 327 | const file_size = alignSize(u64, self.base.options.program_code_size_hint, 0x1000); | 419 | try self.load_commands.append(self.base.allocator, .{ |
| 328 | log.debug("vmsize/filesize = {}", .{file_size}); | 420 | .Segment = .{ |
| 329 | const file_offset = 0; | 421 | .cmd = macho.LC_SEGMENT_64, |
| 330 | const vm_address = self.vm_start_address; // the end of __PAGEZERO segment in VM | 422 | .cmdsize = @sizeOf(macho.segment_command_64), |
| 331 | const protection = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE; | 423 | .segname = makeStaticString(""), |
| 332 | const cmdsize = commandSize(@sizeOf(macho.segment_command_64)); | 424 | .vmaddr = 0, |
| 333 | const text_segment = .{ | 425 | .vmsize = 0, |
| 334 | .cmd = macho.LC_SEGMENT_64, | 426 | .fileoff = 0, |
| 335 | .cmdsize = cmdsize, | 427 | .filesize = 0, |
| 336 | .segname = makeString("__TEXT"), | 428 | .maxprot = 0, |
| 337 | .vmaddr = vm_address, | 429 | .initprot = 0, |
| 338 | .vmsize = file_size, | 430 | .nsects = 0, |
| 339 | .fileoff = 0, // __TEXT segment *always* starts at 0 file offset | 431 | .flags = 0, |
| 340 | .filesize = 0, //file_size, | 432 | }, |
| 341 | .maxprot = protection, | 433 | }); |
| 342 | .initprot = protection, | 434 | self.cmd_table_dirty = true; |
| 343 | .nsects = 0, | 435 | } |
| 344 | .flags = 0, | 436 | if (self.symtab_cmd_index == null) { |
| 345 | }; | 437 | self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); |
| 346 | try self.commands.append(self.base.allocator, .{ | 438 | try self.load_commands.append(self.base.allocator, .{ |
| 347 | .cmd = macho.LC_SEGMENT_64, | 439 | .Symtab = .{ |
| 348 | .cmdsize = cmdsize, | 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 | var buf = [_]u8{0} ** 16; | 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 | mem.copy(u8, buf[0..], bytes); | 567 | mem.copy(u8, buf[0..], bytes); |
| 358 | return buf; | 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 | fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int { | 579 | fn alignSize(comptime Int: type, min_size: anytype, alignment: Int) Int { |
| 362 | const size = @intCast(Int, min_size); | 580 | const size = @intCast(Int, min_size); |
| 363 | if (size % alignment == 0) return size; | 581 | if (size % alignment == 0) return size; |
| ... | @@ -370,7 +588,7 @@ fn commandSize(min_size: anytype) u32 { | ... | @@ -370,7 +588,7 @@ fn commandSize(min_size: anytype) u32 { |
| 370 | return alignSize(u32, min_size, @sizeOf(u64)); | 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 | if (size == 0) return; | 592 | if (size == 0) return; |
| 375 | 593 | ||
| 376 | const buf = try self.base.allocator.alloc(u8, size); | 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,3 +598,151 @@ fn addPadding(self: *MachO, size: u32, file_offset: u64) !void { |
| 380 | 598 | ||
| 381 | try self.base.file.?.pwriteAll(buf, file_offset); | 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,7 +15,7 @@ pub fn analyze( |
| 15 | 15 | ||
| 16 | var table = std.AutoHashMap(*ir.Inst, void).init(gpa); | 16 | var table = std.AutoHashMap(*ir.Inst, void).init(gpa); |
| 17 | defer table.deinit(); | 17 | defer table.deinit(); |
| 18 | try table.ensureCapacity(body.instructions.len); | 18 | try table.ensureCapacity(@intCast(u32, body.instructions.len)); |
| 19 | try analyzeWithTable(arena, &table, null, body); | 19 | try analyzeWithTable(arena, &table, null, body); |
| 20 | } | 20 | } |
| 21 | 21 | ||
| ... | @@ -84,8 +84,11 @@ fn analyzeInst( | ... | @@ -84,8 +84,11 @@ fn analyzeInst( |
| 84 | try analyzeWithTable(arena, table, &then_table, inst.then_body); | 84 | try analyzeWithTable(arena, table, &then_table, inst.then_body); |
| 85 | 85 | ||
| 86 | // Reset the table back to its state from before the branch. | 86 | // Reset the table back to its state from before the branch. |
| 87 | for (then_table.items()) |entry| { | 87 | { |
| 88 | table.removeAssertDiscard(entry.key); | 88 | var it = then_table.iterator(); |
| 89 | while (it.next()) |entry| { | ||
| 90 | table.removeAssertDiscard(entry.key); | ||
| 91 | } | ||
| 89 | } | 92 | } |
| 90 | 93 | ||
| 91 | var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | 94 | var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); |
| ... | @@ -97,28 +100,36 @@ fn analyzeInst( | ... | @@ -97,28 +100,36 @@ fn analyzeInst( |
| 97 | var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | 100 | var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); |
| 98 | defer else_entry_deaths.deinit(); | 101 | defer else_entry_deaths.deinit(); |
| 99 | 102 | ||
| 100 | for (else_table.items()) |entry| { | 103 | { |
| 101 | const else_death = entry.key; | 104 | var it = else_table.iterator(); |
| 102 | if (!then_table.contains(else_death)) { | 105 | while (it.next()) |entry| { |
| 103 | try then_entry_deaths.append(else_death); | 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 | // This loop is the same, except it's for the then branch, and it additionally | 112 | // This loop is the same, except it's for the then branch, and it additionally |
| 107 | // has to put its items back into the table to undo the reset. | 113 | // has to put its items back into the table to undo the reset. |
| 108 | for (then_table.items()) |entry| { | 114 | { |
| 109 | const then_death = entry.key; | 115 | var it = then_table.iterator(); |
| 110 | if (!else_table.contains(then_death)) { | 116 | while (it.next()) |entry| { |
| 111 | try else_entry_deaths.append(then_death); | 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 | // Now we have to correctly populate new_set. | 124 | // Now we have to correctly populate new_set. |
| 116 | if (new_set) |ns| { | 125 | if (new_set) |ns| { |
| 117 | try ns.ensureCapacity(ns.items().len + then_table.items().len + else_table.items().len); | 126 | try ns.ensureCapacity(@intCast(u32, ns.count() + then_table.count() + else_table.count())); |
| 118 | for (then_table.items()) |entry| { | 127 | var it = then_table.iterator(); |
| 128 | while (it.next()) |entry| { | ||
| 119 | _ = ns.putAssumeCapacity(entry.key, {}); | 129 | _ = ns.putAssumeCapacity(entry.key, {}); |
| 120 | } | 130 | } |
| 121 | for (else_table.items()) |entry| { | 131 | it = else_table.iterator(); |
| 132 | while (it.next()) |entry| { | ||
| 122 | _ = ns.putAssumeCapacity(entry.key, {}); | 133 | _ = ns.putAssumeCapacity(entry.key, {}); |
| 123 | } | 134 | } |
| 124 | } | 135 | } |
src-self-hosted/main.zig+2-1| ... | @@ -839,7 +839,8 @@ fn fmtPathFile( | ... | @@ -839,7 +839,8 @@ fn fmtPathFile( |
| 839 | // As a heuristic, we make enough capacity for the same as the input source. | 839 | // As a heuristic, we make enough capacity for the same as the input source. |
| 840 | try fmt.out_buffer.ensureCapacity(source_code.len); | 840 | try fmt.out_buffer.ensureCapacity(source_code.len); |
| 841 | fmt.out_buffer.items.len = 0; | 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 | if (!anything_changed) | 844 | if (!anything_changed) |
| 844 | return; // Good thing we didn't waste any file system access on this. | 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,15 +474,15 @@ pub const TestContext = struct { |
| 474 | var all_errors = try module.getAllErrorsAlloc(); | 474 | var all_errors = try module.getAllErrorsAlloc(); |
| 475 | defer all_errors.deinit(allocator); | 475 | defer all_errors.deinit(allocator); |
| 476 | if (all_errors.list.len != 0) { | 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 | for (all_errors.list) |err| { | 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 | if (case.cbe) { | 481 | if (case.cbe) { |
| 482 | const C = module.bin_file.cast(link.File.C).?; | 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 | std.process.exit(1); | 486 | std.process.exit(1); |
| 487 | } | 487 | } |
| 488 | } | 488 | } |
| ... | @@ -497,12 +497,12 @@ pub const TestContext = struct { | ... | @@ -497,12 +497,12 @@ pub const TestContext = struct { |
| 497 | var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!"); | 497 | var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!"); |
| 498 | 498 | ||
| 499 | if (expected_output.len != out.len) { | 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 | std.process.exit(1); | 501 | std.process.exit(1); |
| 502 | } | 502 | } |
| 503 | for (expected_output) |e, i| { | 503 | for (expected_output) |e, i| { |
| 504 | if (out[i] != e) { | 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 | std.process.exit(1); | 506 | std.process.exit(1); |
| 507 | } | 507 | } |
| 508 | } | 508 | } |
| ... | @@ -526,12 +526,12 @@ pub const TestContext = struct { | ... | @@ -526,12 +526,12 @@ pub const TestContext = struct { |
| 526 | defer test_node.end(); | 526 | defer test_node.end(); |
| 527 | 527 | ||
| 528 | if (expected_output.len != out_zir.items.len) { | 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 | std.process.exit(1); | 530 | std.process.exit(1); |
| 531 | } | 531 | } |
| 532 | for (expected_output) |e, i| { | 532 | for (expected_output) |e, i| { |
| 533 | if (out_zir.items[i] != e) { | 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 | std.process.exit(1); | 535 | std.process.exit(1); |
| 536 | } | 536 | } |
| 537 | } | 537 | } |
| ... | @@ -554,7 +554,7 @@ pub const TestContext = struct { | ... | @@ -554,7 +554,7 @@ pub const TestContext = struct { |
| 554 | break; | 554 | break; |
| 555 | } | 555 | } |
| 556 | } else { | 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 | std.process.exit(1); | 558 | std.process.exit(1); |
| 559 | } | 559 | } |
| 560 | } | 560 | } |
| ... | @@ -562,7 +562,7 @@ pub const TestContext = struct { | ... | @@ -562,7 +562,7 @@ pub const TestContext = struct { |
| 562 | for (handled_errors) |h, i| { | 562 | for (handled_errors) |h, i| { |
| 563 | if (!h) { | 563 | if (!h) { |
| 564 | const er = e[i]; | 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 | std.process.exit(1); | 566 | std.process.exit(1); |
| 567 | } | 567 | } |
| 568 | } | 568 | } |
| ... | @@ -643,7 +643,7 @@ pub const TestContext = struct { | ... | @@ -643,7 +643,7 @@ pub const TestContext = struct { |
| 643 | switch (exec_result.term) { | 643 | switch (exec_result.term) { |
| 644 | .Exited => |code| { | 644 | .Exited => |code| { |
| 645 | if (code != 0) { | 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 | return error.BinaryBadExitCode; | 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,23 +19,9 @@ pub const Error = error{OutOfMemory}; |
| 19 | const TypeError = Error || error{UnsupportedType}; | 19 | const TypeError = Error || error{UnsupportedType}; |
| 20 | const TransError = TypeError || error{UnsupportedTranslation}; | 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 { | 24 | const SymbolTable = std.StringArrayHashMap(*ast.Node); |
| 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); | ||
| 39 | const AliasList = std.ArrayList(struct { | 25 | const AliasList = std.ArrayList(struct { |
| 40 | alias: []const u8, | 26 | alias: []const u8, |
| 41 | name: []const u8, | 27 | name: []const u8, |
| ... | @@ -285,7 +271,7 @@ pub const Context = struct { | ... | @@ -285,7 +271,7 @@ pub const Context = struct { |
| 285 | /// a list of names that we found by visiting all the top level decls without | 271 | /// a list of names that we found by visiting all the top level decls without |
| 286 | /// translating them. The other maps are updated as we translate; this one is updated | 272 | /// translating them. The other maps are updated as we translate; this one is updated |
| 287 | /// up front in a pre-processing step. | 273 | /// up front in a pre-processing step. |
| 288 | global_names: std.StringHashMap(void), | 274 | global_names: std.StringArrayHashMap(void), |
| 289 | 275 | ||
| 290 | fn getMangle(c: *Context) u32 { | 276 | fn getMangle(c: *Context) u32 { |
| 291 | c.mangle_count += 1; | 277 | c.mangle_count += 1; |
| ... | @@ -380,7 +366,7 @@ pub fn translate( | ... | @@ -380,7 +366,7 @@ pub fn translate( |
| 380 | .alias_list = AliasList.init(gpa), | 366 | .alias_list = AliasList.init(gpa), |
| 381 | .global_scope = try arena.allocator.create(Scope.Root), | 367 | .global_scope = try arena.allocator.create(Scope.Root), |
| 382 | .clang_context = ZigClangASTUnit_getASTContext(ast_unit).?, | 368 | .clang_context = ZigClangASTUnit_getASTContext(ast_unit).?, |
| 383 | .global_names = std.StringHashMap(void).init(gpa), | 369 | .global_names = std.StringArrayHashMap(void).init(gpa), |
| 384 | .token_ids = .{}, | 370 | .token_ids = .{}, |
| 385 | .token_locs = .{}, | 371 | .token_locs = .{}, |
| 386 | .errors = .{}, | 372 | .errors = .{}, |
| ... | @@ -6424,7 +6410,8 @@ fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto { | ... | @@ -6424,7 +6410,8 @@ fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto { |
| 6424 | } | 6410 | } |
| 6425 | 6411 | ||
| 6426 | fn addMacros(c: *Context) !void { | 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 | if (getFnProto(c, kv.value)) |proto_node| { | 6415 | if (getFnProto(c, kv.value)) |proto_node| { |
| 6429 | // If a macro aliases a global variable which is a function pointer, we conclude that | 6416 | // If a macro aliases a global variable which is a function pointer, we conclude that |
| 6430 | // the macro is intended to represent a function that assumes the function pointer | 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,7 +163,7 @@ pub const Type = extern union { |
| 163 | // Hot path for common case: | 163 | // Hot path for common case: |
| 164 | if (a.castPointer()) |a_payload| { | 164 | if (a.castPointer()) |a_payload| { |
| 165 | if (b.castPointer()) |b_payload| { | 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 | const is_slice_a = isSlice(a); | 169 | const is_slice_a = isSlice(a); |
| ... | @@ -189,10 +189,10 @@ pub const Type = extern union { | ... | @@ -189,10 +189,10 @@ pub const Type = extern union { |
| 189 | .Array => { | 189 | .Array => { |
| 190 | if (a.arrayLen() != b.arrayLen()) | 190 | if (a.arrayLen() != b.arrayLen()) |
| 191 | return false; | 191 | return false; |
| 192 | if (a.elemType().eql(b.elemType())) | 192 | if (!a.elemType().eql(b.elemType())) |
| 193 | return false; | 193 | return false; |
| 194 | const sentinel_a = a.arraySentinel(); | 194 | const sentinel_a = a.sentinel(); |
| 195 | const sentinel_b = b.arraySentinel(); | 195 | const sentinel_b = b.sentinel(); |
| 196 | if (sentinel_a) |sa| { | 196 | if (sentinel_a) |sa| { |
| 197 | if (sentinel_b) |sb| { | 197 | if (sentinel_b) |sb| { |
| 198 | return sa.eql(sb); | 198 | return sa.eql(sb); |
| ... | @@ -238,7 +238,7 @@ pub const Type = extern union { | ... | @@ -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 | var hasher = std.hash.Wyhash.init(0); | 242 | var hasher = std.hash.Wyhash.init(0); |
| 243 | const zig_type_tag = self.zigTypeTag(); | 243 | const zig_type_tag = self.zigTypeTag(); |
| 244 | std.hash.autoHash(&hasher, zig_type_tag); | 244 | std.hash.autoHash(&hasher, zig_type_tag); |
| ... | @@ -303,7 +303,7 @@ pub const Type = extern union { | ... | @@ -303,7 +303,7 @@ pub const Type = extern union { |
| 303 | // TODO implement more type hashing | 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 | pub fn copy(self: Type, allocator: *Allocator) error{OutOfMemory}!Type { | 309 | pub fn copy(self: Type, allocator: *Allocator) error{OutOfMemory}!Type { |
| ... | @@ -501,9 +501,9 @@ pub const Type = extern union { | ... | @@ -501,9 +501,9 @@ pub const Type = extern union { |
| 501 | .noreturn, | 501 | .noreturn, |
| 502 | => return out_stream.writeAll(@tagName(t)), | 502 | => return out_stream.writeAll(@tagName(t)), |
| 503 | 503 | ||
| 504 | .enum_literal => return out_stream.writeAll("@TypeOf(.EnumLiteral)"), | 504 | .enum_literal => return out_stream.writeAll("@Type(.EnumLiteral)"), |
| 505 | .@"null" => return out_stream.writeAll("@TypeOf(null)"), | 505 | .@"null" => return out_stream.writeAll("@Type(.Null)"), |
| 506 | .@"undefined" => return out_stream.writeAll("@TypeOf(undefined)"), | 506 | .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"), |
| 507 | 507 | ||
| 508 | .@"anyframe" => return out_stream.writeAll("anyframe"), | 508 | .@"anyframe" => return out_stream.writeAll("anyframe"), |
| 509 | .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"), | 509 | .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"), |
| ... | @@ -630,8 +630,8 @@ pub const Type = extern union { | ... | @@ -630,8 +630,8 @@ pub const Type = extern union { |
| 630 | const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise); | 630 | const payload = @fieldParentPtr(Payload.Pointer, "base", ty.ptr_otherwise); |
| 631 | if (payload.sentinel) |some| switch (payload.size) { | 631 | if (payload.sentinel) |some| switch (payload.size) { |
| 632 | .One, .C => unreachable, | 632 | .One, .C => unreachable, |
| 633 | .Many => try out_stream.writeAll("[*:{}]"), | 633 | .Many => try out_stream.print("[*:{}]", .{some}), |
| 634 | .Slice => try out_stream.writeAll("[:{}]"), | 634 | .Slice => try out_stream.print("[:{}]", .{some}), |
| 635 | } else switch (payload.size) { | 635 | } else switch (payload.size) { |
| 636 | .One => try out_stream.writeAll("*"), | 636 | .One => try out_stream.writeAll("*"), |
| 637 | .Many => try out_stream.writeAll("[*]"), | 637 | .Many => try out_stream.writeAll("[*]"), |
| ... | @@ -1341,6 +1341,81 @@ pub const Type = extern union { | ... | @@ -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 | /// Asserts that the type is an optional | 1419 | /// Asserts that the type is an optional |
| 1345 | pub fn isPtrLikeOptional(self: Type) bool { | 1420 | pub fn isPtrLikeOptional(self: Type) bool { |
| 1346 | switch (self.tag()) { | 1421 | switch (self.tag()) { |
| ... | @@ -1585,8 +1660,8 @@ pub const Type = extern union { | ... | @@ -1585,8 +1660,8 @@ pub const Type = extern union { |
| 1585 | }; | 1660 | }; |
| 1586 | } | 1661 | } |
| 1587 | 1662 | ||
| 1588 | /// Asserts the type is an array or vector. | 1663 | /// Asserts the type is an array, pointer or vector. |
| 1589 | pub fn arraySentinel(self: Type) ?Value { | 1664 | pub fn sentinel(self: Type) ?Value { |
| 1590 | return switch (self.tag()) { | 1665 | return switch (self.tag()) { |
| 1591 | .u8, | 1666 | .u8, |
| 1592 | .i8, | 1667 | .i8, |
| ... | @@ -1626,16 +1701,8 @@ pub const Type = extern union { | ... | @@ -1626,16 +1701,8 @@ pub const Type = extern union { |
| 1626 | .fn_naked_noreturn_no_args, | 1701 | .fn_naked_noreturn_no_args, |
| 1627 | .fn_ccc_void_no_args, | 1702 | .fn_ccc_void_no_args, |
| 1628 | .function, | 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 | .const_slice, | 1704 | .const_slice, |
| 1637 | .mut_slice, | 1705 | .mut_slice, |
| 1638 | .single_const_pointer_to_comptime_int, | ||
| 1639 | .const_slice_u8, | 1706 | .const_slice_u8, |
| 1640 | .int_unsigned, | 1707 | .int_unsigned, |
| 1641 | .int_signed, | 1708 | .int_signed, |
| ... | @@ -1651,7 +1718,18 @@ pub const Type = extern union { | ... | @@ -1651,7 +1718,18 @@ pub const Type = extern union { |
| 1651 | .error_set_single, | 1718 | .error_set_single, |
| 1652 | => unreachable, | 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 | .array_sentinel => return self.cast(Payload.ArraySentinel).?.sentinel, | 1733 | .array_sentinel => return self.cast(Payload.ArraySentinel).?.sentinel, |
| 1656 | .array_u8_sentinel_0 => return Value.initTag(.zero), | 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,15 +301,15 @@ pub const Value = extern union { |
| 301 | .comptime_int_type => return out_stream.writeAll("comptime_int"), | 301 | .comptime_int_type => return out_stream.writeAll("comptime_int"), |
| 302 | .comptime_float_type => return out_stream.writeAll("comptime_float"), | 302 | .comptime_float_type => return out_stream.writeAll("comptime_float"), |
| 303 | .noreturn_type => return out_stream.writeAll("noreturn"), | 303 | .noreturn_type => return out_stream.writeAll("noreturn"), |
| 304 | .null_type => return out_stream.writeAll("@TypeOf(null)"), | 304 | .null_type => return out_stream.writeAll("@Type(.Null)"), |
| 305 | .undefined_type => return out_stream.writeAll("@TypeOf(undefined)"), | 305 | .undefined_type => return out_stream.writeAll("@Type(.Undefined)"), |
| 306 | .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"), | 306 | .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"), |
| 307 | .fn_void_no_args_type => return out_stream.writeAll("fn() void"), | 307 | .fn_void_no_args_type => return out_stream.writeAll("fn() void"), |
| 308 | .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), | 308 | .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), |
| 309 | .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"), | 309 | .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"), |
| 310 | .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), | 310 | .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), |
| 311 | .const_slice_u8_type => return out_stream.writeAll("[]const u8"), | 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 | .anyframe_type => return out_stream.writeAll("anyframe"), | 313 | .anyframe_type => return out_stream.writeAll("anyframe"), |
| 314 | 314 | ||
| 315 | .null_value => return out_stream.writeAll("null"), | 315 | .null_value => return out_stream.writeAll("null"), |
| ... | @@ -358,7 +358,8 @@ pub const Value = extern union { | ... | @@ -358,7 +358,8 @@ pub const Value = extern union { |
| 358 | .error_set => { | 358 | .error_set => { |
| 359 | const error_set = val.cast(Payload.ErrorSet).?; | 359 | const error_set = val.cast(Payload.ErrorSet).?; |
| 360 | try out_stream.writeAll("error{"); | 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 | try out_stream.print("{},", .{entry.value}); | 363 | try out_stream.print("{},", .{entry.value}); |
| 363 | } | 364 | } |
| 364 | return out_stream.writeAll("}"); | 365 | return out_stream.writeAll("}"); |
src-self-hosted/zir.zig+42-5| ... | @@ -78,6 +78,13 @@ pub const Inst = struct { | ... | @@ -78,6 +78,13 @@ pub const Inst = struct { |
| 78 | bitor, | 78 | bitor, |
| 79 | /// A labeled block of code, which can return a value. | 79 | /// A labeled block of code, which can return a value. |
| 80 | block, | 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 | /// Boolean NOT. See also `bitnot`. | 88 | /// Boolean NOT. See also `bitnot`. |
| 82 | boolnot, | 89 | boolnot, |
| 83 | /// Return a value from a `Block`. | 90 | /// Return a value from a `Block`. |
| ... | @@ -224,6 +231,10 @@ pub const Inst = struct { | ... | @@ -224,6 +231,10 @@ pub const Inst = struct { |
| 224 | const_slice_type, | 231 | const_slice_type, |
| 225 | /// Create a pointer type with attributes | 232 | /// Create a pointer type with attributes |
| 226 | ptr_type, | 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 | /// Write a value to a pointer. For loading, see `deref`. | 238 | /// Write a value to a pointer. For loading, see `deref`. |
| 228 | store, | 239 | store, |
| 229 | /// String Literal. Makes an anonymous Decl and then takes a pointer to it. | 240 | /// String Literal. Makes an anonymous Decl and then takes a pointer to it. |
| ... | @@ -336,11 +347,17 @@ pub const Inst = struct { | ... | @@ -336,11 +347,17 @@ pub const Inst = struct { |
| 336 | .xor, | 347 | .xor, |
| 337 | .error_union_type, | 348 | .error_union_type, |
| 338 | .merge_error_sets, | 349 | .merge_error_sets, |
| 350 | .slice_start, | ||
| 339 | => BinOp, | 351 | => BinOp, |
| 340 | 352 | ||
| 353 | .block, | ||
| 354 | .block_flat, | ||
| 355 | .block_comptime, | ||
| 356 | .block_comptime_flat, | ||
| 357 | => Block, | ||
| 358 | |||
| 341 | .arg => Arg, | 359 | .arg => Arg, |
| 342 | .array_type_sentinel => ArrayTypeSentinel, | 360 | .array_type_sentinel => ArrayTypeSentinel, |
| 343 | .block => Block, | ||
| 344 | .@"break" => Break, | 361 | .@"break" => Break, |
| 345 | .breakvoid => BreakVoid, | 362 | .breakvoid => BreakVoid, |
| 346 | .call => Call, | 363 | .call => Call, |
| ... | @@ -368,6 +385,7 @@ pub const Inst = struct { | ... | @@ -368,6 +385,7 @@ pub const Inst = struct { |
| 368 | .ptr_type => PtrType, | 385 | .ptr_type => PtrType, |
| 369 | .enum_literal => EnumLiteral, | 386 | .enum_literal => EnumLiteral, |
| 370 | .error_set => ErrorSet, | 387 | .error_set => ErrorSet, |
| 388 | .slice => Slice, | ||
| 371 | }; | 389 | }; |
| 372 | } | 390 | } |
| 373 | 391 | ||
| ... | @@ -392,6 +410,9 @@ pub const Inst = struct { | ... | @@ -392,6 +410,9 @@ pub const Inst = struct { |
| 392 | .bitcast_result_ptr, | 410 | .bitcast_result_ptr, |
| 393 | .bitor, | 411 | .bitor, |
| 394 | .block, | 412 | .block, |
| 413 | .block_flat, | ||
| 414 | .block_comptime, | ||
| 415 | .block_comptime_flat, | ||
| 395 | .boolnot, | 416 | .boolnot, |
| 396 | .breakpoint, | 417 | .breakpoint, |
| 397 | .call, | 418 | .call, |
| ... | @@ -466,6 +487,8 @@ pub const Inst = struct { | ... | @@ -466,6 +487,8 @@ pub const Inst = struct { |
| 466 | .error_union_type, | 487 | .error_union_type, |
| 467 | .bitnot, | 488 | .bitnot, |
| 468 | .error_set, | 489 | .error_set, |
| 490 | .slice, | ||
| 491 | .slice_start, | ||
| 469 | => false, | 492 | => false, |
| 470 | 493 | ||
| 471 | .@"break", | 494 | .@"break", |
| ... | @@ -946,6 +969,20 @@ pub const Inst = struct { | ... | @@ -946,6 +969,20 @@ pub const Inst = struct { |
| 946 | }, | 969 | }, |
| 947 | kw_args: struct {}, | 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 | pub const ErrorMsg = struct { | 988 | pub const ErrorMsg = struct { |
| ... | @@ -1034,7 +1071,7 @@ pub const Module = struct { | ... | @@ -1034,7 +1071,7 @@ pub const Module = struct { |
| 1034 | defer write.loop_table.deinit(); | 1071 | defer write.loop_table.deinit(); |
| 1035 | 1072 | ||
| 1036 | // First, build a map of *Inst to @ or % indexes | 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 | for (self.decls) |decl, decl_i| { | 1076 | for (self.decls) |decl, decl_i| { |
| 1040 | try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); | 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,7 +1707,7 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { |
| 1670 | .arena = std.heap.ArenaAllocator.init(allocator), | 1707 | .arena = std.heap.ArenaAllocator.init(allocator), |
| 1671 | .old_module = &old_module, | 1708 | .old_module = &old_module, |
| 1672 | .next_auto_name = 0, | 1709 | .next_auto_name = 0, |
| 1673 | .names = std.StringHashMap(void).init(allocator), | 1710 | .names = std.StringArrayHashMap(void).init(allocator), |
| 1674 | .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), | 1711 | .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), |
| 1675 | .indent = 0, | 1712 | .indent = 0, |
| 1676 | .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), | 1713 | .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), |
| ... | @@ -1743,7 +1780,7 @@ const EmitZIR = struct { | ... | @@ -1743,7 +1780,7 @@ const EmitZIR = struct { |
| 1743 | arena: std.heap.ArenaAllocator, | 1780 | arena: std.heap.ArenaAllocator, |
| 1744 | old_module: *const IrModule, | 1781 | old_module: *const IrModule, |
| 1745 | decls: std.ArrayListUnmanaged(*Decl), | 1782 | decls: std.ArrayListUnmanaged(*Decl), |
| 1746 | names: std.StringHashMap(void), | 1783 | names: std.StringArrayHashMap(void), |
| 1747 | next_auto_name: usize, | 1784 | next_auto_name: usize, |
| 1748 | primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), | 1785 | primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), |
| 1749 | indent: usize, | 1786 | indent: usize, |
| ... | @@ -2559,7 +2596,7 @@ const EmitZIR = struct { | ... | @@ -2559,7 +2596,7 @@ const EmitZIR = struct { |
| 2559 | var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() }; | 2596 | var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() }; |
| 2560 | const len = Value.initPayload(&len_pl.base); | 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 | const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel); | 2600 | const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel); |
| 2564 | inst.* = .{ | 2601 | inst.* = .{ |
| 2565 | .base = .{ | 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,7 +31,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! |
| 31 | .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?), | 31 | .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?), |
| 32 | .bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?), | 32 | .bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?), |
| 33 | .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?), | 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 | .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?), | 38 | .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?), |
| 36 | .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?), | 39 | .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?), |
| 37 | .breakvoid => return analyzeInstBreakVoid(mod, scope, old_inst.castTag(.breakvoid).?), | 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,6 +132,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! |
| 129 | .error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?), | 132 | .error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?), |
| 130 | .anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?), | 133 | .anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?), |
| 131 | .error_set => return analyzeInstErrorSet(mod, scope, old_inst.castTag(.error_set).?), | 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,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 | try analyzeBody(mod, &block_scope.base, body); | 161 | try analyzeBody(mod, &block_scope.base, body); |
| 152 | for (block_scope.instructions.items) |inst| { | 162 | const result_inst = zir_result_inst.analyzed_inst.?; |
| 153 | if (inst.castTag(.ret)) |ret| { | 163 | const val = try mod.resolveConstValue(&block_scope.base, result_inst); |
| 154 | const val = try mod.resolveConstValue(&block_scope.base, ret.operand); | 164 | return val.toType(block_scope.base.arena()); |
| 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; | ||
| 161 | } | 165 | } |
| 162 | 166 | ||
| 163 | pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { | 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,7 +366,7 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError! |
| 362 | } | 366 | } |
| 363 | 367 | ||
| 364 | fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | 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 | const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; | 370 | const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; |
| 367 | const ret_type = fn_ty.fnReturnType(); | 371 | const ret_type = fn_ty.fnReturnType(); |
| 368 | return mod.constType(scope, inst.base.src, ret_type); | 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,6 +521,7 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError |
| 517 | .decl = parent_block.decl, | 521 | .decl = parent_block.decl, |
| 518 | .instructions = .{}, | 522 | .instructions = .{}, |
| 519 | .arena = parent_block.arena, | 523 | .arena = parent_block.arena, |
| 524 | .is_comptime = parent_block.is_comptime, | ||
| 520 | }; | 525 | }; |
| 521 | defer child_block.instructions.deinit(mod.gpa); | 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,7 +534,29 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError |
| 529 | return &loop_inst.base; | 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 | const parent_block = scope.cast(Scope.Block).?; | 560 | const parent_block = scope.cast(Scope.Block).?; |
| 534 | 561 | ||
| 535 | // Reserve space for a Block instruction so that generated Break instructions can | 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,6 +584,7 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr |
| 557 | .results = .{}, | 584 | .results = .{}, |
| 558 | .block_inst = block_inst, | 585 | .block_inst = block_inst, |
| 559 | }), | 586 | }), |
| 587 | .is_comptime = is_comptime or parent_block.is_comptime, | ||
| 560 | }; | 588 | }; |
| 561 | const label = &child_block.label.?; | 589 | const label = &child_block.label.?; |
| 562 | 590 | ||
| ... | @@ -569,6 +597,28 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr | ... | @@ -569,6 +597,28 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerErr |
| 569 | assert(child_block.instructions.items.len != 0); | 597 | assert(child_block.instructions.items.len != 0); |
| 570 | assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); | 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 | // Need to set the type and emit the Block instruction. This allows machine code generation | 622 | // Need to set the type and emit the Block instruction. This allows machine code generation |
| 573 | // to emit a jump instruction to after the block when it encounters the break. | 623 | // to emit a jump instruction to after the block when it encounters the break. |
| 574 | try parent_block.instructions.append(mod.gpa, &block_inst.base); | 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,8 +645,12 @@ fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) |
| 595 | } | 645 | } |
| 596 | 646 | ||
| 597 | fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { | 647 | fn analyzeInstDbgStmt(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { |
| 598 | const b = try mod.requireRuntimeBlock(scope, inst.base.src); | 648 | if (scope.cast(Scope.Block)) |b| { |
| 599 | return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .dbg_stmt); | 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 | fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { | 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,7 +818,7 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In |
| 764 | .fields = .{}, | 818 | .fields = .{}, |
| 765 | .decl = undefined, // populated below | 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 | for (inst.positionals.fields) |field_name| { | 823 | for (inst.positionals.fields) |field_name| { |
| 770 | const entry = try mod.getErrorValue(field_name); | 824 | const entry = try mod.getErrorValue(field_name); |
| ... | @@ -1083,7 +1137,7 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne | ... | @@ -1083,7 +1137,7 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne |
| 1083 | const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr); | 1137 | const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr); |
| 1084 | const uncasted_index = try resolveInst(mod, scope, inst.positionals.index); | 1138 | const uncasted_index = try resolveInst(mod, scope, inst.positionals.index); |
| 1085 | const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index); | 1139 | const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index); |
| 1086 | 1140 | ||
| 1087 | const elem_ty = switch (array_ptr.ty.zigTypeTag()) { | 1141 | const elem_ty = switch (array_ptr.ty.zigTypeTag()) { |
| 1088 | .Pointer => array_ptr.ty.elemType(), | 1142 | .Pointer => array_ptr.ty.elemType(), |
| 1089 | else => return mod.fail(scope, inst.positionals.array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}), | 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,6 +1174,22 @@ fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inne |
| 1120 | return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); | 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 | fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { | 1193 | fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { |
| 1124 | return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{}); | 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,6 +1257,12 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn |
| 1187 | 1257 | ||
| 1188 | if (casted_lhs.value()) |lhs_val| { | 1258 | if (casted_lhs.value()) |lhs_val| { |
| 1189 | if (casted_rhs.value()) |rhs_val| { | 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 | return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val); | 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,6 +1452,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE |
| 1376 | .decl = parent_block.decl, | 1452 | .decl = parent_block.decl, |
| 1377 | .instructions = .{}, | 1453 | .instructions = .{}, |
| 1378 | .arena = parent_block.arena, | 1454 | .arena = parent_block.arena, |
| 1455 | .is_comptime = parent_block.is_comptime, | ||
| 1379 | }; | 1456 | }; |
| 1380 | defer true_block.instructions.deinit(mod.gpa); | 1457 | defer true_block.instructions.deinit(mod.gpa); |
| 1381 | try analyzeBody(mod, &true_block.base, inst.positionals.then_body); | 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,6 +1463,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE |
| 1386 | .decl = parent_block.decl, | 1463 | .decl = parent_block.decl, |
| 1387 | .instructions = .{}, | 1464 | .instructions = .{}, |
| 1388 | .arena = parent_block.arena, | 1465 | .arena = parent_block.arena, |
| 1466 | .is_comptime = parent_block.is_comptime, | ||
| 1389 | }; | 1467 | }; |
| 1390 | defer false_block.instructions.deinit(mod.gpa); | 1468 | defer false_block.instructions.deinit(mod.gpa); |
| 1391 | try analyzeBody(mod, &false_block.base, inst.positionals.else_body); | 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,9 +15342,14 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr, |
| 15342 | ZigType *array_type = actual_type->data.pointer.child_type; | 15342 | ZigType *array_type = actual_type->data.pointer.child_type; |
| 15343 | bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0 | 15343 | bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0 |
| 15344 | || !actual_type->data.pointer.is_const); | 15344 | || !actual_type->data.pointer.is_const); |
| 15345 | |||
| 15345 | if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, | 15346 | if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, |
| 15346 | array_type->data.array.child_type, source_node, | 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 | // If the pointers both have ABI align, it works. | 15354 | // If the pointers both have ABI align, it works. |
| 15350 | // Or if the array length is 0, alignment doesn't matter. | 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,6 +25689,10 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy |
| 25684 | } | 25689 | } |
| 25685 | set_optional_payload(inner_fields[2], struct_field->init_val); | 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 | ZigValue *name = create_const_str_lit(ira->codegen, struct_field->name)->data.x_ptr.data.ref.pointee; | 25696 | ZigValue *name = create_const_str_lit(ira->codegen, struct_field->name)->data.x_ptr.data.ref.pointee; |
| 25688 | init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(struct_field->name), true); | 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,6 +26301,8 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI |
| 26292 | buf_ptr(&field->type_entry->name), buf_ptr(&field->type_entry->name))); | 26301 | buf_ptr(&field->type_entry->name), buf_ptr(&field->type_entry->name))); |
| 26293 | return ira->codegen->invalid_inst_gen->value->type; | 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 | return entry; | 26308 | return entry; |
test/compile_errors.zig+8| ... | @@ -2,6 +2,14 @@ const tests = @import("tests.zig"); | ... | @@ -2,6 +2,14 @@ const tests = @import("tests.zig"); |
| 2 | const std = @import("std"); | 2 | const std = @import("std"); |
| 3 | 3 | ||
| 4 | pub fn addCases(cases: *tests.CompileErrorContext) void { | 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 | cases.add("@Type with undefined", | 13 | cases.add("@Type with undefined", |
| 6 | \\comptime { | 14 | \\comptime { |
| 7 | \\ _ = @Type(.{ .Array = .{ .len = 0, .child = u8, .sentinel = undefined } }); | 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,3 +418,9 @@ test "Struct.is_tuple" { |
| 418 | expect(@typeInfo(@TypeOf(.{0})).Struct.is_tuple); | 418 | expect(@typeInfo(@TypeOf(.{0})).Struct.is_tuple); |
| 419 | expect(!@typeInfo(@TypeOf(.{ .a = 0 })).Struct.is_tuple); | 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,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 | case.addCompareOutput( | 278 | case.addCompareOutput( |
| 279 | \\export fn _start() noreturn { | 279 | \\export fn _start() noreturn { |
| 280 | \\ sub(7, 4); | 280 | \\ sub(7, 4); |
| ... | @@ -967,10 +967,19 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -967,10 +967,19 @@ pub fn addCases(ctx: *TestContext) !void { |
| 967 | \\fn entry() void {} | 967 | \\fn entry() void {} |
| 968 | , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); | 968 | , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); |
| 969 | 969 | ||
| 970 | ctx.compileError("extern variable has no type", linux_x64, | 970 | { |
| 971 | \\comptime { | 971 | var case = ctx.obj("extern variable has no type", linux_x64); |
| 972 | \\ _ = foo; | 972 | case.addError( |
| 973 | \\} | 973 | \\comptime { |
| 974 | \\extern var foo; | 974 | \\ _ = foo; |
| 975 | , &[_][]const u8{":4:1: error: unable to infer variable type"}); | 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 | } |