lib/choir/src/core/parse.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_arena = @import("alloc_arena");
3
4 const Attribute = @import("attribute.zig").Attribute;
5 const context_mod = @import("context/root.zig");
6 const Context = context_mod.Context;
7 const Location = @import("location.zig").Location;
8 const NamedAttribute = @import("attribute.zig").NamedAttribute;
9 const Operation = @import("operation/root.zig").Operation;
10 const Region = @import("region.zig").Region;
11 const Type = @import("type.zig").Type;
12 const Value = @import("value.zig").Value;
13 const dump = @import("dump.zig");
14 const interfaces = @import("interfaces/root.zig");
15
16 pub const ParseError = error{
17 DuplicateValueId,
18 ExpectedAttributeName,
19 ExpectedBlock,
20 ExpectedByte,
21 ExpectedColon,
22 ExpectedComma,
23 ExpectedEquals,
24 ExpectedHeaderTerminator,
25 ExpectedListEnd,
26 ExpectedNewline,
27 ExpectedOperationName,
28 ExpectedRegion,
29 ExpectedString,
30 ExpectedType,
31 ExpectedValue,
32 InvalidAttribute,
33 InvalidBool,
34 InvalidFloat,
35 InvalidInteger,
36 InvalidType,
37 InvalidValue,
38 MissingValue,
39 TrailingInput,
40 UnexpectedEnd,
41 UnsupportedSuccessors,
42 };
43
44 const ParseTarget = union(enum) {
45 operation: void,
46 region: void,
47 block: *Region,
48 };
49
50 const ParseResult = union(enum) {
51 operation: *Operation,
52 region: *Region,
53 block: void,
54 };
55
56 pub const Source = struct {
57 name: []const u8,
58 text: []const u8,
59 };
60
61 pub fn operation(ctx: *Context, text: []const u8) anyerror!*Operation {
62 return source(ctx, .{ .name = "<string>", .text = text });
63 }
64
65 /// Parses named text with Context-owned filenames and half-open byte ranges.
66 pub fn source(ctx: *Context, input: Source) anyerror!*Operation {
67 var parser = try Parser.init(ctx, input);
68 defer parser.deinit();
69 const op = try parser.parseOperation();
70 parser.skipBlankLines();
71 if (!parser.atEnd()) return ParseError.TrailingInput;
72 return op;
73 }
74
75 const Parser = struct {
76 ctx: *Context,
77 allocator: std.mem.Allocator,
78 operation_allocator: std.mem.Allocator,
79 text: []const u8,
80 source_name: []const u8,
81 line_starts: std.ArrayList(usize),
82 index: usize = 0,
83 values: std.AutoHashMap(usize, *Value),
84
85 fn init(ctx: *Context, input: Source) !Parser {
86 const transient_allocator = context_mod.transientAllocator(ctx);
87 const operation_allocator = context_mod.operationAllocator(ctx);
88 var line_starts: std.ArrayList(usize) = .empty;
89 errdefer line_starts.deinit(transient_allocator);
90 try line_starts.append(transient_allocator, 0);
91 for (input.text, 0..) |byte, index| {
92 if (byte == '\n') try line_starts.append(transient_allocator, index + 1);
93 }
94 const source_name = try operation_allocator.dupe(u8, input.name);
95 return .{
96 .ctx = ctx,
97 .allocator = transient_allocator,
98 .operation_allocator = operation_allocator,
99 .text = input.text,
100 .source_name = source_name,
101 .line_starts = line_starts,
102 .values = std.AutoHashMap(usize, *Value).init(transient_allocator),
103 };
104 }
105
106 fn deinit(self: *Parser) void {
107 self.values.deinit();
108 self.line_starts.deinit(self.allocator);
109 }
110
111 fn position(self: *const Parser, byte: usize) Location.FilePosition {
112 std.debug.assert(byte <= self.text.len);
113 var low: usize = 0;
114 var high = self.line_starts.items.len;
115 while (low + 1 < high) {
116 const middle = low + (high - low) / 2;
117 if (self.line_starts.items[middle] <= byte) {
118 low = middle;
119 } else {
120 high = middle;
121 }
122 }
123 return .{
124 .byte = byte,
125 .line = @intCast(low + 1),
126 .column = @intCast(byte - self.line_starts.items[low] + 1),
127 };
128 }
129
130 fn location(self: *const Parser, start: usize, end: usize) Location {
131 return Location.getFileRange(self.source_name, self.position(start), self.position(end));
132 }
133
134 fn parseOperation(self: *Parser) anyerror!*Operation {
135 const parsed = try self.parseStructure(.{ .operation = {} });
136 return switch (parsed) {
137 .operation => |op| op,
138 else => unreachable,
139 };
140 }
141
142 fn parseStructure(self: *Parser, target: ParseTarget) anyerror!ParseResult {
143 switch (target) {
144 .operation => {
145 self.skipSpaces();
146 const header_start = self.index;
147 const header_length = std.mem.indexOfAny(u8, self.text[header_start..], "\r\n") orelse
148 self.text.len - header_start;
149 const loc = self.location(header_start, header_start + header_length);
150
151 var result_ids: std.ArrayListUnmanaged(usize) = .empty;
152 defer result_ids.deinit(self.allocator);
153 try self.parseResultIds(&result_ids);
154
155 const op_name = try self.parseOperationName();
156 try self.expectByte('(');
157
158 var operands: std.ArrayListUnmanaged(*Value) = .empty;
159 defer operands.deinit(self.allocator);
160 try self.parseOperands(&operands);
161 try self.expectByte(')');
162
163 self.skipSpaces();
164 if (self.consumeArrow()) return ParseError.UnsupportedSuccessors;
165
166 var properties: ?Attribute = null;
167 if (self.consumeLiteral("properties(")) {
168 properties = try self.parseAttribute();
169 self.skipSpaces();
170 try self.expectByte(')');
171 }
172
173 self.skipSpaces();
174 var attrs: std.ArrayListUnmanaged(NamedAttribute) = .empty;
175 defer attrs.deinit(self.allocator);
176 if (self.peekByte() == '{' and !self.peekRegionStart()) {
177 try self.parseAttributes(&attrs);
178 }
179
180 self.skipSpaces();
181 var result_types: std.ArrayListUnmanaged(Type) = .empty;
182 defer result_types.deinit(self.allocator);
183 if (self.consumeByte(':')) {
184 try self.parseTypeListUntilHeaderEnd(&result_types);
185 }
186
187 if (result_ids.items.len != result_types.items.len) return ParseError.InvalidValue;
188
189 var regions: std.ArrayListUnmanaged(*Region) = .empty;
190 defer {
191 for (regions.items) |region| {
192 region.deinit();
193 self.operation_allocator.destroy(region);
194 }
195 regions.deinit(self.allocator);
196 }
197
198 while (true) {
199 self.skipSpaces();
200 if (!self.peekRegionStart()) break;
201 const parsed = try self.parseStructure(.{ .region = {} });
202 const region = switch (parsed) {
203 .region => |region| region,
204 else => unreachable,
205 };
206 try regions.append(self.allocator, region);
207 }
208
209 self.consumeOperationEnd();
210
211 var state = Operation.State.init(op_name, loc);
212 state.addOperands(operands.items);
213 state.addTypes(result_types.items);
214 state.addRawAttributes(attrs.items);
215 if (properties) |payload| try state.setPropertiesAttr(payload);
216 state.addRegionBodies(regions.items);
217
218 const op = try self.ctx.createOperation(state);
219 errdefer op.erase();
220
221 for (result_ids.items, 0..) |id, result_index| {
222 try self.recordValue(id, op.getResult(result_index) orelse return ParseError.InvalidValue);
223 }
224
225 return .{ .operation = op };
226 },
227 .region => {
228 try self.expectByte('{');
229 try self.expectNewline();
230
231 const region = try self.operation_allocator.create(Region);
232 region.* = Region.init(self.operation_allocator);
233 errdefer {
234 region.deinit();
235 self.operation_allocator.destroy(region);
236 }
237
238 while (true) {
239 self.skipSpaces();
240 if (self.consumeByte('}')) break;
241 if (self.atEnd()) return ParseError.UnexpectedEnd;
242 _ = try self.parseStructure(.{ .block = region });
243 }
244
245 return .{ .region = region };
246 },
247 .block => |region| {
248 self.skipSpaces();
249 try self.expectByte('^');
250 try self.expectLiteral("bb");
251 _ = try self.parseUnsigned();
252
253 const block = try region.addBlock();
254
255 if (self.consumeByte('(')) {
256 self.skipSpaces();
257 if (!self.consumeByte(')')) {
258 while (true) {
259 const argument_start = self.index;
260 const id = try self.parseValueId();
261 try self.expectByte(':');
262 self.skipSpaces();
263 const typ = try self.parseType();
264 const loc = self.location(argument_start, self.index);
265 const arg = try block.addArgument(typ, loc);
266 try self.recordValue(id, arg);
267 self.skipSpaces();
268 if (self.consumeByte(')')) break;
269 try self.expectByte(',');
270 self.skipSpaces();
271 }
272 }
273 }
274
275 try self.expectByte(':');
276 try self.expectNewline();
277
278 while (true) {
279 self.skipSpaces();
280 switch (self.peekByte()) {
281 0 => return ParseError.UnexpectedEnd,
282 '^', '}' => return .{ .block = {} },
283 else => {},
284 }
285 const parsed = try self.parseStructure(.{ .operation = {} });
286 const op = switch (parsed) {
287 .operation => |op| op,
288 else => unreachable,
289 };
290 var inserted = false;
291 errdefer if (!inserted) op.erase();
292 try block.addOperation(op);
293 inserted = true;
294 }
295 },
296 }
297 }
298
299 fn parseResultIds(self: *Parser, ids: *std.ArrayListUnmanaged(usize)) !void {
300 if (self.peekByte() != '%') return;
301
302 const start = self.index;
303 while (true) {
304 try ids.append(self.allocator, try self.parseValueId());
305 self.skipSpaces();
306 if (!self.consumeByte(',')) break;
307 self.skipSpaces();
308 }
309 self.skipSpaces();
310 if (self.consumeByte('=')) {
311 self.skipSpaces();
312 return;
313 }
314
315 ids.clearRetainingCapacity();
316 self.index = start;
317 }
318
319 fn parseOperationName(self: *Parser) ![]const u8 {
320 self.skipSpaces();
321 const start = self.index;
322 while (!self.atEnd() and self.peekByte() != '(' and !isNewline(self.peekByte())) {
323 self.index += 1;
324 }
325 const name = std.mem.trim(u8, self.text[start..self.index], " \t\r");
326 if (name.len == 0) return ParseError.ExpectedOperationName;
327 return name;
328 }
329
330 fn parseOperands(self: *Parser, operands: *std.ArrayListUnmanaged(*Value)) !void {
331 self.skipSpaces();
332 if (self.peekByte() == ')') return;
333 while (true) {
334 const id = try self.parseValueId();
335 const value = self.values.get(id) orelse return ParseError.MissingValue;
336 try operands.append(self.allocator, value);
337 self.skipSpaces();
338 if (!self.consumeByte(',')) break;
339 self.skipSpaces();
340 }
341 }
342
343 fn parseAttributes(self: *Parser, attrs: *std.ArrayListUnmanaged(NamedAttribute)) !void {
344 try self.expectByte('{');
345 self.skipSpaces();
346 if (self.consumeByte('}')) return;
347
348 while (true) {
349 const name = try self.parseAttributeName();
350 self.skipSpaces();
351 try self.expectByte('=');
352 self.skipSpaces();
353 const attr = try self.parseAttribute();
354 try attrs.append(self.allocator, .{
355 .name = name,
356 .value = attr,
357 });
358 self.skipSpaces();
359 if (self.consumeByte('}')) return;
360 try self.expectByte(',');
361 self.skipSpaces();
362 }
363 }
364
365 fn parseAttributeName(self: *Parser) ![]const u8 {
366 const start = self.index;
367 while (!self.atEnd()) {
368 const byte = self.peekByte();
369 if (byte == '=' or byte == ' ' or byte == '\t' or byte == '\r' or byte == '\n') break;
370 self.index += 1;
371 }
372 const name = self.text[start..self.index];
373 if (name.len == 0) return ParseError.ExpectedAttributeName;
374 return name;
375 }
376
377 fn parseAttribute(self: *Parser) anyerror!Attribute {
378 self.skipSpaces();
379 if (self.atEnd()) return ParseError.UnexpectedEnd;
380
381 if (self.peekByte() == '"') {
382 const value = try self.parseStringAlloc();
383 defer self.allocator.free(value);
384 return try self.ctx.getStringAttr(value);
385 }
386
387 if (self.consumeLiteral("true")) return try self.ctx.getBoolAttr(true);
388 if (self.consumeLiteral("false")) return try self.ctx.getBoolAttr(false);
389
390 if (self.peekByte() == '@') return try self.parseSymbolRefAttr();
391 if (self.peekByte() == '[') {
392 try self.expectByte('[');
393 self.skipSpaces();
394 if (self.consumeByte(']')) return try self.ctx.getStringListAttr(&.{});
395 if (self.peekByte() == '!') return try self.parseTypeListAttr();
396 if (self.peekByte() == '"') return try self.parseStringListAttr();
397
398 var attrs: std.ArrayListUnmanaged(Attribute) = .empty;
399 defer attrs.deinit(self.allocator);
400 while (true) {
401 try attrs.append(self.allocator, try self.parseAttribute());
402 self.skipSpaces();
403 if (self.consumeByte(']')) break;
404 try self.expectByte(',');
405 self.skipSpaces();
406 }
407 return try self.ctx.getArrayAttr(attrs.items);
408 }
409 if (self.peekByte() == '#') return try self.parseDialectAttr();
410 return try self.parseNumericAttr();
411 }
412
413 fn parseSymbolRefAttr(self: *Parser) !Attribute {
414 try self.expectByte('@');
415 const root = try self.parseSymbolSegment();
416 var nested: std.ArrayListUnmanaged([]const u8) = .empty;
417 defer nested.deinit(self.allocator);
418
419 while (self.consumeLiteral("::")) {
420 try self.expectByte('@');
421 try nested.append(self.allocator, try self.parseSymbolSegment());
422 }
423
424 return try self.ctx.getSymbolRefAttr(root, nested.items);
425 }
426
427 fn parseSymbolSegment(self: *Parser) ![]const u8 {
428 const start = self.index;
429 while (!self.atEnd()) {
430 const byte = self.peekByte();
431 if (byte == ':' or byte == ',' or byte == '}' or byte == ']' or byte == ')' or byte == ' ' or byte == '\t' or byte == '\r' or byte == '\n') break;
432 self.index += 1;
433 }
434 const segment = self.text[start..self.index];
435 if (segment.len == 0) return ParseError.InvalidAttribute;
436 return segment;
437 }
438
439 fn parseTypeListAttr(self: *Parser) anyerror!Attribute {
440 var types: std.ArrayListUnmanaged(Type) = .empty;
441 defer types.deinit(self.allocator);
442 while (true) {
443 try types.append(self.allocator, try self.parseType());
444 self.skipSpaces();
445 if (self.consumeByte(']')) break;
446 try self.expectByte(',');
447 self.skipSpaces();
448 }
449 return try self.ctx.getTypeListAttr(types.items);
450 }
451
452 fn parseStringListAttr(self: *Parser) anyerror!Attribute {
453 var strings: std.ArrayListUnmanaged([]u8) = .empty;
454 defer {
455 for (strings.items) |value| self.allocator.free(value);
456 strings.deinit(self.allocator);
457 }
458 while (true) {
459 try strings.append(self.allocator, try self.parseStringAlloc());
460 self.skipSpaces();
461 if (self.consumeByte(']')) break;
462 try self.expectByte(',');
463 self.skipSpaces();
464 }
465 return try self.ctx.getStringListAttr(strings.items);
466 }
467
468 fn parseDialectAttr(self: *Parser) !Attribute {
469 try self.expectLiteral("#attr<");
470 const name_start = self.index;
471 while (!self.atEnd() and self.peekByte() != '>') self.index += 1;
472 if (self.atEnd()) return ParseError.InvalidAttribute;
473 const name = self.text[name_start..self.index];
474 try self.expectByte('>');
475
476 if (!self.consumeByte('(')) return try self.ctx.getDialectAttr(name, "");
477 const payload = try self.parseStringAlloc();
478 defer self.allocator.free(payload);
479 try self.expectByte(')');
480 return try self.ctx.getDialectAttr(name, payload);
481 }
482
483 fn parseNumericAttr(self: *Parser) !Attribute {
484 const number_start = self.index;
485 if (self.peekByte() == '-') self.index += 1;
486 while (!self.atEnd() and isDigit(self.peekByte())) self.index += 1;
487 if (self.peekByte() == '.') {
488 self.index += 1;
489 while (!self.atEnd() and isDigit(self.peekByte())) self.index += 1;
490 }
491 if (number_start == self.index) return ParseError.InvalidAttribute;
492 const number = self.text[number_start..self.index];
493
494 try self.expectByte(':');
495 if (self.consumeByte('i')) {
496 const width = try self.parseUnsigned();
497 const value = std.fmt.parseInt(i64, number, 10) catch return ParseError.InvalidInteger;
498 return try self.ctx.getIntegerAttr(value, @intCast(width), true);
499 }
500 if (self.consumeByte('f')) {
501 const width = try self.parseUnsigned();
502 const value = std.fmt.parseFloat(f64, number) catch return ParseError.InvalidFloat;
503 return try self.ctx.getFloatAttr(value, @intCast(width));
504 }
505 return ParseError.InvalidAttribute;
506 }
507
508 fn parseTypeListUntilHeaderEnd(self: *Parser, types: *std.ArrayListUnmanaged(Type)) !void {
509 while (true) {
510 self.skipSpaces();
511 try types.append(self.allocator, try self.parseType());
512 self.skipSpaces();
513 if (!self.consumeByte(',')) return;
514 }
515 }
516
517 fn parseType(self: *Parser) !Type {
518 self.skipSpaces();
519 try self.expectByte('!');
520 const name_start = self.index;
521 while (!self.atEnd()) {
522 const byte = self.peekByte();
523 if (byte == '<' or byte == ',' or byte == ')' or byte == ']' or byte == '}' or byte == ' ' or byte == '\t' or byte == '\r' or byte == '\n') break;
524 self.index += 1;
525 }
526 const name = self.text[name_start..self.index];
527 if (name.len == 0) return ParseError.ExpectedType;
528
529 var key: []const u8 = "";
530 if (self.consumeByte('<')) {
531 const key_start = self.index;
532 while (!self.atEnd() and self.peekByte() != '>') self.index += 1;
533 if (self.atEnd()) return ParseError.InvalidType;
534 key = self.text[key_start..self.index];
535 try self.expectByte('>');
536 }
537
538 return try self.ctx.getDialectTypeFromNameWithKey(name, key);
539 }
540
541 fn parseStringAlloc(self: *Parser) ![]u8 {
542 try self.expectByte('"');
543 var out: std.ArrayListUnmanaged(u8) = .empty;
544 errdefer out.deinit(self.allocator);
545
546 while (true) {
547 if (self.atEnd()) return ParseError.ExpectedString;
548 const byte = self.nextByte();
549 if (byte == '"') return try out.toOwnedSlice(self.allocator);
550 if (byte != '\\') {
551 try out.append(self.allocator, byte);
552 continue;
553 }
554 if (self.atEnd()) return ParseError.ExpectedString;
555 const escaped = self.nextByte();
556 try out.append(self.allocator, switch (escaped) {
557 '"' => '"',
558 '\\' => '\\',
559 'n' => '\n',
560 'r' => '\r',
561 't' => '\t',
562 else => return ParseError.ExpectedString,
563 });
564 }
565 }
566
567 fn parseValueId(self: *Parser) !usize {
568 try self.expectByte('%');
569 return try self.parseUnsigned();
570 }
571
572 fn parseUnsigned(self: *Parser) !usize {
573 const start = self.index;
574 while (!self.atEnd() and isDigit(self.peekByte())) self.index += 1;
575 if (start == self.index) return ParseError.InvalidInteger;
576 return std.fmt.parseUnsigned(usize, self.text[start..self.index], 10) catch ParseError.InvalidInteger;
577 }
578
579 fn recordValue(self: *Parser, id: usize, value: *Value) !void {
580 if (self.values.contains(id)) return ParseError.DuplicateValueId;
581 try self.values.put(id, value);
582 }
583
584 fn consumeOperationEnd(self: *Parser) void {
585 self.skipSpaces();
586 _ = self.consumeByte('\n');
587 }
588
589 fn consumeArrow(self: *Parser) bool {
590 const start = self.index;
591 if (!self.consumeByte('-')) return false;
592 if (self.consumeByte('>')) return true;
593 self.index = start;
594 return false;
595 }
596
597 fn consumeByte(self: *Parser, byte: u8) bool {
598 if (self.peekByte() != byte) return false;
599 self.index += 1;
600 return true;
601 }
602
603 fn consumeLiteral(self: *Parser, literal: []const u8) bool {
604 if (!std.mem.startsWith(u8, self.text[self.index..], literal)) return false;
605 self.index += literal.len;
606 return true;
607 }
608
609 fn expectByte(self: *Parser, byte: u8) !void {
610 if (!self.consumeByte(byte)) return ParseError.ExpectedByte;
611 }
612
613 fn expectLiteral(self: *Parser, literal: []const u8) !void {
614 if (!self.consumeLiteral(literal)) return ParseError.ExpectedByte;
615 }
616
617 fn expectNewline(self: *Parser) !void {
618 if (self.consumeByte('\n')) return;
619 return ParseError.ExpectedNewline;
620 }
621
622 fn nextByte(self: *Parser) u8 {
623 const byte = self.text[self.index];
624 self.index += 1;
625 return byte;
626 }
627
628 fn peekByte(self: *const Parser) u8 {
629 if (self.index >= self.text.len) return 0;
630 return self.text[self.index];
631 }
632
633 fn peekRegionStart(self: *const Parser) bool {
634 if (self.peekByte() != '{') return false;
635 const next = self.index + 1;
636 return next < self.text.len and self.text[next] == '\n';
637 }
638
639 fn skipBlankLines(self: *Parser) void {
640 while (!self.atEnd()) {
641 self.skipSpaces();
642 if (!self.consumeByte('\n')) return;
643 }
644 }
645
646 fn skipSpaces(self: *Parser) void {
647 while (!self.atEnd()) {
648 switch (self.peekByte()) {
649 ' ', '\t', '\r' => self.index += 1,
650 else => return,
651 }
652 }
653 }
654
655 fn atEnd(self: *const Parser) bool {
656 return self.index >= self.text.len;
657 }
658 };
659
660 fn isDigit(byte: u8) bool {
661 return byte >= '0' and byte <= '9';
662 }
663
664 fn isNewline(byte: u8) bool {
665 return byte == '\n' or byte == '\r';
666 }
667
668 const TextProperties = struct {
669 payload: ?Attribute = null,
670
671 fn from(storage: *anyopaque) *@This() {
672 return @ptrCast(@alignCast(storage));
673 }
674
675 fn fromConst(storage: *const anyopaque) *const @This() {
676 return @ptrCast(@alignCast(storage));
677 }
678
679 fn init(storage: *anyopaque, _: std.mem.Allocator) anyerror!void {
680 from(storage).* = .{};
681 }
682
683 fn deinit(_: *anyopaque, _: std.mem.Allocator) void {}
684
685 fn get(_: *const Operation, storage: *const anyopaque, name: []const u8) ?Attribute {
686 const payload = fromConst(storage).payload orelse return null;
687 const values = payload.cast(Attribute.ArrayAttr).?.values;
688 if (std.mem.eql(u8, name, "left")) return values[0];
689 if (std.mem.eql(u8, name, "right")) return values[1];
690 return null;
691 }
692
693 fn getProperties(_: *const Operation, storage: *const anyopaque) ?Attribute {
694 return fromConst(storage).payload;
695 }
696
697 fn setProperties(_: *Operation, storage: *anyopaque, attr: Attribute) anyerror!void {
698 const array = attr.cast(Attribute.ArrayAttr) orelse return error.InvalidTestProperties;
699 if (array.values.len != 2) return error.InvalidTestProperties;
700 from(storage).payload = attr;
701 }
702
703 fn copyProperties(dest: *anyopaque, original: *const anyopaque) anyerror!void {
704 from(dest).* = fromConst(original).*;
705 }
706
707 const model = interfaces.OperationPropertiesModel{
708 .name = "test.text.properties",
709 .size = @sizeOf(@This()),
710 .alignment = std.mem.Alignment.fromByteUnits(@alignOf(@This())),
711 .init = init,
712 .deinit = deinit,
713 .getInherentAttr = get,
714 .getPropertiesAsAttr = getProperties,
715 .setPropertiesFromAttr = setProperties,
716 .copyProperties = copyProperties,
717 };
718 };
719
720 fn registerTextProperties(context: *Context) !void {
721 try context.allowUnregistered();
722 _ = try context.registerOperation("test.text_properties", .{});
723 try context.registerOperationInherentAttributeNames(
724 "test.text_properties",
725 &.{ "left", "right" },
726 );
727 try context.registerOperationPropertiesModel(
728 "test.text_properties",
729 TextProperties.model,
730 );
731 }
732
733 test "Choir parse round-trips properties and raw shadows" {
734 const testing = std.testing;
735 var arena = alloc_arena.Arena.init(testing.allocator);
736 defer arena.deinit();
737 const allocator = arena.allocator();
738
739 var context = try Context.init(allocator, Context.Limits.testing);
740 defer context.deinit(allocator);
741 try registerTextProperties(&context);
742
743 const left = try context.getI64Attr(11);
744 const right = try context.getI64Attr(22);
745 const payload = try context.getArrayAttr(&.{ left, right });
746 const raw_left = try context.getI64Attr(99);
747 const note = try context.getStringAttr("kept");
748 const raw_attributes = [_]NamedAttribute{
749 .{ .name = "left", .value = raw_left },
750 .{ .name = "debug.note", .value = note },
751 };
752 var state = Operation.State.init("test.text_properties", Location.getUnknown());
753 state.addRawAttributes(&raw_attributes);
754 try state.setPropertiesAttr(payload);
755 const original = try context.createOperation(state);
756
757 const text = try dump.operationAlloc(allocator, original);
758 const parsed = try operation(&context, text);
759 defer parsed.erase();
760 const reparsed = try dump.operationAlloc(allocator, parsed);
761 try testing.expectEqualStrings(text, reparsed);
762 try testing.expect(std.mem.indexOf(u8, text, "properties([11:i64, 22:i64])") != null);
763 try testing.expectEqual(
764 @as(i64, 11),
765 parsed.getAttr("left").?.cast(Attribute.IntegerAttr).?.value,
766 );
767 try testing.expectEqual(
768 @as(i64, 22),
769 parsed.getAttr("right").?.cast(Attribute.IntegerAttr).?.value,
770 );
771 try testing.expectEqual(@as(usize, 2), parsed.getRawDictionaryAttrs().len);
772 try testing.expectEqual(
773 @as(i64, 99),
774 parsed.raw_dictionary_attrs.get("left").?.cast(Attribute.IntegerAttr).?.value,
775 );
776 }
777
778 test "Choir parse owns source names and block argument ranges" {
779 const testing = std.testing;
780 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
781 defer ctx.deinit(testing.allocator);
782 try ctx.allowUnregistered();
783 var name = "arguments.choir".*;
784 const text =
785 \\test.container() {
786 \\ ^bb0(%0: !test.é, %1: !test.ty):
787 \\ test.use(%0, %1)
788 \\}
789 \\
790 ;
791 const parsed = try source(&ctx, .{ .name = &name, .text = text });
792 defer parsed.erase();
793 @memset(&name, '?');
794 const block = parsed.getRegion(0).?.getEntryBlock().?;
795 const loc = block.getArgumentLocation(1).?;
796 try testing.expect(loc == .file_range);
797 const range = loc.file_range;
798 try testing.expectEqualStrings("arguments.choir", range.filename);
799 try testing.expectEqualDeep(
800 Location.FilePosition{ .byte = 40, .line = 2, .column = 22 },
801 range.start,
802 );
803 try testing.expectEqualDeep(
804 Location.FilePosition{ .byte = 52, .line = 2, .column = 34 },
805 range.end,
806 );
807 try testing.expectEqualStrings("%1: !test.ty", text[range.start.byte..range.end.byte]);
808 try testing.expectEqualStrings("arguments.choir", parsed.location.file_range.filename);
809 var ops = block.getOperations();
810 const user = ops.next().?;
811 try testing.expectEqualStrings("arguments.choir", user.location.file_range.filename);
812 }
813
814 test "Choir parse convenience names an unnamed source" {
815 const testing = std.testing;
816 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
817 defer ctx.deinit(testing.allocator);
818 try ctx.allowUnregistered();
819 const parsed = try operation(&ctx, " test.noop()\n");
820 defer parsed.erase();
821 try testing.expect(parsed.location == .file_range);
822 const range = parsed.location.file_range;
823 try testing.expectEqualStrings("<string>", range.filename);
824 try testing.expectEqualDeep(
825 Location.FilePosition{ .byte = 2, .line = 1, .column = 3 },
826 range.start,
827 );
828 try testing.expectEqualDeep(
829 Location.FilePosition{ .byte = 13, .line = 1, .column = 14 },
830 range.end,
831 );
832 }