lib/choir/src/core/location.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const Location = union(enum) {
4 unknown: void,
5
6 file: FileLocation,
7
8 file_range: FileRangeLocation,
9
10 name: NameLocation,
11
12 fused: FusedLocation,
13
14 call_site: CallSiteLocation,
15
16 pub const FileLocation = struct {
17 filename: []const u8,
18 line: u32,
19 column: u32,
20 };
21
22 pub const FilePosition = struct {
23 byte: u64,
24 line: u32,
25 column: u32,
26 };
27
28 pub const FileRangeLocation = struct {
29 filename: []const u8,
30 start: FilePosition,
31 end: FilePosition,
32 };
33
34 pub const NameLocation = struct {
35 name: []const u8,
36 child: ?*const Location,
37 };
38
39 pub const FusedLocation = struct {
40 locations: []const Location,
41 metadata: ?*const anyopaque,
42 };
43
44 pub const CallSiteLocation = struct {
45 callee: *const Location,
46 caller: *const Location,
47 };
48
49 pub fn getUnknown() Location {
50 return .{ .unknown = {} };
51 }
52
53 pub fn getFile(filename: []const u8, line: u32, column: u32) Location {
54 return .{ .file = .{
55 .filename = filename,
56 .line = line,
57 .column = column,
58 } };
59 }
60
61 pub fn getFileRange(
62 filename: []const u8,
63 start: FilePosition,
64 end: FilePosition,
65 ) Location {
66 std.debug.assert(start.byte <= end.byte);
67 return .{ .file_range = .{
68 .filename = filename,
69 .start = start,
70 .end = end,
71 } };
72 }
73
74 pub fn getName(n: []const u8, child: ?*const Location) Location {
75 return .{ .name = .{
76 .name = n,
77 .child = child,
78 } };
79 }
80
81 pub fn eql(self: Location, other: Location) bool {
82 const Tag = std.meta.Tag(Location);
83 if (@as(Tag, self) != @as(Tag, other)) return false;
84
85 return switch (self) {
86 .unknown => true,
87 .file => |f| {
88 const o = other.file;
89 return f.line == o.line and f.column == o.column and std.mem.eql(u8, f.filename, o.filename);
90 },
91 .file_range => |range| {
92 const o = other.file_range;
93 return range.start.byte == o.start.byte and
94 range.start.line == o.start.line and
95 range.start.column == o.start.column and
96 range.end.byte == o.end.byte and
97 range.end.line == o.end.line and
98 range.end.column == o.end.column and
99 std.mem.eql(u8, range.filename, o.filename);
100 },
101 .name => |n| {
102 const o = other.name;
103 if (!std.mem.eql(u8, n.name, o.name)) return false;
104 if (n.child == null and o.child == null) return true;
105 if (n.child == null or o.child == null) return false;
106 return n.child.?.eql(o.child.?.*);
107 },
108 .fused => |f| {
109 const o = other.fused;
110 if (f.locations.len != o.locations.len) return false;
111 for (f.locations, o.locations) |a, b| {
112 if (!a.eql(b)) return false;
113 }
114 return f.metadata == o.metadata;
115 },
116 .call_site => |c| {
117 const o = other.call_site;
118 return c.callee.eql(o.callee.*) and c.caller.eql(o.caller.*);
119 },
120 };
121 }
122
123 pub fn format(self: Location, writer: *std.Io.Writer) std.Io.Writer.Error!void {
124 switch (self) {
125 .unknown => try writer.writeAll("loc(unknown)"),
126 .file => |f| try writer.print("loc(\"{s}\":{d}:{d})", .{ f.filename, f.line, f.column }),
127 .file_range => |range| try writer.print(
128 "loc(\"{s}\":{d}:{d}-{d}:{d}@{d}..{d})",
129 .{
130 range.filename,
131 range.start.line,
132 range.start.column,
133 range.end.line,
134 range.end.column,
135 range.start.byte,
136 range.end.byte,
137 },
138 ),
139 .name => |n| try writer.print("loc(\"{s}\")", .{n.name}),
140 .fused => try writer.writeAll("loc(fused)"),
141 .call_site => try writer.writeAll("loc(callsite)"),
142 }
143 }
144 };
145
146 test "file ranges retain half-open byte and line coordinates" {
147 const range = Location.getFileRange(
148 "model.gen",
149 .{ .byte = 4, .line = 2, .column = 3 },
150 .{ .byte = 9, .line = 2, .column = 8 },
151 );
152 try std.testing.expect(range.eql(range));
153 try std.testing.expectEqual(@as(u64, 4), range.file_range.start.byte);
154 try std.testing.expectEqual(@as(u64, 9), range.file_range.end.byte);
155 }