authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-04-26 20:41:37-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-04-26 20:46:28-04:00
log2d6520d5d4cd912cdd20db6cfa155e871efc6f59
tree583e47f4af61d3950b34333d48e738733b5e107a
parentac3946620cebfc706255a86103a14c6cb08cff97
signaturelock-open Commit is signed but in an unrecognized format.

zig fmt is built directly into stage1 rather than child process

Previously, `zig fmt` on the stage1 compiler (which is what we currently ship) would perform what equates to `zig run std/special/fmt_runner.zig` Now, `zig fmt` is implemented with the hybrid zig/C++ strategy outlined by #1964. This means Zig no longer has to ship some of the stage2 .zig files, and there is no longer a delay when running `zig fmt` for the first time.

6 files changed, 284 insertions(+), 301 deletions(-)

CMakeLists.txt-5
...@@ -685,7 +685,6 @@ set(ZIG_STD_FILES...@@ -685,7 +685,6 @@ set(ZIG_STD_FILES
685 "special/compiler_rt/udivmodti4.zig"685 "special/compiler_rt/udivmodti4.zig"
686 "special/compiler_rt/udivti3.zig"686 "special/compiler_rt/udivti3.zig"
687 "special/compiler_rt/umodti3.zig"687 "special/compiler_rt/umodti3.zig"
688 "special/fmt_runner.zig"
689 "special/init-exe/build.zig"688 "special/init-exe/build.zig"
690 "special/init-exe/src/main.zig"689 "special/init-exe/src/main.zig"
691 "special/init-lib/build.zig"690 "special/init-lib/build.zig"
...@@ -6744,7 +6743,3 @@ foreach(file ${ZIG_LIBCXX_FILES})...@@ -6744,7 +6743,3 @@ foreach(file ${ZIG_LIBCXX_FILES})
6744 get_filename_component(file_dir "${LIBCXX_FILES_DEST}/${file}" DIRECTORY)6743 get_filename_component(file_dir "${LIBCXX_FILES_DEST}/${file}" DIRECTORY)
6745 install(FILES "${CMAKE_SOURCE_DIR}/libcxx/${file}" DESTINATION "${file_dir}")6744 install(FILES "${CMAKE_SOURCE_DIR}/libcxx/${file}" DESTINATION "${file_dir}")
6746endforeach()6745endforeach()
6747
6748install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/arg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
6749install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/main.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
6750install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/errmsg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
src-self-hosted/stage1.zig+275
...@@ -2,6 +2,7 @@...@@ -2,6 +2,7 @@
2// The prototypes in src/userland.h must match these definitions.2// The prototypes in src/userland.h must match these definitions.
33
4const std = @import("std");4const std = @import("std");
5const builtin = @import("builtin");
56
6// ABI warning7// ABI warning
7export fn stage2_zen(ptr: *[*]const u8, len: *usize) void {8export fn stage2_zen(ptr: *[*]const u8, len: *usize) void {
...@@ -119,3 +120,277 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {...@@ -119,3 +120,277 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
119 };120 };
120 return Error.None;121 return Error.None;
121}122}
123
124// TODO: just use the actual self-hosted zig fmt. Until the coroutine rewrite, we use a blocking implementation.
125export fn stage2_fmt(argc: c_int, argv: [*]const [*]const u8) c_int {
126 if (std.debug.runtime_safety) {
127 fmtMain(argc, argv) catch unreachable;
128 } else {
129 fmtMain(argc, argv) catch |e| {
130 std.debug.warn("{}\n", @errorName(e));
131 return -1;
132 }
133 }
134 return 0;
135}
136
137fn fmtMain(argc: c_int, argv: [*]const [*]const u8) !void {
138 const allocator = std.heap.c_allocator;
139 var args_list = std.ArrayList([]const u8).init(allocator);
140 const argc_usize = @intCast(usize, argc);
141 var arg_i: usize = 0;
142 while (arg_i < argc_usize) : (arg_i += 1) {
143 try args_list.append(std.mem.toSliceConst(u8, argv[arg_i]));
144 }
145
146 var stdout_file = try std.io.getStdOut();
147 var stdout_out_stream = stdout_file.outStream();
148 stdout = &stdout_out_stream.stream;
149
150 stderr_file = try std.io.getStdErr();
151 var stderr_out_stream = stderr_file.outStream();
152 stderr = &stderr_out_stream.stream;
153
154 const args = args_list.toSliceConst();
155 var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args[2..]);
156 defer flags.deinit();
157
158 if (flags.present("help")) {
159 try stdout.write(self_hosted_main.usage_fmt);
160 os.exit(0);
161 }
162
163 const color = blk: {
164 if (flags.single("color")) |color_flag| {
165 if (mem.eql(u8, color_flag, "auto")) {
166 break :blk errmsg.Color.Auto;
167 } else if (mem.eql(u8, color_flag, "on")) {
168 break :blk errmsg.Color.On;
169 } else if (mem.eql(u8, color_flag, "off")) {
170 break :blk errmsg.Color.Off;
171 } else unreachable;
172 } else {
173 break :blk errmsg.Color.Auto;
174 }
175 };
176
177 if (flags.present("stdin")) {
178 if (flags.positionals.len != 0) {
179 try stderr.write("cannot use --stdin with positional arguments\n");
180 os.exit(1);
181 }
182
183 var stdin_file = try io.getStdIn();
184 var stdin = stdin_file.inStream();
185
186 const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size);
187 defer allocator.free(source_code);
188
189 const tree = std.zig.parse(allocator, source_code) catch |err| {
190 try stderr.print("error parsing stdin: {}\n", err);
191 os.exit(1);
192 };
193 defer tree.deinit();
194
195 var error_it = tree.errors.iterator(0);
196 while (error_it.next()) |parse_error| {
197 try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color);
198 }
199 if (tree.errors.len != 0) {
200 os.exit(1);
201 }
202 if (flags.present("check")) {
203 const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree);
204 const code = if (anything_changed) u8(1) else u8(0);
205 os.exit(code);
206 }
207
208 _ = try std.zig.render(allocator, stdout, tree);
209 return;
210 }
211
212 if (flags.positionals.len == 0) {
213 try stderr.write("expected at least one source file argument\n");
214 os.exit(1);
215 }
216
217 var fmt = Fmt{
218 .seen = Fmt.SeenMap.init(allocator),
219 .any_error = false,
220 .color = color,
221 .allocator = allocator,
222 };
223
224 const check_mode = flags.present("check");
225
226 for (flags.positionals.toSliceConst()) |file_path| {
227 try fmtPath(&fmt, file_path, check_mode);
228 }
229 if (fmt.any_error) {
230 os.exit(1);
231 }
232}
233
234const FmtError = error{
235 SystemResources,
236 OperationAborted,
237 IoPending,
238 BrokenPipe,
239 Unexpected,
240 WouldBlock,
241 FileClosed,
242 DestinationAddressRequired,
243 DiskQuota,
244 FileTooBig,
245 InputOutput,
246 NoSpaceLeft,
247 AccessDenied,
248 OutOfMemory,
249 RenameAcrossMountPoints,
250 ReadOnlyFileSystem,
251 LinkQuotaExceeded,
252 FileBusy,
253} || os.File.OpenError;
254
255fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void {
256 const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref);
257 defer fmt.allocator.free(file_path);
258
259 if (try fmt.seen.put(file_path, {})) |_| return;
260
261 const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) {
262 error.IsDir, error.AccessDenied => {
263 // TODO make event based (and dir.next())
264 var dir = try std.os.Dir.open(fmt.allocator, file_path);
265 defer dir.close();
266
267 while (try dir.next()) |entry| {
268 if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) {
269 const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name });
270 try fmtPath(fmt, full_path, check_mode);
271 }
272 }
273 return;
274 },
275 else => {
276 // TODO lock stderr printing
277 try stderr.print("unable to open '{}': {}\n", file_path, err);
278 fmt.any_error = true;
279 return;
280 },
281 };
282 defer fmt.allocator.free(source_code);
283
284 const tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
285 try stderr.print("error parsing file '{}': {}\n", file_path, err);
286 fmt.any_error = true;
287 return;
288 };
289 defer tree.deinit();
290
291 var error_it = tree.errors.iterator(0);
292 while (error_it.next()) |parse_error| {
293 try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color);
294 }
295 if (tree.errors.len != 0) {
296 fmt.any_error = true;
297 return;
298 }
299
300 if (check_mode) {
301 const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree);
302 if (anything_changed) {
303 try stderr.print("{}\n", file_path);
304 fmt.any_error = true;
305 }
306 } else {
307 const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path);
308 defer baf.destroy();
309
310 const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree);
311 if (anything_changed) {
312 try stderr.print("{}\n", file_path);
313 try baf.finish();
314 }
315 }
316}
317
318const Fmt = struct {
319 seen: SeenMap,
320 any_error: bool,
321 color: errmsg.Color,
322 allocator: *mem.Allocator,
323
324 const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
325};
326
327fn printErrMsgToFile(
328 allocator: *mem.Allocator,
329 parse_error: *const ast.Error,
330 tree: *ast.Tree,
331 path: []const u8,
332 file: os.File,
333 color: errmsg.Color,
334) !void {
335 const color_on = switch (color) {
336 errmsg.Color.Auto => file.isTty(),
337 errmsg.Color.On => true,
338 errmsg.Color.Off => false,
339 };
340 const lok_token = parse_error.loc();
341 const span = errmsg.Span{
342 .first = lok_token,
343 .last = lok_token,
344 };
345
346 const first_token = tree.tokens.at(span.first);
347 const last_token = tree.tokens.at(span.last);
348 const start_loc = tree.tokenLocationPtr(0, first_token);
349 const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
350
351 var text_buf = try std.Buffer.initSize(allocator, 0);
352 var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
353 try parse_error.render(&tree.tokens, out_stream);
354 const text = text_buf.toOwnedSlice();
355
356 const stream = &file.outStream().stream;
357 if (!color_on) {
358 try stream.print(
359 "{}:{}:{}: error: {}\n",
360 path,
361 start_loc.line + 1,
362 start_loc.column + 1,
363 text,
364 );
365 return;
366 }
367
368 try stream.print(
369 "{}:{}:{}: error: {}\n{}\n",
370 path,
371 start_loc.line + 1,
372 start_loc.column + 1,
373 text,
374 tree.source[start_loc.line_start..start_loc.line_end],
375 );
376 try stream.writeByteNTimes(' ', start_loc.column);
377 try stream.writeByteNTimes('~', last_token.end - first_token.start);
378 try stream.write("\n");
379}
380
381const os = std.os;
382const io = std.io;
383const mem = std.mem;
384const Allocator = mem.Allocator;
385const ArrayList = std.ArrayList;
386const Buffer = std.Buffer;
387
388const arg = @import("arg.zig");
389const self_hosted_main = @import("main.zig");
390const Args = arg.Args;
391const Flag = arg.Flag;
392const errmsg = @import("errmsg.zig");
393
394var stderr_file: os.File = undefined;
395var stderr: *io.OutStream(os.File.WriteError) = undefined;
396var stdout: *io.OutStream(os.File.WriteError) = undefined;
src/main.cpp+1-31
...@@ -588,37 +588,7 @@ int main(int argc, char **argv) {...@@ -588,37 +588,7 @@ int main(int argc, char **argv) {
588 }588 }
589 return (term.how == TerminationIdClean) ? term.code : -1;589 return (term.how == TerminationIdClean) ? term.code : -1;
590 } else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) {590 } else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) {
591 init_all_targets();591 return stage2_fmt(argc, argv);
592 ZigTarget target;
593 get_native_target(&target);
594 Buf *zig_lib_dir = (override_lib_dir == nullptr) ? get_zig_lib_dir() : override_lib_dir;
595 Buf *fmt_runner_path = buf_alloc();
596 os_path_join(get_zig_special_dir(zig_lib_dir), buf_create_from_str("fmt_runner.zig"), fmt_runner_path);
597 Buf *cache_dir_buf = buf_create_from_str(cache_dir ? cache_dir : default_zig_cache_name);
598 CodeGen *g = codegen_create(main_pkg_path, fmt_runner_path, &target, OutTypeExe,
599 BuildModeDebug, zig_lib_dir, nullptr, nullptr, cache_dir_buf);
600 g->valgrind_support = valgrind_support;
601 g->want_single_threaded = true;
602 codegen_set_out_name(g, buf_create_from_str("fmt"));
603 g->enable_cache = true;
604
605 codegen_build_and_link(g);
606
607 // TODO standardize os.cpp so that the args are supposed to have the exe
608 ZigList<const char*> args_with_exe = {0};
609 ZigList<const char*> args_without_exe = {0};
610 const char *exec_path = buf_ptr(&g->output_file_path);
611 args_with_exe.append(exec_path);
612 for (int i = 2; i < argc; i += 1) {
613 args_with_exe.append(argv[i]);
614 args_without_exe.append(argv[i]);
615 }
616 args_with_exe.append(nullptr);
617 os_execv(exec_path, args_with_exe.items);
618
619 Termination term;
620 os_spawn_process(exec_path, args_without_exe, &term);
621 return term.code;
622 }592 }
623593
624 for (int i = 1; i < argc; i += 1) {594 for (int i = 1; i < argc; i += 1) {
src/userland.cpp+5
...@@ -37,3 +37,8 @@ void stage2_render_ast(struct Stage2Ast *ast, FILE *output_file) {...@@ -37,3 +37,8 @@ void stage2_render_ast(struct Stage2Ast *ast, FILE *output_file) {
37 const char *msg = "stage0 called stage2_render_ast";37 const char *msg = "stage0 called stage2_render_ast";
38 stage2_panic(msg, strlen(msg));38 stage2_panic(msg, strlen(msg));
39}39}
40
41int stage2_fmt(int argc, char **argv) {
42 const char *msg = "stage0 called stage2_fmt";
43 stage2_panic(msg, strlen(msg));
44}
src/userland.h+3
...@@ -114,4 +114,7 @@ ZIG_EXTERN_C void stage2_zen(const char **ptr, size_t *len);...@@ -114,4 +114,7 @@ ZIG_EXTERN_C void stage2_zen(const char **ptr, size_t *len);
114// ABI warning114// ABI warning
115ZIG_EXTERN_C ZIG_ATTRIBUTE_NORETURN void stage2_panic(const char *ptr, size_t len);115ZIG_EXTERN_C ZIG_ATTRIBUTE_NORETURN void stage2_panic(const char *ptr, size_t len);
116116
117// ABI warning
118ZIG_EXTERN_C int stage2_fmt(int argc, char **argv);
119
117#endif120#endif
std/special/fmt_runner.zig deleted-265
...@@ -1,265 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4const os = std.os;
5const io = std.io;
6const mem = std.mem;
7const Allocator = mem.Allocator;
8const ArrayList = std.ArrayList;
9const Buffer = std.Buffer;
10const ast = std.zig.ast;
11
12const arg = @import("fmt/arg.zig");
13const self_hosted_main = @import("fmt/main.zig");
14const Args = arg.Args;
15const Flag = arg.Flag;
16const errmsg = @import("fmt/errmsg.zig");
17
18var stderr_file: os.File = undefined;
19var stderr: *io.OutStream(os.File.WriteError) = undefined;
20var stdout: *io.OutStream(os.File.WriteError) = undefined;
21
22// This brings `zig fmt` to stage 1.
23pub fn main() !void {
24 // Here we use an ArenaAllocator backed by a DirectAllocator because `zig fmt` is a short-lived,
25 // one shot program. We don't need to waste time freeing memory and finding places to squish
26 // bytes into. So we free everything all at once at the very end.
27 var direct_allocator = std.heap.DirectAllocator.init();
28 var arena = std.heap.ArenaAllocator.init(&direct_allocator.allocator);
29 const allocator = &arena.allocator;
30
31 var stdout_file = try std.io.getStdOut();
32 var stdout_out_stream = stdout_file.outStream();
33 stdout = &stdout_out_stream.stream;
34
35 stderr_file = try std.io.getStdErr();
36 var stderr_out_stream = stderr_file.outStream();
37 stderr = &stderr_out_stream.stream;
38 const args = try std.os.argsAlloc(allocator);
39
40 var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args[1..]);
41 defer flags.deinit();
42
43 if (flags.present("help")) {
44 try stdout.write(self_hosted_main.usage_fmt);
45 os.exit(0);
46 }
47
48 const color = blk: {
49 if (flags.single("color")) |color_flag| {
50 if (mem.eql(u8, color_flag, "auto")) {
51 break :blk errmsg.Color.Auto;
52 } else if (mem.eql(u8, color_flag, "on")) {
53 break :blk errmsg.Color.On;
54 } else if (mem.eql(u8, color_flag, "off")) {
55 break :blk errmsg.Color.Off;
56 } else unreachable;
57 } else {
58 break :blk errmsg.Color.Auto;
59 }
60 };
61
62 if (flags.present("stdin")) {
63 if (flags.positionals.len != 0) {
64 try stderr.write("cannot use --stdin with positional arguments\n");
65 os.exit(1);
66 }
67
68 var stdin_file = try io.getStdIn();
69 var stdin = stdin_file.inStream();
70
71 const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size);
72 defer allocator.free(source_code);
73
74 const tree = std.zig.parse(allocator, source_code) catch |err| {
75 try stderr.print("error parsing stdin: {}\n", err);
76 os.exit(1);
77 };
78 defer tree.deinit();
79
80 var error_it = tree.errors.iterator(0);
81 while (error_it.next()) |parse_error| {
82 try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color);
83 }
84 if (tree.errors.len != 0) {
85 os.exit(1);
86 }
87 if (flags.present("check")) {
88 const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree);
89 const code = if (anything_changed) u8(1) else u8(0);
90 os.exit(code);
91 }
92
93 _ = try std.zig.render(allocator, stdout, tree);
94 return;
95 }
96
97 if (flags.positionals.len == 0) {
98 try stderr.write("expected at least one source file argument\n");
99 os.exit(1);
100 }
101
102 var fmt = Fmt{
103 .seen = Fmt.SeenMap.init(allocator),
104 .any_error = false,
105 .color = color,
106 .allocator = allocator,
107 };
108
109 const check_mode = flags.present("check");
110
111 for (flags.positionals.toSliceConst()) |file_path| {
112 try fmtPath(&fmt, file_path, check_mode);
113 }
114 if (fmt.any_error) {
115 os.exit(1);
116 }
117}
118
119const FmtError = error{
120 SystemResources,
121 OperationAborted,
122 IoPending,
123 BrokenPipe,
124 Unexpected,
125 WouldBlock,
126 FileClosed,
127 DestinationAddressRequired,
128 DiskQuota,
129 FileTooBig,
130 InputOutput,
131 NoSpaceLeft,
132 AccessDenied,
133 OutOfMemory,
134 RenameAcrossMountPoints,
135 ReadOnlyFileSystem,
136 LinkQuotaExceeded,
137 FileBusy,
138} || os.File.OpenError;
139
140fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void {
141 const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref);
142 defer fmt.allocator.free(file_path);
143
144 if (try fmt.seen.put(file_path, {})) |_| return;
145
146 const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) {
147 error.IsDir, error.AccessDenied => {
148 // TODO make event based (and dir.next())
149 var dir = try std.os.Dir.open(fmt.allocator, file_path);
150 defer dir.close();
151
152 while (try dir.next()) |entry| {
153 if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) {
154 const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name });
155 try fmtPath(fmt, full_path, check_mode);
156 }
157 }
158 return;
159 },
160 else => {
161 // TODO lock stderr printing
162 try stderr.print("unable to open '{}': {}\n", file_path, err);
163 fmt.any_error = true;
164 return;
165 },
166 };
167 defer fmt.allocator.free(source_code);
168
169 const tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
170 try stderr.print("error parsing file '{}': {}\n", file_path, err);
171 fmt.any_error = true;
172 return;
173 };
174 defer tree.deinit();
175
176 var error_it = tree.errors.iterator(0);
177 while (error_it.next()) |parse_error| {
178 try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color);
179 }
180 if (tree.errors.len != 0) {
181 fmt.any_error = true;
182 return;
183 }
184
185 if (check_mode) {
186 const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree);
187 if (anything_changed) {
188 try stderr.print("{}\n", file_path);
189 fmt.any_error = true;
190 }
191 } else {
192 // TODO make this evented
193 const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path);
194 defer baf.destroy();
195
196 const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree);
197 if (anything_changed) {
198 try stderr.print("{}\n", file_path);
199 try baf.finish();
200 }
201 }
202}
203
204const Fmt = struct {
205 seen: SeenMap,
206 any_error: bool,
207 color: errmsg.Color,
208 allocator: *mem.Allocator,
209
210 const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
211};
212
213fn printErrMsgToFile(
214 allocator: *mem.Allocator,
215 parse_error: *const ast.Error,
216 tree: *ast.Tree,
217 path: []const u8,
218 file: os.File,
219 color: errmsg.Color,
220) !void {
221 const color_on = switch (color) {
222 errmsg.Color.Auto => file.isTty(),
223 errmsg.Color.On => true,
224 errmsg.Color.Off => false,
225 };
226 const lok_token = parse_error.loc();
227 const span = errmsg.Span{
228 .first = lok_token,
229 .last = lok_token,
230 };
231
232 const first_token = tree.tokens.at(span.first);
233 const last_token = tree.tokens.at(span.last);
234 const start_loc = tree.tokenLocationPtr(0, first_token);
235 const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
236
237 var text_buf = try std.Buffer.initSize(allocator, 0);
238 var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
239 try parse_error.render(&tree.tokens, out_stream);
240 const text = text_buf.toOwnedSlice();
241
242 const stream = &file.outStream().stream;
243 if (!color_on) {
244 try stream.print(
245 "{}:{}:{}: error: {}\n",
246 path,
247 start_loc.line + 1,
248 start_loc.column + 1,
249 text,
250 );
251 return;
252 }
253
254 try stream.print(
255 "{}:{}:{}: error: {}\n{}\n",
256 path,
257 start_loc.line + 1,
258 start_loc.column + 1,
259 text,
260 tree.source[start_loc.line_start..start_loc.line_end],
261 );
262 try stream.writeByteNTimes(' ', start_loc.column);
263 try stream.writeByteNTimes('~', last_token.end - first_token.start);
264 try stream.write("\n");
265}