lib/choir/src/core/attribute.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const interfaces = @import("interfaces/root.zig");
3 const Type = @import("type.zig").Type;
4
5 pub const Attribute = struct {
6 attr_id: AttrID,
7
8 impl: *const anyopaque,
9
10 abstract: *const interfaces.AbstractAttribute,
11
12 pub const AttrID = enum(u32) {
13 invalid = 0,
14 _,
15 };
16
17 pub const first_dynamic_attr_id: u32 = @backingInt(AttrID.invalid) + 1;
18
19 pub const DialectAttr = struct {
20 payload: []const u8,
21 context: *const anyopaque,
22 };
23
24 pub const IntegerAttr = struct {
25 value: i64,
26 width: u8,
27 is_signed: bool,
28 context: *const anyopaque,
29
30 pub fn getValue(self: *const IntegerAttr) i64 {
31 return self.value;
32 }
33
34 pub fn getUnsignedValue(self: *const IntegerAttr) u64 {
35 return @bitCast(self.value);
36 }
37 };
38
39 pub const FloatAttr = struct {
40 value: f64,
41 width: u8,
42 context: *const anyopaque,
43
44 pub fn getValue(self: *const FloatAttr) f64 {
45 return self.value;
46 }
47
48 pub fn getF32Value(self: *const FloatAttr) f32 {
49 return @floatCast(self.value);
50 }
51 };
52
53 pub const BoolAttr = struct {
54 value: bool,
55 context: *const anyopaque,
56
57 pub fn getValue(self: *const BoolAttr) bool {
58 return self.value;
59 }
60 };
61
62 pub const StringAttr = struct {
63 value: []const u8,
64 context: *const anyopaque,
65
66 pub fn getValue(self: *const StringAttr) []const u8 {
67 return self.value;
68 }
69 };
70
71 pub const SymbolRefAttr = struct {
72 root_reference: []const u8,
73 nested_references: []const []const u8,
74 context: *const anyopaque,
75
76 pub fn getRootReference(self: *const SymbolRefAttr) []const u8 {
77 return self.root_reference;
78 }
79
80 pub fn getNestedReferences(self: *const SymbolRefAttr) []const []const u8 {
81 return self.nested_references;
82 }
83
84 pub fn getLeafReference(self: *const SymbolRefAttr) []const u8 {
85 if (self.nested_references.len == 0) return self.root_reference;
86 return self.nested_references[self.nested_references.len - 1];
87 }
88
89 pub fn isFlat(self: *const SymbolRefAttr) bool {
90 return self.nested_references.len == 0;
91 }
92 };
93
94 pub const StringListAttr = struct {
95 values: []const []const u8,
96 context: *const anyopaque,
97
98 pub fn getValues(self: *const StringListAttr) []const []const u8 {
99 return self.values;
100 }
101 };
102
103 pub const TypeListAttr = struct {
104 values: []const Type,
105 context: *const anyopaque,
106
107 pub fn getValues(self: *const TypeListAttr) []const Type {
108 return self.values;
109 }
110 };
111
112 pub const ArrayAttr = struct {
113 values: []const Attribute,
114 context: *const anyopaque,
115
116 pub fn getValues(self: *const ArrayAttr) []const Attribute {
117 return self.values;
118 }
119 };
120
121 pub fn attrIdFromInt(id: u32) AttrID {
122 return @fromBackingInt(@intCast(id));
123 }
124
125 pub fn isa(self: Attribute, comptime attr_id: AttrID) bool {
126 return self.attr_id == attr_id;
127 }
128
129 pub fn eql(self: Attribute, other: Attribute) bool {
130 if (self.attr_id != other.attr_id) return false;
131 if (self.impl == other.impl) return true;
132 if (self.getInterface(interfaces.AttributeEqualInterface)) |vtable| {
133 return vtable.eql(self.impl, other.impl);
134 }
135 return false;
136 }
137
138 pub fn getInterface(self: Attribute, comptime IFace: type) ?*const IFace.VTable {
139 if (self.abstract.getInterface(IFace.id)) |iface| {
140 return @ptrCast(@alignCast(iface));
141 }
142 return null;
143 }
144
145 pub fn InterfaceHandle(comptime IFace: type) type {
146 return struct {
147 attr: Attribute,
148 vtable: *const IFace.VTable,
149
150 fn returnType(comptime fn_ptr_type: type) type {
151 const ptr_info = @typeInfo(fn_ptr_type);
152 const fn_type = switch (ptr_info) {
153 .pointer => |p| p.child,
154 else => @compileError("expected interface vtable field to be a function pointer"),
155 };
156 const fn_info = switch (@typeInfo(fn_type)) {
157 .@"fn" => |f| f,
158 else => @compileError("expected interface vtable field to be a function pointer"),
159 };
160 return fn_info.return_type orelse @compileError("generic interface vtable methods are not supported");
161 }
162
163 fn methodFnPtrType(comptime method: std.meta.FieldEnum(IFace.VTable)) type {
164 const dummy: IFace.VTable = undefined;
165 return @TypeOf(@field(dummy, @tagName(method)));
166 }
167
168 pub inline fn call(
169 self: @This(),
170 comptime method: std.meta.FieldEnum(IFace.VTable),
171 args: anytype,
172 ) returnType(methodFnPtrType(method)) {
173 const fn_ptr = @field(self.vtable, @tagName(method));
174 return @call(.auto, fn_ptr, .{self.attr.impl} ++ args);
175 }
176 };
177 }
178
179 pub fn interface(self: Attribute, comptime IFace: type) ?InterfaceHandle(IFace) {
180 const vtable = self.getInterface(IFace) orelse return null;
181 return .{ .attr = self, .vtable = vtable };
182 }
183
184 pub fn getAbstractAttribute(self: Attribute) *const interfaces.AbstractAttribute {
185 return self.abstract;
186 }
187
188 pub fn hasStorageType(self: Attribute, comptime T: type) bool {
189 return storageTypeMatchesName(T, self.abstract.name);
190 }
191
192 pub fn cast(self: Attribute, comptime T: type) ?*const T {
193 if (!self.hasStorageType(T)) return null;
194 return @ptrCast(@alignCast(self.impl));
195 }
196
197 pub fn format(self: Attribute, writer: *std.Io.Writer) std.Io.Writer.Error!void {
198 if (self.getInterface(interfaces.AttributePrintInterface)) |vtable| {
199 return vtable.print(self.impl, writer);
200 }
201
202 if (self.abstract.name.len > 0) {
203 try writer.print("#attr<{s}>", .{self.abstract.name});
204 } else {
205 try writer.print("#attr<{d}>", .{@backingInt(self.attr_id)});
206 }
207 }
208 };
209
210 pub const NamedAttribute = struct {
211 name: []const u8,
212 value: Attribute,
213
214 pub fn format(self: NamedAttribute, writer: *std.Io.Writer) std.Io.Writer.Error!void {
215 try writer.print("{s} = {f}", .{ self.name, self.value });
216 }
217 };
218
219 pub const NamedAttributeList = struct {
220 const small_capacity = 4;
221
222 len: usize = 0,
223 owned_items: []NamedAttribute = &.{},
224 small_items: [small_capacity]NamedAttribute = undefined,
225
226 pub const Lookup = struct {
227 found: bool,
228 index: usize,
229 };
230
231 pub fn items(self: *const NamedAttributeList) []const NamedAttribute {
232 if (self.owned_items.len != 0) return self.owned_items[0..self.len];
233 return self.small_items[0..self.len];
234 }
235
236 pub fn capacity(self: *const NamedAttributeList) usize {
237 if (self.owned_items.len != 0) return self.owned_items.len;
238 return small_capacity;
239 }
240
241 fn storage(self: *NamedAttributeList) []NamedAttribute {
242 if (self.owned_items.len != 0) return self.owned_items;
243 return self.small_items[0..];
244 }
245
246 pub fn deinit(self: *NamedAttributeList, allocator: std.mem.Allocator) void {
247 if (self.owned_items.len != 0) allocator.free(self.owned_items);
248 self.len = 0;
249 self.owned_items = &.{};
250 }
251
252 pub fn clearRetainingCapacity(self: *NamedAttributeList) void {
253 self.len = 0;
254 }
255
256 pub fn ensureTotalCapacity(
257 self: *NamedAttributeList,
258 allocator: std.mem.Allocator,
259 target_capacity: usize,
260 ) std.mem.Allocator.Error!void {
261 if (target_capacity <= self.capacity()) return;
262 if (self.owned_items.len != 0) {
263 self.owned_items = try allocator.realloc(self.owned_items, target_capacity);
264 return;
265 }
266 const allocated_items = try allocator.alloc(NamedAttribute, target_capacity);
267 @memcpy(allocated_items[0..self.len], self.small_items[0..self.len]);
268 self.owned_items = allocated_items;
269 }
270
271 pub fn get(self: *const NamedAttributeList, name: []const u8) ?Attribute {
272 const result = self.findIndexOrInsertPos(name);
273 if (!result.found) return null;
274 return self.items()[result.index].value;
275 }
276
277 pub fn getNamed(self: *const NamedAttributeList, name: []const u8) ?NamedAttribute {
278 const result = self.findIndexOrInsertPos(name);
279 if (!result.found) return null;
280 return self.items()[result.index];
281 }
282
283 pub fn set(
284 self: *NamedAttributeList,
285 allocator: std.mem.Allocator,
286 attr_name: []const u8,
287 value: Attribute,
288 ) !?Attribute {
289 const result = self.findIndexOrInsertPos(attr_name);
290 if (result.found) {
291 const storage_items = self.storage();
292 const previous = storage_items[result.index].value;
293 storage_items[result.index].value = value;
294 return previous;
295 }
296 const required_capacity = std.math.add(usize, self.len, 1) catch
297 return error.OutOfMemory;
298 if (required_capacity > self.capacity()) {
299 try self.ensureTotalCapacity(
300 allocator,
301 @max(required_capacity, self.capacity() *| 2),
302 );
303 }
304 const storage_items = self.storage();
305 std.mem.copyBackwards(NamedAttribute, storage_items[result.index + 1 .. self.len + 1], storage_items[result.index..self.len]);
306 storage_items[result.index] = .{ .name = attr_name, .value = value };
307 self.len += 1;
308 return null;
309 }
310
311 pub fn erase(self: *NamedAttributeList, attr_name: []const u8) ?Attribute {
312 const result = self.findIndexOrInsertPos(attr_name);
313 if (!result.found) return null;
314 const storage_items = self.storage();
315 const previous = storage_items[result.index].value;
316 std.mem.copyForwards(NamedAttribute, storage_items[result.index .. self.len - 1], storage_items[result.index + 1 .. self.len]);
317 self.len -= 1;
318 return previous;
319 }
320
321 pub fn findIndexOrInsertPos(self: *const NamedAttributeList, name: []const u8) Lookup {
322 var left: usize = 0;
323 const current_items = self.items();
324 var right: usize = current_items.len;
325 while (left < right) {
326 const mid = left + (right - left) / 2;
327 const cmp = std.mem.order(u8, current_items[mid].name, name);
328 switch (cmp) {
329 .eq => return .{ .found = true, .index = mid },
330 .lt => left = mid + 1,
331 .gt => right = mid,
332 }
333 }
334 return .{ .found = false, .index = left };
335 }
336 };
337
338 test "NamedAttributeList keeps dictionary semantics" {
339 const testing = std.testing;
340 const TestAttr = struct {
341 const abstract = interfaces.AbstractAttribute{
342 .attr_id = 1,
343 .name = "test.attr",
344 .interfaces = &.{},
345 };
346 const alpha: u8 = 1;
347 const beta: u8 = 2;
348 const gamma: u8 = 3;
349
350 fn value(comptime attr_id: u32, ptr: *const u8) Attribute {
351 return .{
352 .attr_id = Attribute.attrIdFromInt(attr_id),
353 .impl = ptr,
354 .abstract = &abstract,
355 };
356 }
357 };
358
359 const alpha = TestAttr.value(1, &TestAttr.alpha);
360 const beta = TestAttr.value(2, &TestAttr.beta);
361 const gamma = TestAttr.value(3, &TestAttr.gamma);
362
363 var list = NamedAttributeList{};
364 defer list.deinit(testing.allocator);
365
366 try list.ensureTotalCapacity(testing.allocator, 8);
367 try testing.expect(list.capacity() >= 8);
368
369 try testing.expect(try list.set(testing.allocator, "gamma", gamma) == null);
370 try testing.expect(try list.set(testing.allocator, "alpha", alpha) == null);
371 try testing.expect(try list.set(testing.allocator, "beta", beta) == null);
372
373 try testing.expectEqualStrings("alpha", list.items()[0].name);
374 try testing.expectEqualStrings("beta", list.items()[1].name);
375 try testing.expectEqualStrings("gamma", list.items()[2].name);
376 try testing.expectEqual(alpha.impl, list.get("alpha").?.impl);
377
378 const previous = (try list.set(testing.allocator, "beta", gamma)).?;
379 try testing.expectEqual(beta.impl, previous.impl);
380 try testing.expectEqual(gamma.impl, list.get("beta").?.impl);
381
382 const erased = list.erase("alpha").?;
383 try testing.expectEqual(alpha.impl, erased.impl);
384 try testing.expect(list.erase("missing") == null);
385 try testing.expect(list.get("alpha") == null);
386 try testing.expectEqual(@as(usize, 2), list.items().len);
387 }
388
389 test "NamedAttributeList stores small dictionaries without allocation" {
390 const testing = std.testing;
391 const TestAttr = struct {
392 const abstract = interfaces.AbstractAttribute{
393 .attr_id = 1,
394 .name = "test.attr",
395 .interfaces = &.{},
396 };
397 const alpha: u8 = 1;
398
399 fn value(ptr: *const u8) Attribute {
400 return .{
401 .attr_id = Attribute.attrIdFromInt(1),
402 .impl = ptr,
403 .abstract = &abstract,
404 };
405 }
406 };
407
408 const attr = TestAttr.value(&TestAttr.alpha);
409 var list = NamedAttributeList{};
410 defer list.deinit(testing.failing_allocator);
411
412 try testing.expect(try list.set(testing.failing_allocator, "delta", attr) == null);
413 try testing.expect(try list.set(testing.failing_allocator, "alpha", attr) == null);
414 try testing.expect(try list.set(testing.failing_allocator, "gamma", attr) == null);
415 try testing.expect(try list.set(testing.failing_allocator, "beta", attr) == null);
416
417 const attrs = list.items();
418 try testing.expectEqual(@as(usize, 4), attrs.len);
419 try testing.expectEqualStrings("alpha", attrs[0].name);
420 try testing.expectEqualStrings("beta", attrs[1].name);
421 try testing.expectEqualStrings("delta", attrs[2].name);
422 try testing.expectEqualStrings("gamma", attrs[3].name);
423
424 try testing.expectError(error.OutOfMemory, list.set(testing.failing_allocator, "epsilon", attr));
425 try testing.expectEqual(@as(usize, 4), list.items().len);
426 }
427
428 test "NamedAttributeList grows spilled dictionaries geometrically" {
429 const testing = std.testing;
430 const TestAttr = struct {
431 const abstract = interfaces.AbstractAttribute{
432 .attr_id = 1,
433 .name = "test.attr",
434 .interfaces = &.{},
435 };
436 const payload: u8 = 1;
437
438 fn value() Attribute {
439 return .{
440 .attr_id = Attribute.attrIdFromInt(1),
441 .impl = &payload,
442 .abstract = &abstract,
443 };
444 }
445 };
446
447 var failing = testing.FailingAllocator.init(testing.allocator, .{});
448 var list = NamedAttributeList{};
449 defer list.deinit(failing.allocator());
450
451 const names = [_][]const u8{
452 "alpha", "beta", "gamma", "delta", "epsilon",
453 "zeta", "eta", "theta", "iota",
454 };
455 for (names[0..5]) |name| {
456 try testing.expect(try list.set(failing.allocator(), name, TestAttr.value()) == null);
457 }
458 try testing.expectEqual(@as(usize, 8), list.capacity());
459
460 failing.fail_index = failing.alloc_index;
461 failing.resize_fail_index = failing.resize_index;
462 for (names[5..8]) |name| {
463 try testing.expect(try list.set(failing.allocator(), name, TestAttr.value()) == null);
464 }
465 try testing.expectError(
466 error.OutOfMemory,
467 list.set(failing.allocator(), names[8], TestAttr.value()),
468 );
469 try testing.expectEqual(@as(usize, 8), list.items().len);
470
471 failing.fail_index = std.math.maxInt(usize);
472 failing.resize_fail_index = std.math.maxInt(usize);
473 try testing.expect(try list.set(
474 failing.allocator(),
475 names[8],
476 TestAttr.value(),
477 ) == null);
478 try testing.expectEqual(@as(usize, 16), list.capacity());
479 }
480
481 test "Attribute.cast checks comptime storage type" {
482 const testing = std.testing;
483
484 var context_token: u8 = 0;
485 const integer_abstract = interfaces.AbstractAttribute{
486 .attr_id = @backingInt(Attribute.AttrID.invalid) + 1,
487 .name = builtin_attr_names.integer,
488 .interfaces = &.{},
489 };
490 const string_abstract = interfaces.AbstractAttribute{
491 .attr_id = @backingInt(Attribute.AttrID.invalid) + 2,
492 .name = builtin_attr_names.string,
493 .interfaces = &.{},
494 };
495 const dialect_abstract = interfaces.AbstractAttribute{
496 .attr_id = @backingInt(Attribute.AttrID.invalid) + 3,
497 .name = "test.flag",
498 .interfaces = &.{},
499 };
500
501 const integer_storage = Attribute.IntegerAttr{
502 .value = 42,
503 .width = 64,
504 .is_signed = true,
505 .context = &context_token,
506 };
507 const string_storage = Attribute.StringAttr{
508 .value = "name",
509 .context = &context_token,
510 };
511 const dialect_storage = Attribute.DialectAttr{
512 .payload = "payload",
513 .context = &context_token,
514 };
515
516 const int_attr = Attribute{
517 .attr_id = Attribute.attrIdFromInt(integer_abstract.attr_id),
518 .impl = &integer_storage,
519 .abstract = &integer_abstract,
520 };
521 const string_attr = Attribute{
522 .attr_id = Attribute.attrIdFromInt(string_abstract.attr_id),
523 .impl = &string_storage,
524 .abstract = &string_abstract,
525 };
526 const dialect_attr = Attribute{
527 .attr_id = Attribute.attrIdFromInt(dialect_abstract.attr_id),
528 .impl = &dialect_storage,
529 .abstract = &dialect_abstract,
530 };
531
532 try testing.expect(int_attr.hasStorageType(Attribute.IntegerAttr));
533 try testing.expect(int_attr.cast(Attribute.IntegerAttr) != null);
534 try testing.expect(int_attr.cast(Attribute.StringAttr) == null);
535 try testing.expect(int_attr.cast(Attribute.DialectAttr) == null);
536
537 try testing.expect(string_attr.cast(Attribute.StringAttr) != null);
538 try testing.expect(string_attr.cast(Attribute.IntegerAttr) == null);
539
540 try testing.expect(dialect_attr.hasStorageType(Attribute.DialectAttr));
541 try testing.expect(dialect_attr.cast(Attribute.DialectAttr) != null);
542 try testing.expect(dialect_attr.cast(Attribute.BoolAttr) == null);
543 }
544
545 pub const builtin_attr_names = struct {
546 pub const integer = "builtin.integer";
547 pub const float_ = "builtin.float";
548 pub const bool_ = "builtin.bool";
549 pub const string = "builtin.string";
550 pub const symbol_ref = "builtin.symbol_ref";
551 pub const string_list = "builtin.string_list";
552 pub const type_list = "builtin.type_list";
553 pub const array = "builtin.array";
554 };
555
556 const builtin_attr_name_values = [_][]const u8{
557 builtin_attr_names.integer,
558 builtin_attr_names.float_,
559 builtin_attr_names.bool_,
560 builtin_attr_names.string,
561 builtin_attr_names.symbol_ref,
562 builtin_attr_names.string_list,
563 builtin_attr_names.type_list,
564 builtin_attr_names.array,
565 };
566
567 pub fn isBuiltinAttributeName(name: []const u8) bool {
568 inline for (builtin_attr_name_values) |builtin_name| {
569 if (std.mem.eql(u8, name, builtin_name)) return true;
570 }
571 return false;
572 }
573
574 fn storageTypeMatchesName(comptime T: type, name: []const u8) bool {
575 if (T == Attribute.DialectAttr) {
576 return name.len > 0 and !isBuiltinAttributeName(name);
577 }
578 if (T == Attribute.IntegerAttr) return std.mem.eql(u8, name, builtin_attr_names.integer);
579 if (T == Attribute.FloatAttr) return std.mem.eql(u8, name, builtin_attr_names.float_);
580 if (T == Attribute.BoolAttr) return std.mem.eql(u8, name, builtin_attr_names.bool_);
581 if (T == Attribute.StringAttr) return std.mem.eql(u8, name, builtin_attr_names.string);
582 if (T == Attribute.SymbolRefAttr) return std.mem.eql(u8, name, builtin_attr_names.symbol_ref);
583 if (T == Attribute.StringListAttr) return std.mem.eql(u8, name, builtin_attr_names.string_list);
584 if (T == Attribute.TypeListAttr) return std.mem.eql(u8, name, builtin_attr_names.type_list);
585 if (T == Attribute.ArrayAttr) return std.mem.eql(u8, name, builtin_attr_names.array);
586 @compileError("unsupported attribute storage type: " ++ @typeName(T));
587 }
588
589 const EqlVTable = interfaces.AttributeEqualInterface.VTable;
590
591 fn dialect_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
592 const a: *const Attribute.DialectAttr = @ptrCast(@alignCast(self_impl));
593 const b: *const Attribute.DialectAttr = @ptrCast(@alignCast(other_impl));
594 return std.mem.eql(u8, a.payload, b.payload);
595 }
596
597 fn integer_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
598 const a: *const Attribute.IntegerAttr = @ptrCast(@alignCast(self_impl));
599 const b: *const Attribute.IntegerAttr = @ptrCast(@alignCast(other_impl));
600 return a.value == b.value and a.width == b.width and a.is_signed == b.is_signed;
601 }
602
603 fn float_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
604 const a: *const Attribute.FloatAttr = @ptrCast(@alignCast(self_impl));
605 const b: *const Attribute.FloatAttr = @ptrCast(@alignCast(other_impl));
606 return @as(u64, @bitCast(a.value)) == @as(u64, @bitCast(b.value)) and a.width == b.width;
607 }
608
609 fn bool_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
610 const a: *const Attribute.BoolAttr = @ptrCast(@alignCast(self_impl));
611 const b: *const Attribute.BoolAttr = @ptrCast(@alignCast(other_impl));
612 return a.value == b.value;
613 }
614
615 fn string_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
616 const a: *const Attribute.StringAttr = @ptrCast(@alignCast(self_impl));
617 const b: *const Attribute.StringAttr = @ptrCast(@alignCast(other_impl));
618 return std.mem.eql(u8, a.value, b.value);
619 }
620
621 fn symbol_ref_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
622 const a: *const Attribute.SymbolRefAttr = @ptrCast(@alignCast(self_impl));
623 const b: *const Attribute.SymbolRefAttr = @ptrCast(@alignCast(other_impl));
624 if (!std.mem.eql(u8, a.root_reference, b.root_reference)) return false;
625 if (a.nested_references.len != b.nested_references.len) return false;
626 for (a.nested_references, b.nested_references) |lhs, rhs| {
627 if (!std.mem.eql(u8, lhs, rhs)) return false;
628 }
629 return true;
630 }
631
632 fn write_symbol_ref_attr(
633 self_impl: *const anyopaque,
634 writer: *std.Io.Writer,
635 ) std.Io.Writer.Error!void {
636 const attr: *const Attribute.SymbolRefAttr = @ptrCast(@alignCast(self_impl));
637 try writer.print("@{s}", .{attr.root_reference});
638 for (attr.nested_references) |nested| {
639 try writer.print("::@{s}", .{nested});
640 }
641 }
642
643 fn string_list_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
644 const a: *const Attribute.StringListAttr = @ptrCast(@alignCast(self_impl));
645 const b: *const Attribute.StringListAttr = @ptrCast(@alignCast(other_impl));
646 if (a.values.len != b.values.len) return false;
647 for (a.values, b.values) |lhs, rhs| {
648 if (!std.mem.eql(u8, lhs, rhs)) return false;
649 }
650 return true;
651 }
652
653 fn type_list_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
654 const a: *const Attribute.TypeListAttr = @ptrCast(@alignCast(self_impl));
655 const b: *const Attribute.TypeListAttr = @ptrCast(@alignCast(other_impl));
656 if (a.values.len != b.values.len) return false;
657 for (a.values, b.values) |lhs, rhs| {
658 if (!lhs.eql(rhs)) return false;
659 }
660 return true;
661 }
662
663 fn array_attr_eql(self_impl: *const anyopaque, other_impl: *const anyopaque) bool {
664 const a: *const Attribute.ArrayAttr = @ptrCast(@alignCast(self_impl));
665 const b: *const Attribute.ArrayAttr = @ptrCast(@alignCast(other_impl));
666 if (a.values.len != b.values.len) return false;
667 for (a.values, b.values) |lhs, rhs| {
668 if (!lhs.eql(rhs)) return false;
669 }
670 return true;
671 }
672
673 fn array_attr_count(attr_ptr: *const anyopaque) usize {
674 const attr: *const Attribute.ArrayAttr = @ptrCast(@alignCast(attr_ptr));
675 return attr.values.len;
676 }
677
678 fn array_attr_element(attr_ptr: *const anyopaque, index: usize) ?Attribute {
679 const attr: *const Attribute.ArrayAttr = @ptrCast(@alignCast(attr_ptr));
680 if (index >= attr.values.len) return null;
681 return attr.values[index];
682 }
683
684 pub const dialect_attr_eql_vtable = EqlVTable{
685 .eql = dialect_attr_eql,
686 };
687
688 pub const integer_attr_eql_vtable = EqlVTable{
689 .eql = integer_attr_eql,
690 };
691
692 pub const float_attr_eql_vtable = EqlVTable{
693 .eql = float_attr_eql,
694 };
695
696 pub const bool_attr_eql_vtable = EqlVTable{
697 .eql = bool_attr_eql,
698 };
699
700 pub const string_attr_eql_vtable = EqlVTable{
701 .eql = string_attr_eql,
702 };
703
704 pub const symbol_ref_attr_eql_vtable = EqlVTable{
705 .eql = symbol_ref_attr_eql,
706 };
707
708 pub const symbol_ref_attr_print_vtable = interfaces.AttributePrintInterface.VTable{
709 .print = write_symbol_ref_attr,
710 };
711
712 pub const string_list_attr_eql_vtable = EqlVTable{
713 .eql = string_list_attr_eql,
714 };
715
716 pub const type_list_attr_eql_vtable = EqlVTable{
717 .eql = type_list_attr_eql,
718 };
719
720 pub const array_attr_eql_vtable = EqlVTable{
721 .eql = array_attr_eql,
722 };
723
724 pub const array_attr_array_vtable = interfaces.AttributeArrayInterface.VTable{
725 .getCount = array_attr_count,
726 .getElement = array_attr_element,
727 };
728
729 pub const default_abstract = interfaces.AbstractAttribute{
730 .attr_id = 0,
731 .name = "",
732 .interfaces = &.{},
733 };