From 348564d62080d97cbe4abb36db7db415a72a4c39 Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Sat, 15 Aug 2026 00:55:29 +0200 Subject: [PATCH] feat: `woc ` builds from wo.toml - a directory carrying wo.toml is a PROJECT: `woc .` inside it (or `woc path/to/project` from anywhere) reads the manifest and produces /, exactly what `woc build -o ...` produces - schema is the one the sample already carried -- top-level name/ version/description, [runtime] wo (accepted, not yet enforced) -- plus a new [build] section: runtime (wovm to prepend) and target (output dir, default "target"), both relative to the manifest's own directory so the build is invocation-point independent - unknown keys and sections are hard errors: a typo'd key silently ignored would build the wrong thing - directories WITHOUT wo.toml keep check-only semantics -- the corpus is full of those; oop-e2e 71/0, woc-test 565/0, log-watcher 7/0 - sample's wo.toml gains the [build] section; target/ gitignored Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 + compiler/bin/main.ml | 107 +++++++++++++++++++++++++++++- docs/examples/log-watcher/wo.toml | 6 ++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0737a17..ee9ec15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Cargo build artifacts /target +# `woc .` manifest builds (wo.toml [build] target) +/docs/examples/log-watcher/target + # Rust runtime (crates/rt/): compiled binary + build artifacts /crates/rt/target /crates/rt/Cargo.lock diff --git a/compiler/bin/main.ml b/compiler/bin/main.ml index dd71b60..a8f1276 100644 --- a/compiler/bin/main.ml +++ b/compiler/bin/main.ml @@ -40,6 +40,7 @@ let usage_msg = "usage: woc \n\ + usage: woc (builds when /wo.toml exists)\n\ usage: woc --emit -o \n\ usage: woc build -o [--runtime ]\n\ usage: woc --dump-tokens \n\ @@ -56,7 +57,11 @@ let usage_msg = \n\ With no flag, is fully compiled (lexed, parsed, typechecked,\n\ ownership-checked) and nothing is printed on success; diagnostics,\n\ - if any, print to stderr.\n\ + if any, print to stderr. One exception: a directory containing a\n\ + wo.toml manifest is BUILT instead — `woc .` inside a project (or\n\ + `woc path/to/project` from anywhere) reads the manifest's `name` plus\n\ + the optional [build] runtime/target keys (paths relative to the\n\ + manifest) and produces / exactly as `woc build` would.\n\ \n\ --dump-tokens prints one line per lexed token to stdout, in source\n\ order (\"LINE:COL KIND\" or \"LINE:COL KIND(payload)\"), ending with\n\ @@ -559,6 +564,100 @@ let build_mode ~(runtime : string option) (path : string) (out : string) : unit finish collector lookup end +(* ---- manifest build --------------------------------------------------- + `woc ` where /wo.toml exists is a BUILD, not a check: the + manifest names the application, so pointing woc at the project is enough + (`woc .` inside it). The schema is the one the sample already carried — + top-level `name` (the executable's basename), `version`, `description`, + a `[runtime]` section whose `wo` constraint is accepted and not yet + enforced — plus one new section this feature adds: + + [build] + runtime = "../runtime/wovm" # optional: wovm to prepend, relative to + # the manifest's own directory (default: + # `runtime/wovm` relative to the CWD, the + # same as `woc build` with no --runtime) + target = "target" # optional: output directory, relative to + # the manifest's directory + + Anything else is an error: a typo'd key silently ignored would build the + wrong thing. A directory WITHOUT wo.toml keeps today's meaning (check + only), and the corpus is full of those. *) +let manifest_parse (path : string) : (string * string) list = + let fail line msg = + Printf.eprintf "woc: %s:%d: %s\n" path line msg; + exit 2 + in + let ic = try open_in path with Sys_error m -> Printf.eprintf "woc: %s\n" m; exit 2 in + let kvs = ref [] in + let section = ref "" in + let lineno = ref 0 in + (try + while true do + let raw = input_line ic in + incr lineno; + let line = String.trim raw in + if line = "" || (String.length line >= 1 && line.[0] = '#') then () + else if line.[0] = '[' then begin + if line.[String.length line - 1] <> ']' then fail !lineno "malformed section header"; + section := String.sub line 1 (String.length line - 2); + if !section <> "runtime" && !section <> "build" then + fail !lineno (Printf.sprintf "unknown section [%s] (runtime and build exist)" !section) + end + else + match String.index_opt line '=' with + | None -> fail !lineno "expected `key = \"value\"`" + | Some eq -> + let key = String.trim (String.sub line 0 eq) in + let v = String.trim (String.sub line (eq + 1) (String.length line - eq - 1)) in + if String.length v < 2 || v.[0] <> '"' || v.[String.length v - 1] <> '"' then + fail !lineno (Printf.sprintf "`%s`: only quoted string values are supported" key); + let v = String.sub v 1 (String.length v - 2) in + let known = + match (!section, key) with + | "", ("name" | "version" | "description") -> true + | "runtime", "wo" -> true (* accepted, not yet enforced *) + | "build", ("runtime" | "target") -> true + | _ -> false + in + if not known then + fail !lineno + (if !section = "" then + Printf.sprintf "unknown key `%s` (name, version, description exist)" key + else + Printf.sprintf "unknown key `%s` in [%s]" key !section); + kvs := ((if !section = "" then key else !section ^ "." ^ key), v) :: !kvs + done + with End_of_file -> close_in ic); + !kvs + +let manifest_build (dir : string) : unit = + let mf = Filename.concat dir "wo.toml" in + let kvs = manifest_parse mf in + let get k = List.assoc_opt k kvs in + let name = + match get "name" with + | Some n when n <> "" && not (String.contains n '/') -> n + | Some _ -> + Printf.eprintf "woc: %s: `name` must be a bare file name\n" mf; + exit 2 + | None -> + Printf.eprintf "woc: %s: `name` is required\n" mf; + exit 2 + in + (* paths in the manifest are the PROJECT's, so they resolve against the + manifest's directory — `woc .` inside the project and `woc path/to/it` + from anywhere must build the same thing *) + let resolve rel = if Filename.is_relative rel then Filename.concat dir rel else rel in + let runtime = Option.map resolve (get "build.runtime") in + let target = resolve (match get "build.target" with Some t when t <> "" -> t | _ -> "target") in + (if not (Sys.file_exists target) then + try Sys.mkdir target 0o755 + with Sys_error m -> + Printf.eprintf "woc: %s\n" m; + exit 2); + build_mode ~runtime dir (Filename.concat target name) + let () = match Sys.argv with | [| _; "--dump-tokens"; path |] -> dump_tokens path @@ -569,7 +668,11 @@ let () = | [| _; "build"; path; "-o"; out |] -> build_mode ~runtime:None path out | [| _; "build"; path; "-o"; out; "--runtime"; rt |] -> build_mode ~runtime:(Some rt) path out | [| _; "build"; path; "--runtime"; rt; "-o"; out |] -> build_mode ~runtime:(Some rt) path out - | [| _; path |] -> check_only path + | [| _; path |] -> + if Sys.file_exists path && Sys.is_directory path + && Sys.file_exists (Filename.concat path "wo.toml") + then manifest_build path + else check_only path | _ -> prerr_string usage_msg; exit 2 diff --git a/docs/examples/log-watcher/wo.toml b/docs/examples/log-watcher/wo.toml index 7e0caa7..6e9e4cf 100644 --- a/docs/examples/log-watcher/wo.toml +++ b/docs/examples/log-watcher/wo.toml @@ -9,3 +9,9 @@ description = "The Haxe log-watcher ported file-for-file: the systems-track samp [runtime] wo = ">= 0.1" + +# `woc .` in this directory (or `woc docs/examples/log-watcher` from the +# repo root) builds target/log-watcher from this manifest. Paths resolve +# against this file's directory. +[build] +runtime = "../../../runtime/wovm"