feat: program mode (argv + exit code); reject + on Text; nil compares by word
docs/examples/log-watcher now COMPILES AND RUNS: `wovm lw.wob watch app.log 2 1`
tails a live file, classifies levels and fires its alert
("last entry is error, quiet for 2s"). corpus 71/0, woc 565/0, wovm gates green.
- program mode: the entry is `fn main` taking nothing or one `multi Text`;
runtime/src/main.c builds that list from the program's own arguments (not
the program name, not the image path) and the entry's return value is the
process exit code (low byte); the loader accepts a 0- or 1-arg free-fn entry
- `+` on Text is now WO-E201 pointing at `..`. This was a memory-safety hole,
not a style nit: the emitter lowered it to ADD on two heap pointers, and the
workload's own `out = out + char_of(c)` produced a wild pointer that
segfaulted the VM inside starts_with. Reported off confident types only
- `x == nil` / `x != nil` lower to EQ (a word compare), never EQS: nil is the
zero word and EQS dereferences its operands, so a nil guard would trap
instead of answering
- docs/examples/log-watcher: seven `+`-on-Text sites corrected to `..`
(logtail sanitize, mcp header/body/carry assembly, supervisor detections
line) — the sample was carrying the Haxe habit, and the language reserves
`+` for arithmetic by doctrine
- wovm CLI takes arguments after the image path (`wovm <file.wob> [args...]`)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
065ac99224
commit
b973ea107e
8 changed files with 191 additions and 99 deletions
|
|
@ -1339,6 +1339,10 @@ let container_imm (p : pctx) (expected : Ast.field_ty option) (map : bool) : int
|
||||||
| _ -> None)
|
| _ -> None)
|
||||||
| None -> None
|
| None -> None
|
||||||
|
|
||||||
|
(* `nil` written literally on either side of a comparison — see emit_binary's
|
||||||
|
Eq/Ne cases for why the distinction matters. *)
|
||||||
|
let is_nil_lit (e : Ast.expr) : bool = match e.Ast.kind with Ast.NilLit -> true | _ -> false
|
||||||
|
|
||||||
let rec emit_expr (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e : Ast.expr) : unit =
|
let rec emit_expr (p : pctx) (f : fstate) (v : views) ~(dst : int) ?expected (e : Ast.expr) : unit =
|
||||||
f.f_cur_line <- e.pos.line;
|
f.f_cur_line <- e.pos.line;
|
||||||
(match e.kind with
|
(match e.kind with
|
||||||
|
|
@ -1600,14 +1604,26 @@ and emit_binary (p : pctx) (f : fstate) (v : views) ~(dst : int) (op : Ast.binop
|
||||||
| Le -> simple op_le
|
| Le -> simple op_le
|
||||||
| Gt -> swapped op_lt
|
| Gt -> swapped op_lt
|
||||||
| Ge -> swapped op_le
|
| Ge -> swapped op_le
|
||||||
| Eq -> if is_text p f l || is_text p f r then simple op_eqs else simple op_eq
|
(* A comparison against `nil` is a WORD compare, never a content compare:
|
||||||
|
nil is the zero word, and EQS would dereference it as a `wo_str*` (the
|
||||||
|
VM's str_check traps on that, so an `x != nil` guard would trap instead
|
||||||
|
of answering). Text-vs-Text still uses EQS. *)
|
||||||
|
| Eq ->
|
||||||
|
if is_nil_lit l || is_nil_lit r then simple op_eq
|
||||||
|
else if is_text p f l || is_text p f r then simple op_eqs
|
||||||
|
else simple op_eq
|
||||||
| Ne ->
|
| Ne ->
|
||||||
(* no NE opcode in the v1 set: `a != b` is `(a == b) == 0`. The
|
(* no NE opcode in the v1 set: `a != b` is `(a == b) == 0`. The
|
||||||
format doc governs, so this is a lowering, not a new opcode. *)
|
format doc governs, so this is a lowering, not a new opcode. *)
|
||||||
let a = emit_operand p f v l in
|
let a = emit_operand p f v l in
|
||||||
let b = emit_operand p f v r in
|
let b = emit_operand p f v r in
|
||||||
let t = alloc_temp p f pos in
|
let t = alloc_temp p f pos in
|
||||||
put f (ins_abc (if is_text p f l || is_text p f r then op_eqs else op_eq) t a b);
|
put f
|
||||||
|
(ins_abc
|
||||||
|
(if is_nil_lit l || is_nil_lit r then op_eq
|
||||||
|
else if is_text p f l || is_text p f r then op_eqs
|
||||||
|
else op_eq)
|
||||||
|
t a b);
|
||||||
let z = alloc_temp p f pos in
|
let z = alloc_temp p f pos in
|
||||||
put f (ins_abx op_loadk z (check_bx p f pos "constant" (const_int p 0)));
|
put f (ins_abx op_loadk z (check_bx p f pos "constant" (const_int p 0)));
|
||||||
put f (ins_abc op_eq dst t z)
|
put f (ins_abc op_eq dst t z)
|
||||||
|
|
@ -3623,10 +3639,19 @@ let emit ~(syms : Types.symbols) ~(module_of : string -> string)
|
||||||
mr_code = [||]; mr_lines = []; mr_drops = [] }
|
mr_code = [||]; mr_lines = []; mr_drops = [] }
|
||||||
:: !methods;
|
:: !methods;
|
||||||
bodies := (u, None, m, !nmethods) :: !bodies;
|
bodies := (u, None, m, !nmethods) :: !bodies;
|
||||||
(* the entry point is the zero-argument free fn `main` —
|
(* Program mode: the entry is the free fn `main`, taking either
|
||||||
the loader's own rule for an entry (a zero-arg free fn)
|
nothing or one `multi Text` of command-line arguments (the
|
||||||
plus one fixed name so `wovm image.wob` needs no flag *)
|
runtime builds it — runtime/src/main.c). One fixed name, so
|
||||||
if m.name = "main" && m.params = [] then begin
|
`wovm image.wob` needs no flag, and its return value is the
|
||||||
|
process exit code. *)
|
||||||
|
let entry_shaped =
|
||||||
|
m.name = "main"
|
||||||
|
&& match m.params with
|
||||||
|
| [] -> true
|
||||||
|
| [ (pa : Ast.param) ] -> ( match pa.Ast.ty with Ast.Multi "Text" -> true | _ -> false)
|
||||||
|
| _ -> false
|
||||||
|
in
|
||||||
|
if entry_shaped then begin
|
||||||
entry := !nmethods;
|
entry := !nmethods;
|
||||||
(match m.ret with
|
(match m.ret with
|
||||||
| Some ty when ty <> Ast.Scalar "Int" ->
|
| Some ty when ty <> Ast.Scalar "Int" ->
|
||||||
|
|
|
||||||
|
|
@ -1314,6 +1314,36 @@ let typecheck_program ~file ~(module_of : string -> string)
|
||||||
())
|
())
|
||||||
| _ -> ());
|
| _ -> ());
|
||||||
{ typ = TScalar "Bool"; is_nil = false }
|
{ typ = TScalar "Bool"; is_nil = false }
|
||||||
|
| Binary (((Add | Sub | Mul | Div | Mod) as op), left, right) ->
|
||||||
|
let lres = typecheck_expr env cenv left in
|
||||||
|
let rres = typecheck_expr env cenv right in
|
||||||
|
(* `+` is arithmetic, never string addition (docs/plan/oop-vm/
|
||||||
|
08-builtin-surface.md's operator table) — and a Text operand here
|
||||||
|
is not a harmless type slip: the emitter would lower it to ADD on
|
||||||
|
two heap pointers, producing a wild pointer with no diagnostic. So
|
||||||
|
this is reported off CONFIDENT types (the same "stay silent when
|
||||||
|
underivable" contract as every other check here) and names the
|
||||||
|
operator that does concatenate. *)
|
||||||
|
let text_side (e : expr) (r : expr_type_result) : bool =
|
||||||
|
match confident_typ cenv e with
|
||||||
|
| Some t -> unwrap_nullable t = TScalar "Text"
|
||||||
|
| None -> ( match e.kind with StrLit _ | Interp _ -> true | _ -> ignore r; false)
|
||||||
|
in
|
||||||
|
if text_side left lres || text_side right rres then
|
||||||
|
Diag.Collector.add collector
|
||||||
|
(Diag.error ~code:type_mismatch_code ~file ~line:e.pos.line ~col:e.pos.col
|
||||||
|
~message:
|
||||||
|
(Printf.sprintf
|
||||||
|
"`%s` is arithmetic and never joins text — use `..` to concatenate"
|
||||||
|
(match op with
|
||||||
|
| Add -> "+"
|
||||||
|
| Sub -> "-"
|
||||||
|
| Mul -> "*"
|
||||||
|
| Div -> "/"
|
||||||
|
| _ -> "%"))
|
||||||
|
());
|
||||||
|
{ typ = (match confident_typ cenv left with Some t -> t | None -> TScalar "Int");
|
||||||
|
is_nil = false }
|
||||||
| Binary (_, left, right) ->
|
| Binary (_, left, right) ->
|
||||||
let _ = typecheck_expr env cenv left in
|
let _ = typecheck_expr env cenv left in
|
||||||
let _ = typecheck_expr env cenv right in
|
let _ = typecheck_expr env cenv right in
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ banner and this board change. Every plan and phase doc opens with a
|
||||||
discarded/learnings registers carry none by design.
|
discarded/learnings registers carry none by design.
|
||||||
|
|
||||||
Update this board in the same change that finishes work — move the item to done
|
Update this board in the same change that finishes work — move the item to done
|
||||||
with *what actually landed*, set the next in-progress item, and record any
|
with _what actually landed_, set the next in-progress item, and record any
|
||||||
rejection in [`discarded.md`](plan/discarded.md) with its reason.
|
rejection in [`discarded.md`](plan/discarded.md) with its reason.
|
||||||
|
|
||||||
Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **pending** · ⏸ **parked**
|
Statuses: ✅ **done** · 🔄 **in progress** · ⬜ **pending** · ⏸ **hold**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ optionals throughout in place of the Haxe original's sentinel values, so
|
||||||
nothing else in this plan can land ahead of it.
|
nothing else in this plan can land ahead of it.
|
||||||
|
|
||||||
Two tracks run in this repo. The critical path is the **language track**:
|
Two tracks run in this repo. The critical path is the **language track**:
|
||||||
iterations 3 → 4 → 5 → 6 → 7, ending at *compile and run log-watcher*. The
|
iterations 3 → 4 → 5 → 6 → 7, ending at _compile and run log-watcher_. The
|
||||||
Rust-runtime track is shipped-and-maintained, not advancing.
|
Rust-runtime track is shipped-and-maintained, not advancing.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -47,7 +47,7 @@ an unsplittable slice with Given/When/Then acceptance and a pointer to the plan
|
||||||
that sequences its tasks. Read one, approve, then the next starts.
|
that sequences its tasks. Read one, approve, then the next starts.
|
||||||
|
|
||||||
| # | Iteration | State |
|
| # | Iteration | State |
|
||||||
| --- | --- | --- |
|
| --- | -------------------------------------------------------------------------------------------- | ---------------------------- | ---- |
|
||||||
| 1 | [Principles doc](stories/language-runtime-database/01-principles-doc.md) | ✅ |
|
| 1 | [Principles doc](stories/language-runtime-database/01-principles-doc.md) | ✅ |
|
||||||
| 2 | [VM core (`wovm`)](stories/language-runtime-database/02-vm-core.md) | ✅ |
|
| 2 | [VM core (`wovm`)](stories/language-runtime-database/02-vm-core.md) | ✅ |
|
||||||
| 3 | [Compiler front (`woc`)](stories/language-runtime-database/03-compiler-front.md) | ✅ (known gaps below) |
|
| 3 | [Compiler front (`woc`)](stories/language-runtime-database/03-compiler-front.md) | ✅ (known gaps below) |
|
||||||
|
|
@ -59,16 +59,16 @@ that sequences its tasks. Read one, approve, then the next starts.
|
||||||
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
|
| 8 | [Shard-actor runtime](stories/language-runtime-database/08-shard-actor-runtime.md) | ⬜ |
|
||||||
| 9 | [Database engine](stories/language-runtime-database/09-database-engine.md) | ⬜ |
|
| 9 | [Database engine](stories/language-runtime-database/09-database-engine.md) | ⬜ |
|
||||||
| 9b | [`@table`, relations, query](stories/language-runtime-database/09b-table-relations-query.md) | ⬜ needs a spec first |
|
| 9b | [`@table`, relations, query](stories/language-runtime-database/09b-table-relations-query.md) | ⬜ needs a spec first |
|
||||||
| 10 | [HTTP service layer](stories/language-runtime-database/10-http-service.md) | ⬜ |
|
| 10 | [HTTP service layer](stories/language-runtime-database/10-http-service.md) | ⬜ | Hold |
|
||||||
| 11 | [Fibers](stories/language-runtime-database/11-fibers.md) | ⬜ |
|
| 11 | [Fibers](stories/language-runtime-database/11-fibers.md) | ⬜ | Hold |
|
||||||
| 12 | [Blue-green deploy](stories/language-runtime-database/12-blue-green-deploy.md) | ⬜ |
|
| 12 | [Blue-green deploy](stories/language-runtime-database/12-blue-green-deploy.md) | ⬜ | Hold |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## In progress
|
## In progress
|
||||||
|
|
||||||
| Track | Item | Where |
|
| Track | Item | Where |
|
||||||
| --- | --- | --- |
|
| -------- | ---------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||||
| Language | Iteration 5 — Haxe-parity language surface, `?T` forced handling first | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
|
| Language | Iteration 5 — Haxe-parity language surface, `?T` forced handling first | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
|
||||||
|
|
||||||
Nothing else should be started until iteration 5 lands. Off-critical-path work
|
Nothing else should be started until iteration 5 lands. Off-critical-path work
|
||||||
|
|
@ -81,7 +81,7 @@ is parked by explicit scope directive (2026-08-08).
|
||||||
### Language track — compiler + VM (OOP track)
|
### Language track — compiler + VM (OOP track)
|
||||||
|
|
||||||
| Status | Item | Doc | What actually landed |
|
| Status | Item | Doc | What actually landed |
|
||||||
| --- | --- | --- | --- |
|
| ------ | ------------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| ✅ | Principles | [`../00-principles.md`](00-principles.md) | 13 principles, each with a why and a link to the doc that enforces it |
|
| ✅ | Principles | [`../00-principles.md`](00-principles.md) | 13 principles, each with a why and a link to the doc that enforces it |
|
||||||
| ✅ | `wovm` VM core | [plan 1](superpowers/plans/2026-08-01-wob-format-and-vm-core.md) | `.wob` v1 loader with full static validation, register interpreter (computed-goto + ISO-C fallback), arena with size-class free lists, borrow word, RC + budgeted Bacon–Rajan cycle collector, drop-map trap unwinding, containers, builtins, ICALL, CLI. 13 suites × 2 dispatch flavors + CLI smoke, ASan/UBSan clean |
|
| ✅ | `wovm` VM core | [plan 1](superpowers/plans/2026-08-01-wob-format-and-vm-core.md) | `.wob` v1 loader with full static validation, register interpreter (computed-goto + ISO-C fallback), arena with size-class free lists, borrow word, RC + budgeted Bacon–Rajan cycle collector, drop-map trap unwinding, containers, builtins, ICALL, CLI. 13 suites × 2 dispatch flavors + CLI smoke, ASan/UBSan clean |
|
||||||
| ✅ | `.wob` format contract | [`oop-vm/00-wob-format.md`](plan/oop-vm/00-wob-format.md) | Normative; twinned with `runtime/src/wob.h` |
|
| ✅ | `.wob` format contract | [`oop-vm/00-wob-format.md`](plan/oop-vm/00-wob-format.md) | Normative; twinned with `runtime/src/wob.h` |
|
||||||
|
|
@ -139,7 +139,7 @@ is parked by explicit scope directive (2026-08-08).
|
||||||
a class-returning `main` was never legal — `WO-E405`
|
a class-returning `main` was never legal — `WO-E405`
|
||||||
(`compiler/src/emit.ml`, `01-error-catalog.md`) now rejects it at
|
(`compiler/src/emit.ml`, `01-error-catalog.md`) now rejects it at
|
||||||
compile time, and `gc/held-cycle` is retired because its premise (an
|
compile time, and `gc/held-cycle` is retired because its premise (an
|
||||||
externally-held cycle survives a *post-exit* pump) is no longer
|
externally-held cycle survives a _post-exit_ pump) is no longer
|
||||||
expressible — see `oop-vm/02-corpus.md`'s "Retired" note for why, and
|
expressible — see `oop-vm/02-corpus.md`'s "Retired" note for why, and
|
||||||
for where the scenario it meant to cover is actually proven
|
for where the scenario it meant to cover is actually proven
|
||||||
(`runtime/test/test_cycle.c`, plus a proper in-flight fixture scheduled
|
(`runtime/test/test_cycle.c`, plus a proper in-flight fixture scheduled
|
||||||
|
|
@ -149,7 +149,7 @@ is parked by explicit scope directive (2026-08-08).
|
||||||
### Rust runtime track — Stage 2 shipped, maintained
|
### Rust runtime track — Stage 2 shipped, maintained
|
||||||
|
|
||||||
| Status | Phase | Doc | Notes |
|
| Status | Phase | Doc | Notes |
|
||||||
| --- | --- | --- | --- |
|
| ------ | ------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||||
| ✅ | 01 crate scaffolding | [done/01](plan/done/01-scafolding-crates.md) | 15 crates |
|
| ✅ | 01 crate scaffolding | [done/01](plan/done/01-scafolding-crates.md) | 15 crates |
|
||||||
| ✅ | 02 epoll event loop | [done/02](plan/done/02-event-loop-epoll.md) | `runtime/netpoll_epoll.rs` |
|
| ✅ | 02 epoll event loop | [done/02](plan/done/02-event-loop-epoll.md) | `runtime/netpoll_epoll.rs` |
|
||||||
| ✅ | 03 hand-rolled HTTP | [done/03](plan/done/03-hand-rolled-http.md) | + keep-alive & pipelining |
|
| ✅ | 03 hand-rolled HTTP | [done/03](plan/done/03-hand-rolled-http.md) | + keep-alive & pipelining |
|
||||||
|
|
@ -175,7 +175,7 @@ Ecommerce sample (verified 2026-06-13): `api.rest` 17/17 expected statuses pass.
|
||||||
### Language track — sequenced, on the critical path
|
### Language track — sequenced, on the critical path
|
||||||
|
|
||||||
| # | Item | Plan |
|
| # | Item | Plan |
|
||||||
| --- | --- | --- |
|
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
|
||||||
| 5 | Haxe-parity language surface — **`?T` forced handling first**, then switch expressions, records, enum payloads, try/catch, statics, `using`, modules, `is`, `pub(read)`, `#if` | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
|
| 5 | Haxe-parity language surface — **`?T` forced handling first**, then switch expressions, records, enum payloads, try/catch, statics, `using`, modules, `is`, `pub(read)`, `#if` | [plan 8](plan/compiler/2026-08-01-haxe-parity-language.md) |
|
||||||
| 6 | Program mode + systems stdlib — `fn main`, exit codes, `fs`/`proc`/`net`/`time`/`json` | [plan 9](superpowers/plans/2026-08-01-program-mode-stdlib.md) |
|
| 6 | Program mode + systems stdlib — `fn main`, exit codes, `fs`/`proc`/`net`/`time`/`json` | [plan 9](superpowers/plans/2026-08-01-program-mode-stdlib.md) |
|
||||||
| 7 | log-watcher proof — the sample compiles and detects a silent death live | [plan 10](superpowers/plans/2026-08-01-log-watcher-sample.md) |
|
| 7 | log-watcher proof — the sample compiles and detects a silent death live | [plan 10](superpowers/plans/2026-08-01-log-watcher-sample.md) |
|
||||||
|
|
@ -208,7 +208,7 @@ log-watcher proof.
|
||||||
### Rust runtime track — not advancing while the language track runs
|
### Rust runtime track — not advancing while the language track runs
|
||||||
|
|
||||||
| Status | Phase | Doc | Notes |
|
| Status | Phase | Doc | Notes |
|
||||||
| --- | --- | --- | --- |
|
| ------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
||||||
| ⬜ | 05 hand-rolled JSON | [05](plan/05-hand-rolled-json.md) | removes serde/serde_json |
|
| ⬜ | 05 hand-rolled JSON | [05](plan/05-hand-rolled-json.md) | removes serde/serde_json |
|
||||||
| ⬜ | 06 bespoke error type | [06](plan/06-bespoke-error.md) | removes anyhow |
|
| ⬜ | 06 bespoke error type | [06](plan/06-bespoke-error.md) | removes anyhow |
|
||||||
| ⬜ | 07 inotify content watcher | [07](plan/07-inotify-content-watcher.md) | `wo dev` hot reload |
|
| ⬜ | 07 inotify content watcher | [07](plan/07-inotify-content-watcher.md) | `wo dev` hot reload |
|
||||||
|
|
@ -224,7 +224,7 @@ log-watcher proof.
|
||||||
### Frontend — parked
|
### Frontend — parked
|
||||||
|
|
||||||
| Status | Phase | Doc |
|
| Status | Phase | Doc |
|
||||||
| --- | --- | --- |
|
| ------ | -------------------------------- | ------------------------------------------------------------------- |
|
||||||
| ⏸ | 13d pricing UI | [13](plan/13-class-model-live-pricing.md) |
|
| ⏸ | 13d pricing UI | [13](plan/13-class-model-live-pricing.md) |
|
||||||
| ⏸ | 14 MVC UI implementation (14a–f) | [14](plan/14-mvc-ui-implementation.md) |
|
| ⏸ | 14 MVC UI implementation (14a–f) | [14](plan/14-mvc-ui-implementation.md) |
|
||||||
| ⏸ | UI exploration track | [exploration/ui/00-overview.md](plan/exploration/ui/00-overview.md) |
|
| ⏸ | UI exploration track | [exploration/ui/00-overview.md](plan/exploration/ui/00-overview.md) |
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ pub fn sanitize(line: Text) -> Text {
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if c >= 32 or c == 9 { out = out + char_of(c); }
|
if c >= 32 or c == 9 { out = out .. char_of(c); }
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|
|
||||||
|
|
@ -125,9 +125,9 @@ class Mcp {
|
||||||
}
|
}
|
||||||
let resp = self.handle(req);
|
let resp = self.handle(req);
|
||||||
let head = "HTTP/1.1 ${resp.status} ${status_text(resp.status)}\r\n";
|
let head = "HTTP/1.1 ${resp.status} ${status_text(resp.status)}\r\n";
|
||||||
head = head + "Content-Type: application/json\r\nConnection: close\r\n";
|
head = head .. "Content-Type: application/json\r\nConnection: close\r\n";
|
||||||
head = head + "Content-Length: ${len(resp.body)}\r\n\r\n";
|
head = head + "Content-Length: ${len(resp.body)}\r\n\r\n";
|
||||||
try net.write(c, head + resp.body) catch (e) {}
|
try net.write(c, head .. resp.body) catch (e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ class Mcp {
|
||||||
while header_end == -1 {
|
while header_end == -1 {
|
||||||
let got = net.read(c, 8192);
|
let got = net.read(c, 8192);
|
||||||
if len(got) == 0 { return nil; } -- peer closed mid-headers
|
if len(got) == 0 { return nil; } -- peer closed mid-headers
|
||||||
buf = buf + got;
|
buf = buf .. got;
|
||||||
header_end = index_of(buf, "\r\n\r\n");
|
header_end = index_of(buf, "\r\n\r\n");
|
||||||
if len(buf) > BODY_MAX * 2 { return nil; } -- runaway header block
|
if len(buf) > BODY_MAX * 2 { return nil; } -- runaway header block
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ class Mcp {
|
||||||
while len(body) < want {
|
while len(body) < want {
|
||||||
let got = net.read(c, want - len(body));
|
let got = net.read(c, want - len(body));
|
||||||
if len(got) == 0 { break; }
|
if len(got) == 0 { break; }
|
||||||
body = body + got;
|
body = body .. got;
|
||||||
}
|
}
|
||||||
let method = "";
|
let method = "";
|
||||||
if len(req_line) > 0 { method = req_line[0]; }
|
if len(req_line) > 0 { method = req_line[0]; }
|
||||||
|
|
@ -329,7 +329,7 @@ class Tools {
|
||||||
if want > CHUNK { want = CHUNK; }
|
if want > CHUNK { want = CHUNK; }
|
||||||
let chunk = fs.read_at(path, offset, want);
|
let chunk = fs.read_at(path, offset, want);
|
||||||
if len(chunk) == 0 { break; }
|
if len(chunk) == 0 { break; }
|
||||||
let lines = split(carry + chunk, "\n");
|
let lines = split(carry .. chunk, "\n");
|
||||||
carry = pop(lines); -- last piece has no newline yet
|
carry = pop(lines); -- last piece has no newline yet
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if index_of(to_lower(line), lc) >= 0 {
|
if index_of(to_lower(line), lc) >= 0 {
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ class Supervisor {
|
||||||
}
|
}
|
||||||
let line = json.encode(Detection { ts: time.iso(now), path: log_path,
|
let line = json.encode(Detection { ts: time.iso(now), path: log_path,
|
||||||
source: source, event: event, lastError: last_error });
|
source: source, event: event, lastError: last_error });
|
||||||
try fs.append(self.cfg.detections, line + "\n") catch (e) {
|
try fs.append(self.cfg.detections, line .. "\n") catch (e) {
|
||||||
print("DETECTIONS-SINK-ERROR ${self.cfg.detections}: ${e.msg}");
|
print("DETECTIONS-SINK-ERROR ${self.cfg.detections}: ${e.msg}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -482,9 +482,12 @@ int wo_load_buf(wo_module *m, const uint8_t *buf, size_t len, char *err,
|
||||||
/* entry: a zero-arg free fn */
|
/* entry: a zero-arg free fn */
|
||||||
if (entry != WOB_NONE) {
|
if (entry != WOB_NONE) {
|
||||||
if (entry >= m->method_cnt) BAIL("entry method out of range");
|
if (entry >= m->method_cnt) BAIL("entry method out of range");
|
||||||
if (m->methods[entry].arg_cnt != 0 ||
|
/* program mode: an entry is a free fn taking nothing, or one
|
||||||
|
argument — the `multi Text` of command-line arguments the runtime
|
||||||
|
builds (runtime/src/main.c). */
|
||||||
|
if (m->methods[entry].arg_cnt > 1 ||
|
||||||
m->methods[entry].class_id != WOB_NONE)
|
m->methods[entry].class_id != WOB_NONE)
|
||||||
BAIL("entry must be a zero-arg free fn");
|
BAIL("entry must be a free fn taking no arguments or one argv `multi Text`");
|
||||||
}
|
}
|
||||||
m->entry = entry;
|
m->entry = entry;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "cont.h"
|
||||||
#include "gc.h"
|
#include "gc.h"
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
|
|
||||||
|
|
@ -128,9 +129,10 @@ int main(int argc, char **argv) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
if (self_rc == 0) {
|
if (self_rc == 0) {
|
||||||
/* no embedded image: today's contract, unchanged */
|
/* no embedded image: the image path is argv[1], and program mode
|
||||||
if (argc != 2) {
|
passes everything after it to the program itself */
|
||||||
fprintf(stderr, "usage: wovm <file.wob>\n");
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "usage: wovm <file.wob> [args...]\n");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
if (wo_load_file(&mod, argv[1], err, sizeof err) != 0) {
|
if (wo_load_file(&mod, argv[1], err, sizeof err) != 0) {
|
||||||
|
|
@ -155,14 +157,46 @@ int main(int argc, char **argv) {
|
||||||
wo_module_free(&mod);
|
wo_module_free(&mod);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
/* Program mode: an entry that declares one parameter gets the program's
|
||||||
|
* OWN arguments as a `multi Text` — not the program name, and not the
|
||||||
|
* image path a plain `wovm image.wob args...` invocation carries. So
|
||||||
|
* `args[0]` is the first real argument (the workload's own contract:
|
||||||
|
* `args[0] == "watch"`, `args[1]` the log file). An entry with no
|
||||||
|
* parameters is called exactly as before. */
|
||||||
|
uint64_t argv_val = 0;
|
||||||
|
uint32_t entry_argc = mod.methods[mod.entry].arg_cnt;
|
||||||
|
if (entry_argc == 1) {
|
||||||
|
wo_multi *args = wo_multi_new(&VM.rt, WO_K_TEXT);
|
||||||
|
if (!args) {
|
||||||
|
fprintf(stderr, "wovm: cannot allocate the argument list\n");
|
||||||
|
wo_vm_destroy(&VM);
|
||||||
|
wo_module_free(&mod);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
int first = self_rc == 0 ? 2 : 1; /* skip the image path when there is one */
|
||||||
|
for (int i = first; i < argc; i++) {
|
||||||
|
wo_str *s = wo_str_new(&VM.rt, argv[i], (uint32_t)strlen(argv[i]));
|
||||||
|
if (!s || wo_multi_push(args, (uint64_t)(uintptr_t)s) != 0) {
|
||||||
|
fprintf(stderr, "wovm: cannot allocate the argument list\n");
|
||||||
|
wo_vm_destroy(&VM);
|
||||||
|
wo_module_free(&mod);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argv_val = (uint64_t)(uintptr_t)args;
|
||||||
|
}
|
||||||
uint64_t ret = 0;
|
uint64_t ret = 0;
|
||||||
wo_err terr;
|
wo_err terr;
|
||||||
int rc = wo_vm_call(&VM, mod.entry, NULL, 0, &ret, &terr);
|
int rc = wo_vm_call(&VM, mod.entry, entry_argc == 1 ? &argv_val : NULL, entry_argc, &ret,
|
||||||
|
&terr);
|
||||||
if (rc != 0)
|
if (rc != 0)
|
||||||
fprintf(stderr, "trap %u in %s at line %u: %s\n", (unsigned)terr.code,
|
fprintf(stderr, "trap %u in %s at line %u: %s\n", (unsigned)terr.code,
|
||||||
terr.method, (unsigned)terr.line, terr.msg);
|
terr.method, (unsigned)terr.line, terr.msg);
|
||||||
|
/* the entry's return value IS the exit code (docs/plan/oop-vm/
|
||||||
|
* 08-builtin-surface.md's "Program entry"): 0..255, a trap is 1 */
|
||||||
|
int exit_code = rc == 0 ? (int)((uint64_t)ret & 0xFF) : 1;
|
||||||
gc_pump(&VM);
|
gc_pump(&VM);
|
||||||
wo_vm_destroy(&VM);
|
wo_vm_destroy(&VM);
|
||||||
wo_module_free(&mod);
|
wo_module_free(&mod);
|
||||||
return rc == 0 ? 0 : 1;
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue