| ... | ... | @@ -6,8 +6,7 @@ const zir = @import("zir.zig"); |
| 6 | 6 | const Package = @import("Package.zig"); |
| 7 | 7 | |
| 8 | 8 | test "self-hosted" { |
| 9 | | var ctx: TestContext = undefined; |
| 10 | | try ctx.init(); |
| 9 | var ctx = TestContext.init(); |
| 11 | 10 | defer ctx.deinit(); |
| 12 | 11 | |
| 13 | 12 | try @import("stage2_tests").addCases(&ctx); |
| ... | ... | @@ -15,46 +14,152 @@ test "self-hosted" { |
| 15 | 14 | try ctx.run(); |
| 16 | 15 | } |
| 17 | 16 | |
| 17 | const ErrorMsg = struct { |
| 18 | msg: []const u8, |
| 19 | line: u32, |
| 20 | column: u32, |
| 21 | }; |
| 22 | |
| 18 | 23 | pub const TestContext = struct { |
| 24 | // TODO: remove these. They are deprecated. |
| 19 | 25 | zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), |
| 20 | | zir_transform_cases: std.ArrayList(ZIRTransformCase), |
| 21 | 26 | |
| 27 | /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases) |
| 28 | zir_cases: std.ArrayList(ZIRCase), |
| 29 | |
| 30 | // TODO: remove |
| 22 | 31 | pub const ZIRCompareOutputCase = struct { |
| 23 | 32 | name: []const u8, |
| 24 | 33 | src_list: []const []const u8, |
| 25 | 34 | expected_stdout_list: []const []const u8, |
| 26 | 35 | }; |
| 27 | 36 | |
| 28 | | pub const ZIRTransformCase = struct { |
| 29 | | name: []const u8, |
| 30 | | cross_target: std.zig.CrossTarget, |
| 31 | | updates: std.ArrayList(Update), |
| 32 | | |
| 33 | | pub const Update = struct { |
| 34 | | expected: Expected, |
| 35 | | src: [:0]const u8, |
| 36 | | }; |
| 37 | pub const ZIRUpdateType = enum { |
| 38 | /// A transformation update transforms the input ZIR and tests against |
| 39 | /// the expected output |
| 40 | Transformation, |
| 41 | /// An error update attempts to compile bad code, and ensures that it |
| 42 | /// fails to compile, and for the expected reasons |
| 43 | Error, |
| 44 | /// An execution update compiles and runs the input ZIR, feeding in |
| 45 | /// provided input and ensuring that the outputs match what is expected |
| 46 | Execution, |
| 47 | /// A compilation update checks that the ZIR compiles without any issues |
| 48 | Compiles, |
| 49 | }; |
| 37 | 50 | |
| 38 | | pub const Expected = union(enum) { |
| 39 | | zir: []const u8, |
| 40 | | errors: []const []const u8, |
| 41 | | }; |
| 51 | pub const ZIRUpdate = struct { |
| 52 | /// The input to the current update. We simulate an incremental update |
| 53 | /// with the file's contents changed to this value each update. |
| 54 | /// |
| 55 | /// This value can change entirely between updates, which would be akin |
| 56 | /// to deleting the source file and creating a new one from scratch; or |
| 57 | /// you can keep it mostly consistent, with small changes, testing the |
| 58 | /// effects of the incremental compilation. |
| 59 | src: [:0]const u8, |
| 60 | case: union(ZIRUpdateType) { |
| 61 | /// The expected output ZIR |
| 62 | Transformation: [:0]const u8, |
| 63 | /// A slice containing the expected errors *in sequential order*. |
| 64 | Error: []const ErrorMsg, |
| 65 | |
| 66 | /// Input to feed to the program, and expected outputs. |
| 67 | /// |
| 68 | /// If stdout, stderr, and exit_code are all null, addZIRCase will |
| 69 | /// discard the test. To test for successful compilation, use a |
| 70 | /// dedicated Compile update instead. |
| 71 | Execution: struct { |
| 72 | stdin: ?[]const u8, |
| 73 | stdout: ?[]const u8, |
| 74 | stderr: ?[]const u8, |
| 75 | exit_code: ?u8, |
| 76 | }, |
| 77 | /// A Compiles test checks only that compilation of the given ZIR |
| 78 | /// succeeds. To test outputs, use an Execution test. It is good to |
| 79 | /// use a Compiles test before an Execution, as the overhead should |
| 80 | /// be low (due to incremental compilation) and TODO: provide a way |
| 81 | /// to check changed / new / etc decls in testing mode |
| 82 | /// (usingnamespace a debug info struct with a comptime flag?) |
| 83 | Compiles: void, |
| 84 | }, |
| 85 | }; |
| 42 | 86 | |
| 43 | | pub fn addZIR(case: *ZIRTransformCase, src: [:0]const u8, zir_text: []const u8) void { |
| 44 | | case.updates.append(.{ |
| 87 | /// A ZIRCase consists of a set of *updates*. A update can transform ZIR, |
| 88 | /// compile it, ensure that compilation fails, and more. The same Module is |
| 89 | /// used for each update, so each update's source is treated as a single file |
| 90 | /// being updated by the test harness and incrementally compiled. |
| 91 | pub const ZIRCase = struct { |
| 92 | name: []const u8, |
| 93 | /// The platform the ZIR targets. For non-native platforms, an emulator |
| 94 | /// such as QEMU is required for tests to complete. |
| 95 | target: std.zig.CrossTarget, |
| 96 | updates: std.ArrayList(ZIRUpdate), |
| 97 | |
| 98 | /// Adds a subcase in which the module is updated with new ZIR, and the |
| 99 | /// resulting ZIR is validated. |
| 100 | pub fn addTransform(self: *ZIRCase, src: [:0]const u8, result: [:0]const u8) void { |
| 101 | self.updates.append(.{ |
| 45 | 102 | .src = src, |
| 46 | | .expected = .{ .zir = zir_text }, |
| 103 | .case = .{ .Transformation = result }, |
| 47 | 104 | }) catch unreachable; |
| 48 | 105 | } |
| 49 | 106 | |
| 50 | | pub fn addError(case: *ZIRTransformCase, src: [:0]const u8, errors: []const []const u8) void { |
| 51 | | case.updates.append(.{ |
| 52 | | .src = src, |
| 53 | | .expected = .{ .errors = errors }, |
| 54 | | }) catch unreachable; |
| 107 | /// Adds a subcase in which the module is updated with invalid ZIR, and |
| 108 | /// ensures that compilation fails for the expected reasons. |
| 109 | /// |
| 110 | /// Errors must be specified in sequential order. |
| 111 | pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { |
| 112 | var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; |
| 113 | for (errors) |e, i| { |
| 114 | if (e[0] != ':') { |
| 115 | std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); |
| 116 | } |
| 117 | var cur = e[1..]; |
| 118 | var line_index = std.mem.indexOf(u8, cur, ":"); |
| 119 | if (line_index == null) { |
| 120 | std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); |
| 121 | } |
| 122 | const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); |
| 123 | cur = cur[line_index.? + 1 ..]; |
| 124 | const column_index = std.mem.indexOf(u8, cur, ":"); |
| 125 | if (column_index == null) { |
| 126 | std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); |
| 127 | } |
| 128 | const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); |
| 129 | cur = cur[column_index.? + 2 ..]; |
| 130 | if (!std.mem.eql(u8, cur[0..7], "error: ")) { |
| 131 | std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); |
| 132 | } |
| 133 | const msg = cur[7..]; |
| 134 | |
| 135 | if (line == 0 or column == 0) { |
| 136 | @panic("Invalid test: error line and column must be specified starting at one!"); |
| 137 | } |
| 138 | |
| 139 | array[i] = .{ |
| 140 | .msg = msg, |
| 141 | .line = line - 1, |
| 142 | .column = column - 1, |
| 143 | }; |
| 144 | } |
| 145 | self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable; |
| 55 | 146 | } |
| 56 | 147 | }; |
| 57 | 148 | |
| 149 | pub fn addZIRMulti( |
| 150 | ctx: *TestContext, |
| 151 | name: []const u8, |
| 152 | target: std.zig.CrossTarget, |
| 153 | ) *ZIRCase { |
| 154 | const case = ZIRCase{ |
| 155 | .name = name, |
| 156 | .target = target, |
| 157 | .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), |
| 158 | }; |
| 159 | ctx.zir_cases.append(case) catch unreachable; |
| 160 | return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; |
| 161 | } |
| 162 | |
| 58 | 163 | pub fn addZIRCompareOutput( |
| 59 | 164 | ctx: *TestContext, |
| 60 | 165 | name: []const u8, |
| ... | ... | @@ -71,70 +176,164 @@ pub const TestContext = struct { |
| 71 | 176 | pub fn addZIRTransform( |
| 72 | 177 | ctx: *TestContext, |
| 73 | 178 | name: []const u8, |
| 74 | | cross_target: std.zig.CrossTarget, |
| 179 | target: std.zig.CrossTarget, |
| 75 | 180 | src: [:0]const u8, |
| 76 | | expected_zir: []const u8, |
| 181 | result: [:0]const u8, |
| 77 | 182 | ) void { |
| 78 | | const case = ctx.zir_transform_cases.addOne() catch unreachable; |
| 79 | | case.* = .{ |
| 80 | | .name = name, |
| 81 | | .cross_target = cross_target, |
| 82 | | .updates = std.ArrayList(ZIRTransformCase.Update).init(std.heap.page_allocator), |
| 83 | | }; |
| 84 | | case.updates.append(.{ |
| 85 | | .src = src, |
| 86 | | .expected = .{ .zir = expected_zir }, |
| 87 | | }) catch unreachable; |
| 183 | var c = ctx.addZIRMulti(name, target); |
| 184 | c.addTransform(src, result); |
| 88 | 185 | } |
| 89 | 186 | |
| 90 | | pub fn addZIRMulti( |
| 187 | pub fn addZIRError( |
| 91 | 188 | ctx: *TestContext, |
| 92 | 189 | name: []const u8, |
| 93 | | cross_target: std.zig.CrossTarget, |
| 94 | | ) *ZIRTransformCase { |
| 95 | | const case = ctx.zir_transform_cases.addOne() catch unreachable; |
| 96 | | case.* = .{ |
| 97 | | .name = name, |
| 98 | | .cross_target = cross_target, |
| 99 | | .updates = std.ArrayList(ZIRTransformCase.Update).init(std.heap.page_allocator), |
| 100 | | }; |
| 101 | | return case; |
| 190 | target: std.zig.CrossTarget, |
| 191 | src: [:0]const u8, |
| 192 | expected_errors: []const []const u8, |
| 193 | ) void { |
| 194 | var c = ctx.addZIRMulti(name, target); |
| 195 | c.addError(src, expected_errors); |
| 102 | 196 | } |
| 103 | 197 | |
| 104 | | fn init(self: *TestContext) !void { |
| 105 | | self.* = .{ |
| 106 | | .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(std.heap.page_allocator), |
| 107 | | .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(std.heap.page_allocator), |
| 198 | fn init() TestContext { |
| 199 | const allocator = std.heap.page_allocator; |
| 200 | return .{ |
| 201 | .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), |
| 202 | .zir_cases = std.ArrayList(ZIRCase).init(allocator), |
| 108 | 203 | }; |
| 109 | 204 | } |
| 110 | 205 | |
| 111 | 206 | fn deinit(self: *TestContext) void { |
| 112 | 207 | self.zir_cmp_output_cases.deinit(); |
| 113 | | self.zir_transform_cases.deinit(); |
| 208 | for (self.zir_cases.items) |c| { |
| 209 | for (c.updates.items) |u| { |
| 210 | if (u.case == .Error) { |
| 211 | c.updates.allocator.free(u.case.Error); |
| 212 | } |
| 213 | } |
| 214 | c.updates.deinit(); |
| 215 | } |
| 216 | self.zir_cases.deinit(); |
| 114 | 217 | self.* = undefined; |
| 115 | 218 | } |
| 116 | 219 | |
| 117 | 220 | fn run(self: *TestContext) !void { |
| 118 | 221 | var progress = std.Progress{}; |
| 119 | | const root_node = try progress.start("zir", self.zir_cmp_output_cases.items.len + |
| 120 | | self.zir_transform_cases.items.len); |
| 222 | const root_node = try progress.start("zir", self.zir_cases.items.len); |
| 121 | 223 | defer root_node.end(); |
| 122 | 224 | |
| 123 | 225 | const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{}); |
| 124 | 226 | |
| 125 | | for (self.zir_cmp_output_cases.items) |case| { |
| 227 | for (self.zir_cases.items) |case| { |
| 126 | 228 | std.testing.base_allocator_instance.reset(); |
| 127 | | try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); |
| 229 | const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); |
| 230 | try self.runOneZIRCase(std.testing.allocator, root_node, case, info.target); |
| 128 | 231 | try std.testing.allocator_instance.validate(); |
| 129 | 232 | } |
| 130 | | for (self.zir_transform_cases.items) |case| { |
| 233 | |
| 234 | // TODO: wipe the rest of this function |
| 235 | for (self.zir_cmp_output_cases.items) |case| { |
| 131 | 236 | std.testing.base_allocator_instance.reset(); |
| 132 | | const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); |
| 133 | | try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, info.target); |
| 237 | try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); |
| 134 | 238 | try std.testing.allocator_instance.validate(); |
| 135 | 239 | } |
| 136 | 240 | } |
| 137 | 241 | |
| 242 | fn runOneZIRCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void { |
| 243 | var tmp = std.testing.tmpDir(.{}); |
| 244 | defer tmp.cleanup(); |
| 245 | |
| 246 | const tmp_src_path = "test_case.zir"; |
| 247 | const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); |
| 248 | defer root_pkg.destroy(); |
| 249 | |
| 250 | var prg_node = root_node.start(case.name, case.updates.items.len); |
| 251 | prg_node.activate(); |
| 252 | defer prg_node.end(); |
| 253 | |
| 254 | var module = try Module.init(allocator, .{ |
| 255 | .target = target, |
| 256 | // This is an Executable, as opposed to e.g. a *library*. This does |
| 257 | // not mean no ZIR is generated. |
| 258 | // |
| 259 | // TODO: support tests for object file building, and library builds |
| 260 | // and linking. This will require a rework to support multi-file |
| 261 | // tests. |
| 262 | .output_mode = .Obj, |
| 263 | // TODO: support testing optimizations |
| 264 | .optimize_mode = .Debug, |
| 265 | .bin_file_dir = tmp.dir, |
| 266 | .bin_file_path = "test_case.o", |
| 267 | .root_pkg = root_pkg, |
| 268 | }); |
| 269 | defer module.deinit(); |
| 270 | |
| 271 | for (case.updates.items) |update| { |
| 272 | var update_node = prg_node.start("update", 4); |
| 273 | update_node.activate(); |
| 274 | defer update_node.end(); |
| 275 | |
| 276 | var sync_node = update_node.start("write", null); |
| 277 | sync_node.activate(); |
| 278 | try tmp.dir.writeFile(tmp_src_path, update.src); |
| 279 | sync_node.end(); |
| 280 | |
| 281 | var module_node = update_node.start("parse/analysis/codegen", null); |
| 282 | module_node.activate(); |
| 283 | try module.update(); |
| 284 | module_node.end(); |
| 285 | |
| 286 | switch (update.case) { |
| 287 | .Transformation => |expected_output| { |
| 288 | var emit_node = update_node.start("emit", null); |
| 289 | emit_node.activate(); |
| 290 | var new_zir_module = try zir.emit(allocator, module); |
| 291 | defer new_zir_module.deinit(allocator); |
| 292 | emit_node.end(); |
| 293 | |
| 294 | var write_node = update_node.start("write", null); |
| 295 | write_node.activate(); |
| 296 | var out_zir = std.ArrayList(u8).init(allocator); |
| 297 | defer out_zir.deinit(); |
| 298 | try new_zir_module.writeToStream(allocator, out_zir.outStream()); |
| 299 | write_node.end(); |
| 300 | |
| 301 | std.testing.expectEqualSlices(u8, expected_output, out_zir.items); |
| 302 | }, |
| 303 | .Error => |e| { |
| 304 | var handled_errors = try allocator.alloc(bool, e.len); |
| 305 | defer allocator.free(handled_errors); |
| 306 | for (handled_errors) |*h| { |
| 307 | h.* = false; |
| 308 | } |
| 309 | var all_errors = try module.getAllErrorsAlloc(); |
| 310 | defer all_errors.deinit(allocator); |
| 311 | for (all_errors.list) |a| { |
| 312 | for (e) |ex, i| { |
| 313 | if (a.line == ex.line and a.column == ex.column and std.mem.eql(u8, ex.msg, a.msg)) { |
| 314 | handled_errors[i] = true; |
| 315 | break; |
| 316 | } |
| 317 | } else { |
| 318 | std.debug.warn("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); |
| 319 | std.process.exit(1); |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | for (handled_errors) |h, i| { |
| 324 | if (!h) { |
| 325 | const er = e[i]; |
| 326 | std.debug.warn("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg }); |
| 327 | std.process.exit(1); |
| 328 | } |
| 329 | } |
| 330 | }, |
| 331 | |
| 332 | else => return error.unimplemented, |
| 333 | } |
| 334 | } |
| 335 | } |
| 336 | |
| 138 | 337 | fn runOneZIRCmpOutputCase( |
| 139 | 338 | self: *TestContext, |
| 140 | 339 | allocator: *Allocator, |
| ... | ... | @@ -208,118 +407,4 @@ pub const TestContext = struct { |
| 208 | 407 | } |
| 209 | 408 | } |
| 210 | 409 | } |
| 211 | | |
| 212 | | fn runOneZIRTransformCase( |
| 213 | | self: *TestContext, |
| 214 | | allocator: *Allocator, |
| 215 | | root_node: *std.Progress.Node, |
| 216 | | case: ZIRTransformCase, |
| 217 | | target: std.Target, |
| 218 | | ) !void { |
| 219 | | var tmp = std.testing.tmpDir(.{}); |
| 220 | | defer tmp.cleanup(); |
| 221 | | |
| 222 | | var update_node = root_node.start(case.name, case.updates.items.len); |
| 223 | | update_node.activate(); |
| 224 | | defer update_node.end(); |
| 225 | | |
| 226 | | const tmp_src_path = "test-case.zir"; |
| 227 | | const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); |
| 228 | | defer root_pkg.destroy(); |
| 229 | | |
| 230 | | var module = try Module.init(allocator, .{ |
| 231 | | .target = target, |
| 232 | | .output_mode = .Obj, |
| 233 | | .optimize_mode = .Debug, |
| 234 | | .bin_file_dir = tmp.dir, |
| 235 | | .bin_file_path = "test-case.o", |
| 236 | | .root_pkg = root_pkg, |
| 237 | | }); |
| 238 | | defer module.deinit(); |
| 239 | | |
| 240 | | for (case.updates.items) |update| { |
| 241 | | var prg_node = update_node.start("", 3); |
| 242 | | prg_node.activate(); |
| 243 | | defer prg_node.end(); |
| 244 | | |
| 245 | | try tmp.dir.writeFile(tmp_src_path, update.src); |
| 246 | | |
| 247 | | var module_node = prg_node.start("parse/analysis/codegen", null); |
| 248 | | module_node.activate(); |
| 249 | | try module.update(); |
| 250 | | module_node.end(); |
| 251 | | |
| 252 | | switch (update.expected) { |
| 253 | | .zir => |expected_zir| { |
| 254 | | var emit_node = prg_node.start("emit", null); |
| 255 | | emit_node.activate(); |
| 256 | | var new_zir_module = try zir.emit(allocator, module); |
| 257 | | defer new_zir_module.deinit(allocator); |
| 258 | | emit_node.end(); |
| 259 | | |
| 260 | | var write_node = prg_node.start("write", null); |
| 261 | | write_node.activate(); |
| 262 | | var out_zir = std.ArrayList(u8).init(allocator); |
| 263 | | defer out_zir.deinit(); |
| 264 | | try new_zir_module.writeToStream(allocator, out_zir.outStream()); |
| 265 | | write_node.end(); |
| 266 | | |
| 267 | | std.testing.expectEqualSlices(u8, expected_zir, out_zir.items); |
| 268 | | }, |
| 269 | | .errors => |expected_errors| { |
| 270 | | var all_errors = try module.getAllErrorsAlloc(); |
| 271 | | defer all_errors.deinit(module.allocator); |
| 272 | | for (expected_errors) |expected_error| { |
| 273 | | for (all_errors.list) |full_err_msg| { |
| 274 | | const text = try std.fmt.allocPrint(allocator, ":{}:{}: error: {}", .{ |
| 275 | | full_err_msg.line + 1, |
| 276 | | full_err_msg.column + 1, |
| 277 | | full_err_msg.msg, |
| 278 | | }); |
| 279 | | defer allocator.free(text); |
| 280 | | if (std.mem.eql(u8, text, expected_error)) { |
| 281 | | break; |
| 282 | | } |
| 283 | | } else { |
| 284 | | std.debug.warn( |
| 285 | | "{}\nExpected this error:\n================\n{}\n================\nBut found these errors:\n================\n", |
| 286 | | .{ case.name, expected_error }, |
| 287 | | ); |
| 288 | | for (all_errors.list) |full_err_msg| { |
| 289 | | std.debug.warn(":{}:{}: error: {}\n", .{ |
| 290 | | full_err_msg.line + 1, |
| 291 | | full_err_msg.column + 1, |
| 292 | | full_err_msg.msg, |
| 293 | | }); |
| 294 | | } |
| 295 | | std.debug.warn("================\nTest failed\n", .{}); |
| 296 | | std.process.exit(1); |
| 297 | | } |
| 298 | | } |
| 299 | | }, |
| 300 | | } |
| 301 | | } |
| 302 | | } |
| 303 | 410 | }; |
| 304 | | |
| 305 | | fn debugPrintErrors(src: []const u8, errors: var) void { |
| 306 | | std.debug.warn("\n", .{}); |
| 307 | | var nl = true; |
| 308 | | var line: usize = 1; |
| 309 | | for (src) |byte| { |
| 310 | | if (nl) { |
| 311 | | std.debug.warn("{: >3}| ", .{line}); |
| 312 | | nl = false; |
| 313 | | } |
| 314 | | if (byte == '\n') { |
| 315 | | nl = true; |
| 316 | | line += 1; |
| 317 | | } |
| 318 | | std.debug.warn("{c}", .{byte}); |
| 319 | | } |
| 320 | | std.debug.warn("\n", .{}); |
| 321 | | for (errors) |err_msg| { |
| 322 | | const loc = std.zig.findLineColumn(src, err_msg.byte_offset); |
| 323 | | std.debug.warn("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, err_msg.msg }); |
| 324 | | } |
| 325 | | } |