diff options
-rw-r--r-- | src/ast/base.zig | 208 |
1 files changed, 194 insertions, 14 deletions
diff --git a/src/ast/base.zig b/src/ast/base.zig index a76ebc087..36800cd7b 100644 --- a/src/ast/base.zig +++ b/src/ast/base.zig @@ -43,28 +43,174 @@ pub const RefCtx = struct { } }; -pub const Ref = packed struct { +/// Sets the range of bits starting at `start_bit` upto and excluding `start_bit` + `number_of_bits` +/// to be specific, if the range is N bits long, the N lower bits of `value` will be used; if any of +/// the other bits in `value` are set to 1, this function will panic. +/// +/// ```zig +/// var val: u8 = 0b10000000; +/// setBits(&val, 2, 4, 0b00001101); +/// try testing.expectEqual(@as(u8, 0b10110100), val); +/// ``` +/// +/// ## Panics +/// This method will panic if the `value` exceeds the bit range of the type of `target` +pub fn setBits( + comptime TargetType: type, + target: TargetType, + comptime start_bit: comptime_int, + comptime number_of_bits: comptime_int, + value: TargetType, +) TargetType { + const end_bit = start_bit + number_of_bits; + + comptime { + if (number_of_bits == 0) @compileError("non-zero number_of_bits must be provided"); + + if (@typeInfo(TargetType) == .Int) { + if (@typeInfo(TargetType).Int.signedness != .unsigned) { + @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); + } + if (start_bit >= @bitSizeOf(TargetType)) { + @compileError("start_bit index is out of bounds of the bit field"); + } + if (end_bit > @bitSizeOf(TargetType)) { + @compileError("start_bit + number_of_bits is out of bounds of the bit field"); + } + } else if (@typeInfo(TargetType) == .ComptimeInt) { + @compileError("comptime_int is unsupported"); + } else { + @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); + } + } + + if (comptime std.debug.runtime_safety) { + if (getBits(TargetType, value, 0, (end_bit - start_bit)) != value) @panic("value exceeds bit range"); + } + + const bitmask: TargetType = comptime blk: { + var bitmask = ~@as(TargetType, 0); + bitmask <<= (@bitSizeOf(TargetType) - end_bit); + bitmask >>= (@bitSizeOf(TargetType) - end_bit); + bitmask >>= start_bit; + bitmask <<= start_bit; + break :blk ~bitmask; + }; + + return (target & bitmask) | (value << start_bit); +} + +pub fn getBits(comptime TargetType: type, target: anytype, comptime start_bit: comptime_int, comptime number_of_bits: comptime_int) TargetType { + comptime { + if (number_of_bits == 0) @compileError("non-zero number_of_bits must be provided"); + + if (@typeInfo(TargetType) == .Int) { + if (@typeInfo(TargetType).Int.signedness != .unsigned) { + @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); + } + if (start_bit >= @bitSizeOf(TargetType)) { + @compileError("start_bit index is out of bounds of the bit field"); + } + if (start_bit + number_of_bits > @bitSizeOf(TargetType)) { + @compileError("start_bit + number_of_bits is out of bounds of the bit field"); + } + } else if (@typeInfo(TargetType) == .ComptimeInt) { + if (target < 0) { + @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); + } + } else { + @compileError("requires an unsigned integer, found " ++ @typeName(TargetType)); + } + } + + return @truncate(TargetType, target >> start_bit); +} + +pub const Ref = enum(TotalSize) { + default = std.math.maxInt(TotalSize), + _, + + pub const TotalSize = u62; + + pub fn format(ref: Ref, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try std.fmt.format( + writer, + "Ref: sourceIndex = {d}, innerIndex = {d}, is_source_contents_slice = {}", + .{ + ref.sourceIndex(), + ref.innerIndex(), + ref.isSourceContentsSlice(), + }, + ); + } + const max_ref_int = std.math.maxInt(Ref.Int); pub const BitInt = std.meta.Int(.unsigned, @bitSizeOf(Ref)); - source_index: Int = max_ref_int, - inner_index: Int = 0, - is_source_contents_slice: bool = false, - pub inline fn asBitInt(this: Ref) BitInt { return @bitCast(BitInt, this); } // 2 bits of padding for whatever is the parent pub const Int = u30; - pub const None = Ref{ - .inner_index = max_ref_int, - .source_index = max_ref_int, - }; - pub const RuntimeRef = Ref{ - .inner_index = max_ref_int, - .source_index = max_ref_int - 1, + pub const None = Ref.init(std.math.maxInt(u30), std.math.maxInt(u30), false); + pub const RuntimeRef = Ref.init(std.math.maxInt(u30), std.math.maxInt(u30) - 1, false); + + const source_index_offset = 1; + const inner_index_offset = 1 + 30; + + pub inline fn sourceIndex(this: Ref) Int { + return @truncate(Int, getBits(TotalSize, @enumToInt(this), source_index_offset, 30)); + } + + pub inline fn innerIndex(this: Ref) Int { + return @truncate(Int, getBits(TotalSize, @enumToInt(this), inner_index_offset, 30)); + } + + pub inline fn isSourceContentsSlice(this: Ref) bool { + return (getBits(TotalSize, @enumToInt(this), 0, 1) & 1) != 0; + } + + pub fn atIndex(value: anytype) Ref { + return @intToEnum(Ref, setBits(TotalSize, 0, inner_index_offset, 30, @truncate(Int, value))); + } + + pub fn init(inner_index: Int, source_index: Int, is_source_contents_slice: bool) Ref { + return @intToEnum( + Ref, + setBits( + TotalSize, + 0, + 0, + 1, + @as( + TotalSize, + @boolToInt(is_source_contents_slice), + ), + ) | setBits( + TotalSize, + 0, + source_index_offset, + 30, + source_index, + ) | setBits( + TotalSize, + 0, + inner_index_offset, + 30, + inner_index, + ), + ); + } + + const Old = struct { + inner_index: Int = 0, + source_index: Int = std.math.maxInt(Int), + is_source_contents_slice: bool = false, }; + pub fn initSourceEnd(old: Old) Ref { + return init(old.inner_index, old.source_index, old.is_source_contents_slice); + } pub fn toInt(int: anytype) Int { return @intCast(Int, int); @@ -81,7 +227,7 @@ pub const Ref = packed struct { // but we want to ensure that the value of the unused bits in the u64 are 0 // i have not looked at the assembly to verify that the unused bits default to 0 // so we set it to u64 0 just to be sure - return @as(u64, 0) | @as(u64, key.asBitInt()); + return @as(u64, @enumToInt(key)); } pub inline fn hash64(key: Ref) u64 { @@ -100,10 +246,44 @@ pub const Ref = packed struct { } pub fn jsonStringify(self: *const Ref, options: anytype, writer: anytype) !void { - return try std.json.stringify([2]u32{ self.source_index, self.inner_index }, options, writer); + return try std.json.stringify([2]u32{ self.sourceIndex(), self.innerIndex() }, options, writer); } }; +test "Ref" { + { + const first = .{ .inner_index = 0, .source_index = 1, .is_source_contents_slice = true }; + const ref = Ref.initSourceEnd(.{ .inner_index = 0, .source_index = 1, .is_source_contents_slice = true }); + try std.testing.expectEqual(ref.innerIndex(), first.inner_index); + try std.testing.expectEqual(ref.sourceIndex(), first.source_index); + try std.testing.expectEqual(ref.isSourceContentsSlice(), first.is_source_contents_slice); + } + + { + const first = .{ .inner_index = 100, .source_index = 0, .is_source_contents_slice = true }; + const ref = Ref.initSourceEnd(.{ .inner_index = 100, .source_index = 0, .is_source_contents_slice = true }); + try std.testing.expectEqual(ref.innerIndex(), first.inner_index); + try std.testing.expectEqual(ref.sourceIndex(), first.source_index); + try std.testing.expectEqual(ref.isSourceContentsSlice(), first.is_source_contents_slice); + } + + { + const first = .{ .inner_index = 20, .source_index = 100, .is_source_contents_slice = true }; + const ref = Ref.initSourceEnd(.{ .inner_index = 20, .source_index = 100, .is_source_contents_slice = true }); + try std.testing.expectEqual(ref.innerIndex(), first.inner_index); + try std.testing.expectEqual(ref.sourceIndex(), first.source_index); + try std.testing.expectEqual(ref.isSourceContentsSlice(), first.is_source_contents_slice); + } + + { + const first = .{ .inner_index = 30, .source_index = 100, .is_source_contents_slice = false }; + const ref = Ref.initSourceEnd(.{ .inner_index = 30, .source_index = 100, .is_source_contents_slice = false }); + try std.testing.expectEqual(ref.innerIndex(), first.inner_index); + try std.testing.expectEqual(ref.sourceIndex(), first.source_index); + try std.testing.expectEqual(ref.isSourceContentsSlice(), first.is_source_contents_slice); + } +} + // This is kind of the wrong place, but it's shared between files pub const RequireOrImportMeta = struct { // CommonJS files will return the "require_*" wrapper function and an invalid |