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 }