From 7d8bac7bf02d1f9cd80aac5e6a7a97f2d8bc2660 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Mon, 3 Feb 2025 14:39:31 +1100 Subject: [PATCH 1/3] implement a shell of the cli --- build.zig | 26 +++++++++++++++ build.zig.zon | 13 ++++++++ src/main.zig | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++ test.roc | 7 ++++ 4 files changed, 137 insertions(+) create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/main.zig create mode 100644 test.roc diff --git a/build.zig b/build.zig new file mode 100644 index 00000000000..f655a31c806 --- /dev/null +++ b/build.zig @@ -0,0 +1,26 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "roc", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Build and run the roc cli"); + run_step.dependOn(&run_cmd.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 00000000000..d88d27b84e2 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = "roc", + .version = "0.0.0", + .minimum_zig_version = "0.13.0", + .dependencies = .{}, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "LICENSE", + "LEGAL_DETAILS", + }, +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 00000000000..1f58c967611 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,91 @@ +const std = @import("std"); +const mem = std.mem; +const Allocator = std.mem.Allocator; + +const usage = + \\Usage: + \\ + \\ roc [options] [roc_file] [args] + \\ roc [command] [options] + \\ + \\Commands: + \\ + \\ build Build a binary from the given .roc file, but don't run it + \\ test Run all top-level `expect`s in a main module and any modules it imports + \\ repl Launch the interactive Read Eval Print Loop (REPL) + \\ format Format a .roc file or the .roc files contained in a directory using standard Roc formatting + \\ version Print the Roc compiler’s version, which is currently built from commit 90db3b2db0, committed at 2025-01-28 18:26:51 UTC + \\ check Check the code for problems, but don’t build or run it + \\ docs Generate documentation for a Roc package + \\ glue Generate glue code between a platform's Roc API and its host language + \\ + \\General Options: + \\ + \\ -h, --help Print command-specific usage +; + +pub fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.log.err(format, args); + std.process.exit(1); +} + +pub fn log( + comptime level: std.log.Level, + comptime format: []const u8, + args: anytype, +) void { + const prefix = comptime level.asText(); + + // Print the message to stderr, silently ignoring any errors + std.debug.print(prefix ++ ": " ++ format ++ "\n", args); +} + +pub fn main() !void { + var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; + defer { + _ = general_purpose_allocator.deinit(); + } + const gpa = general_purpose_allocator.allocator(); + + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + const args = try std.process.argsAlloc(arena); + + return mainArgs(gpa, arena, args); +} + +fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { + _ = gpa; + _ = arena; + + if (args.len <= 1) { + std.log.info("{s}", .{usage}); + fatal("expected command argument", .{}); + } + + const cmd = args[1]; + // const cmd_args = args[2..]; + if (mem.eql(u8, cmd, "build")) { + log(.info, "TODO roc build", .{}); + } else if (mem.eql(u8, cmd, "test")) { + log(.info, "TODO roc test", .{}); + } else if (mem.eql(u8, cmd, "repl")) { + log(.info, "TODO roc repl", .{}); + } else if (mem.eql(u8, cmd, "format")) { + log(.info, "TODO roc format", .{}); + } else if (mem.eql(u8, cmd, "version")) { + log(.info, "TODO roc version", .{}); + } else if (mem.eql(u8, cmd, "check")) { + log(.info, "TODO roc check", .{}); + } else if (mem.eql(u8, cmd, "docs")) { + log(.info, "TODO roc docs", .{}); + } else if (mem.eql(u8, cmd, "glue")) { + log(.info, "TODO roc glue", .{}); + } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { + return std.io.getStdOut().writeAll(usage); + } + + fatal("subcommand not yet implemented", .{}); +} diff --git a/test.roc b/test.roc new file mode 100644 index 00000000000..c4e945cd8e4 --- /dev/null +++ b/test.roc @@ -0,0 +1,7 @@ +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.19.0/Hj-J_zxz7V9YurCSTFcFdu6cQJie4guzsPMUi5kBYUk.tar.br" } + +main! = |_| + if Bool.true then + return Err(Exit(1,"This is a test error message")) + else + Ok({}) From 2f6c071c6f886502b07dc439c1cfdc71fe7ff57c Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Mon, 3 Feb 2025 15:46:44 +1100 Subject: [PATCH 2/3] use fatal and std.log.info, add options, remove test.roc --- src/main.zig | 45 ++++++++++++++++++++++++++++++++++----------- test.roc | 7 ------- 2 files changed, 34 insertions(+), 18 deletions(-) delete mode 100644 test.roc diff --git a/src/main.zig b/src/main.zig index 1f58c967611..7a3c3d4a94c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -68,24 +68,47 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { const cmd = args[1]; // const cmd_args = args[2..]; if (mem.eql(u8, cmd, "build")) { - log(.info, "TODO roc build", .{}); + fatal("TODO roc build", .{}); } else if (mem.eql(u8, cmd, "test")) { - log(.info, "TODO roc test", .{}); + fatal("TODO roc test", .{}); } else if (mem.eql(u8, cmd, "repl")) { - log(.info, "TODO roc repl", .{}); + fatal("TODO roc repl", .{}); } else if (mem.eql(u8, cmd, "format")) { - log(.info, "TODO roc format", .{}); + fatal("TODO roc format", .{}); } else if (mem.eql(u8, cmd, "version")) { - log(.info, "TODO roc version", .{}); + fatal("TODO roc version", .{}); } else if (mem.eql(u8, cmd, "check")) { - log(.info, "TODO roc check", .{}); + fatal("TODO roc check", .{}); } else if (mem.eql(u8, cmd, "docs")) { - log(.info, "TODO roc docs", .{}); + fatal("TODO roc docs", .{}); } else if (mem.eql(u8, cmd, "glue")) { - log(.info, "TODO roc glue", .{}); - } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { - return std.io.getStdOut().writeAll(usage); + fatal("TODO roc glue", .{}); + } else if (mem.eql(u8, cmd, "help")) { + try print_help(); + } else if (std.mem.startsWith(u8, cmd, "-")) { + // Handle General Options + if (mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { + try print_help(); + } else { + std.log.info("{s}", .{usage}); + fatal("unknown option: {s}", .{cmd}); + } + } else if (std.fs.path.extension(cmd).len > 0) { + if (!mem.eql(u8, std.fs.path.extension(cmd), ".roc")) { + fatal("expected .roc file to run, got: {s}", .{cmd}); + } + + const roc_file = cmd; + const roc_args = args[2..]; + _ = roc_args; // Remove when implemented + fatal("TODO: run the file {s}", .{roc_file}); + } else { + std.log.info("{s}", .{usage}); + fatal("unknown command: {s}", .{args[1]}); } +} - fatal("subcommand not yet implemented", .{}); +fn print_help() !void { + try std.io.getStdOut().writeAll(usage); + std.process.exit(0); } diff --git a/test.roc b/test.roc deleted file mode 100644 index c4e945cd8e4..00000000000 --- a/test.roc +++ /dev/null @@ -1,7 +0,0 @@ -app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.19.0/Hj-J_zxz7V9YurCSTFcFdu6cQJie4guzsPMUi5kBYUk.tar.br" } - -main! = |_| - if Bool.true then - return Err(Exit(1,"This is a test error message")) - else - Ok({}) From d8b3fde155356aeff106937fc86b6ec4f3d3f0d9 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Mon, 3 Feb 2025 20:31:32 +1100 Subject: [PATCH 3/3] use StaticStringMap --- build.zig | 10 +++- src/main.zig | 146 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 110 insertions(+), 46 deletions(-) diff --git a/build.zig b/build.zig index f655a31c806..6f473729a96 100644 --- a/build.zig +++ b/build.zig @@ -4,13 +4,21 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const main_path = b.path("src/main.zig"); + const exe = b.addExecutable(.{ .name = "roc", - .root_source_file = b.path("src/main.zig"), + .root_source_file = main_path, .target = target, .optimize = optimize, }); + const main_tests = b.addTest(.{ .root_source_file = main_path }); + const test_cmd = b.addRunArtifact(main_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&test_cmd.step); + b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); diff --git a/src/main.zig b/src/main.zig index 7a3c3d4a94c..39b49830a63 100644 --- a/src/main.zig +++ b/src/main.zig @@ -29,16 +29,42 @@ pub fn fatal(comptime format: []const u8, args: anytype) noreturn { std.process.exit(1); } -pub fn log( - comptime level: std.log.Level, - comptime format: []const u8, - args: anytype, -) void { - const prefix = comptime level.asText(); - - // Print the message to stderr, silently ignoring any errors - std.debug.print(prefix ++ ": " ++ format ++ "\n", args); -} +const RocCmd = enum { + roc_build, + roc_test, + roc_repl, + roc_format, + roc_version, + roc_check, + roc_docs, + roc_glue, + roc_help, + + // Parse from string, return null if not found + pub fn fromString(str: []const u8) ?RocCmd { + inline for (std.meta.fields(RocCmd)) |field| { + if (mem.eql(u8, str, field.name)) { + return @enumFromInt(field.value); + } + } + return null; + } + + // Define the function type for command handlers + const CommandFn = *const fn (allocator: Allocator, args: []const []const u8) anyerror!void; + + const table = std.static_string_map.StaticStringMap(CommandFn).initComptime(.{ + .{ "build", rocBuild }, + .{ "test", rocTest }, + .{ "repl", rocRepl }, + .{ "format", rocFormat }, + .{ "version", rocVersion }, + .{ "check", rocCheck }, + .{ "docs", rocDocs }, + .{ "glue", rocGlue }, + .{ "help", rocHelp }, + }); +}; pub fn main() !void { var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; @@ -58,7 +84,6 @@ pub fn main() !void { fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { _ = gpa; - _ = arena; if (args.len <= 1) { std.log.info("{s}", .{usage}); @@ -66,45 +91,21 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } const cmd = args[1]; - // const cmd_args = args[2..]; - if (mem.eql(u8, cmd, "build")) { - fatal("TODO roc build", .{}); - } else if (mem.eql(u8, cmd, "test")) { - fatal("TODO roc test", .{}); - } else if (mem.eql(u8, cmd, "repl")) { - fatal("TODO roc repl", .{}); - } else if (mem.eql(u8, cmd, "format")) { - fatal("TODO roc format", .{}); - } else if (mem.eql(u8, cmd, "version")) { - fatal("TODO roc version", .{}); - } else if (mem.eql(u8, cmd, "check")) { - fatal("TODO roc check", .{}); - } else if (mem.eql(u8, cmd, "docs")) { - fatal("TODO roc docs", .{}); - } else if (mem.eql(u8, cmd, "glue")) { - fatal("TODO roc glue", .{}); - } else if (mem.eql(u8, cmd, "help")) { - try print_help(); - } else if (std.mem.startsWith(u8, cmd, "-")) { - // Handle General Options - if (mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { - try print_help(); - } else { - std.log.info("{s}", .{usage}); - fatal("unknown option: {s}", .{cmd}); - } + const cmd_args = args[2..]; + + if (RocCmd.table.get(cmd)) |handler| { + try handler(arena, cmd_args); } else if (std.fs.path.extension(cmd).len > 0) { if (!mem.eql(u8, std.fs.path.extension(cmd), ".roc")) { - fatal("expected .roc file to run, got: {s}", .{cmd}); + fatal("expected .roc file, got: {s}", .{cmd}); } - - const roc_file = cmd; - const roc_args = args[2..]; - _ = roc_args; // Remove when implemented - fatal("TODO: run the file {s}", .{roc_file}); + // Handle .roc file execution + fatal("TODO: run .roc file: {s}", .{cmd}); + } else if (mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { + try rocHelp(arena, cmd_args); } else { std.log.info("{s}", .{usage}); - fatal("unknown command: {s}", .{args[1]}); + fatal("unknown command: {s}", .{cmd}); } } @@ -112,3 +113,58 @@ fn print_help() !void { try std.io.getStdOut().writeAll(usage); std.process.exit(0); } + +fn rocBuild(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + fatal("TODO roc build", .{}); +} + +fn rocTest(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + fatal("TODO roc test", .{}); +} + +fn rocRepl(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + fatal("TODO roc repl", .{}); +} + +fn rocFormat(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + fatal("TODO roc format", .{}); +} + +fn rocVersion(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + fatal("TODO roc version", .{}); +} + +fn rocCheck(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + fatal("TODO roc check", .{}); +} + +fn rocDocs(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + fatal("TODO roc docs", .{}); +} + +fn rocGlue(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + fatal("TODO roc glue", .{}); +} + +fn rocHelp(allocator: Allocator, args: []const []const u8) !void { + _ = allocator; + _ = args; + + try std.io.getStdOut().writeAll(usage); +}