- lexer: backtick raw text literal — content verbatim, no escape
processing, common source margin removed at lex time; `${ }` raw and
`{{ }}` auto-escaping holes
- `{{ e }}` desugars to `esc(${e})` in parser.ml — a Call on the `esc`
in scope, so types/owner/emit/.wob/VM are untouched
- WO-E004 unterminated raw literal; WO-E005 newline inside "..." —
closes a hole where a missing quote silently ate the rest of the file
- wo-html: `Component` interface, `render_all`, `Layout`, README
- framework: `ok_html` joins ok_text/ok_json in http/types.wo
- site + shop restructured to one-feature-one-module MVC (view +
controller per directory, model at the root, bootstrap-only main)
- removed the filler `pad: Int` convention — verified unnecessary for
plain classes, interface dispatch, containers and actors
- corrected recorded claims: gap #1 blocks neither the build nor the
layout; a class crosses module lines, only a free fn is scoped
- docs/guides/language-surface.md — the full grammar inventory
- story 37 landed and moved to done/
Gates: oop-accept MET, oop-e2e 116/0, woc-test 556/0, site 11/0,
web-app 46/0, fibers 10/0, db-actor 8/0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
9.1 KiB
Text
100 lines
9.1 KiB
Text
-- content.wo — the tutorial chapters, seeded into the Chapter table on
|
|
-- first boot (types.wo's seed_if_empty). Model CONTENT, so it sits in
|
|
-- the root module beside types.wo: it inserts rows. Bodies are HTML fragments
|
|
-- BUILT with the wo-html dep — prose in el(), code samples through
|
|
-- code_block() which escapes them. Editing a chapter later (the admin
|
|
-- route) overwrites body/title in place; the WAL keeps the edit across
|
|
-- restarts, which is exactly chapter 6's lesson demonstrated by the
|
|
-- site that teaches it.
|
|
use html
|
|
|
|
fn ch_hello() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"A writeonce program is one directory of <code>.wo</code> files and one entry: a free " .. "function named <code>main</code>. It returns the process exit code. There is no " .. "runtime to install separately and no build pipeline — <code>woc build</code> produces " .. "ONE self-contained binary with the VM and your bytecode inside.");
|
|
b = b .. code_block("fn main() -> Int {\n print(\"hello, writeonce\");\n return 0;\n}");
|
|
b = b .. el("p", "leading-relaxed mt-4",
|
|
"Run it: <code>woc build . -o hello && ./hello</code>. " .. "Statements end with <code>;</code>, blocks use braces, comments start with <code>--</code>.");
|
|
return b;
|
|
}
|
|
|
|
fn ch_values() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"<code>let</code> binds a value; the type is inferred. Scalars: <code>Int</code> (64-bit), " .. "<code>Float</code>, <code>Bool</code>, <code>Text</code> (bytes, binary-safe). Text " .. "interpolates with <code>\${...}</code> and concatenates with <code>..</code>. " .. "Integer literals speak hex and binary, and the full bitwise set is here: " .. "<code>& | ^ << >></code> — grouped Go-style, so a mask compare needs no parentheses.");
|
|
b = b .. code_block("let port = 8080;\nlet pi = 3.14159;\nlet name = \"writeonce\";\nlet msg = \"listening on \${port}\";\n\nlet flags = 0b1010_0001;\nlet high = flags & 0xF0; -- bitwise AND, then == compares\nlet shifted = 1 << 12; -- 4096\nif flags & 0x80 != 0 {\n print(\"top bit set\"); -- groups (flags & 0x80) != 0\n}");
|
|
return b;
|
|
}
|
|
|
|
fn ch_containers() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"Two containers: <code>multi T</code> (a growable list) and <code>map<K, V></code>. " .. "A map read <code>m[k]</code> answers nil when the key is absent — the everyday idiom " .. "for optional lookups like HTTP headers. <code>for .. in</code> walks both.");
|
|
b = b .. code_block("let langs: multi Text = [\"c\", \"ocaml\", \"writeonce\"];\npush(langs, \"more\");\nprint(\"count \${len(langs)}\");\n\nlet ages: map<Text, Int> = {};\nages[\"ada\"] = 36;\nlet a = ages[\"grace\"]; -- ?Int: nil, no trap\nif a == nil { print(\"unknown\"); }\n\nfor l in langs {\n print(l);\n}\nfor k, v in ages {\n print(\"\${k} is \${v}\");\n}");
|
|
return b;
|
|
}
|
|
|
|
fn ch_classes() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"Classes hold fields and methods. There are NO function values and NO closures — a " .. "deliberate doctrine: behavior travels as a class satisfying an interface, and " .. "satisfaction is structural (same method name and shape, Go-style, no " .. "<code>implements</code>). This is how the web framework takes handlers.");
|
|
b = b .. code_block("interface Handler {\n fn handle(req: Req) -> Resp\n}\n\nclass Hello {\n greeting: Text\n fn handle(req: Req) -> Resp {\n return ok_text(\"\${self.greeting}, \${req.path}\");\n }\n}\n\n-- any class with a matching handle() satisfies Handler\napp.get(\"/hello\", Hello { greeting: \"hi\" });");
|
|
return b;
|
|
}
|
|
|
|
fn ch_optionals() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"<code>?T</code> is a value or nil, and the compiler forces the check before use. " .. "Failures are TRAPS: named, catchable, never silent. <code>try ... catch (e)</code> is " .. "an expression; <code>e</code> carries code, line, method and message. Anything " .. "uncaught ends the program with the same structured report.");
|
|
b = b .. code_block("let n = parse_int(\"42x\"); -- ?Int\nif n == nil {\n print(\"not a number\");\n}\n\nlet r = try fs.read_all(\"/etc/missing\", 4096) catch (e) e.msg;\nprint(r); -- the file's bytes, or \"No such file or directory\"\n\nlet d = 0;\nlet q = try 10 / d catch (e) 0 - 1; -- DIV0 is a trap, caught here");
|
|
return b;
|
|
}
|
|
|
|
fn ch_tables() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"The database is IN the language. <code>@table</code> makes a class a table; " .. "<code>insert</code> writes a row; queries are first-class expressions; an UPDATE is a " .. "plain field assignment on a query result. With <code>WO_DATA=<dir></code> every " .. "commit is WAL-durable before it is acknowledged and replays on restart — this very " .. "site stores these chapters that way, and the admin form's edits survive a kill.");
|
|
b = b .. code_block("@table(name: \"notes\", index: [tag])\nclass Note {\n tag: Text @unique\n val: Int\n}\n\ninsert Note { tag: \"first\", val: 1 };\n\nfor n in from x in Note where x.val > 0 order by x.tag select x {\n print(\"\${n.tag} = \${n.val}\");\n}\n\nlet hits = from x in Note where x.tag == \"first\" take 1 select x;\nif len(hits) == 1 {\n hits[0].val = 2; -- an update: assign through the row\n}");
|
|
return b;
|
|
}
|
|
|
|
fn ch_actors() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"Concurrency is actors on fibers: <code>spawn</code> makes an actor from a class with a " .. "<code>receive</code> method, <code>send</code> delivers one message at a time, and " .. "ownership MOVES with the message — no locks, no shared mutable state, no data races " .. "by construction. Blocking calls park the fiber; the shard serves others meanwhile. " .. "One VM per core by default; mailboxes are bounded (a full one is a catchable trap).");
|
|
b = b .. code_block("class Counter {\n total: Int\n fn receive(msg: Tick) {\n self.total = self.total + msg.n;\n print(\"total \${self.total}\");\n }\n}\n\nclass Tick {\n n: Int\n}\n\nfn main() -> Int {\n let c: actor Tick = spawn Counter { total: 0 };\n send(c, Tick { n: 1 });\n send(c, Tick { n: 2 });\n time.sleep(50); -- parks this fiber; the actor runs\n return 0;\n}");
|
|
return b;
|
|
}
|
|
|
|
fn ch_deps() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"Dependencies are git repositories pinned in <code>wo.toml</code>; <code>wo.lock</code> " .. "records the exact revision, and locked builds work offline. The [deps] KEY names the " .. "module you <code>use</code>. This site has two: the web framework, and the wo-html " .. "library that rendered the page you are reading.");
|
|
b = b .. code_block(`
|
|
[deps]
|
|
framework = { git = "https://github.com/shoneyj/writeonce-framework", rev = "v0.1.0" }
|
|
html = { git = "https://github.com/shoneyj/wo-html", rev = "v0.1.0" }`);
|
|
b = b .. code_block(`
|
|
use framework
|
|
use framework/http
|
|
use html
|
|
|
|
-- html's builders + tailwind-style utilities, zero JS, no build step:
|
|
let body = el("h1", "text-3xl font-bold", "Hello");
|
|
return ok_html(page("Hello", body));`);
|
|
return b;
|
|
}
|
|
|
|
fn ch_serving() -> Text {
|
|
let b = el("p", "leading-relaxed mb-4",
|
|
"The whole stack of this site: routes with <code>:param</code> captures, a middleware " .. "chain, handler classes, <code>@table</code> persistence, and server-rendered HTML — " .. "one binary behind a proxy. This is the site's own main, abbreviated:");
|
|
b = b .. code_block("fn main(args: multi Text) -> Int {\n seed_if_empty();\n let app = App { middleware: [], routes: [] };\n app.use_mw(Mw { m: Logging {} });\n app.get(\"/\", Home {});\n app.get(\"/ch/:slug\", ShowChapter {});\n app.post(\"/admin/ch/:slug\", AdminEdit { token: token });\n return app.serve(\"127.0.0.1\", port);\n}");
|
|
b = b .. el("p", "leading-relaxed mt-4",
|
|
"The admin route checks its bearer token in the handler — mechanism lives in the " .. "framework (<code>bearer_token</code>, constant-time <code>ct_eq</code>), POLICY stays " .. "in the app. Try editing this chapter: " .. "<code>curl -X POST -H \"authorization: Bearer ...\" -d \"title=...&body=...\" /admin/ch/serving</code>.");
|
|
return b;
|
|
}
|
|
|
|
-- One seed row per chapter: (ord, slug, title, body-builder above).
|
|
pub fn seed_chapters() {
|
|
insert Chapter { slug: "hello", ord: 1, title: "Hello, writeonce", body: ch_hello() };
|
|
insert Chapter { slug: "values", ord: 2, title: "Values, Text and bitwise", body: ch_values() };
|
|
insert Chapter { slug: "containers", ord: 3, title: "multi and map", body: ch_containers() };
|
|
insert Chapter { slug: "classes", ord: 4, title: "Classes and interfaces", body: ch_classes() };
|
|
insert Chapter { slug: "optionals", ord: 5, title: "Optionals and traps", body: ch_optionals() };
|
|
insert Chapter { slug: "tables", ord: 6, title: "@table: the built-in database", body: ch_tables() };
|
|
insert Chapter { slug: "actors", ord: 7, title: "Actors and fibers", body: ch_actors() };
|
|
insert Chapter { slug: "deps", ord: 8, title: "Dependencies", body: ch_deps() };
|
|
insert Chapter { slug: "serving", ord: 9, title: "Serving the web (this site)", body: ch_serving() };
|
|
}
|