aontu 0.61.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/agentsmd.d.ts +1 -0
- package/dist/agentsmd.js +7 -1
- package/dist/agentsmd.js.map +1 -1
- package/dist/allow.d.ts +23 -0
- package/dist/allow.js +230 -0
- package/dist/allow.js.map +1 -0
- package/dist/aontu.d.ts +4 -2
- package/dist/aontu.js +4 -2
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +10 -1
- package/dist/cli.js +941 -24
- package/dist/cli.js.map +1 -1
- package/dist/format.js +57 -1
- package/dist/format.js.map +1 -1
- package/dist/helpdoc.d.ts +16 -0
- package/dist/helpdoc.js +59 -0
- package/dist/helpdoc.js.map +1 -0
- package/dist/hints.js +15 -2
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +93 -40
- package/dist/lang.js.map +1 -1
- package/dist/lower.d.ts +3 -0
- package/dist/lower.js +3 -0
- package/dist/lower.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.js +1 -1
- package/dist/lsp.js.map +1 -1
- package/dist/relation.d.ts +2 -0
- package/dist/relation.js +7 -1
- package/dist/relation.js.map +1 -1
- package/dist/render.js +26 -21
- package/dist/render.js.map +1 -1
- package/dist/sigdecl.js +1 -1
- package/dist/sigdecl.js.map +1 -1
- package/dist/std.js +104 -69
- package/dist/std.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/val/AggFuncVal.js +2 -2
- package/dist/val/AggFuncVal.js.map +1 -1
- package/dist/val/CmpFuncVal.d.ts +20 -0
- package/dist/val/CmpFuncVal.js +245 -0
- package/dist/val/CmpFuncVal.js.map +1 -0
- package/dist/val/EachFuncVal.d.ts +1 -2
- package/dist/val/EachFuncVal.js +15 -29
- package/dist/val/EachFuncVal.js.map +1 -1
- package/dist/val/FormFuncVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js +17 -1
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/NamerFuncVal.d.ts +12 -0
- package/dist/val/NamerFuncVal.js +176 -0
- package/dist/val/NamerFuncVal.js.map +1 -0
- package/dist/val/NilVal.js +29 -3
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/NomFuncVal.d.ts +12 -0
- package/dist/val/NomFuncVal.js +187 -0
- package/dist/val/NomFuncVal.js.map +1 -0
- package/dist/val/PackFuncVal.js +3 -3
- package/dist/val/PackFuncVal.js.map +1 -1
- package/dist/val/RefVal.js +1 -1
- package/dist/val/TranslateFuncVal.d.ts +12 -0
- package/dist/val/TranslateFuncVal.js +101 -0
- package/dist/val/TranslateFuncVal.js.map +1 -0
- package/dist/val/UpperFuncVal.js +17 -1
- package/dist/val/UpperFuncVal.js.map +1 -1
- package/dist/val/caserange.d.ts +3 -0
- package/dist/val/caserange.js +110 -0
- package/dist/val/caserange.js.map +1 -0
- package/dist/vet.d.ts +12 -0
- package/dist/vet.js +209 -1
- package/dist/vet.js.map +1 -1
- package/grammar/aontu.abnf +1 -1
- package/grammar/aontu.gbnf +1 -1
- package/grammar/aontu.lark +1 -1
- package/grammar/aontu.tmLanguage.json +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +8 -0
- package/skill/init/check.sh +28 -0
- package/skill/init/data.aon +12 -0
- package/skill/init/model.aon +19 -0
- package/skill/tasks.md +151 -0
- package/src/agentsmd.ts +13 -2
- package/src/allow.ts +316 -0
- package/src/aontu.ts +10 -1
- package/src/cli.ts +1066 -28
- package/src/format.ts +75 -3
- package/src/helpdoc.ts +77 -0
- package/src/hints.ts +16 -2
- package/src/lang.ts +98 -42
- package/src/lower.ts +3 -3
- package/src/lsp.ts +1 -1
- package/src/relation.ts +21 -1
- package/src/render.ts +26 -21
- package/src/sigdecl.ts +1 -1
- package/src/std.ts +104 -69
- package/src/val/AggFuncVal.ts +2 -2
- package/src/val/CmpFuncVal.ts +411 -0
- package/src/val/EachFuncVal.ts +49 -50
- package/src/val/LowerFuncVal.ts +18 -1
- package/src/val/NilVal.ts +29 -3
- package/src/val/NomFuncVal.ts +287 -0
- package/src/val/PackFuncVal.ts +3 -3
- package/src/val/RefVal.ts +1 -1
- package/src/val/TranslateFuncVal.ts +182 -0
- package/src/val/UpperFuncVal.ts +18 -1
- package/src/val/caserange.ts +115 -0
- package/src/vet.ts +287 -1
- package/src/val/FormFuncVal.ts +0 -119
package/src/format.ts
CHANGED
|
@@ -1190,15 +1190,59 @@ function mergeDeep(p: Node): Node {
|
|
|
1190
1190
|
return { ...p, value: 'pair' === v.t ? body[0] : { ...v, body }, orig: [p] }
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
1193
|
+
// Whether a value is a RECORD: a braced map of several entries, every
|
|
1194
|
+
// one of them a VALUE rather than another map. A field, an error, a
|
|
1195
|
+
// rule row -- something whose keys are what it IS, as against a level
|
|
1196
|
+
// of the tree, whose keys are a way through to something else.
|
|
1197
|
+
//
|
|
1198
|
+
// A CHAIN IS NOT ONE, whatever it holds: a one-entry map is D1's, and
|
|
1199
|
+
// D1 writes it as a chain at every width. Nor is a map holding a
|
|
1200
|
+
// spread, which says something about the map's MEMBERS and which D1's
|
|
1201
|
+
// exception already gives a spelling of its own inside a repeat.
|
|
1202
|
+
function record(v: Node, entries: Node[]): boolean {
|
|
1203
|
+
if ('map' !== v.t) {
|
|
1204
|
+
return false
|
|
1205
|
+
}
|
|
1206
|
+
let pairs = 0
|
|
1207
|
+
for (const e of entries) {
|
|
1208
|
+
if ('spread' === e.t) {
|
|
1209
|
+
return false
|
|
1210
|
+
}
|
|
1211
|
+
if ('pair' !== e.t) {
|
|
1212
|
+
continue
|
|
1213
|
+
}
|
|
1214
|
+
if (undefined !== plainEntries(e.value!)) {
|
|
1215
|
+
return false
|
|
1216
|
+
}
|
|
1217
|
+
pairs++
|
|
1218
|
+
}
|
|
1219
|
+
return 1 < pairs
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1193
1222
|
// The lines of a map repeated under a prefix (§3.4, rule 2): every
|
|
1194
1223
|
// entry written with the prefix in front of it as one line, or --
|
|
1195
|
-
// where an entry's value is a map that does not fit --
|
|
1224
|
+
// where an entry's value is a map that does not fit -- descended into
|
|
1196
1225
|
// under the longer prefix. Comments and blank lines are kept where
|
|
1197
1226
|
// they stood. Undefined where an entry cannot be one line: a list that
|
|
1198
1227
|
// does not fit, a value that spans lines, a comment closing the map
|
|
1199
1228
|
// (which a repeat could not keep in the map) -- and where the map holds
|
|
1200
1229
|
// two spreads, which repeated would be two maps, and a different meet.
|
|
1201
|
-
|
|
1230
|
+
//
|
|
1231
|
+
// A DESCENT ENDS AT A RECORD (§3.4, D2's amendment). The prefix reaches
|
|
1232
|
+
// through maps that hold maps, because those keys are a path and a line
|
|
1233
|
+
// carrying the whole path says where it is. It stops at a map that
|
|
1234
|
+
// holds only values: there the keys are the thing's own fields, the
|
|
1235
|
+
// prefix in front of each of them is the same prefix again, and the
|
|
1236
|
+
// map is written as a braced BLOCK under the prefix instead --
|
|
1237
|
+
// `entity: planet: field: id: {` and its seven facts indented once.
|
|
1238
|
+
// The statement's own map is not an entry of anything and is
|
|
1239
|
+
// unaffected, so a flat `service: host: …` is still one repeat.
|
|
1240
|
+
type Line = {
|
|
1241
|
+
t: 'text' | 'comment' | 'blank' | 'block'
|
|
1242
|
+
text?: string
|
|
1243
|
+
node?: Node
|
|
1244
|
+
trail?: string
|
|
1245
|
+
}
|
|
1202
1246
|
|
|
1203
1247
|
function repeatLines(entries: Node[], prefix: string, indent: number): Line[] | undefined {
|
|
1204
1248
|
if (0 === entries.length || 'comment' === entries[entries.length - 1].t ||
|
|
@@ -1240,8 +1284,27 @@ function repeatLines(entries: Node[], prefix: string, indent: number): Line[] |
|
|
|
1240
1284
|
if (undefined === lines) {
|
|
1241
1285
|
return undefined
|
|
1242
1286
|
}
|
|
1287
|
+
// THE DESCENT COULD GO ON, AND WHAT IT REACHES IS A RECORD: it
|
|
1288
|
+
// stops, and the record is a block under the prefix instead. The
|
|
1289
|
+
// deeper repeat is asked for first and thrown away deliberately --
|
|
1290
|
+
// the block REPLACES a descent that would have worked, and never
|
|
1291
|
+
// rescues one that would not, so a map this rule cannot reach two
|
|
1292
|
+
// ways round is laid out exactly as it was before the amendment.
|
|
1293
|
+
if (record(e.value!, sub)) {
|
|
1294
|
+
out.push({ t: 'block', text: head, node: e.value!, trail })
|
|
1295
|
+
continue
|
|
1296
|
+
}
|
|
1243
1297
|
if ('' !== trail) {
|
|
1244
|
-
|
|
1298
|
+
// Onto the last line written for this entry -- and after the
|
|
1299
|
+
// CLOSER where that line is a block, which is where the trailing
|
|
1300
|
+
// comment of the entry the block came from also stands.
|
|
1301
|
+
const last = lines[lines.length - 1]
|
|
1302
|
+
if ('block' === last.t) {
|
|
1303
|
+
last.trail = (last.trail ?? '') + trail
|
|
1304
|
+
}
|
|
1305
|
+
else {
|
|
1306
|
+
last.text += trail
|
|
1307
|
+
}
|
|
1245
1308
|
}
|
|
1246
1309
|
out.push(...lines)
|
|
1247
1310
|
}
|
|
@@ -1297,6 +1360,15 @@ function emitStatement(w: Writer, p: Node, indent: number, stmt: Stmt, prefix: s
|
|
|
1297
1360
|
pending = false
|
|
1298
1361
|
count++
|
|
1299
1362
|
w.text(line.text!)
|
|
1363
|
+
if ('block' === line.t) {
|
|
1364
|
+
// The record the descent stopped at: its entries are
|
|
1365
|
+
// statements in turn, and the whole statement this repeat
|
|
1366
|
+
// belongs to is what carries the check, so they are covered.
|
|
1367
|
+
emitBlock(w, '{', '}', line.node!, indent, { meet: stmt.meet, covered: true })
|
|
1368
|
+
if (undefined !== line.trail) {
|
|
1369
|
+
w.text(line.trail)
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1300
1372
|
}
|
|
1301
1373
|
rewritten = true
|
|
1302
1374
|
}
|
package/src/helpdoc.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// GENERATED by ts/scripts/helpdoc.cjs (`make helpdoc`) from
|
|
4
|
+
// docs/skill/*.md and grammar/aontu.abnf — DO NOT EDIT. The skill
|
|
5
|
+
// sources are the source; ts/test/helpdoc.test.ts asserts this copy
|
|
6
|
+
// is identical with them.
|
|
7
|
+
|
|
8
|
+
type HelpTopic = {
|
|
9
|
+
topic: string
|
|
10
|
+
summary: string
|
|
11
|
+
source: string
|
|
12
|
+
text: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const HELPDOC: HelpTopic[] = [
|
|
16
|
+
{
|
|
17
|
+
"topic": "tasks",
|
|
18
|
+
"summary": "what do you want to do? — the verb for a job",
|
|
19
|
+
"source": "docs/skill/tasks.md",
|
|
20
|
+
"text": "# What do you want to do?\n\nThe verb for a job, found by the word you arrived with. aontu's own\nvocabulary is on the right; yours is probably on the left.\n\n## Start from a document that works\n\nNothing written yet? Do not invent the first document, edit one:\n\n```\naontu init # into the working directory\naontu init model/ # or into a named one\nsh check.sh # the four questions, on what was just written\n```\n\n`init` writes `model.aon` (an entity map constrained with `&:`),\n`data.aon` (an instance of it that holds) and `check.sh` (the four\nchecks to run after every edit). It never overwrites: if any\nof the three already stands there, it refuses and writes none of them.\n\n## Describe a domain\n\nAn **ontology**, a **schema**, a **data model**, a **contract** — in\naontu these are all one thing: a document. Write the entities as a\n**map keyed by name**, and say what every entry must satisfy with the\n`&:` template:\n\n```\nentity: {\n &: {\n table: string\n fields: { &: { type: string, required: *false | boolean } }\n }\n planet: { table: \"planets\", fields: { name: { type: \"string\" } } }\n moon: { table: \"moons\", fields: { name: { type: \"string\" } } }\n}\n```\n\n`&:` is the construct to reach for first, and the one most easily\nmissed. It meets **every** key of the map it sits in. A quoted `\"*\"`\nis not a wildcard — it is a key named `*`, and a schema written that\nway constrains nothing while still reporting `valid`.\n\nPrefer a named map to a list unless the order is a fact (a migration\nsequence, a rule table tried in order). A key is an address, a name\nother parts can refer to, and a diff that shows one insertion instead\nof every following element renumbered.\n\n## Check data against a model\n\n```\naontu vet model.aon data.aon\n```\n\nExit `0` valid, `1` a contradiction, `3` incomplete (nothing\ncontradicts, but the model is not yet satisfied), `4` the model does\nnot stand up on its own. `--format json` for the machine-readable\nreport, `--closed` to refuse keys the model does not declare,\n`--max-errors <n>` to cap the list.\n\n**Make the check prove it checked something.** A check that examined\nnothing answers exactly like one that passed:\n\n```\naontu vet --coverage model.aon data.aon # what did it examine?\naontu vet --strict-coverage model.aon data.aon # exit 1 if nothing\n```\n\n`--coverage` reports how many data leaves a declaration constrained,\nthe data paths none did, and the declarations no data met.\n`--strict-coverage` exits 1 when the answer is nothing. The usual\ncause is the `\"*\"` mistake above: reach for `&:`.\n\n## Check the model is coherent with itself\n\n```\naontu relations model.aon # declared entity edges: targets resolve, no cycles\naontu reaches planet moon model.aon # does one entity reach another, at any remove?\naontu trim --check model.aon # entries whose removal changes nothing\n```\n\n## Check code against a model\n\n```\naontu render --check src/ --profile go.aon model.aon\n```\n\n`render` writes code from the model; `--check` writes nothing and\nlists what on disk differs from what the model implies. That is the\ngate: the model is the truth, the code is the claim, and drift is a\nfinding. `--coverage` reports the other direction — model paths no\noutput consumed, and rendered declarations no rule produced.\n\n## Ask what a model says, and why\n\n```\naontu get $.entity.planet.table model.aon\naontu why $.entity.planet.table model.aon\naontu get $.entity --keys model.aon\naontu get $.entity --types model.aon\n```\n\n`why` names **every** contribution to a value, with the file and line\neach was written on. It is the first thing to run when a value is not\nwhat you expected, and the second thing to run when `vet` refuses.\n\n## Change a value without editing the file\n\n```\naontu set '$.entity.planet.table=planet_v2' --entry model.aon --overlay local.aon\n```\n\nThe change is checked before it is written; a change that contradicts\na pinned value is refused, and the file is left alone.\n\n## Gate a change to the model itself\n\n```\naontu subsume old.aon new.aon # does the general admit every specific?\naontu breaking --against git#HEAD~1 model.aon\naontu hash model.aon # a pin that survives reformatting\n```\n\n## Hand a model to another agent\n\n```\naontu agentsmd --write AGENTS.md model.aon\n```\n\nSplices a derived stanza — the pin, the root keys, the shape, and the\ncommands spelled with paths that exist — between two markers, and\nleaves the rest of the file alone. Re-run it in the commit that\nchanges the model.\n\n## When something refuses\n\nRead the code in the brackets, then look it up:\n\n```\naontu explain no_scalar_unify\n```\n\nEvery finding carries a `code`, a `class`, a `path` and the **two\nsites** that disagree — the value and the constraint it failed, each\nwith its file and line. The class says what kind of answer it is:\n`conflict` (two things cannot both hold), `incomplete` (nothing is\nwrong yet, something is missing), `parse`, `reference`, `budget`.\n\nFull index: [`error-codes.md`](error-codes.md). The language on one\npage: [`grammar-card.md`](grammar-card.md). The worked ladder from\nplain JSON upward: [`examples.md`](examples.md).\n"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"topic": "language",
|
|
24
|
+
"summary": "the grammar card — everything the language spells, on one page",
|
|
25
|
+
"source": "docs/skill/grammar-card.md",
|
|
26
|
+
"text": "# aontu grammar card\n\nEverything the language spells, on one page. The published machine\ngrammars are [`grammar/aontu.abnf`](../../grammar/aontu.abnf) — the\nnotation a person reads, with railroad diagrams — and\n[`aontu.gbnf`](../../grammar/aontu.gbnf), `aontu.lark` and\n`aontu.tmLanguage.json` for constrained decoding, parsing and\nhighlighting; this is their human twin.\n\n## Values\n\n| Write | Means |\n|-------|-------|\n| `{\"a\":1}`, `a: 1` | a map; keys may be bare or quoted |\n| `[1,2]` | a list |\n| `\"s\"`, `1`, `1.5`, `true`, `null` | scalars, as JSON |\n| `0d9007199254740993` | an EXACT number, at any magnitude |\n| `string integer number float boolean null` | kinds: any value of that kind |\n| `top` | anything at all; `nil` is the failure |\n\n## Combining\n\n| Write | Means |\n|-------|-------|\n| `a & b` | both must hold — the meet |\n| `a \\| b` | either may hold — the join |\n| `*1 \\| integer` | either, with `1` the DEFAULT a generation picks |\n| | the branch keeps its kind: a later `1.5` is refused, not admitted |\n| `x: 1` twice | the two meet; `1 & 2` is an error, `1 & integer` is `1` |\n| `$.a.b` | the value at that path of this document |\n| `1 + 2` | arithmetic on concrete numbers |\n\n## Maps that say more\n\n| Write | Means |\n|-------|-------|\n| `{&: {k: integer}}` | a TEMPLATE every key must satisfy |\n| `close({a:1})` | no key beyond those named |\n| `{a?: 1}` | optional: dropped if unresolved |\n| `hide(x)` | evaluated, then dropped from the output |\n| `type(x)` | a definition, not a value: it generates nothing |\n| `deprecate(x, {msg:\"…\"})` | still works, and says so |\n| `refer(t) & path($.a.b)` | a checked LINK: the tree address must resolve, and `t` flows into it |\n\n## Constraints\n\n`min(n) max(n) above(n) below(n)` bound a number;\n`length(n)` and `unique()` bound a list or string;\n`re(\"^…$\")` matches a string; `neq(v)` refuses one value;\n`must(cond, \"why\")` is the escape hatch.\n\nBounds compose: `integer & min(1) & max(10)` is a range, and two\nranges meet to their overlap.\n\n## Files\n\n`@\"other.aon\"` includes another document — it unifies in, it does not\nconcatenate. (A constrained decoder is not given this: generated\ndocuments describe values, they do not load files.)\n"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"topic": "examples",
|
|
30
|
+
"summary": "the ladder — plain JSON, then one addition at a time",
|
|
31
|
+
"source": "docs/skill/examples.md",
|
|
32
|
+
"text": "# aontu by example\n\nThe ladder: start with the JSON you already know, add one thing at a\ntime. Every rung is a valid document.\n\n## 1. It is JSON\n\n```\n{\"service\": {\"name\": \"auth\", \"port\": 8080}}\n```\n\nBare keys and no commas also parse, so this is the same document:\n\n```\nservice: {\n name: \"auth\"\n port: 8080\n}\n```\n\n## 2. Say what a value MAY be\n\n```\nservice: {\n name: string\n port: integer\n}\n```\n\nNothing generates yet — this is a truth, not a value. `aontu vet\nservice.aon deploy.aon` says whether a deployment satisfies it.\n\n## 3. Say what it SHOULD be\n\n```\nservice: {\n name: string\n port: *8080 | integer # 8080 unless told otherwise\n replicas: *1 | integer\n}\n```\n\nNow it generates: `{\"service\":{\"name\":…,\"port\":8080,\"replicas\":1}}` —\nexcept `name`, which nothing has supplied. Defaults are picked at\ngeneration; they do not stop a caller from choosing something else.\n\n`*8080 | integer` means what it reads as: a caller's `9090` overrides\nit, and a caller's `1.5` is refused, because the branch admits only the\nkind it names. Write `*8080 | number` if any numeric value should be\nallowed to win.\n\n## 4. Say it once, for every key\n\n```\nservices: {\n &: { port: integer, replicas: *1 | integer }\n auth: { port: 8080 }\n db: { port: 5432, replicas: 3 }\n}\n```\n\nThe `&:` template meets EVERY key. `aontu why $.services.db.replicas`\nreports both contributions — the template's `*1|integer` and the\nliteral `3` — with the line each was written on.\n\n## 5. Bound it\n\n```\nservices: {\n &: {\n port: integer & min(1) & max(65535)\n name: string & re(\"^[a-z][a-z0-9-]*$\")\n }\n}\n```\n\nA port of `0` is now an error with a path, not a runtime surprise.\n\n## 6. Layer it\n\n```\n# base.aon\nservice: { image: string, replicas: *1 | integer }\n\n# prod.aon\n@\"base.aon\"\nservice: { image: \"auth:v2.3\", replicas: 5 }\n```\n\nUnification is order-independent: `base & prod` is `prod & base`. To\nchange a value without editing the file, append to an overlay:\n\n```\naontu set '$.service.replicas=7' --entry prod.aon --overlay local.aon\n```\n\nIf the value is PINNED rather than open, that command refuses and\nnames the pinning site — which is the honest answer, and where\n`aontu why` takes you next.\n"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"topic": "codes",
|
|
36
|
+
"summary": "what a refusal means, and what to do about it",
|
|
37
|
+
"source": "docs/skill/error-codes.md",
|
|
38
|
+
"text": "# What a refusal means\n\nEvery aontu error carries a **code**, and every code has a **class**.\nThe class says what kind of thing went wrong, which is what decides\nyour next move. The full registry — every code, its class, and the\nversion it appeared in — is\n[`test/spec/errcodes.tsv`](../../test/spec/errcodes.tsv), which the\ntest suite holds to the engine in both implementations.\n\n| Class | Meaning | What to do |\n|-------|---------|------------|\n| `parse` | the text is not a document | fix the syntax at the site the frame points at |\n| `conflict` | two values cannot both hold | one of them is wrong: `aontu why <path>` names both and where they were written |\n| `incomplete` | nothing contradicts, but the value is not concrete | supply what is missing, or accept it with `--partial` |\n| `reference` | a path names nothing | check the spelling; `aontu get $ --keys` lists what is there |\n| `compat` | a change breaks an earlier version | that is `aontu subsume` / `aontu breaking` talking: widen the change or version it |\n| `budget` | evaluation hit a deterministic limit | usually a cycle; simplify, or raise the budget deliberately |\n| `internal` | the engine surprised itself | a bug worth reporting |\n\n## The repair loop\n\n```\naontu vet schema.aon mine.aon --format json\n```\n\nThe exit code branches for you: `0` valid, `1` invalid (fix the\ndata), `3` incomplete (supply more), `4` the schema itself is\nunusable (fix the truth, not the data). Every finding carries\n`path`, `code`, and the sites on both sides — the data site first,\nbecause that is the one to edit. A site names **the file whose text\nit excerpts**, so its row and column are safe to edit at even when\nthe document loads others.\n\nRead `hint` before guessing. `message` is the one-line headline;\n`hint` is the engine's own explanation of the failure class with the\noffending values filled in, and for several codes it names the fix\noutright — `lossy_integer_literal` tells you to write the literal as\n`0d…`. It is absent for codes that have no hint text.\n\nFor a conflict, `aontu why <path> mine.aon` lists every contribution\nto that path with its role and source line, which turns \"these\ndisagree\" into \"these two lines disagree\".\n\nThen fix it:\n\n```\naontu set '$.replicas=5' --entry schema.aon --overlay mine.aon --in-place\n```\n\n`--in-place` rewrites the pinned literal **where it was written**, so\ncomments and layout survive. Without it, `set` APPENDS — which is the\nright thing when the document left a hole, and cannot work when it\npinned the wrong value, because unification only narrows.\n\nThe edit is verified before a byte is written: a site carries the\nsource text it covers, and the text at the span must match it. Where\nthat cannot be established — the value comes from a `&:` template or a\n`$ref`, two statements pin the path, the site names the opening token\nof a compound like `min(1)`, or the overlay `@\"includes\"` another\ndocument — the assignment is appended as usual and a **warning** says\nwhich case it hit. A warning never changes the verdict, so asking for\n`--in-place` cannot make a run fail that would have succeeded.\n"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"topic": "grammar",
|
|
42
|
+
"summary": "the published ABNF, for a parser or a constrained decoder",
|
|
43
|
+
"source": "grammar/aontu.abnf",
|
|
44
|
+
"text": "; Aontu --- published grammar (ABNF, RFC 5234 + RFC 7405).\n;\n; The same language as grammar/aontu.gbnf, rule for rule and name for\n; name, in the notation an RFC reader expects. The GBNF form is for a\n; constrained decoder; THIS form is for a person, and for the railroad\n; diagram in the language reference, which is generated from this file\n; (ts/scripts/figures.cjs).\n;\n; It covers the DOCUMENTED EMISSION SURFACE: what a model should be\n; allowed to write, which is a superset of JSON plus the operators,\n; constraints and marks the language's own canonical form emits. It is\n; CONSERVATIVE BY CONSTRUCTION --- it may accept less than the parser\n; does, never more. The same two deliberate exclusions the GBNF has:\n;\n; - `@\"...\"` includes. A generated document should describe values,\n; not reach for files.\n; - Unquoted keys and the other spellings the parser tolerates.\n; Canon quotes every key, and one spelling is what a grammar is\n; for.\n;\n; TWO NOTATION CHOICES worth stating, because both are load-bearing.\n;\n; EVERY LITERAL IS CASE-SENSITIVE, spelled `%s\"...\"` (RFC 7405). A\n; plain `\"...\"` is case-INSENSITIVE in RFC 5234, and Aontu is not:\n; `TRUE` is a bare word and `true` is a boolean. The reader in\n; ts/scripts/abnf.cjs refuses a bare quoted string rather than guess\n; which was meant.\n;\n; ALTERNATION IS ORDERED. RFC 5234's `/` is formally unordered, and\n; the consumers of this file (a PEG-style matcher here, a railroad\n; renderer) take the first branch that matches. Where one spelling is\n; a prefix of another the longer comes first --- `exact` before\n; `number`, `refer` before `rem` before `re`, `biginteger` before\n; `boolean` --- so the ordered reading and the unordered one accept\n; the same language. ts/test/grammar.test.ts holds this file to the\n; whole canon corpus of test/spec/*.tsv, which is what proves it.\n\nroot = ws value ws\n\n; No pipe. `|>` was parse-time sugar; it was removed, `x |> f(a)`\n; being written `f(x, a)`.\nvalue = disjunct\n\ndisjunct = conjunct *( ws %s\"|\" ws conjunct )\n\nconjunct = prefixed *( ws %s\"&\" ws prefixed )\n\n; A preference marks the alternative a generation picks.\nprefixed = %s\"*\" ws prefixed / sum\n\nsum = atom *( ws %s\"+\" ws atom )\n\natom = map\n / list\n / func\n / ref\n / kind\n / place\n / scalar\n / %s\"(\" ws value ws %s\")\"\n\nmap = %s\"{\" ws [ entry *( ws %s\",\" ws entry ) ws ] %s\"}\"\n\nentry = spread / pair\n\n; The template every key of the bag must also satisfy.\nspread = %s\"&\" ws %s\":\" ws value\n\npair = string ws [ %s\"?\" ] ws %s\":\" ws value\n\n; A list ELEMENT is a value, not a pair --- only the template is keyed.\nlist = %s\"[\" ws [ element *( ws %s\",\" ws element ) ws ] %s\"]\"\n\nelement = spread / value\n\n; The builtins, applied. The name set is closed on purpose: an unknown\n; function is a parse-time error in the engine, so a grammar that\n; allowed any name would over-approximate.\nfunc = name %s\"(\" ws [ value *( ws %s\",\" ws value ) ws ] %s\")\"\n\n; ORDER MATTERS where one name is a prefix of another: `refer` is\n; listed before `re`, and `rem` before `re` as well.\nname = %s\"above\" / %s\"acyclic\" / %s\"add\" / %s\"below\" / %s\"close\"\n / %s\"copy\" / %s\"deprecate\" / %s\"div\"\n / %s\"each\" / %s\"emit\" / %s\"esc\" / %s\"filter\" / %s\"greatest\"\n / %s\"hide\"\n / %s\"inverse\" / %s\"join\" / %s\"key\" / %s\"least\"\n / %s\"length\" / %s\"list\" / %s\"lower\" / %s\"map\" / %s\"match\"\n / %s\"max\" / %s\"min\" / %s\"mod\" / %s\"move\"\n / %s\"mul\" / %s\"must\"\n / %s\"neq\"\n / %s\"open\" / %s\"pack\" / %s\"path\" / %s\"pick\" / %s\"pref\"\n / %s\"refer\" / %s\"rel\" / %s\"rem\" / %s\"rep\" / %s\"re\" / %s\"split\"\n / %s\"super\"\n / %s\"sub\" / %s\"sum\"\n / %s\"type\"\n / %s\"unique\" / %s\"upper\" / %s\"usc\"\n\n; A path reference, absolute from the document root. At least one\n; segment: a bare `$` is an incomplete expression, not a reference.\n; A RELATIVE reference has no `$`: `.n` names a sibling, resolved\n; against the enclosing map rather than the document root. Canon emits\n; one wherever a spread template survives unresolved, so a grammar\n; without it cannot parse this engine's own output. No conflict with\n; `number`: a numeric literal always starts with a digit or `-`.\nref = %s\"$\" 1*( %s\".\" segment ) / 1*( %s\".\" segment )\n\n; No `-`: it is not a bare-text character (test/spec/op-chars.tsv pins\n; `a:6-2` as a parse error), so admitting it would over-approximate.\nsegment = 1*( ALPHA / DIGIT / %s\"_\" )\n\n; The placeholder: a hole a call is filled through. BARE only --- `\"_\"`\n; is an ordinary string, and a longer bare word containing it is\n; ordinary text.\nplace = %s\"_\"\n\n; `biginteger` before `bigdecimal` is not required (they differ at the\n; fourth character), but `biginteger` before `boolean` is: see the\n; ordered-alternation note above.\nkind = %s\"biginteger\" / %s\"bigdecimal\" / %s\"boolean\" / %s\"float\"\n / %s\"integer\" / %s\"number\" / %s\"string\" / %s\"top\" / %s\"nil\"\n\n; `exact` before `number`, or `0d5` would match `number` as the single\n; digit `0` and leave `d5` unconsumed.\nscalar = string / exact / number / %s\"true\" / %s\"false\" / %s\"null\"\n\nstring = DQUOTE *char DQUOTE\n\nchar = unescaped / %x5C escape\n\n; Every code point but the quote and the backslash, which is what the\n; GBNF's `[^\"\\\\]` says. Control characters included: canon writes them\n; escaped, and a grammar that refused them would refuse less than the\n; parser accepts in the one direction this file may not.\nunescaped = %x00-21 / %x23-5B / %x5D-10FFFF\n\nescape = DQUOTE / %x5C / %s\"/\" / %s\"b\" / %s\"f\" / %s\"n\" / %s\"r\"\n / %s\"t\" / %s\"u\" hex hex hex hex\n\nhex = DIGIT / %x41-46 / %x61-66\n\n; The exact leaves: arbitrary precision, spelled with the 0d marker.\nexact = [ %s\"-\" ] %s\"0d\" digits [ %s\".\" digits ] [ exponent ]\n\nnumber = [ %s\"-\" ] digits [ %s\".\" digits ] [ exponent ]\n\nexponent = ( %s\"e\" / %s\"E\" ) [ %s\"-\" / %s\"+\" ] digits\n\ndigits = 1*DIGIT\n\nws = *( %x20 / %x09 / %x0A / %x0D )\n\n; RFC 5234's core rules, written out so this file stands alone.\nALPHA = %x41-5A / %x61-7A\nDIGIT = %x30-39\nDQUOTE = %x22\n"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
type InitFile = {
|
|
49
|
+
file: string
|
|
50
|
+
mode: number
|
|
51
|
+
name: string
|
|
52
|
+
text: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const INITDOC: InitFile[] = [
|
|
56
|
+
{
|
|
57
|
+
"file": "docs/skill/init/model.aon",
|
|
58
|
+
"name": "model.aon",
|
|
59
|
+
"mode": 420,
|
|
60
|
+
"text": "# model.aon --- the truth. `aontu vet model.aon data.aon` asks whether\n# a document satisfies it.\n#\n# `&:` is the construct to learn first: it meets EVERY key of the map\n# it sits in, so one statement constrains every entity. A quoted \"*\" is\n# not a wildcard -- it is a key named `*`, it meets nothing, and a\n# schema written that way reports `valid` over data that violates it.\n# `aontu vet --strict-coverage` refuses to pass such a check.\nentity: {\n &: {\n table: string & re(\"^[a-z][a-z0-9_]*$\")\n fields: {\n &: {\n type: \"string\" | \"integer\" | \"boolean\"\n required: *false | boolean\n }\n }\n }\n}\n"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"file": "docs/skill/init/data.aon",
|
|
64
|
+
"name": "data.aon",
|
|
65
|
+
"mode": 420,
|
|
66
|
+
"text": "# data.aon --- an instance of the truth in model.aon. Edit it and run\n# check.sh: every change is checked against the model, and a change the\n# model refuses is named with the line it is on.\nentity: {\n planet: {\n table: \"planets\"\n fields: {\n name: { type: \"string\", required: true }\n diameter: { type: \"integer\" }\n }\n }\n}\n"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"file": "docs/skill/init/check.sh",
|
|
70
|
+
"name": "check.sh",
|
|
71
|
+
"mode": 493,
|
|
72
|
+
"text": "#!/usr/bin/env sh\n# check.sh --- the four questions to ask of a model. Run it after\n# every edit to model.aon or data.aon.\n#\n# `aontu help tasks` maps a job to a verb; `aontu explain <code>` says\n# what a refusal means; `aontu help language` is the whole grammar on\n# one page. None of them needs a network.\nset -eu\n\nAONTU=\"${AONTU:-aontu}\"\ncd \"$(dirname \"$0\")\"\n\n# 1. Does the data satisfy the truth -- and did the check examine\n# anything? --strict-coverage exits 1 on a check that constrained no\n# value, which is the failure a passing gate hides.\n$AONTU vet --strict-coverage model.aon data.aon\n\n# 2. What does it say at a path?\n$AONTU get '$.entity.planet.table' data.aon\n\n# 3. Why does it say that? Every contribution, with the line it is on.\n$AONTU why '$.entity.planet.table' data.aon\n\n# 4. A pin for the truth: it survives reformatting and moves on any\n# change of meaning.\n$AONTU hash model.aon\n\necho \"ok --- model.aon and data.aon agree\"\n"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
export type { HelpTopic, InitFile }
|
|
77
|
+
export { HELPDOC, INITDOC }
|
package/src/hints.ts
CHANGED
|
@@ -203,6 +203,17 @@ const hints: Record<string, string> = {
|
|
|
203
203
|
view_profile_unknown: 'The figure kind does not render into the profile asked for: there is no\ntext form of a node-link drawing and no Mermaid form of a matrix. The\nnote lists the profiles the kind declares; the first is its default.',
|
|
204
204
|
|
|
205
205
|
format_check: 'The formatted text is not the same document, so nothing was written.\nThis is a formatter defect: please report it, with the source.',
|
|
206
|
+
|
|
207
|
+
// THIS PORT NEVER RAISES decimal_syntax -- Go's construct.go does,
|
|
208
|
+
// and go/hints.go's header records that the CODE is Go-only. The
|
|
209
|
+
// TEXT is here anyway, verbatim, because since G11 phase 3 this
|
|
210
|
+
// table is a LOOKUP surface as well as a message source: the code is
|
|
211
|
+
// in the shared registry (test/spec/errcodes.tsv), so
|
|
212
|
+
// `aontu explain decimal_syntax` must answer the same in both ports
|
|
213
|
+
// or the agent that met the error under one binary learns nothing
|
|
214
|
+
// from the other. An entry for a code this port cannot raise is
|
|
215
|
+
// never read on an error path, only on that lookup.
|
|
216
|
+
decimal_syntax: 'This 0d literal is not a valid exact number.',
|
|
206
217
|
view_style_profile: 'Each profile has ONE way to carry the meaning of a figure\'s marks:\nSGR escapes for text, CSS classes for svg. Asking for the other one is\na usage error rather than a silent no-op. `none` works everywhere.',
|
|
207
218
|
view_style_unknown: 'The styles are none, ansi and css, plus `auto` at the command line,\nwhich the command resolves before the library runs: whether the\ndestination is a terminal is not something a library can see.',
|
|
208
219
|
|
|
@@ -225,7 +236,7 @@ const hints: Record<string, string> = {
|
|
|
225
236
|
|
|
226
237
|
pack_key: 'A list packed by pack() holds something that is not a string. The\nelements of a packed list ARE the generated keys, and only a string\nis a key — an element keyed by its position would churn every\ngenerated child the moment the list was reordered.\n \nExamples:\n pack([a,b], {x:1}) -> {..} # Names;\n pack(["a b"], {x:1}) -> {..} # ... a quoted name is still a name;\n pack([1,2], {x:1}) -> nil # ... but a number is not one.',
|
|
227
238
|
|
|
228
|
-
each_data: 'The first argument to each() is not a bag. `each` makes one list\nelement per child of its DATA, so the data has to have children: a\nlist, or a map whose values
|
|
239
|
+
each_data: 'The first argument to each() is not a bag. `each` makes one list\nelement per child of its DATA, so the data has to have children: a\nlist, or a map whose values are taken in sorted-key order.\n \nExamples:\n each([a,b], upper(_)) -> [..] # A list, in source order;\n each({b:2,a:1}, _) -> [..] # ... a map, in sorted-key order;\n each(1, _) -> nil # ... but a scalar has no children.',
|
|
229
240
|
|
|
230
241
|
filter_data: 'The first argument to filter() is not a bag. `filter` keeps the\nchildren of its DATA that already satisfy a condition, so the data\nhas to have children: a list, or a map.\n \nExamples:\n filter([1,x], integer) -> [..] # A list;\n filter({a:1,b:x}, integer) -> {..} # ... or a map, keys kept;\n filter(1, integer) -> nil # ... but a scalar has none.',
|
|
231
242
|
|
|
@@ -249,6 +260,9 @@ const hints: Record<string, string> = {
|
|
|
249
260
|
|
|
250
261
|
replace_value: 'A replacement value is not text: the `replace` key {key} came to\n{value} at the node. A value reaches the body as a string -- a number\nor a boolean spells itself, as it does after `+` -- and it has to\nhave settled by the time the dispatch fires, so a map, a list, a\nnull or a value still unresolved is refused rather than written into\na file as something else.\n \nExamples:\n replace: {PIN: .pin} # A string field;\n replace: {PORT: .port} # ... or a number, as digits;\n replace: {ALL: _} # ... but the node itself is a map.',
|
|
251
262
|
|
|
263
|
+
// RETIRED with the name `form` (ADR-027): the function is `each` now
|
|
264
|
+
// and answers `each_data`. Registered still, errcodes.tsv being
|
|
265
|
+
// append-only, so a code a released engine raised keeps its meaning.
|
|
252
266
|
form_data: 'The first argument to form() is not a bag. `form` makes one list\nelement per child of its DATA, so the data has to have children: a\nlist, or a map whose values are taken in sorted-key order.\n \nExamples:\n form([a,b], upper(_)) -> [..] # A list, in source order;\n form({b:2,a:1}, _) -> [..] # ... a map, in sorted-key order;\n form(1, _) -> nil # ... but a scalar has no children.',
|
|
253
267
|
|
|
254
268
|
esc_variant: 'esc(), usc() or a template\'s `esc:` key were given a variant that\nnames no convention.\nA variant names a CONVENTION rather than a language, because several\nlanguages share one and one language has several. The names are `sq`,\n`sql`, `shell`, `xml`, `uri` and `regex`; written with no variant at\nall it is the C escape, JSON canonical, which covers the double-quoted\nliteral of every C-family language.\n \nExamples:\n esc(text) -> ... # C / JSON, the default;\n esc(text, sq) -> ... # ... single-quoted C-family;\n esc(text, pascal) -> nil # ... but that is not a convention.',
|
|
@@ -626,7 +640,7 @@ const codeClasses: Record<string, string> = {
|
|
|
626
640
|
// template checks are class `parse`: what is wrong is the TEMPLATE
|
|
627
641
|
// as written, before any node. `replace_value` is class `conflict`:
|
|
628
642
|
// the node's value and the body that wanted text disagreed.
|
|
629
|
-
// `form_data` is `each_data`'s twin.
|
|
643
|
+
// `form_data` is `each_data`'s retired twin (ADR-027).
|
|
630
644
|
replace_overlap: 'parse',
|
|
631
645
|
replace_unused: 'parse',
|
|
632
646
|
replace_value: 'conflict',
|
package/src/lang.ts
CHANGED
|
@@ -142,8 +142,10 @@ import { DeprecateFuncVal } from './val/DeprecateFuncVal'
|
|
|
142
142
|
import { ReferFuncVal, RelFuncVal } from './val/ReferFuncVal'
|
|
143
143
|
import { AcyclicFuncVal, InverseFuncVal } from './val/GraphAtomVal'
|
|
144
144
|
import { PackFuncVal } from './val/PackFuncVal'
|
|
145
|
+
import { CMP_FUNCS } from './val/CmpFuncVal'
|
|
146
|
+
import { NomFuncVal } from './val/NomFuncVal'
|
|
147
|
+
import { TranslateFuncVal } from './val/TranslateFuncVal'
|
|
145
148
|
import { EachFuncVal } from './val/EachFuncVal'
|
|
146
|
-
import { FormFuncVal } from './val/FormFuncVal'
|
|
147
149
|
import { FilterFuncVal } from './val/FilterFuncVal'
|
|
148
150
|
import { MatchFuncVal } from './val/MatchFuncVal'
|
|
149
151
|
import { EmitFuncVal } from './val/EmitFuncVal'
|
|
@@ -915,21 +917,24 @@ help isolate the syntax error.`,
|
|
|
915
917
|
acyclic: AcyclicFuncVal,
|
|
916
918
|
inverse: InverseFuncVal,
|
|
917
919
|
|
|
918
|
-
// G8 phase 1:
|
|
919
|
-
// child per child of its data,
|
|
920
|
-
//
|
|
921
|
-
//
|
|
922
|
-
// G8 phase 0).
|
|
920
|
+
// G8 phase 1: generation to KEYED CHILDREN. `pack` makes one keyed
|
|
921
|
+
// child per child of its data, cloning its template per
|
|
922
|
+
// destination exactly as a spread does, and waits for the model to
|
|
923
|
+
// settle before it fires (the staging rule, G8 phase 0).
|
|
923
924
|
pack: PackFuncVal,
|
|
924
|
-
each: EachFuncVal,
|
|
925
925
|
|
|
926
|
-
// RENDER P6: the order-preserving map
|
|
927
|
-
// element per child of its data, being the
|
|
928
|
-
// bound to the source child
|
|
929
|
-
//
|
|
930
|
-
//
|
|
931
|
-
//
|
|
932
|
-
|
|
926
|
+
// RENDER P6: the order-preserving map, and generation to a LIST.
|
|
927
|
+
// `each` makes one list element per child of its data, being the
|
|
928
|
+
// template with `_` bound to the source child. It exists because
|
|
929
|
+
// `pick(pack(...))` re-sorts to code-point order, and a struct's
|
|
930
|
+
// fields or a file's imports are the model's order or they are
|
|
931
|
+
// wrong.
|
|
932
|
+
//
|
|
933
|
+
// Mentioning the hole makes it a BOUND instead: `each(d, _ & t)`
|
|
934
|
+
// meets each child with `t`, and `each(d, _)` is a bag's members
|
|
935
|
+
// as a list. That is what the retired meet-only `each` spelled
|
|
936
|
+
// (ADR-026), and why this one could take the name (ADR-027).
|
|
937
|
+
each: EachFuncVal,
|
|
933
938
|
|
|
934
939
|
// G8 phase 2: selection. `filter` keeps the children of a bag that
|
|
935
940
|
// unify with a condition; `match` picks the first arm whose
|
|
@@ -962,7 +967,8 @@ help isolate the syntax error.`,
|
|
|
962
967
|
|
|
963
968
|
// Projection, which is what lets the aggregates reach a bag of
|
|
964
969
|
// RECORDS: `sum(pick($.lines, amountCents))`. Not a clever `each`
|
|
965
|
-
// template -- `each` MEETS each child, and a meet cannot
|
|
970
|
+
// template -- `each(d, _ & t)` MEETS each child, and a meet cannot
|
|
971
|
+
// select.
|
|
966
972
|
pick: PickFuncVal,
|
|
967
973
|
|
|
968
974
|
// G9 phase 2: the fold to a STRING. `sum` folds with `add`; this
|
|
@@ -989,6 +995,39 @@ help isolate the syntax error.`,
|
|
|
989
995
|
usc: UscFuncVal,
|
|
990
996
|
rep: RepFuncVal,
|
|
991
997
|
split: SplitFuncVal,
|
|
998
|
+
|
|
999
|
+
// THE COMPONENT PRIMITIVES -- SPIKE (ts/src/val/CmpFuncVal.ts,
|
|
1000
|
+
// docs/design/JOSTRACA.0.md). jostraca's component set, spelled
|
|
1001
|
+
// as functions -- LOWER CASE, like every other builtin in this
|
|
1002
|
+
// language, while the node each one builds names the jostraca
|
|
1003
|
+
// component it drives (`file(...)` is aontu, `"File"` is
|
|
1004
|
+
// jostraca's). A document then says what FILES it produces
|
|
1005
|
+
// instead of filling in the `aontu:code` vocabulary as data.
|
|
1006
|
+
// Eight of jostraca's ten: `copy` and `list` are already builtins
|
|
1007
|
+
// here with unrelated meanings, so those two have no lower-case
|
|
1008
|
+
// spelling to take (see ts/src/val/CmpFuncVal.ts).
|
|
1009
|
+
//
|
|
1010
|
+
// TypeScript only, and so deliberately NOT in
|
|
1011
|
+
// test/spec/signature.tsv, BUILTIN_FUNCS (ts/src/lsp.ts) or the
|
|
1012
|
+
// grammar/ files -- each is asserted in cross-port parity and a
|
|
1013
|
+
// TS-only entry turns the Go suite red. Arity and argument shape
|
|
1014
|
+
// are refused in CmpFuncVal.resolve for the same reason.
|
|
1015
|
+
...CMP_FUNCS,
|
|
1016
|
+
|
|
1017
|
+
// NAME TRANSFORMATION -- SPIKE (ts/src/val/NamerFuncVal.ts).
|
|
1018
|
+
// Generated code is mostly names, and no two targets spell them
|
|
1019
|
+
// the same way. Splits a name into words whatever format it is
|
|
1020
|
+
// written in, then renders it in the target's. TypeScript only,
|
|
1021
|
+
// and out of the parity-pinned registries for the reason the
|
|
1022
|
+
// component primitives are.
|
|
1023
|
+
nom: NomFuncVal,
|
|
1024
|
+
|
|
1025
|
+
// PER-CHARACTER SUBSTITUTION AND DELETION -- SPIKE
|
|
1026
|
+
// (ts/src/val/TranslateFuncVal.ts), after `tr`. Reads the source
|
|
1027
|
+
// once and consults a table, which is what `rep` cannot do: a
|
|
1028
|
+
// per-character map spelled as N `rep` calls composes wrongly,
|
|
1029
|
+
// since each pass sees the previous one's output.
|
|
1030
|
+
translate: TranslateFuncVal,
|
|
992
1031
|
}
|
|
993
1032
|
|
|
994
1033
|
|
|
@@ -2250,13 +2289,13 @@ function makeModelResolver(options: any) {
|
|
|
2250
2289
|
// remain available under every capability but 'none' — they are
|
|
2251
2290
|
// host-provided, not document-requested, so confining them would
|
|
2252
2291
|
// confine the host against itself.
|
|
2253
|
-
//
|
|
2254
|
-
//
|
|
2255
|
-
//
|
|
2256
|
-
//
|
|
2257
|
-
//
|
|
2258
|
-
//
|
|
2259
|
-
//
|
|
2292
|
+
// A LANGUAGE-SUPPLIED MODEL DOES NOT RIDE THIS LEG (ADR-028). Every
|
|
2293
|
+
// bundled schema is named under `aontu:` and answered by the scheme
|
|
2294
|
+
// leg above, which returns before the memory resolver is built, so
|
|
2295
|
+
// neither a host entry nor a capability's own file set can stand in
|
|
2296
|
+
// front of one: `aontu:system` is the engine's, always. That is what
|
|
2297
|
+
// the prefix buys, and it is why the bare-name leg that once let a
|
|
2298
|
+
// caller shadow `std/system` is gone.
|
|
2260
2299
|
let memResolver = makeMemResolver(memCapability
|
|
2261
2300
|
? { ...(capability as any).mem }
|
|
2262
2301
|
: { ...(options.resolver?.mem || {}) })
|
|
@@ -2426,10 +2465,14 @@ function makeModelResolver(options: any) {
|
|
|
2426
2465
|
// else -- the memory, module, file and package legs are never
|
|
2427
2466
|
// asked, so nothing on disk can shadow one and a typo is refused
|
|
2428
2467
|
// here, naming the set, rather than searched for. Available under
|
|
2429
|
-
// every capability but `none`, checked just above
|
|
2430
|
-
//
|
|
2431
|
-
//
|
|
2432
|
-
//
|
|
2468
|
+
// every capability but `none`, checked just above. A path that is
|
|
2469
|
+
// not a string (`a: @1`) is not a name at all: it falls through to
|
|
2470
|
+
// the legs below and is not found there, as it always was.
|
|
2471
|
+
//
|
|
2472
|
+
// This is the ONLY leg that serves a bundled model (ADR-028). The
|
|
2473
|
+
// vocabularies once had bare names (`std/system`) and a second leg
|
|
2474
|
+
// below to match them; the prefix is now the whole spelling, so one
|
|
2475
|
+
// leg answers for every language-supplied schema.
|
|
2433
2476
|
if ('string' === typeof path && path.startsWith(AONTU_SCHEME)) {
|
|
2434
2477
|
const model = STD_SOURCES[path]
|
|
2435
2478
|
if (null == model) {
|
|
@@ -2439,19 +2482,6 @@ function makeModelResolver(options: any) {
|
|
|
2439
2482
|
return { found: true, path, full: path, kind: 'aon', src: model, search: [] }
|
|
2440
2483
|
}
|
|
2441
2484
|
|
|
2442
|
-
// THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts): served from
|
|
2443
|
-
// the engine itself, so it needs neither the filesystem nor package
|
|
2444
|
-
// resolution and is available under every capability but `none` —
|
|
2445
|
-
// checked just above, that being what `none` means. Matched against
|
|
2446
|
-
// the name the author WROTE, before the memory leg, so the kind is
|
|
2447
|
-
// stated rather than guessed from an extension the bare name does
|
|
2448
|
-
// not have.
|
|
2449
|
-
const std = STD_SOURCES[path]
|
|
2450
|
-
if (null != std) {
|
|
2451
|
-
record(ctx, path, 'std')
|
|
2452
|
-
return { found: true, path, full: path, kind: 'aon', src: std, search: [] }
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2455
2485
|
let search: any = []
|
|
2456
2486
|
let res = memResolver(path, popts, rule, ctx, jsonic)
|
|
2457
2487
|
res.path = path
|
|
@@ -2581,10 +2611,27 @@ for (const name in funcSig) {
|
|
|
2581
2611
|
}
|
|
2582
2612
|
}
|
|
2583
2613
|
|
|
2614
|
+
// THE COMPONENT PRIMITIVES, listed by hand (the spike, see funcMap
|
|
2615
|
+
// above and ts/src/val/CmpFuncVal.ts). They carry no signature
|
|
2616
|
+
// declaration, so the derivation above cannot see them -- and
|
|
2617
|
+
// `file("main.ts", [...])` without an entry arrives as ONE raw array
|
|
2618
|
+
// term, which the call then reads as its spec. The leaves are here
|
|
2619
|
+
// too, so that a written second argument is COUNTED and refused as
|
|
2620
|
+
// the arity mistake it is rather than arriving as a one-element list
|
|
2621
|
+
// that is merely the wrong shape.
|
|
2622
|
+
for (const name of Object.keys(CMP_FUNCS)) {
|
|
2623
|
+
POSITIONAL_ARG_FUNCS[name] = true
|
|
2624
|
+
}
|
|
2625
|
+
POSITIONAL_ARG_FUNCS['nom'] = true
|
|
2626
|
+
POSITIONAL_ARG_FUNCS['translate'] = true
|
|
2627
|
+
|
|
2584
2628
|
|
|
2585
|
-
// [min, max]; a max of -1 is unbounded. Every name
|
|
2629
|
+
// [min, max]; a max of -1 is unbounded. Every DECLARED name has an
|
|
2586
2630
|
// entry, and the arity is a property of the language rather than of
|
|
2587
|
-
// either port -- go/func.go derives the same table.
|
|
2631
|
+
// either port -- go/func.go derives the same table. The spike's
|
|
2632
|
+
// component primitives are the one exception: they carry no
|
|
2633
|
+
// declaration, so they have no entry here and refuse a bad count in
|
|
2634
|
+
// their own resolve (ts/src/val/CmpFuncVal.ts). A required slot
|
|
2588
2635
|
// counts toward the minimum; a rest slot makes the maximum unbounded
|
|
2589
2636
|
// and counts its group size (one, for a plain rest type) toward the
|
|
2590
2637
|
// minimum, which is what gives `match` its floor of three and `neq`
|
|
@@ -2646,7 +2693,16 @@ function arityText(lo: number, hi: number): string {
|
|
|
2646
2693
|
return 'one or more arguments'
|
|
2647
2694
|
}
|
|
2648
2695
|
if (lo !== hi) {
|
|
2649
|
-
|
|
2696
|
+
if (0 === lo) {
|
|
2697
|
+
return 'no arguments or one'
|
|
2698
|
+
}
|
|
2699
|
+
// The case range gave `upper` and `lower` a span rather than a
|
|
2700
|
+
// pair, and a two-arm phrasing cannot say it: [1,3] read as "one
|
|
2701
|
+
// argument or two", which is a wrong count rather than an
|
|
2702
|
+
// imprecise one. Each arm here is a shape the table actually
|
|
2703
|
+
// carries, and each is pinned by a row -- a phrasing for a count no
|
|
2704
|
+
// entry has would be untested prose pretending to be tested.
|
|
2705
|
+
return 3 === hi ? 'one to three arguments' : 'one argument or two'
|
|
2650
2706
|
}
|
|
2651
2707
|
// The {0,0} arm returned with the container kinds and acyclic()
|
|
2652
2708
|
// (ADR-015): `map(1)` must not claim map takes exactly one.
|
package/src/lower.ts
CHANGED
|
@@ -103,19 +103,19 @@ export function splitWords(name: string): string[] {
|
|
|
103
103
|
return words
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
function lowerASCII(s: string): string {
|
|
106
|
+
export function lowerASCII(s: string): string {
|
|
107
107
|
return Array.from(s).map((c) =>
|
|
108
108
|
isUpper(c) ? String.fromCharCode(c.charCodeAt(0) + 32) : c).join('')
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
function upperASCII(s: string): string {
|
|
111
|
+
export function upperASCII(s: string): string {
|
|
112
112
|
return Array.from(s).map((c) =>
|
|
113
113
|
isLower(c) ? String.fromCharCode(c.charCodeAt(0) - 32) : c).join('')
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// A word capitalised: the acronym set wins, so `id` is `ID` under a
|
|
117
117
|
// profile that lists it and `Id` under one that does not.
|
|
118
|
-
function capitalise(word: string, acronyms: string[]): string {
|
|
118
|
+
export function capitalise(word: string, acronyms: string[]): string {
|
|
119
119
|
const low = lowerASCII(word)
|
|
120
120
|
for (const a of acronyms) {
|
|
121
121
|
if (lowerASCII(a) === low) {
|
package/src/lsp.ts
CHANGED
|
@@ -528,7 +528,7 @@ const COMPLETION_KEYWORD = 14
|
|
|
528
528
|
const BUILTIN_FUNCS = [
|
|
529
529
|
'above', 'acyclic', 'add', 'below', 'close', 'copy', 'deprecate', 'div',
|
|
530
530
|
'each', 'emit', 'esc',
|
|
531
|
-
'filter', '
|
|
531
|
+
'filter', 'greatest',
|
|
532
532
|
'hide', 'inverse', 'join', 'key', 'least', 'length', 'list', 'lower',
|
|
533
533
|
'map', 'match', 'max', 'min', 'mod', 'move', 'mul', 'must', 'neq', 'open',
|
|
534
534
|
'pack', 'path', 'pick',
|
package/src/relation.ts
CHANGED
|
@@ -61,6 +61,12 @@ export type RelationReport = {
|
|
|
61
61
|
// arrive with an empty list -- something is wrong, and nothing about
|
|
62
62
|
// what. Present ONLY on an `error` verdict.
|
|
63
63
|
errors?: VetFinding[]
|
|
64
|
+
|
|
65
|
+
// How many relation declarations the document makes (G11 phase 4).
|
|
66
|
+
// ZERO is the answer that matters: `pass` over no declarations means
|
|
67
|
+
// nothing was checked, and without this the two are one word.
|
|
68
|
+
// Absent unless the run asked for it (`RelationOptions.count`).
|
|
69
|
+
declared?: number
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
export type RelationOptions = {
|
|
@@ -76,6 +82,14 @@ export type RelationOptions = {
|
|
|
76
82
|
// Rides beside `trust` because it is the other half of what an
|
|
77
83
|
// include may read.
|
|
78
84
|
textExt?: string[]
|
|
85
|
+
|
|
86
|
+
// Count the relation declarations the document makes (G11 phase 4),
|
|
87
|
+
// so a caller can tell a graph that CHECKED CLEAN from one there was
|
|
88
|
+
// nothing to check. `pass` is the same word for both, which is the
|
|
89
|
+
// whole reason this exists. Absent from the report unless asked for,
|
|
90
|
+
// so no existing caller's report changes shape -- `vet --coverage`
|
|
91
|
+
// sets the precedent.
|
|
92
|
+
count?: boolean
|
|
79
93
|
}
|
|
80
94
|
|
|
81
95
|
|
|
@@ -271,8 +285,13 @@ export function relationCheck(
|
|
|
271
285
|
}
|
|
272
286
|
|
|
273
287
|
const decls: Map<string, RelDecl> = (ctx as any)._reldecls
|
|
288
|
+
// The count rides every report from here down: the engine knows it
|
|
289
|
+
// at exactly this point, and a caller asking "was there anything to
|
|
290
|
+
// check?" should not have to evaluate the document a second time to
|
|
291
|
+
// find out.
|
|
292
|
+
const counted = true === options.count ? { declared: decls.size } : {}
|
|
274
293
|
if (0 === decls.size) {
|
|
275
|
-
return { verdict: 'pass', findings: [] }
|
|
294
|
+
return { verdict: 'pass', findings: [], ...counted }
|
|
276
295
|
}
|
|
277
296
|
|
|
278
297
|
// NOR IS A DOCUMENT THAT CANNOT BE GENERATED. Unification can
|
|
@@ -299,5 +318,6 @@ export function relationCheck(
|
|
|
299
318
|
return {
|
|
300
319
|
verdict: 0 === findings.length ? 'pass' : 'fail',
|
|
301
320
|
findings,
|
|
321
|
+
...counted,
|
|
302
322
|
}
|
|
303
323
|
}
|