From 93739ab4a9911abfcf93357ca867d706dce5c188 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 21:55:22 +0000 Subject: [PATCH 1/2] Dogfood getd/min/max/eq_ci/snxt sugar; densify store/HTTP apps Add density sugar for dict defaults, numeric min/max, case-insensitive equality, and store next-id allocation. Ship eqci/minmax/getd CLIs and densify notes, rest_tasks, task_api, webhook_relay, and json_get. Co-authored-by: William Elias --- apps/eqci_cli/README.md | 8 + apps/eqci_cli/eqci_cli.howl | 7 + apps/eqci_cli/eqci_cli_test.go | 31 +++ apps/getd_cli/README.md | 8 + apps/getd_cli/getd_cli.howl | 9 + apps/getd_cli/getd_cli_test.go | 34 +++ apps/json_get/json_get.howl | 2 +- apps/minmax_cli/README.md | 8 + apps/minmax_cli/minmax_cli.howl | 8 + apps/minmax_cli/minmax_cli_test.go | 23 ++ apps/notes_cli/notes_cli.howl | 7 +- apps/rest_tasks/rest_tasks.howl | 12 +- apps/task_api/task_api.howl | 42 +-- apps/webhook_relay/webhook_relay.howl | 23 +- benchmarks/real_apps/eqci_cli/go/main.go | 24 ++ benchmarks/real_apps/eqci_cli/node/main.js | 17 ++ benchmarks/real_apps/eqci_cli/python/main.py | 17 ++ benchmarks/real_apps/getd_cli/go/main.go | 38 +++ benchmarks/real_apps/getd_cli/node/main.js | 26 ++ benchmarks/real_apps/getd_cli/python/main.py | 23 ++ benchmarks/real_apps/minmax_cli/go/main.go | 31 +++ benchmarks/real_apps/minmax_cli/node/main.js | 16 ++ .../real_apps/minmax_cli/python/main.py | 16 ++ benchmarks/real_apps/prove_real_apps.py | 126 +++++++++ .../2026-10-10_dogfood_apps_round11.md | 33 +++ docs/reference/construct_coverage.md | 5 + internal/ast/desugar.go | 239 +++++++++++++++++- internal/ast/desugar_test.go | 18 ++ internal/construct/construct.go | 5 + 29 files changed, 793 insertions(+), 63 deletions(-) create mode 100644 apps/eqci_cli/README.md create mode 100644 apps/eqci_cli/eqci_cli.howl create mode 100644 apps/eqci_cli/eqci_cli_test.go create mode 100644 apps/getd_cli/README.md create mode 100644 apps/getd_cli/getd_cli.howl create mode 100644 apps/getd_cli/getd_cli_test.go create mode 100644 apps/minmax_cli/README.md create mode 100644 apps/minmax_cli/minmax_cli.howl create mode 100644 apps/minmax_cli/minmax_cli_test.go create mode 100644 benchmarks/real_apps/eqci_cli/go/main.go create mode 100644 benchmarks/real_apps/eqci_cli/node/main.js create mode 100644 benchmarks/real_apps/eqci_cli/python/main.py create mode 100644 benchmarks/real_apps/getd_cli/go/main.go create mode 100644 benchmarks/real_apps/getd_cli/node/main.js create mode 100644 benchmarks/real_apps/getd_cli/python/main.py create mode 100644 benchmarks/real_apps/minmax_cli/go/main.go create mode 100644 benchmarks/real_apps/minmax_cli/node/main.js create mode 100644 benchmarks/real_apps/minmax_cli/python/main.py create mode 100644 docs/journals/2026-10-10_dogfood_apps_round11.md diff --git a/apps/eqci_cli/README.md b/apps/eqci_cli/README.md new file mode 100644 index 00000000..56b1d750 --- /dev/null +++ b/apps/eqci_cli/README.md @@ -0,0 +1,8 @@ +# eqci_cli + +Case-insensitive string equality via `eq_ci` density sugar. + +```bash +go run ../../howlframe.go -compile-bc eqci_cli.howl +go run ../../howlframe.go -run-bc eqci_cli.howl.bc.bin Hello hello +``` diff --git a/apps/eqci_cli/eqci_cli.howl b/apps/eqci_cli/eqci_cli.howl new file mode 100644 index 00000000..b7786852 --- /dev/null +++ b/apps/eqci_cli/eqci_cli.howl @@ -0,0 +1,7 @@ +(do + (let (a (cli_args 0) b (cli_args 1)) + (if (or (blank a) (blank b)) + (do (print "Usage: eqci_cli ") (exit 2)) + (if (eq_ci a b) + (do (print "YES") (exit 0)) + (do (print "NO") (exit 1)))))) diff --git a/apps/eqci_cli/eqci_cli_test.go b/apps/eqci_cli/eqci_cli_test.go new file mode 100644 index 00000000..fa4e2aff --- /dev/null +++ b/apps/eqci_cli/eqci_cli_test.go @@ -0,0 +1,31 @@ +package eqci_cli_test + +import ( + "os/exec" + "strings" + "testing" +) + +func TestEqciCLI(t *testing.T) { + cmd := exec.Command("go", "run", "../../howlframe.go", "-compile-bc", "eqci_cli.howl") + cmd.Dir = "." + if err := cmd.Run(); err != nil { + t.Fatalf("compile: %v", err) + } + + cmd = exec.Command("go", "run", "../../howlframe.go", "-run-bc", + "eqci_cli.howl.bc.bin", "Hello", "hello") + cmd.Dir = "." + out, err := cmd.CombinedOutput() + if err != nil || !strings.Contains(string(out), "YES") { + t.Fatalf("yes: %v %s", err, out) + } + + cmd = exec.Command("go", "run", "../../howlframe.go", "-run-bc", + "eqci_cli.howl.bc.bin", "Hello", "world") + cmd.Dir = "." + out, err = cmd.CombinedOutput() + if err == nil || !strings.Contains(string(out), "NO") { + t.Fatalf("no: err=%v out=%s", err, out) + } +} diff --git a/apps/getd_cli/README.md b/apps/getd_cli/README.md new file mode 100644 index 00000000..708e6baf --- /dev/null +++ b/apps/getd_cli/README.md @@ -0,0 +1,8 @@ +# getd_cli + +JSON key lookup with default via `getd` density sugar. + +```bash +go run ../../howlframe.go -compile-bc getd_cli.howl +go run ../../howlframe.go -run-bc -allow-caps filesystem getd_cli.howl.bc.bin sample.json name +``` diff --git a/apps/getd_cli/getd_cli.howl b/apps/getd_cli/getd_cli.howl new file mode 100644 index 00000000..8bb937b5 --- /dev/null +++ b/apps/getd_cli/getd_cli.howl @@ -0,0 +1,9 @@ +(do + (let (path (cli_args 0) key (cli_args 1) def (cli_args 2)) + (if (or (blank path) (blank key)) + (do (print "Usage: getd_cli [default]") (exit 2)) + (try_let (content (read_text path)) + (catch e (do (print "error=" e) (exit 2))) + (try_let (obj (parse_json Any content)) + (catch e (do (print "error=" e) (exit 2))) + (print (getd obj key (if (blank def) "MISSING" def)))))))) diff --git a/apps/getd_cli/getd_cli_test.go b/apps/getd_cli/getd_cli_test.go new file mode 100644 index 00000000..76e055d4 --- /dev/null +++ b/apps/getd_cli/getd_cli_test.go @@ -0,0 +1,34 @@ +package getd_cli_test + +import ( + "os" + "os/exec" + "strings" + "testing" +) + +func TestGetdCLI(t *testing.T) { + cmd := exec.Command("go", "run", "../../howlframe.go", "-compile-bc", "getd_cli.howl") + cmd.Dir = "." + if err := cmd.Run(); err != nil { + t.Fatalf("compile: %v", err) + } + os.WriteFile("sample.json", []byte(`{"name":"ada"}`), 0644) + defer os.Remove("sample.json") + + cmd = exec.Command("go", "run", "../../howlframe.go", "-run-bc", "-allow-caps", "filesystem", + "getd_cli.howl.bc.bin", "sample.json", "name") + cmd.Dir = "." + out, err := cmd.CombinedOutput() + if err != nil || !strings.Contains(string(out), "ada") { + t.Fatalf("hit: %v %s", err, out) + } + + cmd = exec.Command("go", "run", "../../howlframe.go", "-run-bc", "-allow-caps", "filesystem", + "getd_cli.howl.bc.bin", "sample.json", "missing", "fallback") + cmd.Dir = "." + out, err = cmd.CombinedOutput() + if err != nil || !strings.Contains(string(out), "fallback") { + t.Fatalf("default: %v %s", err, out) + } +} diff --git a/apps/json_get/json_get.howl b/apps/json_get/json_get.howl index a7a52b1f..0b0a93d0 100644 --- a/apps/json_get/json_get.howl +++ b/apps/json_get/json_get.howl @@ -1,6 +1,6 @@ (do (let (path (cli_args 0) key (cli_args 1)) - (if (or (= path "") (= key "")) + (if (or (blank path) (blank key)) (do (print "Usage: json_get ") (exit 2)) (try_let (content (read_text path)) (catch e (do (print "error=" e) (exit 2))) diff --git a/apps/minmax_cli/README.md b/apps/minmax_cli/README.md new file mode 100644 index 00000000..5831e458 --- /dev/null +++ b/apps/minmax_cli/README.md @@ -0,0 +1,8 @@ +# minmax_cli + +Print min and max of two integers via `min`/`max` density sugar. + +```bash +go run ../../howlframe.go -compile-bc minmax_cli.howl +go run ../../howlframe.go -run-bc minmax_cli.howl.bc.bin 3 9 +``` diff --git a/apps/minmax_cli/minmax_cli.howl b/apps/minmax_cli/minmax_cli.howl new file mode 100644 index 00000000..8bb19a98 --- /dev/null +++ b/apps/minmax_cli/minmax_cli.howl @@ -0,0 +1,8 @@ +(do + (let (a (cli_args 0) b (cli_args 1)) + (if (or (blank a) (blank b)) + (do (print "Usage: minmax_cli ") (exit 2)) + (do + (print (str "min=" (min (to_int a) (to_int b)))) + (print (str "max=" (max (to_int a) (to_int b)))) + (exit 0))))) diff --git a/apps/minmax_cli/minmax_cli_test.go b/apps/minmax_cli/minmax_cli_test.go new file mode 100644 index 00000000..60f6ada4 --- /dev/null +++ b/apps/minmax_cli/minmax_cli_test.go @@ -0,0 +1,23 @@ +package minmax_cli_test + +import ( + "os/exec" + "strings" + "testing" +) + +func TestMinmaxCLI(t *testing.T) { + cmd := exec.Command("go", "run", "../../howlframe.go", "-compile-bc", "minmax_cli.howl") + cmd.Dir = "." + if err := cmd.Run(); err != nil { + t.Fatalf("compile: %v", err) + } + + cmd = exec.Command("go", "run", "../../howlframe.go", "-run-bc", + "minmax_cli.howl.bc.bin", "3", "9") + cmd.Dir = "." + out, err := cmd.CombinedOutput() + if err != nil || !strings.Contains(string(out), "min=3") || !strings.Contains(string(out), "max=9") { + t.Fatalf("minmax: %v %s", err, out) + } +} diff --git a/apps/notes_cli/notes_cli.howl b/apps/notes_cli/notes_cli.howl index 7720a915..a19c25b3 100644 --- a/apps/notes_cli/notes_cli.howl +++ b/apps/notes_cli/notes_cli.howl @@ -1,17 +1,16 @@ (do (let (uri (env "NOTES_STORE")) (do - (store_open kv (if (= uri "") "file://notes.store" uri)) + (store_open kv (if (blank uri) "file://notes.store" uri)) (let (a (cli_args) i 0) (while (< i (list_len a)) (do (switch (list_get a i) ("add" (do (inc i) - (let (title (list_get a i) n (to_int (nz (sget kv "next_id") "1"))) + (let (title (list_get a i) n (snxt kv "next_id")) (do - (sput kv (to_string n) title) - (sput kv "next_id" (to_string (+ n 1))) + (sput kv n title) (print "ADDED" n title))))) ("list" (for k (store_keys kv) diff --git a/apps/rest_tasks/rest_tasks.howl b/apps/rest_tasks/rest_tasks.howl index 47ae4e0b..290b5589 100644 --- a/apps/rest_tasks/rest_tasks.howl +++ b/apps/rest_tasks/rest_tasks.howl @@ -2,13 +2,7 @@ (defun next_id () (do (store_open kv "memory://session") - (let (rec (store_get kv "next_id") n 1) - (do - (if (is_nil rec) - (set n 1) - (set n (to_int (map_get rec "val")))) - (store_put kv "next_id" (dict ("val" (to_string (+ n 1))))) - (return (to_string n)))))) + (return (snxt kv "next_id")))) (route "/tasks" (lambda (req) (cond @@ -25,8 +19,8 @@ ((= (req_method req) "POST") (try_let (body (parse_json Any req.body)) (catch e (res_json 400 (dict ("error" "invalid_json")))) - (let (title (map_get body "title")) - (if (= title "") + (let (title (getd body "title" "")) + (if (blank title) (res_json 400 (dict ("error" "title_required"))) (let (id (next_id) task (dict ("id" id) ("title" title) ("status" "open"))) (do diff --git a/apps/task_api/task_api.howl b/apps/task_api/task_api.howl index 341de2e5..10ca4213 100644 --- a/apps/task_api/task_api.howl +++ b/apps/task_api/task_api.howl @@ -3,44 +3,20 @@ (defun next_task_id () (do (store_open kv "memory://session") - (let (rec (store_get kv "next_id")) - (let (n 1) - (do - (if (is_nil rec) - (set n 1) - (let (v (map_get rec "val")) - (set n (to_int v)) - ) - ) - (store_put kv "next_id" (dict ("val" (to_string (+ n 1))))) - (return (to_string n)) - ) - ) - ) - ) - ) + (return (snxt kv "next_id")))) (route "/tasks/create" (lambda (req) (try_let (body (parse_json TaskInput req.body)) (catch parse_err - (res_json 400 (dict ("error" "invalid_json"))) - ) - (let (title (map_get body "title")) - (if (= title "") + (res_json 400 (dict ("error" "invalid_json")))) + (let (title (getd body "title" "")) + (if (blank title) (res_json 400 (dict ("error" "title_required"))) - (let (id (call next_task_id)) - (let (task (dict ("id" id) ("title" title) ("status" "open"))) - (do - (store_open kv "memory://session") - (store_put kv id task) - (res_json 201 task) - ) - ) - ) - ) - ) - ) - )) + (let (id (call next_task_id) task (dict ("id" id) ("title" title) ("status" "open"))) + (do + (store_open kv "memory://session") + (store_put kv id task) + (res_json 201 task)))))))) (route "/tasks/list" (lambda (req) (do diff --git a/apps/webhook_relay/webhook_relay.howl b/apps/webhook_relay/webhook_relay.howl index 12562388..346325c4 100644 --- a/apps/webhook_relay/webhook_relay.howl +++ b/apps/webhook_relay/webhook_relay.howl @@ -1,16 +1,15 @@ (http_server 8098 (route "/relay" (lambda (req) (let (upstream (env "UPSTREAM_URL")) - (if (= upstream "") + (if (blank upstream) (res_json 500 (dict ("error" "missing_UPSTREAM_URL"))) - (let (method (req_method req)) - (cond - ((= method "GET") - (let (r (http_request upstream "GET")) - (http_res r))) - ((= method "POST") - (try_let (body (parse_json Any req.body)) - (catch e (res_json 400 (dict ("error" "invalid_json")))) - (let (r (http_request upstream "POST" (encode_json body))) - (http_res r)))) - (else (res_json 405 (dict ("error" "method_not_allowed"))))))))))) + (switch (req_method req) + ("GET" + (let (r (http_request upstream "GET")) + (http_res r))) + ("POST" + (try_let (body (parse_json Any req.body)) + (catch e (res_json 400 (dict ("error" "invalid_json")))) + (let (r (http_request upstream "POST" (encode_json body))) + (http_res r)))) + (else (res_json 405 (dict ("error" "method_not_allowed")))))))))) diff --git a/benchmarks/real_apps/eqci_cli/go/main.go b/benchmarks/real_apps/eqci_cli/go/main.go new file mode 100644 index 00000000..7d3fef28 --- /dev/null +++ b/benchmarks/real_apps/eqci_cli/go/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "os" + "strings" +) + +func main() { + os.Exit(run(os.Args[1:])) +} + +func run(args []string) int { + if len(args) < 2 || args[0] == "" || args[1] == "" { + fmt.Println("Usage: eqci_cli ") + return 2 + } + if strings.EqualFold(args[0], args[1]) { + fmt.Println("YES") + return 0 + } + fmt.Println("NO") + return 1 +} diff --git a/benchmarks/real_apps/eqci_cli/node/main.js b/benchmarks/real_apps/eqci_cli/node/main.js new file mode 100644 index 00000000..988adb21 --- /dev/null +++ b/benchmarks/real_apps/eqci_cli/node/main.js @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +function main(argv) { + const args = argv.slice(2); + if (args.length < 2 || args[0] === "" || args[1] === "") { + console.log("Usage: eqci_cli "); + return 2; + } + if (args[0].toLowerCase() === args[1].toLowerCase()) { + console.log("YES"); + return 0; + } + console.log("NO"); + return 1; +} + +process.exit(main(process.argv)); diff --git a/benchmarks/real_apps/eqci_cli/python/main.py b/benchmarks/real_apps/eqci_cli/python/main.py new file mode 100644 index 00000000..37479440 --- /dev/null +++ b/benchmarks/real_apps/eqci_cli/python/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +import sys + + +def main(argv: list[str]) -> int: + if len(argv) < 3 or argv[1] == "" or argv[2] == "": + print("Usage: eqci_cli ") + return 2 + if argv[1].lower() == argv[2].lower(): + print("YES") + return 0 + print("NO") + return 1 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/benchmarks/real_apps/getd_cli/go/main.go b/benchmarks/real_apps/getd_cli/go/main.go new file mode 100644 index 00000000..b00f9279 --- /dev/null +++ b/benchmarks/real_apps/getd_cli/go/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +func main() { + os.Exit(run(os.Args[1:])) +} + +func run(args []string) int { + if len(args) < 2 || args[0] == "" || args[1] == "" { + fmt.Println("Usage: getd_cli [default]") + return 2 + } + def := "MISSING" + if len(args) > 2 && args[2] != "" { + def = args[2] + } + raw, err := os.ReadFile(args[0]) + if err != nil { + fmt.Printf("error=%v\n", err) + return 2 + } + var obj map[string]any + if err := json.Unmarshal(raw, &obj); err != nil { + fmt.Printf("error=%v\n", err) + return 2 + } + if v, ok := obj[args[1]]; ok { + fmt.Println(v) + return 0 + } + fmt.Println(def) + return 0 +} diff --git a/benchmarks/real_apps/getd_cli/node/main.js b/benchmarks/real_apps/getd_cli/node/main.js new file mode 100644 index 00000000..9a125373 --- /dev/null +++ b/benchmarks/real_apps/getd_cli/node/main.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node +const fs = require("fs"); + +function main(argv) { + const args = argv.slice(2); + if (args.length < 2 || args[0] === "" || args[1] === "") { + console.log("Usage: getd_cli [default]"); + return 2; + } + const def = args.length > 2 && args[2] !== "" ? args[2] : "MISSING"; + let obj; + try { + obj = JSON.parse(fs.readFileSync(args[0], "utf8")); + } catch (e) { + console.log("error=" + e); + return 2; + } + if (Object.prototype.hasOwnProperty.call(obj, args[1])) { + console.log(obj[args[1]]); + return 0; + } + console.log(def); + return 0; +} + +process.exit(main(process.argv)); diff --git a/benchmarks/real_apps/getd_cli/python/main.py b/benchmarks/real_apps/getd_cli/python/main.py new file mode 100644 index 00000000..8f82ef57 --- /dev/null +++ b/benchmarks/real_apps/getd_cli/python/main.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +import json +import sys + + +def main(argv: list[str]) -> int: + if len(argv) < 3 or argv[1] == "" or argv[2] == "": + print("Usage: getd_cli [default]") + return 2 + path, key = argv[1], argv[2] + default = argv[3] if len(argv) > 3 and argv[3] != "" else "MISSING" + try: + with open(path, encoding="utf-8") as f: + obj = json.load(f) + except (OSError, json.JSONDecodeError) as e: + print(f"error={e}") + return 2 + print(obj[key] if key in obj else default) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/benchmarks/real_apps/minmax_cli/go/main.go b/benchmarks/real_apps/minmax_cli/go/main.go new file mode 100644 index 00000000..06191705 --- /dev/null +++ b/benchmarks/real_apps/minmax_cli/go/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "os" + "strconv" +) + +func main() { + os.Exit(run(os.Args[1:])) +} + +func run(args []string) int { + if len(args) < 2 || args[0] == "" || args[1] == "" { + fmt.Println("Usage: minmax_cli ") + return 2 + } + a, err1 := strconv.Atoi(args[0]) + b, err2 := strconv.Atoi(args[1]) + if err1 != nil || err2 != nil { + fmt.Println("Usage: minmax_cli ") + return 2 + } + lo, hi := a, b + if b < a { + lo, hi = b, a + } + fmt.Printf("min=%d\n", lo) + fmt.Printf("max=%d\n", hi) + return 0 +} diff --git a/benchmarks/real_apps/minmax_cli/node/main.js b/benchmarks/real_apps/minmax_cli/node/main.js new file mode 100644 index 00000000..ac3dd475 --- /dev/null +++ b/benchmarks/real_apps/minmax_cli/node/main.js @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +function main(argv) { + const args = argv.slice(2); + if (args.length < 2 || args[0] === "" || args[1] === "") { + console.log("Usage: minmax_cli "); + return 2; + } + const a = parseInt(args[0], 10); + const b = parseInt(args[1], 10); + console.log("min=" + Math.min(a, b)); + console.log("max=" + Math.max(a, b)); + return 0; +} + +process.exit(main(process.argv)); diff --git a/benchmarks/real_apps/minmax_cli/python/main.py b/benchmarks/real_apps/minmax_cli/python/main.py new file mode 100644 index 00000000..92e1b5f0 --- /dev/null +++ b/benchmarks/real_apps/minmax_cli/python/main.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +import sys + + +def main(argv: list[str]) -> int: + if len(argv) < 3 or argv[1] == "" or argv[2] == "": + print("Usage: minmax_cli ") + return 2 + a, b = int(argv[1]), int(argv[2]) + print(f"min={min(a, b)}") + print(f"max={max(a, b)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/benchmarks/real_apps/prove_real_apps.py b/benchmarks/real_apps/prove_real_apps.py index 10458e51..e4f28f6f 100644 --- a/benchmarks/real_apps/prove_real_apps.py +++ b/benchmarks/real_apps/prove_real_apps.py @@ -314,6 +314,33 @@ def count_tokens(text: str) -> int: "howl_caps": "filesystem", "test_pkg": "./apps/icontains_cli/", }, + "eqci_cli": { + "howlframe": ROOT / "apps/eqci_cli/eqci_cli.howl", + "python": HERE / "eqci_cli/python/main.py", + "go": HERE / "eqci_cli/go/main.go", + "node": HERE / "eqci_cli/node/main.js", + "kind": "cli_eqci", + "howl_caps": "", + "test_pkg": "./apps/eqci_cli/", + }, + "minmax_cli": { + "howlframe": ROOT / "apps/minmax_cli/minmax_cli.howl", + "python": HERE / "minmax_cli/python/main.py", + "go": HERE / "minmax_cli/go/main.go", + "node": HERE / "minmax_cli/node/main.js", + "kind": "cli_minmax", + "howl_caps": "", + "test_pkg": "./apps/minmax_cli/", + }, + "getd_cli": { + "howlframe": ROOT / "apps/getd_cli/getd_cli.howl", + "python": HERE / "getd_cli/python/main.py", + "go": HERE / "getd_cli/go/main.go", + "node": HERE / "getd_cli/node/main.js", + "kind": "cli_getd", + "howl_caps": "filesystem", + "test_pkg": "./apps/getd_cli/", + }, } LANGS = ["howlframe", "python", "go", "node"] @@ -1505,6 +1532,102 @@ def parity_icontains(lang: str, source: Path, howl_bin: str, caps: str) -> None: raise RuntimeError(f"{lang} icontains missing failed: {r.returncode} {out!r}") +def run_cli_eqci(lang: str, source: Path, howl_bin: str, caps: str, args: list[str], cwd: Path): + if lang == "howlframe": + bc = cwd / "eqci.hfbc" + compile_howl(howl_bin, source, bc) + cmd = [howl_bin, "-run-bc", str(bc), *args] + if caps: + cmd = [howl_bin, "-run-bc", "-allow-caps", caps, str(bc), *args] + return run(cmd, cwd=cwd) + if lang == "python": + return run([sys.executable, str(source), *args], cwd=cwd) + if lang == "go": + exe = cwd / "eqci_cli" + r = run(["go", "build", "-o", str(exe), str(source)]) + if r.returncode != 0: + raise RuntimeError(r.stderr) + return run([str(exe), *args], cwd=cwd) + if lang == "node": + return run(["node", str(source), *args], cwd=cwd) + raise ValueError(lang) + + +def parity_eqci(lang: str, source: Path, howl_bin: str, caps: str) -> None: + with tempfile.TemporaryDirectory() as td: + cwd = Path(td) + r = run_cli_eqci(lang, source, howl_bin, caps, ["Hello", "hello"], cwd) + out = r.stdout + r.stderr + if r.returncode != 0 or "YES" not in out: + raise RuntimeError(f"{lang} eqci yes failed: {r.returncode} {out!r}") + r = run_cli_eqci(lang, source, howl_bin, caps, ["Hello", "world"], cwd) + out = r.stdout + r.stderr + if r.returncode == 0 or "NO" not in out: + raise RuntimeError(f"{lang} eqci no failed: {r.returncode} {out!r}") + + +def run_cli_minmax(lang: str, source: Path, howl_bin: str, caps: str, args: list[str], cwd: Path): + if lang == "howlframe": + bc = cwd / "minmax.hfbc" + compile_howl(howl_bin, source, bc) + cmd = [howl_bin, "-run-bc", str(bc), *args] + if caps: + cmd = [howl_bin, "-run-bc", "-allow-caps", caps, str(bc), *args] + return run(cmd, cwd=cwd) + if lang == "python": + return run([sys.executable, str(source), *args], cwd=cwd) + if lang == "go": + exe = cwd / "minmax_cli" + r = run(["go", "build", "-o", str(exe), str(source)]) + if r.returncode != 0: + raise RuntimeError(r.stderr) + return run([str(exe), *args], cwd=cwd) + if lang == "node": + return run(["node", str(source), *args], cwd=cwd) + raise ValueError(lang) + + +def parity_minmax(lang: str, source: Path, howl_bin: str, caps: str) -> None: + with tempfile.TemporaryDirectory() as td: + r = run_cli_minmax(lang, source, howl_bin, caps, ["3", "9"], Path(td)) + out = r.stdout + r.stderr + if r.returncode != 0 or "min=3" not in out or "max=9" not in out: + raise RuntimeError(f"{lang} minmax failed: {r.returncode} {out!r}") + + +def run_cli_getd(lang: str, source: Path, howl_bin: str, caps: str, args: list[str], cwd: Path): + if lang == "howlframe": + bc = cwd / "getd.hfbc" + compile_howl(howl_bin, source, bc) + return run([howl_bin, "-run-bc", "-allow-caps", caps, str(bc), *args], cwd=cwd) + if lang == "python": + return run([sys.executable, str(source), *args], cwd=cwd) + if lang == "go": + exe = cwd / "getd_cli" + r = run(["go", "build", "-o", str(exe), str(source)]) + if r.returncode != 0: + raise RuntimeError(r.stderr) + return run([str(exe), *args], cwd=cwd) + if lang == "node": + return run(["node", str(source), *args], cwd=cwd) + raise ValueError(lang) + + +def parity_getd(lang: str, source: Path, howl_bin: str, caps: str) -> None: + with tempfile.TemporaryDirectory() as td: + cwd = Path(td) + sample = cwd / "sample.json" + sample.write_text('{"name":"ada"}') + r = run_cli_getd(lang, source, howl_bin, caps, [str(sample), "name"], cwd) + out = r.stdout + r.stderr + if r.returncode != 0 or "ada" not in out: + raise RuntimeError(f"{lang} getd hit failed: {r.returncode} {out!r}") + r = run_cli_getd(lang, source, howl_bin, caps, [str(sample), "missing", "fallback"], cwd) + out = r.stdout + r.stderr + if r.returncode != 0 or "fallback" not in out: + raise RuntimeError(f"{lang} getd default failed: {r.returncode} {out!r}") + + def run_app_tests(pkg: str) -> None: r = run(["go", "test", pkg, "-count=1"], cwd=ROOT) if r.returncode != 0: @@ -1567,6 +1690,9 @@ def main() -> int: "cli_upper": parity_upper, "cli_trim": parity_trim, "cli_icontains": parity_icontains, + "cli_eqci": parity_eqci, + "cli_minmax": parity_minmax, + "cli_getd": parity_getd, }[meta["kind"]] for lang in LANGS: try: diff --git a/docs/journals/2026-10-10_dogfood_apps_round11.md b/docs/journals/2026-10-10_dogfood_apps_round11.md new file mode 100644 index 00000000..4006aad3 --- /dev/null +++ b/docs/journals/2026-10-10_dogfood_apps_round11.md @@ -0,0 +1,33 @@ +# Dogfood common apps round 11 (2026-10-10) + +## Goal + +General-use density sugar for dict defaults, numeric min/max, case-insensitive +equality, and store next-id allocation; ship dogfood CLIs and densify store/HTTP apps. + +## Blockers found and fixed + +| Gap | Symptom vs other languages | Fix | +|---|---|---| +| No dict `.get(k, default)` | Verbose `map_has` + `map_get` pairs. | `(getd m k d)` sugar. | +| No min/max | Manual if/compare noise. | `(min a b)` / `(max a b)` sugar. | +| Case-insensitive equality | Repeated `to_lower` on both sides. | `(eq_ci a b)` sugar. | +| Next-id counter boilerplate | Repeated sget/sput/+1 in notes/REST. | `(snxt handle key)` sugar. | + +## Apps shipped / densified + +1. **`apps/eqci_cli`**, **`apps/minmax_cli`**, **`apps/getd_cli`** +2. Densified `notes_cli` (`snxt`/`blank`), `rest_tasks`/`task_api` (`snxt`/`getd`/`blank`), + `webhook_relay` (`blank`/`switch`), `json_get` (`blank`) + +## Token proof + +```bash +python3 benchmarks/real_apps/prove_real_apps.py +``` + +## Remaining limits + +- `middleware` still Unsupported +- `match` still Unsupported (prod-flip lock) +- `schema` / SQL migrations still Unsupported diff --git a/docs/reference/construct_coverage.md b/docs/reference/construct_coverage.md index fa6cd3f3..383dce09 100644 --- a/docs/reference/construct_coverage.md +++ b/docs/reference/construct_coverage.md @@ -37,12 +37,14 @@ | `ends_with` | No | None | Yes | No | - | AUTHORITATIVE | | `env` | Yes | environment | No | No | - | AUTHORITATIVE | | `ephemeral_circuit` | Yes | network | No | No | - | AUTHORITATIVE | +| `eq_ci` | No | None | Yes | No | - | AUTHORITATIVE | | `exec` | Yes | process | No | No | - | AUTHORITATIVE | | `exit` | Yes | None | No | No | - | AUTHORITATIVE | | `export` | No | None | Yes | No | - | AUTHORITATIVE | | `fetch` | Yes | network | No | No | - | AUTHORITATIVE | | `for` | Yes | None | No | No | - | AUTHORITATIVE | | `fuzzy_cast` | No | None | No | Yes | - | AUTHORITATIVE | +| `getd` | No | None | Yes | No | - | AUTHORITATIVE | | `go_import` | No | None | No | Yes | - | AUTHORITATIVE | | `html_escape` | Yes | None | No | No | - | AUTHORITATIVE | | `http_body` | No | None | Yes | No | - | AUTHORITATIVE | @@ -69,7 +71,9 @@ | `map_keys` | Yes | None | No | No | - | AUTHORITATIVE | | `map_set` | Yes | None | No | No | - | AUTHORITATIVE | | `match` | No | None | No | Yes | - | AUTHORITATIVE | +| `max` | No | None | Yes | No | - | AUTHORITATIVE | | `middleware` | No | None | No | Yes | - | AUTHORITATIVE | +| `min` | No | None | Yes | No | - | AUTHORITATIVE | | `mkdir` | Yes | filesystem | No | No | - | AUTHORITATIVE | | `module` | No | None | Yes | No | - | AUTHORITATIVE | | `neural_circuit` | Yes | network | No | No | - | AUTHORITATIVE | @@ -100,6 +104,7 @@ | `sget` | No | None | Yes | No | - | AUTHORITATIVE | | `sinc` | No | None | Yes | No | - | AUTHORITATIVE | | `sleep` | Yes | None | No | No | - | AUTHORITATIVE | +| `snxt` | No | None | Yes | No | - | AUTHORITATIVE | | `spawn` | Yes | process | No | No | - | AUTHORITATIVE | | `spawn_agent` | Yes | process | No | No | - | AUTHORITATIVE | | `sput` | No | None | Yes | No | - | AUTHORITATIVE | diff --git a/internal/ast/desugar.go b/internal/ast/desugar.go index 79369df4..2c93eef7 100644 --- a/internal/ast/desugar.go +++ b/internal/ast/desugar.go @@ -29,6 +29,10 @@ package ast // - (not x) → boolean negation via if // - (blank x) → (= x "") // - (unix_now) → (to_string (time_now)) +// - (getd m k d) → map_has gated map_get default +// - (min a b) / (max a b) → comparison via let+if +// - (eq_ci a b) → case-insensitive = via to_lower +// - (snxt handle key) → next string id from store counter // - (fname args...) becomes (call fname args...) when fname is a defun // // Desugar is idempotent and mutates the tree in place. Call it after @@ -211,6 +215,26 @@ func desugarInContext(node *Node, parentHead string, childIndex int) { if isSugarExprContext(parentHead, childIndex) { desugarUnixNow(node) } + case "getd": + if isSugarExprContext(parentHead, childIndex) { + desugarGetd(node) + desugarInContext(node, parentHead, childIndex) + } + case "min", "max": + if isSugarExprContext(parentHead, childIndex) { + desugarMinMax(node) + desugarInContext(node, parentHead, childIndex) + } + case "eq_ci": + if isSugarExprContext(parentHead, childIndex) { + desugarEqCi(node) + desugarInContext(node, parentHead, childIndex) + } + case "snxt": + if isSugarExprContext(parentHead, childIndex) { + desugarSNxt(node) + desugarInContext(node, parentHead, childIndex) + } } } @@ -228,7 +252,8 @@ func isSugarExprContext(parentHead string, childIndex int) bool { "switch", "inc", "dec", "str", "nz", "map_has", "when", "unless", "str_contains", "str_replace", "starts_with", "ends_with", "http_body", "http_status", "http_res", "list_contains", "not", - "blank", "unix_now": + "blank", "unix_now", "getd", "min", "max", "eq_ci", "snxt", + "to_lower", "to_upper", "str_trim": return true case "for": return childIndex == 2 || childIndex == 3 @@ -326,7 +351,7 @@ var reservedCallHeads = map[string]bool{ "switch": true, "inc": true, "dec": true, "str": true, "nz": true, "map_has": true, "when": true, "unless": true, "str_contains": true, "str_replace": true, "starts_with": true, "ends_with": true, "http_body": true, "http_status": true, "http_res": true, "list_contains": true, "not": true, - "blank": true, "unix_now": true, + "blank": true, "unix_now": true, "getd": true, "min": true, "max": true, "eq_ci": true, "snxt": true, "dict": true, "exit": true, "for": true, "if": true, "let": true, "list": true, "print": true, "return": true, "set": true, "stderr": true, "to_lower": true, "to_upper": true, "str_trim": true, @@ -911,6 +936,211 @@ func desugarUnixNow(node *Node) { } } +func desugarGetd(node *Node) { + // (getd m k d) → (let (__m m __k k) (if (map_has __m __k) (map_get __m __k) d)) + // Missing keys are "" in HowlFrame, so membership (not is_nil) is the default gate. + if len(node.Children) != 4 { + ReportError("getd expects (getd dict key default)", node.Line, node.Column) + return + } + mSym := &Node{Type: "SYMBOL", Value: "__m", Line: node.Line, Column: node.Column, Filename: node.Filename} + kSym := &Node{Type: "SYMBOL", Value: "__k", Line: node.Line, Column: node.Column, Filename: node.Filename} + has := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "map_has", Line: node.Line, Column: node.Column, Filename: node.Filename}, + mSym, + kSym, + }, + } + mapGet := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "map_get", Line: node.Line, Column: node.Column, Filename: node.Filename}, + mSym, + kSym, + }, + } + iff := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "if", Line: node.Line, Column: node.Column, Filename: node.Filename}, + has, + mapGet, + node.Children[3], + }, + } + node.Children = []*Node{ + {Type: "SYMBOL", Value: "let", Line: node.Line, Column: node.Column, Filename: node.Filename}, + { + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{mSym, node.Children[1], kSym, node.Children[2]}, + }, + iff, + } +} + +func desugarMinMax(node *Node) { + // (min a b) → (let (__a a __b b) (if (< __a __b) __a __b)) + // (max a b) → (let (__a a __b b) (if (> __a __b) __a __b)) + if len(node.Children) != 3 { + ReportError("min/max expects (min a b) or (max a b)", node.Line, node.Column) + return + } + head := node.Children[0].Value + cmp := "<" + if head == "max" { + cmp = ">" + } + aSym := &Node{Type: "SYMBOL", Value: "__a", Line: node.Line, Column: node.Column, Filename: node.Filename} + bSym := &Node{Type: "SYMBOL", Value: "__b", Line: node.Line, Column: node.Column, Filename: node.Filename} + cmpNode := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: cmp, Line: node.Line, Column: node.Column, Filename: node.Filename}, + aSym, + bSym, + }, + } + iff := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "if", Line: node.Line, Column: node.Column, Filename: node.Filename}, + cmpNode, + aSym, + bSym, + }, + } + node.Children = []*Node{ + {Type: "SYMBOL", Value: "let", Line: node.Line, Column: node.Column, Filename: node.Filename}, + { + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{aSym, node.Children[1], bSym, node.Children[2]}, + }, + iff, + } +} + +func desugarEqCi(node *Node) { + // (eq_ci a b) → (= (to_lower a) (to_lower b)) + if len(node.Children) != 3 { + ReportError("eq_ci expects (eq_ci a b)", node.Line, node.Column) + return + } + lowerA := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "to_lower", Line: node.Line, Column: node.Column, Filename: node.Filename}, + node.Children[1], + }, + } + lowerB := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "to_lower", Line: node.Line, Column: node.Column, Filename: node.Filename}, + node.Children[2], + }, + } + node.Children = []*Node{ + {Type: "SYMBOL", Value: "=", Line: node.Line, Column: node.Column, Filename: node.Filename}, + lowerA, + lowerB, + } +} + +func desugarSNxt(node *Node) { + // (snxt handle key) → allocate next string id from a string counter + // (let (__k key __v (sget handle __k) __n (if (is_nil __v) 1 (to_int __v))) + // (do (sput handle __k (to_string (+ __n 1))) (to_string __n))) + if len(node.Children) != 3 { + ReportError("snxt expects (snxt handle key)", node.Line, node.Column) + return + } + handle := node.Children[1] + key := node.Children[2] + kSym := &Node{Type: "SYMBOL", Value: "__k", Line: node.Line, Column: node.Column, Filename: node.Filename} + vSym := &Node{Type: "SYMBOL", Value: "__v", Line: node.Line, Column: node.Column, Filename: node.Filename} + nSym := &Node{Type: "SYMBOL", Value: "__n", Line: node.Line, Column: node.Column, Filename: node.Filename} + sget := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "sget", Line: node.Line, Column: node.Column, Filename: node.Filename}, + handle, + kSym, + }, + } + isNilV := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "is_nil", Line: node.Line, Column: node.Column, Filename: node.Filename}, + vSym, + }, + } + toInt := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "to_int", Line: node.Line, Column: node.Column, Filename: node.Filename}, + vSym, + }, + } + nInit := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "if", Line: node.Line, Column: node.Column, Filename: node.Filename}, + isNilV, + {Type: "INT", Value: "1", Line: node.Line, Column: node.Column, Filename: node.Filename}, + toInt, + }, + } + plus := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "+", Line: node.Line, Column: node.Column, Filename: node.Filename}, + nSym, + {Type: "INT", Value: "1", Line: node.Line, Column: node.Column, Filename: node.Filename}, + }, + } + toStringNext := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "to_string", Line: node.Line, Column: node.Column, Filename: node.Filename}, + plus, + }, + } + sput := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "sput", Line: node.Line, Column: node.Column, Filename: node.Filename}, + handle, + kSym, + toStringNext, + }, + } + toStringN := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "to_string", Line: node.Line, Column: node.Column, Filename: node.Filename}, + nSym, + }, + } + body := &Node{ + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{ + {Type: "SYMBOL", Value: "do", Line: node.Line, Column: node.Column, Filename: node.Filename}, + sput, + toStringN, + }, + } + node.Children = []*Node{ + {Type: "SYMBOL", Value: "let", Line: node.Line, Column: node.Column, Filename: node.Filename}, + { + Type: "List", Line: node.Line, Column: node.Column, Filename: node.Filename, + Children: []*Node{kSym, key, vSym, sget, nSym, nInit}, + }, + body, + } +} + func desugarListContains(node *Node) { // (list_contains lst x) → (let (__lst lst __x x __h 0) // (do (for __i __lst (if (= __i __x) (set __h 1))) (= __h 1))) @@ -1409,8 +1639,9 @@ var exprBodyHeads = map[string]bool{ "lines": true, "str_split": true, "str_join": true, "str": true, "str_contains": true, "str_replace": true, "starts_with": true, "ends_with": true, "http_body": true, "http_status": true, "list_contains": true, "not": true, "blank": true, - "unix_now": true, "regex_match": true, - "parse_json": true, "encode_json": true, "is_nil": true, "nz": true, "map_has": true, + "unix_now": true, "getd": true, "min": true, "max": true, "eq_ci": true, "snxt": true, + "regex_match": true, + "parse_json": true, "encode_json": true, "is_nil": true, "nz": true, "map_has": true, "html_escape": true, "attr_escape": true, "time_now": true, "req_header": true, "req_method": true, "req_path": true, "req_query": true, "http_request": true, "fetch": true, diff --git a/internal/ast/desugar_test.go b/internal/ast/desugar_test.go index a9e72a59..e56d4fb7 100644 --- a/internal/ast/desugar_test.go +++ b/internal/ast/desugar_test.go @@ -342,6 +342,24 @@ func TestDesugarBlankUnixNow(t *testing.T) { } } +func TestDesugarGetdMinMaxEqCiSnxt(t *testing.T) { + root := parseDesugar(t, `(cli_app + (print (getd m "k" "d")) + (print (min 1 2)) + (print (max 1 2)) + (print (eq_ci "A" "a")) + (print (snxt kv "next_id")))`) + src := ast.Stringify(root) + for _, head := range []string{"getd", "min", "max", "eq_ci", "snxt"} { + if strings.Contains(src, "("+head+" ") || strings.Contains(src, "("+head+")") { + t.Fatalf("%s should desugar: %s", head, src) + } + } + if !strings.Contains(src, "to_lower") || !strings.Contains(src, "map_get") { + t.Fatalf("expected to_lower/map_get in expansion: %s", src) + } +} + func TestDesugarNaryOr(t *testing.T) { root := parseDesugar(t, `(cli_app (print (or (= 1 2) (= 2 3) (= 3 3))))`) src := ast.Stringify(root) diff --git a/internal/construct/construct.go b/internal/construct/construct.go index dacdd07c..01568307 100644 --- a/internal/construct/construct.go +++ b/internal/construct/construct.go @@ -112,12 +112,14 @@ var table = []Entry{ {Name: "ends_with", Support: CompileTimeOnly, Note: "density sugar; suffix test via sentinel+str_contains by ast.Desugar"}, {Name: "env", Support: Supported, Note: "ENV"}, {Name: "ephemeral_circuit", Support: Supported, Note: "EPHEMERAL_CIRCUIT"}, + {Name: "eq_ci", Support: CompileTimeOnly, Note: "density sugar; case-insensitive = via to_lower by ast.Desugar"}, {Name: "exec", Support: Supported, Note: "EXEC"}, {Name: "exit", Support: Supported, Note: "EXIT"}, {Name: "export", Support: CompileTimeOnly, Note: "unwrapped by ast.ResolveModules inside a top-level (module ...) child before checker.Check or lowering ever run (improvements.md #95); a surviving export means the source is malformed and fails earlier, in checker.Check"}, {Name: "fetch", Support: Supported, Note: "FETCH"}, {Name: "for", Support: Supported, Note: "FOR_INIT / FOR_NEXT"}, {Name: "fuzzy_cast", Support: Unsupported, Note: "LLM struct coercion; no opcode, Go backend only"}, + {Name: "getd", Support: CompileTimeOnly, Note: "density sugar; (getd m k d) → map_has gated map_get default by ast.Desugar"}, {Name: "go_import", Support: Unsupported, Note: "Go-backend-only directive; the standalone VM cannot honour a Go package dependency"}, {Name: "html_escape", Support: Supported, Note: "HTML_ESCAPE; encodes &, <, >, \", and ' with no capability"}, {Name: "http_body", Support: CompileTimeOnly, Note: "density sugar; (http_body r) → bytes_to_string of map_get body by ast.Desugar"}, @@ -144,7 +146,9 @@ var table = []Entry{ {Name: "map_keys", Support: Supported, Note: "MAP_KEYS; keys in UTF-8 byte order (sort.Strings), including non-BMP"}, {Name: "map_set", Support: Supported, Note: "MAP_SET"}, {Name: "match", Support: Unsupported, Note: "no opcode; the interpreter also fails closed on it (internal/vm/vm.go); use switch"}, + {Name: "max", Support: CompileTimeOnly, Note: "density sugar; (max a b) → let+if comparison by ast.Desugar"}, {Name: "middleware", Support: Unsupported, Note: "HTTP middleware chain; no opcode, Go backend only"}, + {Name: "min", Support: CompileTimeOnly, Note: "density sugar; (min a b) → let+if comparison by ast.Desugar"}, {Name: "mkdir", Support: Supported, Note: "MKDIR"}, {Name: "module", Support: CompileTimeOnly, Note: "parser.ExpandIncludes/ast.ResolveModules consume (module ...) children of the root before checker.Check or lowering ever run (improvements.md #95); a root that is itself a module (no importer) is a malformed standalone program and fails earlier, in checker.Check"}, {Name: "neural_circuit", Support: Supported, Note: "NEURAL_CIRCUIT"}, @@ -175,6 +179,7 @@ var table = []Entry{ {Name: "sget", Support: CompileTimeOnly, Note: "density sugar; store string-val get via store_get/map_get by ast.Desugar"}, {Name: "sinc", Support: CompileTimeOnly, Note: "density sugar; store string-counter increment+print by ast.Desugar"}, {Name: "sleep", Support: Supported, Note: "SLEEP"}, + {Name: "snxt", Support: CompileTimeOnly, Note: "density sugar; next string id from store counter by ast.Desugar"}, {Name: "spawn", Support: Supported, Note: "SPAWN"}, {Name: "spawn_agent", Support: Supported, Note: "SPAWN_AGENT"}, {Name: "sput", Support: CompileTimeOnly, Note: "density sugar; store string-val put via store_put/dict by ast.Desugar"}, From 8cb2f056b49cab92f931354aeeaa803fd8c63a17 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 10 Oct 2026 21:56:09 +0000 Subject: [PATCH 2/2] Record round11 prove totals (4431 vs 5995/6418/5802) Co-authored-by: William Elias --- docs/journals/2026-10-10_dogfood_apps_round11.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/journals/2026-10-10_dogfood_apps_round11.md b/docs/journals/2026-10-10_dogfood_apps_round11.md index 4006aad3..d82e86c5 100644 --- a/docs/journals/2026-10-10_dogfood_apps_round11.md +++ b/docs/journals/2026-10-10_dogfood_apps_round11.md @@ -20,7 +20,13 @@ equality, and store next-id allocation; ship dogfood CLIs and densify store/HTTP 2. Densified `notes_cli` (`snxt`/`blank`), `rest_tasks`/`task_api` (`snxt`/`getd`/`blank`), `webhook_relay` (`blank`/`switch`), `json_get` (`blank`) -## Token proof +## Token proof (`cl100k_base`, 33 apps) + +| | HowlFrame | Python | Go | Node | +|---|---|---|---|---| +| **TOTAL** | **4431** | 5995 | 6418 | 5802 | + +`notes_cli` densified from 331 → 303 tokens via `snxt`/`blank`. ```bash python3 benchmarks/real_apps/prove_real_apps.py