mirror of
https://github.com/ziglang/zig.git
synced 2024-11-27 23:52:31 +00:00
870 lines
32 KiB
Zig
870 lines
32 KiB
Zig
const std = @import("std");
|
|
const io = std.io;
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const process = std.process;
|
|
const Allocator = mem.Allocator;
|
|
const ArrayList = std.ArrayList;
|
|
const ast = std.zig.ast;
|
|
const Module = @import("Module.zig");
|
|
const link = @import("link.zig");
|
|
const Package = @import("Package.zig");
|
|
const zir = @import("zir.zig");
|
|
|
|
// TODO Improve async I/O enough that we feel comfortable doing this.
|
|
//pub const io_mode = .evented;
|
|
|
|
pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
|
|
|
|
pub const Color = enum {
|
|
Auto,
|
|
Off,
|
|
On,
|
|
};
|
|
|
|
const usage =
|
|
\\Usage: zig [command] [options]
|
|
\\
|
|
\\Commands:
|
|
\\
|
|
\\ build-exe [source] Create executable from source or object files
|
|
\\ build-lib [source] Create library from source or object files
|
|
\\ build-obj [source] Create object from source or assembly
|
|
\\ fmt [source] Parse file and render in canonical zig format
|
|
\\ targets List available compilation targets
|
|
\\ version Print version number and exit
|
|
\\ zen Print zen of zig and exit
|
|
\\
|
|
\\
|
|
;
|
|
|
|
pub fn log(
|
|
comptime level: std.log.Level,
|
|
comptime scope: @TypeOf(.EnumLiteral),
|
|
comptime format: []const u8,
|
|
args: var,
|
|
) void {
|
|
if (@enumToInt(level) > @enumToInt(std.log.level))
|
|
return;
|
|
|
|
const scope_prefix = "(" ++ switch (scope) {
|
|
// Uncomment to hide logs
|
|
//.compiler,
|
|
.link => return,
|
|
|
|
else => @tagName(scope),
|
|
} ++ "): ";
|
|
|
|
const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
|
|
|
|
// Print the message to stderr, silently ignoring any errors
|
|
std.debug.print(prefix ++ format, args);
|
|
}
|
|
|
|
pub fn main() !void {
|
|
// TODO general purpose allocator in the zig std lib
|
|
const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator;
|
|
var arena_instance = std.heap.ArenaAllocator.init(gpa);
|
|
defer arena_instance.deinit();
|
|
const arena = &arena_instance.allocator;
|
|
|
|
const args = try process.argsAlloc(arena);
|
|
|
|
if (args.len <= 1) {
|
|
std.debug.warn("expected command argument\n\n{}", .{usage});
|
|
process.exit(1);
|
|
}
|
|
|
|
const cmd = args[1];
|
|
const cmd_args = args[2..];
|
|
if (mem.eql(u8, cmd, "build-exe")) {
|
|
return buildOutputType(gpa, arena, cmd_args, .Exe);
|
|
} else if (mem.eql(u8, cmd, "build-lib")) {
|
|
return buildOutputType(gpa, arena, cmd_args, .Lib);
|
|
} else if (mem.eql(u8, cmd, "build-obj")) {
|
|
return buildOutputType(gpa, arena, cmd_args, .Obj);
|
|
} else if (mem.eql(u8, cmd, "fmt")) {
|
|
return cmdFmt(gpa, cmd_args);
|
|
} else if (mem.eql(u8, cmd, "targets")) {
|
|
const info = try std.zig.system.NativeTargetInfo.detect(arena, .{});
|
|
const stdout = io.getStdOut().outStream();
|
|
return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
|
|
} else if (mem.eql(u8, cmd, "version")) {
|
|
// Need to set up the build script to give the version as a comptime value.
|
|
std.debug.warn("TODO version command not implemented yet\n", .{});
|
|
return error.Unimplemented;
|
|
} else if (mem.eql(u8, cmd, "zen")) {
|
|
try io.getStdOut().writeAll(info_zen);
|
|
} else if (mem.eql(u8, cmd, "help")) {
|
|
try io.getStdOut().writeAll(usage);
|
|
} else {
|
|
std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage });
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
const usage_build_generic =
|
|
\\Usage: zig build-exe <options> [files]
|
|
\\ zig build-lib <options> [files]
|
|
\\ zig build-obj <options> [files]
|
|
\\
|
|
\\Supported file types:
|
|
\\ .zig Zig source code
|
|
\\ .zir Zig Intermediate Representation code
|
|
\\ (planned) .o ELF object file
|
|
\\ (planned) .o MACH-O (macOS) object file
|
|
\\ (planned) .obj COFF (Windows) object file
|
|
\\ (planned) .lib COFF (Windows) static library
|
|
\\ (planned) .a ELF static library
|
|
\\ (planned) .so ELF shared object (dynamic link)
|
|
\\ (planned) .dll Windows Dynamic Link Library
|
|
\\ (planned) .dylib MACH-O (macOS) dynamic library
|
|
\\ (planned) .s Target-specific assembly source code
|
|
\\ (planned) .S Assembly with C preprocessor (requires LLVM extensions)
|
|
\\ (planned) .c C source code (requires LLVM extensions)
|
|
\\ (planned) .cpp C++ source code (requires LLVM extensions)
|
|
\\ Other C++ extensions: .C .cc .cxx
|
|
\\
|
|
\\General Options:
|
|
\\ -h, --help Print this help and exit
|
|
\\ --watch Enable compiler REPL
|
|
\\ --color [auto|off|on] Enable or disable colored error messages
|
|
\\ -femit-bin[=path] (default) output machine code
|
|
\\ -fno-emit-bin Do not output machine code
|
|
\\
|
|
\\Compile Options:
|
|
\\ -target [name] <arch><sub>-<os>-<abi> see the targets command
|
|
\\ -mcpu [cpu] Specify target CPU and feature set
|
|
\\ --name [name] Override output name
|
|
\\ --mode [mode] Set the build mode
|
|
\\ Debug (default) optimizations off, safety on
|
|
\\ ReleaseFast optimizations on, safety off
|
|
\\ ReleaseSafe optimizations on, safety on
|
|
\\ ReleaseSmall optimize for small binary, safety off
|
|
\\ --dynamic Force output to be dynamically linked
|
|
\\ --strip Exclude debug symbols
|
|
\\
|
|
\\Link Options:
|
|
\\ -l[lib], --library [lib] Link against system library
|
|
\\ --dynamic-linker [path] Set the dynamic interpreter path (usually ld.so)
|
|
\\ --version [ver] Dynamic library semver
|
|
\\
|
|
\\Debug Options (Zig Compiler Development):
|
|
\\ -ftime-report Print timing diagnostics
|
|
\\ --debug-tokenize verbose tokenization
|
|
\\ --debug-ast-tree verbose parsing into an AST (tree view)
|
|
\\ --debug-ast-fmt verbose parsing into an AST (render source)
|
|
\\ --debug-ir verbose Zig IR
|
|
\\ --debug-link verbose linking
|
|
\\ --debug-codegen verbose machine code generation
|
|
\\
|
|
;
|
|
|
|
const Emit = union(enum) {
|
|
no,
|
|
yes_default_path,
|
|
yes: []const u8,
|
|
};
|
|
|
|
fn buildOutputType(
|
|
gpa: *Allocator,
|
|
arena: *Allocator,
|
|
args: []const []const u8,
|
|
output_mode: std.builtin.OutputMode,
|
|
) !void {
|
|
var color: Color = .Auto;
|
|
var build_mode: std.builtin.Mode = .Debug;
|
|
var provided_name: ?[]const u8 = null;
|
|
var link_mode: ?std.builtin.LinkMode = null;
|
|
var root_src_file: ?[]const u8 = null;
|
|
var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 };
|
|
var strip = false;
|
|
var watch = false;
|
|
var debug_tokenize = false;
|
|
var debug_ast_tree = false;
|
|
var debug_ast_fmt = false;
|
|
var debug_link = false;
|
|
var debug_ir = false;
|
|
var debug_codegen = false;
|
|
var time_report = false;
|
|
var emit_bin: Emit = .yes_default_path;
|
|
var emit_zir: Emit = .no;
|
|
var target_arch_os_abi: []const u8 = "native";
|
|
var target_mcpu: ?[]const u8 = null;
|
|
var target_dynamic_linker: ?[]const u8 = null;
|
|
|
|
var system_libs = std.ArrayList([]const u8).init(gpa);
|
|
defer system_libs.deinit();
|
|
|
|
{
|
|
var i: usize = 0;
|
|
while (i < args.len) : (i += 1) {
|
|
const arg = args[i];
|
|
if (mem.startsWith(u8, arg, "-")) {
|
|
if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
|
|
try io.getStdOut().writeAll(usage_build_generic);
|
|
process.exit(0);
|
|
} else if (mem.eql(u8, arg, "--color")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected [auto|on|off] after --color\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
const next_arg = args[i];
|
|
if (mem.eql(u8, next_arg, "auto")) {
|
|
color = .Auto;
|
|
} else if (mem.eql(u8, next_arg, "on")) {
|
|
color = .On;
|
|
} else if (mem.eql(u8, next_arg, "off")) {
|
|
color = .Off;
|
|
} else {
|
|
std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
|
|
process.exit(1);
|
|
}
|
|
} else if (mem.eql(u8, arg, "--mode")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
const next_arg = args[i];
|
|
if (mem.eql(u8, next_arg, "Debug")) {
|
|
build_mode = .Debug;
|
|
} else if (mem.eql(u8, next_arg, "ReleaseSafe")) {
|
|
build_mode = .ReleaseSafe;
|
|
} else if (mem.eql(u8, next_arg, "ReleaseFast")) {
|
|
build_mode = .ReleaseFast;
|
|
} else if (mem.eql(u8, next_arg, "ReleaseSmall")) {
|
|
build_mode = .ReleaseSmall;
|
|
} else {
|
|
std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});
|
|
process.exit(1);
|
|
}
|
|
} else if (mem.eql(u8, arg, "--name")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected parameter after --name\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
provided_name = args[i];
|
|
} else if (mem.eql(u8, arg, "--library")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected parameter after --library\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
try system_libs.append(args[i]);
|
|
} else if (mem.eql(u8, arg, "--version")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected parameter after --version\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
version = std.builtin.Version.parse(args[i]) catch |err| {
|
|
std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) });
|
|
process.exit(1);
|
|
};
|
|
} else if (mem.eql(u8, arg, "-target")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected parameter after -target\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
target_arch_os_abi = args[i];
|
|
} else if (mem.eql(u8, arg, "-mcpu")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected parameter after -mcpu\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
target_mcpu = args[i];
|
|
} else if (mem.startsWith(u8, arg, "-mcpu=")) {
|
|
target_mcpu = arg["-mcpu=".len..];
|
|
} else if (mem.eql(u8, arg, "--dynamic-linker")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected parameter after --dynamic-linker\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
target_dynamic_linker = args[i];
|
|
} else if (mem.eql(u8, arg, "--watch")) {
|
|
watch = true;
|
|
} else if (mem.eql(u8, arg, "-ftime-report")) {
|
|
time_report = true;
|
|
} else if (mem.eql(u8, arg, "-femit-bin")) {
|
|
emit_bin = .yes_default_path;
|
|
} else if (mem.startsWith(u8, arg, "-femit-bin=")) {
|
|
emit_bin = .{ .yes = arg["-femit-bin=".len..] };
|
|
} else if (mem.eql(u8, arg, "-fno-emit-bin")) {
|
|
emit_bin = .no;
|
|
} else if (mem.eql(u8, arg, "-femit-zir")) {
|
|
emit_zir = .yes_default_path;
|
|
} else if (mem.startsWith(u8, arg, "-femit-zir=")) {
|
|
emit_zir = .{ .yes = arg["-femit-zir=".len..] };
|
|
} else if (mem.eql(u8, arg, "-fno-emit-zir")) {
|
|
emit_zir = .no;
|
|
} else if (mem.eql(u8, arg, "-dynamic")) {
|
|
link_mode = .Dynamic;
|
|
} else if (mem.eql(u8, arg, "-static")) {
|
|
link_mode = .Static;
|
|
} else if (mem.eql(u8, arg, "--strip")) {
|
|
strip = true;
|
|
} else if (mem.eql(u8, arg, "--debug-tokenize")) {
|
|
debug_tokenize = true;
|
|
} else if (mem.eql(u8, arg, "--debug-ast-tree")) {
|
|
debug_ast_tree = true;
|
|
} else if (mem.eql(u8, arg, "--debug-ast-fmt")) {
|
|
debug_ast_fmt = true;
|
|
} else if (mem.eql(u8, arg, "--debug-link")) {
|
|
debug_link = true;
|
|
} else if (mem.eql(u8, arg, "--debug-ir")) {
|
|
debug_ir = true;
|
|
} else if (mem.eql(u8, arg, "--debug-codegen")) {
|
|
debug_codegen = true;
|
|
} else if (mem.startsWith(u8, arg, "-l")) {
|
|
try system_libs.append(arg[2..]);
|
|
} else {
|
|
std.debug.warn("unrecognized parameter: '{}'", .{arg});
|
|
process.exit(1);
|
|
}
|
|
} else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) {
|
|
std.debug.warn("assembly files not supported yet", .{});
|
|
process.exit(1);
|
|
} else if (mem.endsWith(u8, arg, ".o") or
|
|
mem.endsWith(u8, arg, ".obj") or
|
|
mem.endsWith(u8, arg, ".a") or
|
|
mem.endsWith(u8, arg, ".lib"))
|
|
{
|
|
std.debug.warn("object files and static libraries not supported yet", .{});
|
|
process.exit(1);
|
|
} else if (mem.endsWith(u8, arg, ".c") or
|
|
mem.endsWith(u8, arg, ".cpp"))
|
|
{
|
|
std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{});
|
|
process.exit(1);
|
|
} else if (mem.endsWith(u8, arg, ".so") or
|
|
mem.endsWith(u8, arg, ".dylib") or
|
|
mem.endsWith(u8, arg, ".dll"))
|
|
{
|
|
std.debug.warn("linking against dynamic libraries not yet supported", .{});
|
|
process.exit(1);
|
|
} else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) {
|
|
if (root_src_file) |other| {
|
|
std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other });
|
|
process.exit(1);
|
|
} else {
|
|
root_src_file = arg;
|
|
}
|
|
} else {
|
|
std.debug.warn("unrecognized file extension of parameter '{}'", .{arg});
|
|
}
|
|
}
|
|
}
|
|
|
|
const root_name = if (provided_name) |n| n else blk: {
|
|
if (root_src_file) |file| {
|
|
const basename = fs.path.basename(file);
|
|
var it = mem.split(basename, ".");
|
|
break :blk it.next() orelse basename;
|
|
} else {
|
|
std.debug.warn("--name [name] not provided and unable to infer\n", .{});
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
if (system_libs.items.len != 0) {
|
|
std.debug.warn("linking against system libraries not yet supported", .{});
|
|
process.exit(1);
|
|
}
|
|
|
|
var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{};
|
|
const cross_target = std.zig.CrossTarget.parse(.{
|
|
.arch_os_abi = target_arch_os_abi,
|
|
.cpu_features = target_mcpu,
|
|
.dynamic_linker = target_dynamic_linker,
|
|
.diagnostics = &diags,
|
|
}) catch |err| switch (err) {
|
|
error.UnknownCpuModel => {
|
|
std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
|
|
diags.cpu_name.?,
|
|
@tagName(diags.arch.?),
|
|
});
|
|
for (diags.arch.?.allCpuModels()) |cpu| {
|
|
std.debug.warn(" {}\n", .{cpu.name});
|
|
}
|
|
process.exit(1);
|
|
},
|
|
error.UnknownCpuFeature => {
|
|
std.debug.warn(
|
|
\\Unknown CPU feature: '{}'
|
|
\\Available CPU features for architecture '{}':
|
|
\\
|
|
, .{
|
|
diags.unknown_feature_name,
|
|
@tagName(diags.arch.?),
|
|
});
|
|
for (diags.arch.?.allFeaturesList()) |feature| {
|
|
std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
|
|
}
|
|
process.exit(1);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
|
|
const object_format: ?std.builtin.ObjectFormat = null;
|
|
var target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target);
|
|
if (target_info.cpu_detection_unimplemented) {
|
|
// TODO We want to just use detected_info.target but implementing
|
|
// CPU model & feature detection is todo so here we rely on LLVM.
|
|
std.debug.warn("CPU features detection is not yet available for this system without LLVM extensions\n", .{});
|
|
process.exit(1);
|
|
}
|
|
|
|
const src_path = root_src_file orelse {
|
|
std.debug.warn("expected at least one file argument", .{});
|
|
process.exit(1);
|
|
};
|
|
|
|
const bin_path = switch (emit_bin) {
|
|
.no => {
|
|
std.debug.warn("-fno-emit-bin not supported yet", .{});
|
|
process.exit(1);
|
|
},
|
|
.yes_default_path => try std.zig.binNameAlloc(arena, root_name, target_info.target, output_mode, link_mode),
|
|
.yes => |p| p,
|
|
};
|
|
|
|
const zir_out_path: ?[]const u8 = switch (emit_zir) {
|
|
.no => null,
|
|
.yes_default_path => blk: {
|
|
if (root_src_file) |rsf| {
|
|
if (mem.endsWith(u8, rsf, ".zir")) {
|
|
break :blk try std.fmt.allocPrint(arena, "{}.out.zir", .{root_name});
|
|
}
|
|
}
|
|
break :blk try std.fmt.allocPrint(arena, "{}.zir", .{root_name});
|
|
},
|
|
.yes => |p| p,
|
|
};
|
|
|
|
const root_pkg = try Package.create(gpa, fs.cwd(), ".", src_path);
|
|
defer root_pkg.destroy();
|
|
|
|
var module = try Module.init(gpa, .{
|
|
.target = target_info.target,
|
|
.output_mode = output_mode,
|
|
.root_pkg = root_pkg,
|
|
.bin_file_dir = fs.cwd(),
|
|
.bin_file_path = bin_path,
|
|
.link_mode = link_mode,
|
|
.object_format = object_format,
|
|
.optimize_mode = build_mode,
|
|
.keep_source_files_loaded = zir_out_path != null,
|
|
});
|
|
defer module.deinit();
|
|
|
|
const stdin = std.io.getStdIn().inStream();
|
|
const stderr = std.io.getStdErr().outStream();
|
|
var repl_buf: [1024]u8 = undefined;
|
|
|
|
try updateModule(gpa, &module, zir_out_path);
|
|
|
|
while (watch) {
|
|
try stderr.print("🦎 ", .{});
|
|
if (output_mode == .Exe) {
|
|
try module.makeBinFileExecutable();
|
|
}
|
|
if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| {
|
|
try stderr.print("\nUnable to parse command: {}\n", .{@errorName(err)});
|
|
continue;
|
|
}) |line| {
|
|
if (mem.eql(u8, line, "update")) {
|
|
if (output_mode == .Exe) {
|
|
try module.makeBinFileWritable();
|
|
}
|
|
try updateModule(gpa, &module, zir_out_path);
|
|
} else if (mem.eql(u8, line, "exit")) {
|
|
break;
|
|
} else if (mem.eql(u8, line, "help")) {
|
|
try stderr.writeAll(repl_help);
|
|
} else {
|
|
try stderr.print("unknown command: {}\n", .{line});
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void {
|
|
var timer = try std.time.Timer.start();
|
|
try module.update();
|
|
const update_nanos = timer.read();
|
|
|
|
var errors = try module.getAllErrorsAlloc();
|
|
defer errors.deinit(module.allocator);
|
|
|
|
if (errors.list.len != 0) {
|
|
for (errors.list) |full_err_msg| {
|
|
std.debug.warn("{}:{}:{}: error: {}\n", .{
|
|
full_err_msg.src_path,
|
|
full_err_msg.line + 1,
|
|
full_err_msg.column + 1,
|
|
full_err_msg.msg,
|
|
});
|
|
}
|
|
} else {
|
|
std.log.info(.compiler, "Update completed in {} ms\n", .{update_nanos / std.time.ns_per_ms});
|
|
}
|
|
|
|
if (zir_out_path) |zop| {
|
|
var new_zir_module = try zir.emit(gpa, module.*);
|
|
defer new_zir_module.deinit(gpa);
|
|
|
|
const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{});
|
|
defer baf.destroy();
|
|
|
|
try new_zir_module.writeToStream(gpa, baf.stream());
|
|
|
|
try baf.finish();
|
|
}
|
|
}
|
|
|
|
const repl_help =
|
|
\\Commands:
|
|
\\ update Detect changes to source files and update output files.
|
|
\\ help Print this text
|
|
\\ exit Quit this repl
|
|
\\
|
|
;
|
|
|
|
pub const usage_fmt =
|
|
\\usage: zig fmt [file]...
|
|
\\
|
|
\\ Formats the input files and modifies them in-place.
|
|
\\ Arguments can be files or directories, which are searched
|
|
\\ recursively.
|
|
\\
|
|
\\Options:
|
|
\\ --help Print this help and exit
|
|
\\ --color [auto|off|on] Enable or disable colored error messages
|
|
\\ --stdin Format code from stdin; output to stdout
|
|
\\ --check List non-conforming files and exit with an error
|
|
\\ if the list is non-empty
|
|
\\
|
|
\\
|
|
;
|
|
|
|
const Fmt = struct {
|
|
seen: SeenMap,
|
|
any_error: bool,
|
|
color: Color,
|
|
gpa: *Allocator,
|
|
out_buffer: std.ArrayList(u8),
|
|
|
|
const SeenMap = std.AutoHashMap(fs.File.INode, void);
|
|
};
|
|
|
|
pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
|
|
const stderr_file = io.getStdErr();
|
|
var color: Color = .Auto;
|
|
var stdin_flag: bool = false;
|
|
var check_flag: bool = false;
|
|
var input_files = ArrayList([]const u8).init(gpa);
|
|
|
|
{
|
|
var i: usize = 0;
|
|
while (i < args.len) : (i += 1) {
|
|
const arg = args[i];
|
|
if (mem.startsWith(u8, arg, "-")) {
|
|
if (mem.eql(u8, arg, "--help")) {
|
|
const stdout = io.getStdOut().outStream();
|
|
try stdout.writeAll(usage_fmt);
|
|
process.exit(0);
|
|
} else if (mem.eql(u8, arg, "--color")) {
|
|
if (i + 1 >= args.len) {
|
|
std.debug.warn("expected [auto|on|off] after --color\n", .{});
|
|
process.exit(1);
|
|
}
|
|
i += 1;
|
|
const next_arg = args[i];
|
|
if (mem.eql(u8, next_arg, "auto")) {
|
|
color = .Auto;
|
|
} else if (mem.eql(u8, next_arg, "on")) {
|
|
color = .On;
|
|
} else if (mem.eql(u8, next_arg, "off")) {
|
|
color = .Off;
|
|
} else {
|
|
std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
|
|
process.exit(1);
|
|
}
|
|
} else if (mem.eql(u8, arg, "--stdin")) {
|
|
stdin_flag = true;
|
|
} else if (mem.eql(u8, arg, "--check")) {
|
|
check_flag = true;
|
|
} else {
|
|
std.debug.warn("unrecognized parameter: '{}'", .{arg});
|
|
process.exit(1);
|
|
}
|
|
} else {
|
|
try input_files.append(arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stdin_flag) {
|
|
if (input_files.items.len != 0) {
|
|
std.debug.warn("cannot use --stdin with positional arguments\n", .{});
|
|
process.exit(1);
|
|
}
|
|
|
|
const stdin = io.getStdIn().inStream();
|
|
|
|
const source_code = try stdin.readAllAlloc(gpa, max_src_size);
|
|
defer gpa.free(source_code);
|
|
|
|
const tree = std.zig.parse(gpa, source_code) catch |err| {
|
|
std.debug.warn("error parsing stdin: {}\n", .{err});
|
|
process.exit(1);
|
|
};
|
|
defer tree.deinit();
|
|
|
|
for (tree.errors) |parse_error| {
|
|
try printErrMsgToFile(gpa, parse_error, tree, "<stdin>", stderr_file, color);
|
|
}
|
|
if (tree.errors.len != 0) {
|
|
process.exit(1);
|
|
}
|
|
if (check_flag) {
|
|
const anything_changed = try std.zig.render(gpa, io.null_out_stream, tree);
|
|
const code = if (anything_changed) @as(u8, 1) else @as(u8, 0);
|
|
process.exit(code);
|
|
}
|
|
|
|
const stdout = io.getStdOut().outStream();
|
|
_ = try std.zig.render(gpa, stdout, tree);
|
|
return;
|
|
}
|
|
|
|
if (input_files.items.len == 0) {
|
|
std.debug.warn("expected at least one source file argument\n", .{});
|
|
process.exit(1);
|
|
}
|
|
|
|
var fmt = Fmt{
|
|
.gpa = gpa,
|
|
.seen = Fmt.SeenMap.init(gpa),
|
|
.any_error = false,
|
|
.color = color,
|
|
.out_buffer = std.ArrayList(u8).init(gpa),
|
|
};
|
|
defer fmt.seen.deinit();
|
|
defer fmt.out_buffer.deinit();
|
|
|
|
for (input_files.span()) |file_path| {
|
|
// Get the real path here to avoid Windows failing on relative file paths with . or .. in them.
|
|
const real_path = fs.realpathAlloc(gpa, file_path) catch |err| {
|
|
std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
|
|
process.exit(1);
|
|
};
|
|
defer gpa.free(real_path);
|
|
|
|
try fmtPath(&fmt, file_path, check_flag, fs.cwd(), real_path);
|
|
}
|
|
if (fmt.any_error) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
const FmtError = error{
|
|
SystemResources,
|
|
OperationAborted,
|
|
IoPending,
|
|
BrokenPipe,
|
|
Unexpected,
|
|
WouldBlock,
|
|
FileClosed,
|
|
DestinationAddressRequired,
|
|
DiskQuota,
|
|
FileTooBig,
|
|
InputOutput,
|
|
NoSpaceLeft,
|
|
AccessDenied,
|
|
OutOfMemory,
|
|
RenameAcrossMountPoints,
|
|
ReadOnlyFileSystem,
|
|
LinkQuotaExceeded,
|
|
FileBusy,
|
|
EndOfStream,
|
|
} || fs.File.OpenError;
|
|
|
|
fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void {
|
|
fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
|
|
error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
|
|
else => {
|
|
std.debug.warn("unable to format '{}': {}\n", .{ file_path, err });
|
|
fmt.any_error = true;
|
|
return;
|
|
},
|
|
};
|
|
}
|
|
|
|
fn fmtPathDir(
|
|
fmt: *Fmt,
|
|
file_path: []const u8,
|
|
check_mode: bool,
|
|
parent_dir: fs.Dir,
|
|
parent_sub_path: []const u8,
|
|
) FmtError!void {
|
|
var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });
|
|
defer dir.close();
|
|
|
|
const stat = try dir.stat();
|
|
if (try fmt.seen.put(stat.inode, {})) |_| return;
|
|
|
|
var dir_it = dir.iterate();
|
|
while (try dir_it.next()) |entry| {
|
|
const is_dir = entry.kind == .Directory;
|
|
if (is_dir or mem.endsWith(u8, entry.name, ".zig")) {
|
|
const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name });
|
|
defer fmt.gpa.free(full_path);
|
|
|
|
if (is_dir) {
|
|
try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
|
|
} else {
|
|
fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
|
|
std.debug.warn("unable to format '{}': {}\n", .{ full_path, err });
|
|
fmt.any_error = true;
|
|
return;
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fmtPathFile(
|
|
fmt: *Fmt,
|
|
file_path: []const u8,
|
|
check_mode: bool,
|
|
dir: fs.Dir,
|
|
sub_path: []const u8,
|
|
) FmtError!void {
|
|
const source_file = try dir.openFile(sub_path, .{});
|
|
var file_closed = false;
|
|
errdefer if (!file_closed) source_file.close();
|
|
|
|
const stat = try source_file.stat();
|
|
|
|
if (stat.kind == .Directory)
|
|
return error.IsDir;
|
|
|
|
const source_code = source_file.readAllAlloc(fmt.gpa, stat.size, max_src_size) catch |err| switch (err) {
|
|
error.ConnectionResetByPeer => unreachable,
|
|
error.ConnectionTimedOut => unreachable,
|
|
else => |e| return e,
|
|
};
|
|
source_file.close();
|
|
file_closed = true;
|
|
defer fmt.gpa.free(source_code);
|
|
|
|
// Add to set after no longer possible to get error.IsDir.
|
|
if (try fmt.seen.put(stat.inode, {})) |_| return;
|
|
|
|
const tree = try std.zig.parse(fmt.gpa, source_code);
|
|
defer tree.deinit();
|
|
|
|
for (tree.errors) |parse_error| {
|
|
try printErrMsgToFile(fmt.gpa, parse_error, tree, file_path, std.io.getStdErr(), fmt.color);
|
|
}
|
|
if (tree.errors.len != 0) {
|
|
fmt.any_error = true;
|
|
return;
|
|
}
|
|
|
|
if (check_mode) {
|
|
const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree);
|
|
if (anything_changed) {
|
|
std.debug.warn("{}\n", .{file_path});
|
|
fmt.any_error = true;
|
|
}
|
|
} else {
|
|
// As a heuristic, we make enough capacity for the same as the input source.
|
|
try fmt.out_buffer.ensureCapacity(source_code.len);
|
|
fmt.out_buffer.items.len = 0;
|
|
const anything_changed = try std.zig.render(fmt.gpa, fmt.out_buffer.writer(), tree);
|
|
if (!anything_changed)
|
|
return; // Good thing we didn't waste any file system access on this.
|
|
|
|
var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode });
|
|
defer af.deinit();
|
|
|
|
try af.file.writeAll(fmt.out_buffer.items);
|
|
try af.finish();
|
|
std.debug.warn("{}\n", .{file_path});
|
|
}
|
|
}
|
|
|
|
fn printErrMsgToFile(
|
|
gpa: *mem.Allocator,
|
|
parse_error: ast.Error,
|
|
tree: *ast.Tree,
|
|
path: []const u8,
|
|
file: fs.File,
|
|
color: Color,
|
|
) !void {
|
|
const color_on = switch (color) {
|
|
.Auto => file.isTty(),
|
|
.On => true,
|
|
.Off => false,
|
|
};
|
|
const lok_token = parse_error.loc();
|
|
const span_first = lok_token;
|
|
const span_last = lok_token;
|
|
|
|
const first_token = tree.token_locs[span_first];
|
|
const last_token = tree.token_locs[span_last];
|
|
const start_loc = tree.tokenLocationLoc(0, first_token);
|
|
const end_loc = tree.tokenLocationLoc(first_token.end, last_token);
|
|
|
|
var text_buf = std.ArrayList(u8).init(gpa);
|
|
defer text_buf.deinit();
|
|
const out_stream = text_buf.outStream();
|
|
try parse_error.render(tree.token_ids, out_stream);
|
|
const text = text_buf.span();
|
|
|
|
const stream = file.outStream();
|
|
try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text });
|
|
|
|
if (!color_on) return;
|
|
|
|
// Print \r and \t as one space each so that column counts line up
|
|
for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| {
|
|
try stream.writeByte(switch (byte) {
|
|
'\r', '\t' => ' ',
|
|
else => byte,
|
|
});
|
|
}
|
|
try stream.writeByte('\n');
|
|
try stream.writeByteNTimes(' ', start_loc.column);
|
|
try stream.writeByteNTimes('~', last_token.end - first_token.start);
|
|
try stream.writeByte('\n');
|
|
}
|
|
|
|
pub const info_zen =
|
|
\\
|
|
\\ * Communicate intent precisely.
|
|
\\ * Edge cases matter.
|
|
\\ * Favor reading code over writing code.
|
|
\\ * Only one obvious way to do things.
|
|
\\ * Runtime crashes are better than bugs.
|
|
\\ * Compile errors are better than runtime crashes.
|
|
\\ * Incremental improvements.
|
|
\\ * Avoid local maximums.
|
|
\\ * Reduce the amount one must remember.
|
|
\\ * Minimize energy spent on coding style.
|
|
\\ * Resource deallocation must succeed.
|
|
\\ * Together we serve end users.
|
|
\\
|
|
\\
|
|
;
|