hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit c280641ded6777d7ced18f4d03f22b10792babd8
parent 475ed776987af2c5487fc4ec7c0dbc46340e55da
Author: Drew DeVault <sir@cmpwn.com>
Date:   Thu, 11 Mar 2021 14:00:10 -0500

Expand option parsing and add subcmds.ha

Diffstat:
MMakefile | 1+
Mmain.ha | 174++++---------------------------------------------------------------------------
Mplan.ha | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asubcmds.ha | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 188 insertions(+), 166 deletions(-)

diff --git a/Makefile b/Makefile @@ -19,6 +19,7 @@ include mk/stdlib.mk hare_srcs=\ plan.ha \ + subcmds.ha \ main.ha $(HARECACHE)/hare.ssa: $(hare_srcs) $(hare_stdlib_deps) diff --git a/main.ha b/main.ha @@ -10,184 +10,26 @@ use path; use strio; use temp; -type goal = enum { - // Print dependency graph - DEP, - // Build executable - EXE, - // Build object - OBJ, - // Run program - RUN, - // Run tests - TEST, -}; - export fn main() void = { let verbose = false, goal = goal::EXE; let output = ""; let help: []getopt::help = [ "compile, run, and test Hare programs", - ('c', "produce an object instead of an executable"), - ('u', "print dependency graph"), - ('v', "enable verbose logging"), - ('D', "ident:type=value", "define a constant"), - ('l', "name", "link with a system library"), - ('o', "path", "set output path"), - ('t', "arch", "set target architecture"), - ('T', "tags...", "set build tags"), - ('X', "tags...", "unset build tags"), - "subcommand", "args..." + "<build | run | test | version>", "args...", ]; let cmd = getopt::parse(os::args, help...); defer getopt::finish(&cmd); - - for (let i = 0z; i < len(cmd.opts); i += 1) { - let opt = cmd.opts[i]; - switch (opt.0) { - 'c' => goal = goal::OBJ, - 'u' => goal = goal::DEP, - 'v' => verbose = true, - 'D' => abort(), // TODO - 'l' => abort(), // TODO - 'o' => output = opt.1 as getopt::parameter, - 't' => abort(), // TODO - 'T' => abort(), // TODO - 'X' => abort(), // TODO - }; - }; - if (len(cmd.args) < 1) { getopt::print_usage(os::stderr, os::args[0], help...); os::exit(1); }; - let newgoal = - if (cmd.args[0] == "build") goal::EXE - else if (cmd.args[0] == "run") { - if (output != "") { - fmt::fatal("Cannot combine -o and run command"); - }; - goal::RUN; - } - else if (cmd.args[0] == "test") goal::TEST - else if (cmd.args[0] == "version") abort() // TODO - else { - getopt::print_usage(os::stderr, os::args[0], help...); - os::exit(1); - }; - if (goal != goal::DEP && goal != goal::OBJ) { - newgoal = goal; - }; - cmd.args = cmd.args[1..]; - - let input = if (len(cmd.args) == 0) { - os::getcwd(); - } else { - assert(len(cmd.args) == 1); // TODO? - cmd.args[0]; - }; - - let plan = plan { - workdir = temp::dir(), - ... - }; - defer { - os::rmdirall(plan.workdir); - free(plan.workdir); - - for (let i = 0z; i < len(plan.complete); i += 1) { - let task = plan.complete[i]; - task_free(task); - }; - - free(plan.scheduled); - free(plan.complete); - }; - - let ctx = module::context_init([module::tag { - name = os::machine(), - mode = module::tag_mode::INCLUSIVE, - }, module::tag { - // TEMP: - name = "linux", - mode = module::tag_mode::INCLUSIVE, - }]); - defer module::context_finish(&ctx); - - const rtdir = match (module::lookup(&ctx, ["rt"])) { - err: module::error => fmt::fatal("Error resolving rt: {}", - module::errstr(err)), - ver: module::version => ver.basedir, - }; - plan.script = path::join(rtdir, "hare.sc"); - - let ver = match (module::scan(&ctx, input)) { - ver: module::version => ver, - err: module::error => fmt::fatal("Error scanning module: {}", - module::errstr(err)), - }; - - let depends: []*task = []; - for (let i = 0z; i < len(ver.depends); i += 1z) { - const dep = ver.depends[i]; - match (module::lookup(&ctx, dep)) { - err: module::error => { - let ident = ast::ident_unparse_s(dep); - fmt::fatal("Error resolving {}: {}", - ident, module::errstr(err)); - }, - ver: module::version => { - let ns = ast::ident_unparse_s(dep); - // TODO: - // - Use the cache - // - Transitive dependencies - let obj = sched_hare_object(&plan, - ver.inputs, ns); - append(depends, obj); - }, - }; - }; - - if (output == "") { - output = path::basename(ver.basedir); - }; - sched_hare_exe(&plan, ver.inputs, output, depends...); - - for (len(plan.scheduled) != 0) { - let next: nullable *task = null; - let i = 0z; - for (i < len(plan.scheduled); i += 1) { - let task = plan.scheduled[i]; - let eligible = true; - for (let j = 0z; j < len(task.depend); j += 1) { - if (task.depend[j].status != status::COMPLETE) { - eligible = false; - break; - }; - }; - if (eligible) { - next = task; - break; - }; - }; - // TODO: This can be a type assertion - let task = match (next) { - null => abort(), - t: *task => t, - }; - - match (execute(&ctx, task, verbose)) { - err: exec::error => fmt::fatal("Error: {}: {}", - task.cmd[0], exec::errstr(err)), - err: exec::exit_status! => fmt::fatal("Error: {}: {}", - task.cmd[0], exec::exitstr(err)), - void => void, - }; - - task.status = status::COMPLETE; - - delete(plan.scheduled[i]); - append(plan.complete, task); + if (cmd.args[0] == "build") build(cmd.args) + else if (cmd.args[0] == "run") run(cmd.args) + else if (cmd.args[0] == "test") test(cmd.args) + else if (cmd.args[0] == "version") version(cmd.args) + else { + getopt::print_usage(os::stderr, os::args[0], help...); + os::exit(1); }; }; diff --git a/plan.ha b/plan.ha @@ -4,6 +4,7 @@ use os::exec; use os; use path; use strings; +use temp; type status = enum { SCHEDULED, @@ -25,6 +26,13 @@ type plan = struct { script: str, }; +type goal = enum { + OBJ, + EXE, + RUN, + TEST, +}; + fn task_free(task: *task) void = { free(task.depend); free(task.output); @@ -32,6 +40,32 @@ fn task_free(task: *task) void = { free(task); }; +fn mkplan(ctx: *module::context) plan = { + const rtdir = match (module::lookup(ctx, ["rt"])) { + err: module::error => fmt::fatal("Error resolving rt: {}", + module::errstr(err)), + ver: module::version => ver.basedir, + }; + return plan { + workdir = temp::dir(), + script = path::join(rtdir, "hare.sc"), + ... + }; +}; + +fn plan_finish(plan: *plan) void = { + os::rmdirall(plan.workdir); + free(plan.workdir); + + for (let i = 0z; i < len(plan.complete); i += 1) { + let task = plan.complete[i]; + task_free(task); + }; + + free(plan.scheduled); + free(plan.complete); +}; + // Schedules a task which compiles objects into an executable. fn sched_ld(plan: *plan, output: str, depend: *task...) *task = { let task = alloc(task { @@ -211,3 +245,42 @@ fn mkdepends(t: *task...) []*task = { append(deps, ...t); return deps; }; + +fn plan_execute(ctx: *module::context, plan: *plan, verbose: bool) void = { + for (len(plan.scheduled) != 0) { + let next: nullable *task = null; + let i = 0z; + for (i < len(plan.scheduled); i += 1) { + let task = plan.scheduled[i]; + let eligible = true; + for (let j = 0z; j < len(task.depend); j += 1) { + if (task.depend[j].status != status::COMPLETE) { + eligible = false; + break; + }; + }; + if (eligible) { + next = task; + break; + }; + }; + // TODO: This can be a type assertion + let task = match (next) { + null => abort(), + t: *task => t, + }; + + match (execute(ctx, task, verbose)) { + err: exec::error => fmt::fatal("Error: {}: {}", + task.cmd[0], exec::errstr(err)), + err: exec::exit_status! => fmt::fatal("Error: {}: {}", + task.cmd[0], exec::exitstr(err)), + void => void, + }; + + task.status = status::COMPLETE; + + delete(plan.scheduled[i]); + append(plan.complete, task); + }; +}; diff --git a/subcmds.ha b/subcmds.ha @@ -0,0 +1,106 @@ +use fmt; +use getopt; +use hare::ast; +use hare::module; +use os; +use path; + +fn build(args: []str) void = { + let help: []getopt::help = [ + "compiles Hare programs", + ('c', "build object instead of executable"), + ('v', "print executed commands"), + ('D', "ident:type=value", "define a constant"), + ('l', "name", "link with a system library"), + ('o', "path", "set output file name"), + ('t', "arch", "set target architecture"), + ('T', "tags...", "set build tags"), + ('X', "tags...", "unset build tags"), + "<path | files...>" + ]; + let cmd = getopt::parse(args, help...); + defer getopt::finish(&cmd); + + let verbose = false; + let output = ""; + let goal = goal::EXE; + for (let i = 0z; i < len(cmd.opts); i += 1) { + let opt = cmd.opts[i]; + switch (opt.0) { + 'c' => goal::OBJ, + 'v' => verbose = true, + 'D' => abort(), // TODO + 'l' => abort(), // TODO + 'o' => output = opt.1 as getopt::parameter, + 't' => abort(), // TODO + 'T' => abort(), // TODO + 'X' => abort(), // TODO + }; + }; + + assert(goal == goal::EXE); // TODO + + let input = if (len(cmd.args) == 0) os::getcwd() else { + assert(len(cmd.args) == 1); // TODO? + cmd.args[0]; + }; + + let ctx = module::context_init([module::tag { + name = os::machine(), + mode = module::tag_mode::INCLUSIVE, + }, module::tag { + // TEMP: + name = "linux", + mode = module::tag_mode::INCLUSIVE, + }]); + defer module::context_finish(&ctx); + + let plan = mkplan(&ctx); + defer plan_finish(&plan); + + let ver = match (module::scan(&ctx, input)) { + ver: module::version => ver, + err: module::error => fmt::fatal("Error scanning module: {}", + module::errstr(err)), + }; + + // TODO: + // - Use the hare cache + // - Transitive dependencies + // - Move this into a separate function, e.g. plan_module + let depends: []*task = []; + for (let i = 0z; i < len(ver.depends); i += 1z) { + const dep = ver.depends[i]; + match (module::lookup(&ctx, dep)) { + err: module::error => { + let ident = ast::ident_unparse_s(dep); + fmt::fatal("Error resolving {}: {}", + ident, module::errstr(err)); + }, + ver: module::version => { + let ns = ast::ident_unparse_s(dep); + let obj = sched_hare_object( + &plan, ver.inputs, ns); + append(depends, obj); + }, + }; + }; + + if (output == "") { + output = path::basename(ver.basedir); + }; + sched_hare_exe(&plan, ver.inputs, output, depends...); + plan_execute(&ctx, &plan, verbose); +}; + +fn run(args: []str) void = { + abort(); // TODO +}; + +fn test(args: []str) void = { + abort(); // TODO +}; + +fn version(args: []str) void = { + abort(); // TODO +};