aontu 0.62.0 → 0.64.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.
Files changed (373) hide show
  1. package/README.md +7 -7
  2. package/dist/agentsmd.js +0 -27
  3. package/dist/agentsmd.js.map +1 -1
  4. package/dist/alias.js.map +1 -1
  5. package/dist/allow.js +0 -92
  6. package/dist/allow.js.map +1 -1
  7. package/dist/aontu.d.ts +3 -3
  8. package/dist/aontu.js +4 -84
  9. package/dist/aontu.js.map +1 -1
  10. package/dist/aontumodel.d.ts +4 -0
  11. package/dist/aontumodel.js +27 -0
  12. package/dist/aontumodel.js.map +1 -0
  13. package/dist/casing.d.ts +5 -0
  14. package/dist/casing.js +90 -0
  15. package/dist/casing.js.map +1 -0
  16. package/dist/cli.d.ts +2 -2
  17. package/dist/cli.js +228 -935
  18. package/dist/cli.js.map +1 -1
  19. package/dist/ctx.js +0 -48
  20. package/dist/ctx.js.map +1 -1
  21. package/dist/diff.js +0 -32
  22. package/dist/diff.js.map +1 -1
  23. package/dist/err.js +0 -40
  24. package/dist/err.js.map +1 -1
  25. package/dist/escape.js +0 -45
  26. package/dist/escape.js.map +1 -1
  27. package/dist/exactjson.d.ts +0 -35
  28. package/dist/exactjson.js +0 -131
  29. package/dist/exactjson.js.map +1 -1
  30. package/dist/format.js +13 -203
  31. package/dist/format.js.map +1 -1
  32. package/dist/grammar.d.ts +9 -0
  33. package/dist/grammar.js +54 -0
  34. package/dist/grammar.js.map +1 -0
  35. package/dist/graph.js +0 -26
  36. package/dist/graph.js.map +1 -1
  37. package/dist/hcanon.js +0 -82
  38. package/dist/hcanon.js.map +1 -1
  39. package/dist/helpdoc.js +2 -2
  40. package/dist/helpdoc.js.map +1 -1
  41. package/dist/hints.d.ts +0 -6
  42. package/dist/hints.js +57 -55
  43. package/dist/hints.js.map +1 -1
  44. package/dist/jsonschema.js +0 -114
  45. package/dist/jsonschema.js.map +1 -1
  46. package/dist/keyorder.d.ts +0 -7
  47. package/dist/keyorder.js +0 -41
  48. package/dist/keyorder.js.map +1 -1
  49. package/dist/lang.js +17 -915
  50. package/dist/lang.js.map +1 -1
  51. package/dist/lsp-server.js +0 -16
  52. package/dist/lsp-server.js.map +1 -1
  53. package/dist/lsp.d.ts +1 -1
  54. package/dist/lsp.js +12 -159
  55. package/dist/lsp.js.map +1 -1
  56. package/dist/mcp-server.js +0 -26
  57. package/dist/mcp-server.js.map +1 -1
  58. package/dist/mcp.js +0 -149
  59. package/dist/mcp.js.map +1 -1
  60. package/dist/mod-tool.js +16 -131
  61. package/dist/mod-tool.js.map +1 -1
  62. package/dist/mod.js +0 -162
  63. package/dist/mod.js.map +1 -1
  64. package/dist/patch.js +0 -217
  65. package/dist/patch.js.map +1 -1
  66. package/dist/profile.d.ts +9 -0
  67. package/dist/profile.js +28 -0
  68. package/dist/profile.js.map +1 -0
  69. package/dist/provenance.js +0 -140
  70. package/dist/provenance.js.map +1 -1
  71. package/dist/query.js +0 -75
  72. package/dist/query.js.map +1 -1
  73. package/dist/reach.js +0 -43
  74. package/dist/reach.js.map +1 -1
  75. package/dist/relation.js +0 -61
  76. package/dist/relation.js.map +1 -1
  77. package/dist/report-sarif.d.ts +0 -11
  78. package/dist/report-sarif.js +0 -28
  79. package/dist/report-sarif.js.map +1 -1
  80. package/dist/sig.js +0 -35
  81. package/dist/sig.js.map +1 -1
  82. package/dist/sigdecl.js +1 -1
  83. package/dist/sigdecl.js.map +1 -1
  84. package/dist/siggate.js +0 -4
  85. package/dist/siggate.js.map +1 -1
  86. package/dist/site.js +3 -29
  87. package/dist/site.js.map +1 -1
  88. package/dist/subsume.d.ts +0 -10
  89. package/dist/subsume.js +0 -137
  90. package/dist/subsume.js.map +1 -1
  91. package/dist/template.d.ts +2 -1
  92. package/dist/template.js +58 -138
  93. package/dist/template.js.map +1 -1
  94. package/dist/trace.d.ts +21 -0
  95. package/dist/trace.js +107 -0
  96. package/dist/trace.js.map +1 -0
  97. package/dist/trim.js +0 -41
  98. package/dist/trim.js.map +1 -1
  99. package/dist/tsconfig.tsbuildinfo +1 -1
  100. package/dist/type.js.map +1 -1
  101. package/dist/unify.js +13 -248
  102. package/dist/unify.js.map +1 -1
  103. package/dist/utility.js +0 -22
  104. package/dist/utility.js.map +1 -1
  105. package/dist/val/AbnfFuncVal.d.ts +18 -0
  106. package/dist/val/AbnfFuncVal.js +132 -0
  107. package/dist/val/AbnfFuncVal.js.map +1 -0
  108. package/dist/val/AbsentVal.d.ts +11 -0
  109. package/dist/val/AbsentVal.js +30 -0
  110. package/dist/val/AbsentVal.js.map +1 -0
  111. package/dist/val/AggFuncVal.d.ts +10 -1
  112. package/dist/val/AggFuncVal.js +104 -116
  113. package/dist/val/AggFuncVal.js.map +1 -1
  114. package/dist/val/ArithFuncVal.js +0 -12
  115. package/dist/val/ArithFuncVal.js.map +1 -1
  116. package/dist/val/BagVal.js +1 -78
  117. package/dist/val/BagVal.js.map +1 -1
  118. package/dist/val/BigDecimalVal.js +0 -16
  119. package/dist/val/BigDecimalVal.js.map +1 -1
  120. package/dist/val/BigIntegerVal.js +0 -16
  121. package/dist/val/BigIntegerVal.js.map +1 -1
  122. package/dist/val/CloseFuncVal.js +0 -9
  123. package/dist/val/CloseFuncVal.js.map +1 -1
  124. package/dist/val/CmpFuncVal.d.ts +2 -0
  125. package/dist/val/CmpFuncVal.js +49 -72
  126. package/dist/val/CmpFuncVal.js.map +1 -1
  127. package/dist/val/ConjunctVal.js +0 -29
  128. package/dist/val/ConjunctVal.js.map +1 -1
  129. package/dist/val/ConstraintVal.js +0 -500
  130. package/dist/val/ConstraintVal.js.map +1 -1
  131. package/dist/val/ContainerKindVal.js +0 -2
  132. package/dist/val/ContainerKindVal.js.map +1 -1
  133. package/dist/val/CopyFuncVal.js +0 -3
  134. package/dist/val/CopyFuncVal.js.map +1 -1
  135. package/dist/val/Decimal.js +0 -179
  136. package/dist/val/Decimal.js.map +1 -1
  137. package/dist/val/DeprecateFuncVal.js.map +1 -1
  138. package/dist/val/DisjunctVal.js +0 -152
  139. package/dist/val/DisjunctVal.js.map +1 -1
  140. package/dist/val/EachFuncVal.js +0 -3
  141. package/dist/val/EachFuncVal.js.map +1 -1
  142. package/dist/val/EmitFuncVal.d.ts +1 -1
  143. package/dist/val/EmitFuncVal.js +6 -119
  144. package/dist/val/EmitFuncVal.js.map +1 -1
  145. package/dist/val/ExpectVal.js +0 -62
  146. package/dist/val/ExpectVal.js.map +1 -1
  147. package/dist/val/FilterFuncVal.js +0 -25
  148. package/dist/val/FilterFuncVal.js.map +1 -1
  149. package/dist/val/FuncBaseVal.d.ts +1 -0
  150. package/dist/val/FuncBaseVal.js +7 -127
  151. package/dist/val/FuncBaseVal.js.map +1 -1
  152. package/dist/val/GraphAtomVal.js +0 -15
  153. package/dist/val/GraphAtomVal.js.map +1 -1
  154. package/dist/val/HideFuncVal.js +0 -13
  155. package/dist/val/HideFuncVal.js.map +1 -1
  156. package/dist/val/IntegerVal.js +0 -61
  157. package/dist/val/IntegerVal.js.map +1 -1
  158. package/dist/val/JunctionVal.js +0 -20
  159. package/dist/val/JunctionVal.js.map +1 -1
  160. package/dist/val/KeyFuncVal.js +0 -46
  161. package/dist/val/KeyFuncVal.js.map +1 -1
  162. package/dist/val/ListVal.js +0 -57
  163. package/dist/val/ListVal.js.map +1 -1
  164. package/dist/val/LowerFuncVal.js +0 -11
  165. package/dist/val/LowerFuncVal.js.map +1 -1
  166. package/dist/val/MapVal.js +0 -151
  167. package/dist/val/MapVal.js.map +1 -1
  168. package/dist/val/MatchFuncVal.js +0 -27
  169. package/dist/val/MatchFuncVal.js.map +1 -1
  170. package/dist/val/{FormFuncVal.d.ts → MaybeFuncVal.d.ts} +5 -5
  171. package/dist/val/MaybeFuncVal.js +50 -0
  172. package/dist/val/MaybeFuncVal.js.map +1 -0
  173. package/dist/val/MoveFuncVal.js +0 -18
  174. package/dist/val/MoveFuncVal.js.map +1 -1
  175. package/dist/val/NilVal.js +0 -60
  176. package/dist/val/NilVal.js.map +1 -1
  177. package/dist/val/NomFuncVal.js +18 -54
  178. package/dist/val/NomFuncVal.js.map +1 -1
  179. package/dist/val/NumberVal.js +0 -15
  180. package/dist/val/NumberVal.js.map +1 -1
  181. package/dist/val/OpBaseVal.d.ts +1 -0
  182. package/dist/val/OpBaseVal.js +3 -15
  183. package/dist/val/OpBaseVal.js.map +1 -1
  184. package/dist/val/PackFuncVal.js +0 -34
  185. package/dist/val/PackFuncVal.js.map +1 -1
  186. package/dist/val/PathFuncVal.js +0 -6
  187. package/dist/val/PathFuncVal.js.map +1 -1
  188. package/dist/val/PathVal.js +0 -41
  189. package/dist/val/PathVal.js.map +1 -1
  190. package/dist/val/PlaceVal.js +0 -25
  191. package/dist/val/PlaceVal.js.map +1 -1
  192. package/dist/val/PlusOpVal.d.ts +1 -7
  193. package/dist/val/PlusOpVal.js +13 -74
  194. package/dist/val/PlusOpVal.js.map +1 -1
  195. package/dist/val/PrefFuncVal.js +0 -1
  196. package/dist/val/PrefFuncVal.js.map +1 -1
  197. package/dist/val/PrefVal.js +0 -167
  198. package/dist/val/PrefVal.js.map +1 -1
  199. package/dist/val/RecurseVal.js +0 -55
  200. package/dist/val/RecurseVal.js.map +1 -1
  201. package/dist/val/RefVal.js +0 -282
  202. package/dist/val/RefVal.js.map +1 -1
  203. package/dist/val/ReferFuncVal.js +3 -232
  204. package/dist/val/ReferFuncVal.js.map +1 -1
  205. package/dist/val/ScalarKindVal.js +0 -49
  206. package/dist/val/ScalarKindVal.js.map +1 -1
  207. package/dist/val/ScalarVal.js +0 -11
  208. package/dist/val/ScalarVal.js.map +1 -1
  209. package/dist/val/StrFuncVal.js +0 -18
  210. package/dist/val/StrFuncVal.js.map +1 -1
  211. package/dist/val/SuperFuncVal.js +0 -32
  212. package/dist/val/SuperFuncVal.js.map +1 -1
  213. package/dist/val/TopVal.js +0 -1
  214. package/dist/val/TopVal.js.map +1 -1
  215. package/dist/val/TranslateFuncVal.js +1 -3
  216. package/dist/val/TranslateFuncVal.js.map +1 -1
  217. package/dist/val/UpperFuncVal.js +0 -11
  218. package/dist/val/UpperFuncVal.js.map +1 -1
  219. package/dist/val/Val.d.ts +1 -0
  220. package/dist/val/Val.js +2 -133
  221. package/dist/val/Val.js.map +1 -1
  222. package/dist/val/VarVal.js +0 -12
  223. package/dist/val/VarVal.js.map +1 -1
  224. package/dist/val/arith.js +0 -37
  225. package/dist/val/arith.js.map +1 -1
  226. package/dist/val/caserange.js +0 -61
  227. package/dist/val/caserange.js.map +1 -1
  228. package/dist/val/members.js +0 -6
  229. package/dist/val/members.js.map +1 -1
  230. package/dist/val/numcmp.js +0 -11
  231. package/dist/val/numcmp.js.map +1 -1
  232. package/dist/val/numkind.js +0 -145
  233. package/dist/val/numkind.js.map +1 -1
  234. package/dist/val/valutil.js +0 -16
  235. package/dist/val/valutil.js.map +1 -1
  236. package/dist/vet.js +0 -461
  237. package/dist/vet.js.map +1 -1
  238. package/dist/view.js +0 -414
  239. package/dist/view.js.map +1 -1
  240. package/dist/walk.js +0 -41
  241. package/dist/walk.js.map +1 -1
  242. package/grammar/aontu.abnf +8 -6
  243. package/grammar/aontu.gbnf +4 -4
  244. package/grammar/aontu.lark +4 -4
  245. package/grammar/aontu.tmLanguage.json +1 -1
  246. package/package.json +4 -2
  247. package/skill/tasks.md +9 -7
  248. package/src/agentsmd.ts +0 -35
  249. package/src/alias.ts +0 -39
  250. package/src/allow.ts +1 -96
  251. package/src/aontu.ts +4 -116
  252. package/src/aontumodel.ts +26 -0
  253. package/src/casing.ts +95 -0
  254. package/src/cli.ts +258 -1024
  255. package/src/ctx.ts +0 -103
  256. package/src/diff.ts +0 -40
  257. package/src/err.ts +0 -40
  258. package/src/escape.ts +0 -46
  259. package/src/exactjson.ts +0 -131
  260. package/src/format.ts +14 -257
  261. package/src/grammar.ts +72 -0
  262. package/src/graph.ts +0 -61
  263. package/src/hcanon.ts +0 -82
  264. package/src/helpdoc.ts +2 -2
  265. package/src/hints.ts +69 -57
  266. package/src/jsonschema.ts +0 -123
  267. package/src/keyorder.ts +0 -42
  268. package/src/lang.ts +19 -931
  269. package/src/lsp-server.ts +0 -16
  270. package/src/lsp.ts +12 -180
  271. package/src/mcp-server.ts +0 -31
  272. package/src/mcp.ts +0 -169
  273. package/src/mod-tool.ts +18 -159
  274. package/src/mod.ts +0 -178
  275. package/src/patch.ts +0 -232
  276. package/src/profile.ts +42 -0
  277. package/src/provenance.ts +0 -183
  278. package/src/query.ts +0 -84
  279. package/src/reach.ts +0 -53
  280. package/src/relation.ts +0 -84
  281. package/src/report-sarif.ts +0 -48
  282. package/src/sig.ts +0 -35
  283. package/src/sigdecl.ts +1 -1
  284. package/src/siggate.ts +0 -30
  285. package/src/site.ts +3 -29
  286. package/src/subsume.ts +1 -161
  287. package/src/template.ts +69 -140
  288. package/src/trace.ts +157 -0
  289. package/src/trim.ts +0 -53
  290. package/src/type.ts +2 -45
  291. package/src/unify.ts +14 -257
  292. package/src/utility.ts +0 -31
  293. package/src/val/AbnfFuncVal.ts +181 -0
  294. package/src/val/AbsentVal.ts +54 -0
  295. package/src/val/AggFuncVal.ts +152 -188
  296. package/src/val/ArithFuncVal.ts +0 -20
  297. package/src/val/BagVal.ts +1 -78
  298. package/src/val/BigDecimalVal.ts +0 -16
  299. package/src/val/BigIntegerVal.ts +0 -16
  300. package/src/val/CloseFuncVal.ts +0 -9
  301. package/src/val/CmpFuncVal.ts +68 -184
  302. package/src/val/ConjunctVal.ts +0 -33
  303. package/src/val/ConstraintVal.ts +2 -537
  304. package/src/val/ContainerKindVal.ts +0 -18
  305. package/src/val/CopyFuncVal.ts +0 -5
  306. package/src/val/Decimal.ts +1 -185
  307. package/src/val/DeprecateFuncVal.ts +0 -10
  308. package/src/val/DisjunctVal.ts +0 -157
  309. package/src/val/EachFuncVal.ts +0 -40
  310. package/src/val/EmitFuncVal.ts +8 -208
  311. package/src/val/ExpectVal.ts +0 -62
  312. package/src/val/FilterFuncVal.ts +0 -55
  313. package/src/val/FuncBaseVal.ts +9 -130
  314. package/src/val/GraphAtomVal.ts +0 -42
  315. package/src/val/HideFuncVal.ts +0 -15
  316. package/src/val/IntegerVal.ts +0 -61
  317. package/src/val/JunctionVal.ts +0 -20
  318. package/src/val/KeyFuncVal.ts +0 -48
  319. package/src/val/ListVal.ts +0 -59
  320. package/src/val/LowerFuncVal.ts +0 -12
  321. package/src/val/MapVal.ts +0 -151
  322. package/src/val/MatchFuncVal.ts +0 -59
  323. package/src/val/MaybeFuncVal.ts +86 -0
  324. package/src/val/MoveFuncVal.ts +0 -20
  325. package/src/val/NilVal.ts +0 -60
  326. package/src/val/NomFuncVal.ts +10 -99
  327. package/src/val/NumberVal.ts +0 -16
  328. package/src/val/OpBaseVal.ts +4 -17
  329. package/src/val/PackFuncVal.ts +0 -63
  330. package/src/val/PathFuncVal.ts +0 -32
  331. package/src/val/PathVal.ts +0 -66
  332. package/src/val/PlaceVal.ts +0 -45
  333. package/src/val/PlusOpVal.ts +18 -75
  334. package/src/val/PrefFuncVal.ts +0 -1
  335. package/src/val/PrefVal.ts +0 -179
  336. package/src/val/RecurseVal.ts +0 -81
  337. package/src/val/RefVal.ts +1 -285
  338. package/src/val/ReferFuncVal.ts +4 -255
  339. package/src/val/ScalarKindVal.ts +0 -50
  340. package/src/val/ScalarVal.ts +0 -12
  341. package/src/val/StrFuncVal.ts +0 -44
  342. package/src/val/SuperFuncVal.ts +0 -42
  343. package/src/val/TopVal.ts +0 -1
  344. package/src/val/TranslateFuncVal.ts +1 -51
  345. package/src/val/UpperFuncVal.ts +0 -12
  346. package/src/val/Val.ts +3 -192
  347. package/src/val/VarVal.ts +0 -15
  348. package/src/val/arith.ts +0 -92
  349. package/src/val/caserange.ts +0 -62
  350. package/src/val/members.ts +0 -23
  351. package/src/val/numcmp.ts +1 -27
  352. package/src/val/numkind.ts +0 -149
  353. package/src/val/valutil.ts +0 -16
  354. package/src/vet.ts +1 -582
  355. package/src/view.ts +0 -507
  356. package/src/walk.ts +0 -41
  357. package/dist/lower.d.ts +0 -23
  358. package/dist/lower.js +0 -578
  359. package/dist/lower.js.map +0 -1
  360. package/dist/render.d.ts +0 -53
  361. package/dist/render.js +0 -547
  362. package/dist/render.js.map +0 -1
  363. package/dist/std.d.ts +0 -3
  364. package/dist/std.js +0 -672
  365. package/dist/std.js.map +0 -1
  366. package/dist/val/FormFuncVal.js +0 -55
  367. package/dist/val/FormFuncVal.js.map +0 -1
  368. package/dist/val/NamerFuncVal.d.ts +0 -12
  369. package/dist/val/NamerFuncVal.js +0 -176
  370. package/dist/val/NamerFuncVal.js.map +0 -1
  371. package/src/lower.ts +0 -636
  372. package/src/render.ts +0 -732
  373. package/src/std.ts +0 -683
package/src/format.ts CHANGED
@@ -1,26 +1,5 @@
1
1
  /* Copyright (c) 2026 Richard Rodger, MIT License */
2
2
 
3
- // THE SOURCE FORMATTER (docs/design/FMT.0.md): `aontu fmt`, in the
4
- // tradition of gofmt. One agreed form for Aontu source, so that layout
5
- // is never argued about and a diff shows only what changed.
6
- //
7
- // It reads the token stream the parser reads -- the lex subscriber the
8
- // parser stack exposes -- so it sees what the value tree throws away:
9
- // comments, blank lines, the quote a string used, the spelling of a
10
- // number. From that stream it builds a layout tree, decides the shape
11
- // of every container by the rules of the note's §3, and emits. Before
12
- // returning it re-parses what it wrote and compares the two parse
13
- // trees: a formatter that cannot prove its output is the same document
14
- // refuses rather than return it.
15
- //
16
- // Two tiers. The syntactic (P1): whitespace, commas, quotes, bare
17
- // keys, chains and pair elements, none of which changes the parse
18
- // tree. The lawful (P2), over it: repeat the prefix, and merge what
19
- // repeats -- rewrites that rest on the meet, each checked by the meet
20
- // in isolation and kept only where the engine agrees.
21
- //
22
- // The Go twin is go/format.go, function for function; the shared
23
- // behaviour is test/spec/fmt.tsv, executed by both spec runners.
24
3
 
25
4
  import { Aontu } from './aontu'
26
5
  import { failureFinding } from './vet'
@@ -29,16 +8,8 @@ import type { Resolver } from './type'
29
8
  import { desugarTemplate, resugarTemplate, templateOutputs } from './template'
30
9
 
31
10
 
32
- // The packing budget (§3.1). It decides which of two legal spellings
33
- // to use, one line or several, and nothing else: the formatter never
34
- // breaks a line, so a value wider than this stays as wide as it is.
35
11
  const BUDGET = 80
36
12
 
37
- // THE DEPTH BUDGET. The layout is recursive, as the tree it reads is,
38
- // and the canonical port's stack is finite: past the evaluation budget
39
- // of 1000 levels -- the depth at which unification itself refuses --
40
- // the formatter stops reading and refuses, so a pathological document
41
- // is a finding rather than a crash.
42
13
  const MAX_DEPTH = 1000
43
14
 
44
15
  export type FormatOptions = {
@@ -47,11 +18,6 @@ export type FormatOptions = {
47
18
  // Report the style findings of §4 -- key case, repeated shapes --
48
19
  // beside the text. The formatter never acts on them.
49
20
  lint?: boolean
50
- // THE SOURCE IS A GENERATOR, and this is its marker
51
- // (docs/design/TEMPLATE.0.md; FMT.0.md §3.14). The file is desugared,
52
- // formatted and resugared, so `text` is a template again -- the aontu
53
- // in the agreed form, indented after the marker, and every line of
54
- // output exactly where it was.
55
21
  template?: string
56
22
  }
57
23
 
@@ -82,18 +48,10 @@ export type FormatReport =
82
48
 
83
49
  type Tok = { name: string, src: string, val: any, sI: number }
84
50
 
85
- // EVERY INCLUDE RESOLVES TO NOTHING. The formatter reads the file it is
86
- // given and no other (§3.13), so `@"..."` is answered from memory with
87
- // an empty source: the directive parses, the include is a token like
88
- // any other, and no capability is needed because no file is read.
89
51
  const stubResolver: Resolver = ((spec: any) => ({
90
52
  ...spec, kind: 'aon', full: '__fmt__.aon', src: '', found: true, search: [],
91
53
  })) as any
92
54
 
93
- // ONE ENGINE, ONE SUBSCRIBER. The parser's subscriber list is
94
- // append-only, so the subscription is made once and writes to
95
- // whichever sink the current parse installed; the sink is cleared
96
- // before the parse returns, so the check's re-parse collects nothing.
97
55
  let ENGINE: Aontu | undefined
98
56
  let SINK: Tok[] | undefined
99
57
 
@@ -102,10 +60,6 @@ function engine(): Aontu {
102
60
  ENGINE = new Aontu({ resolver: stubResolver })
103
61
  ENGINE.lang.jsonic.sub({
104
62
  lex: (tkn: any) => {
105
- // Spaces carry nothing the layout needs, and the end token
106
- // arrives once per nested parse -- the stub's empty includes
107
- // among them -- so both are dropped here rather than skipped
108
- // everywhere below.
109
63
  if (undefined !== SINK && '#SP' !== tkn.name && '#ZZ' !== tkn.name) {
110
64
  SINK.push({ name: tkn.name, src: tkn.src, val: tkn.val, sI: tkn.sI })
111
65
  }
@@ -138,21 +92,14 @@ function parseDoc(src: string, path: string | undefined, sink: Tok[] | undefined
138
92
  }
139
93
 
140
94
 
141
- // ---------------------------------------------------------------------
142
- // The layout tree
143
-
144
95
  // One node shape for the whole tree, so the Go twin is one struct:
145
96
  // the kind says which fields are meaningful.
146
97
  type Node = {
147
98
  t: 'pair' | 'spread' | 'include' | 'comment' | 'blank' | 'map' | 'list'
148
99
  | 'atom' | 'call' | 'paren' | 'expr' | 'op' | 'prefix' | 'note'
149
100
 
150
- // atom, include, comment, note, op, prefix: the text as written,
151
- // normalised where §3.9 says (quotes), and nothing else.
152
101
  text?: string
153
102
 
154
- // pair: the key as it will be written, the optional marker, and the
155
- // value; spread: the value.
156
103
  key?: string
157
104
  opt?: boolean
158
105
  // pair: written with `=`, the alias declaration operator, rather than
@@ -175,7 +122,6 @@ type Node = {
175
122
  // comment inside the expression), in source order.
176
123
  items?: Node[]
177
124
 
178
- // op: the author broke the line at this operator (§3.11).
179
125
  brk?: boolean
180
126
 
181
127
  // A comment on the last line of this entry.
@@ -273,10 +219,6 @@ class Reader {
273
219
  ('#QM' === this.name(1) && '#CL' === this.name(2)))
274
220
  }
275
221
 
276
- // The entries of a container up to its closer, or of the document up
277
- // to its end. Comments attach by the rules of §3.7: on the line of
278
- // the entry that precedes them, or of the opener, they trail it;
279
- // alone on a line they stand as entries and precede what follows.
280
222
  body(close: string, opened: boolean): { body: Node[], open?: string } {
281
223
  const body: Node[] = []
282
224
  let open: string | undefined
@@ -584,11 +526,6 @@ function unwrap(root: Node[]): Node[] {
584
526
  // ---------------------------------------------------------------------
585
527
  // The layout
586
528
 
587
- // D1: a one-pair map in value position is written as a chain, and a
588
- // one-pair map as a list element as a pair element. A map whose only
589
- // entry is a spread keeps its braces (X-7), and one holding a comment
590
- // keeps them too, because the comment needs the lines. A trailing
591
- // comment on the map's line joins the pair's own.
592
529
  function chain(node: Node): Node {
593
530
  if ('map' !== node.t || undefined !== node.open || 1 !== node.body!.length ||
594
531
  'pair' !== node.body![0].t) {
@@ -605,19 +542,17 @@ function width(s: string): number {
605
542
  return Array.from(s).length
606
543
  }
607
544
 
608
- function pairHead(node: Node, tight: boolean): string {
545
+ function pairHead(node: Node, tight: boolean, value?: string): string {
609
546
  // An alias declaration is `%name = value` at every width: the `=` is
610
547
  // an operator, and operators are spaced (§3.2).
611
548
  if (node.alias) {
612
549
  return node.key! + ' = '
613
550
  }
614
- return node.key! + (node.opt ? '?' : '') + (tight ? ':' : ': ')
551
+ const pad = !tight ||
552
+ (undefined !== value && '' !== value && OPENS.includes(value[0]))
553
+ return node.key! + (node.opt ? '?' : '') + (pad ? ': ' : ':')
615
554
  }
616
555
 
617
- // The one-line spelling of a node, or undefined where it has none: a
618
- // comment, a blank line, a break the author kept, a string that spans
619
- // lines. `tight` is the inline form of a pair, `a:1`, used inside a
620
- // container; a statement's pair is `a: 1`.
621
556
  function inline(node: Node, tight: boolean): string | undefined {
622
557
  if (undefined !== node.trail || node.held) {
623
558
  return undefined
@@ -628,7 +563,7 @@ function inline(node: Node, tight: boolean): string | undefined {
628
563
  return node.text!.includes('\n') ? undefined : node.text
629
564
  case 'pair': {
630
565
  const v = inline(chain(node.value!), tight)
631
- return undefined === v ? undefined : pairHead(node, tight) + v
566
+ return undefined === v ? undefined : pairHead(node, tight, v) + v
632
567
  }
633
568
  case 'spread': {
634
569
  // `{ &: integer }`, padded inside braces too: the marker reads as
@@ -685,12 +620,17 @@ function inlineSeq(items: Node[]): string | undefined {
685
620
  return out
686
621
  }
687
622
 
623
+ // A value that OPENS keeps the space after the colon (§3.2); `|` is
624
+ // tight within a line and spaced where it leads one (§3.11, §7.7).
625
+ const OPENS = '*{[('
626
+ const TIGHT_OP = '|'
627
+
688
628
  function sepOf(node: Node): string {
689
629
  return node.sep ? ', ' : ' '
690
630
  }
691
631
 
692
- // Binary operators spaced, prefixes tight (§3.11). An operand is
693
- // never directly after an operand: the reader ends a value there.
632
+ // Binary operators spaced, prefixes and `|` tight (§3.11). An operand
633
+ // is never directly after an operand: the reader ends a value there.
694
634
  function inlineExpr(items: Node[]): string | undefined {
695
635
  let out = ''
696
636
  for (const it of items) {
@@ -698,7 +638,7 @@ function inlineExpr(items: Node[]): string | undefined {
698
638
  return undefined
699
639
  }
700
640
  if ('op' === it.t) {
701
- out += ' ' + it.text + ' '
641
+ out += TIGHT_OP === it.text ? it.text : ' ' + it.text + ' '
702
642
  continue
703
643
  }
704
644
  if ('prefix' === it.t) {
@@ -755,9 +695,6 @@ class Writer {
755
695
  return this.lines.slice(mark).concat([this.line]).map(rtrim).join('\n') + '\n'
756
696
  }
757
697
 
758
- // A blank line above the line at an index: the gap of §3.8, opened
759
- // once the statement below it turns out to be a tree. Never at the
760
- // top of the page, and never a second time.
761
698
  gap(at: number): void {
762
699
  if (0 < at && '' !== this.lines[at - 1]) {
763
700
  this.lines.splice(at, 0, '')
@@ -790,21 +727,11 @@ function rtrim(s: string): string {
790
727
  }
791
728
 
792
729
 
793
- // The entries of a body, one per line at the indentation, with the
794
- // blank lines the author kept between them (§3.8) -- never at the
795
- // start or the end. In STATEMENT position (`stmt`: the root, and the
796
- // body of a plain map that is itself the value of a statement) a pair
797
- // is laid out by §3.4, which may repeat its key; anywhere else -- a
798
- // list, an operand, an argument -- by §3.5 alone.
799
730
  function emitBody(
800
731
  w: Writer, body: Node[], indent: number, stmt: Stmt | undefined, root?: boolean
801
732
  ): void {
802
733
  let pending = false
803
734
  let count = 0
804
- // Where the run being written begins: a statement, with the comments
805
- // standing directly above it, so that the gap below opens ABOVE the
806
- // comments rather than between them and what they describe. A blank
807
- // line ends a run -- comments across a gap belong to what is above.
808
735
  let head = 0
809
736
  let noted = false
810
737
  for (const node of body) {
@@ -836,14 +763,6 @@ function emitBody(
836
763
  w.text(' ' + e.trail)
837
764
  }
838
765
  }
839
- // A TOP-LEVEL STATEMENT WRITTEN AS A TREE STANDS APART (§3.8). A
840
- // document states several things -- a service, then its entities,
841
- // then its errors -- and where one of them is a tree rather than a
842
- // line, the eye finds it by the space around it. What counts as a
843
- // tree is measured rather than guessed: the statement took more
844
- // than one line to write. So `a: 1` beside `b: 2` is left alone,
845
- // and this rule cannot fire below the root, where a blank line is
846
- // the author's (§3.8) and nothing else.
847
766
  if (root && from < w.mark()) {
848
767
  w.gap(head)
849
768
  pending = true
@@ -892,18 +811,6 @@ function emitValue(w: Writer, node: Node, indent: number): void {
892
811
  }
893
812
  }
894
813
 
895
- // A call, or a parenthesis, that has no one-line form or is too wide
896
- // for the budget. Three shapes. Arguments that are all FLAT -- none
897
- // holds a container -- stay on the one line however wide it is: a
898
- // scalar is no narrower on a line of its own, and the formatter never
899
- // breaks a line. The last argument HUGS the parentheses, `hide({` ...
900
- // `})`, `close($.E & {` ... `})`, when it is a container, or an
901
- // expression the author did not break that ends in one, and the
902
- // arguments before it fit on the opener's line: the container decides
903
- // its own lines. Otherwise the parenthesis opens a block: one argument
904
- // per line one level in, the closer alone at the opener's level. A
905
- // call whose last argument hugs is hugged in turn, `type(close({` ...
906
- // `}))`: the schema idiom.
907
814
  function emitCall(w: Writer, node: Node, indent: number): void {
908
815
  const items = 'call' === node.t ? node.args! : node.inner!
909
816
  const open = ('call' === node.t ? node.name! : '') + '('
@@ -1005,21 +912,8 @@ function emitBlock(
1005
912
  w.text(close)
1006
913
  }
1007
914
 
1008
- // An expression that has no one-line form, or one too wide for the
1009
- // budget: the author's breaks are kept, each at its operator, which
1010
- // leads its continuation line (§3.11). The continuation is one level
1011
- // in when the expression follows a key on its line, and level with
1012
- // the first operand when the expression has the line to itself -- an
1013
- // argument of a block call, say -- so a disjunction of alternatives
1014
- // reads as the list it is. A container operand that does not fit from
1015
- // where it stands is a block whose closer lines up with the line that
1016
- // opened it.
1017
915
  function emitExpr(w: Writer, items: Node[], indent: number): void {
1018
916
  const cont = w.fresh() ? indent : indent + 2
1019
- // Whether the last item was an operand: a comment after one is a
1020
- // space away, and after an operator or the colon it is not. An
1021
- // operand is never directly after an operand (the reader ends a
1022
- // value there), so operands need no such check.
1023
917
  let operand = false
1024
918
  let cur = indent
1025
919
  for (const it of items) {
@@ -1032,7 +926,7 @@ function emitExpr(w: Writer, items: Node[], indent: number): void {
1032
926
  w.text(it.text + ' ')
1033
927
  }
1034
928
  else {
1035
- w.text(' ' + it.text + ' ')
929
+ w.text(TIGHT_OP === it.text ? it.text! : ' ' + it.text + ' ')
1036
930
  }
1037
931
  operand = false
1038
932
  continue
@@ -1058,25 +952,6 @@ function emitExpr(w: Writer, items: Node[], indent: number): void {
1058
952
  }
1059
953
 
1060
954
 
1061
- // ---------------------------------------------------------------------
1062
- // The lawful tier (§3.4): repeat the prefix, and merge what repeats.
1063
- //
1064
- // Both rewrites rest on the meet. `s: a: 1` / `s: b: 2` is one document
1065
- // with `s: { a:1 b:2 }`, because a key written twice is a meet and the
1066
- // meet of two maps with disjoint keys is their union. So they apply
1067
- // only to a PLAIN map in STATEMENT position -- an entry of the root, or
1068
- // of a map that is itself the plain value of such an entry -- and never
1069
- // to a map that is an operand, an argument or a list element, where
1070
- // splitting it would change the document (`close({a:1})` /
1071
- // `close({b:2})` does not evaluate at all). And every statement the
1072
- // tier rewrites is checked by unification, locally (§7.3): the spelling
1073
- // before and the spelling after must come to the same meet, or the
1074
- // statement keeps the spelling before. The check is the engine's
1075
- // agreement, not the formatter's self-check -- the engine's own repros
1076
- // hold maps whose two spellings it evaluates differently -- so failing
1077
- // it is no refusal.
1078
-
1079
- // The check of one rewrite: the spelling before and the spelling after.
1080
955
  type Meet = (before: string, after: string) => boolean
1081
956
 
1082
957
  // Statement position: the check, and whether the statement being laid
@@ -1084,10 +959,6 @@ type Meet = (before: string, after: string) => boolean
1084
959
  // Undefined anywhere else -- a list, an operand, an argument.
1085
960
  type Stmt = { meet: Meet, covered: boolean }
1086
961
 
1087
- // The entries of a plain map value: a braced map, or a chain, which is
1088
- // a one-entry map. A map with a comment on its opener keeps its braces
1089
- // (§3.7), so it is not plain here; nor is a map holding an include,
1090
- // which the local check cannot follow.
1091
962
  function plainEntries(v: Node): Node[] | undefined {
1092
963
  if ('pair' === v.t) {
1093
964
  return [v]
@@ -1115,17 +986,6 @@ function members(p: Node): Node[] | undefined {
1115
986
  return entries.slice(0, -1).concat([{ ...last, trail }])
1116
987
  }
1117
988
 
1118
- // Adjacent statements naming one key, whose values are plain maps, are
1119
- // one map: their entries in order, with the comments and blank lines
1120
- // between the statements travelling with the statement they preceded.
1121
- // Only ADJACENT statements merge -- a `server:` line, something else,
1122
- // then another `server:` line stays as it is, because merging them
1123
- // would move a statement, and the formatter never reorders (§3.13).
1124
- // Nor do two statements merge into a map with two spreads: the engine
1125
- // keeps those as a conjunction, which is not the meet of the two maps.
1126
- // The tree is not changed: a merged statement is a new node that keeps
1127
- // the statements it replaces as its `orig`, its spelling before, and a
1128
- // statement merged somewhere below is copied the same way.
1129
989
  function mergeRuns(body: Node[]): Node[] {
1130
990
  const out: Node[] = []
1131
991
  let i = 0
@@ -1190,15 +1050,6 @@ function mergeDeep(p: Node): Node {
1190
1050
  return { ...p, value: 'pair' === v.t ? body[0] : { ...v, body }, orig: [p] }
1191
1051
  }
1192
1052
 
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
1053
  function record(v: Node, entries: Node[]): boolean {
1203
1054
  if ('map' !== v.t) {
1204
1055
  return false
@@ -1219,24 +1070,6 @@ function record(v: Node, entries: Node[]): boolean {
1219
1070
  return 1 < pairs
1220
1071
  }
1221
1072
 
1222
- // The lines of a map repeated under a prefix (§3.4, rule 2): every
1223
- // entry written with the prefix in front of it as one line, or --
1224
- // where an entry's value is a map that does not fit -- descended into
1225
- // under the longer prefix. Comments and blank lines are kept where
1226
- // they stood. Undefined where an entry cannot be one line: a list that
1227
- // does not fit, a value that spans lines, a comment closing the map
1228
- // (which a repeat could not keep in the map) -- and where the map holds
1229
- // two spreads, which repeated would be two maps, and a different meet.
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
1073
  type Line = {
1241
1074
  t: 'text' | 'comment' | 'blank' | 'block'
1242
1075
  text?: string
@@ -1284,12 +1117,6 @@ function repeatLines(entries: Node[], prefix: string, indent: number): Line[] |
1284
1117
  if (undefined === lines) {
1285
1118
  return undefined
1286
1119
  }
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
1120
  if (record(e.value!, sub)) {
1294
1121
  out.push({ t: 'block', text: head, node: e.value!, trail })
1295
1122
  continue
@@ -1315,18 +1142,6 @@ function fits(indent: number, text: string): boolean {
1315
1142
  return indent + width(text) <= BUDGET
1316
1143
  }
1317
1144
 
1318
- // A pair in statement position, by §3.4. `prefix` is what stands
1319
- // before it on its line: the heads of the chain it hangs from, not yet
1320
- // written. Its value is laid out by §3.5 unless it is a plain map, and
1321
- // then in this order: a chain, when the map holds exactly one pair
1322
- // (D1); one line, when that fits the budget; the key repeated over the
1323
- // entries, when every entry can be one line that way; a braced block
1324
- // otherwise, whose entries are statements in turn. Whether the
1325
- // statement was rewritten by this tier -- merged, or repeated -- is
1326
- // returned, and the outermost such statement is checked: its spelling
1327
- // on the page against what the syntactic tier writes for the
1328
- // statements it came from, at the same indentation, which is what
1329
- // stays on the page when the check fails.
1330
1145
  function emitStatement(w: Writer, p: Node, indent: number, stmt: Stmt, prefix: string): boolean {
1331
1146
  const mark = w.mark()
1332
1147
  let rewritten = undefined !== p.orig
@@ -1407,12 +1222,6 @@ function emit(root: Node[], meet: Meet | undefined): string {
1407
1222
  }
1408
1223
 
1409
1224
 
1410
- // ---------------------------------------------------------------------
1411
- // The lint (§4): what the formatter points at and never touches. Two
1412
- // rules, both advice: the formatter never renames a key (§4.1) and
1413
- // never introduces an alias (§4.2), and a rule with a mechanical fix
1414
- // that keeps the document would belong to §3 instead (§4.3).
1415
-
1416
1225
  // The shape width at which a repeat is worth an alias (§4.2): below
1417
1226
  // it, `{ a:1 }` twice is the shorter spelling. Measured over the use
1418
1227
  // cases when the lint landed (§7.10).
@@ -1464,11 +1273,6 @@ function lineCol(text: string, at: number): { line: number, col: number } {
1464
1273
  return { line: before.split('\n').length, col: at - before.lastIndexOf('\n') }
1465
1274
  }
1466
1275
 
1467
- // D4 (§4.1): keys are lower-case words, or CamelCase when a key is
1468
- // several. A bare key holding `_`, or beginning with two capitals, is
1469
- // reported with the spelling that would follow the form; a quoted key
1470
- // is a deliberate spelling and a key of underscores alone names
1471
- // nothing the rule can respell.
1472
1276
  function keyCase(node: Node, text: string, out: LintFinding[]): void {
1473
1277
  if ('pair' === node.t && BARE.test(node.key!) && /[A-Za-z]/.test(node.key!)) {
1474
1278
  const why = node.key!.includes('_') ? 'holds an underscore'
@@ -1485,9 +1289,6 @@ function keyCase(node: Node, text: string, out: LintFinding[]): void {
1485
1289
  }
1486
1290
  }
1487
1291
 
1488
- // The key as lower-case words or CamelCase: `credit_cents` is
1489
- // `creditCents`, `HTTP_PORT` is `httpPort`, `HTTPServer` is
1490
- // `httpServer`, `ID` is `id`.
1491
1292
  function camel(key: string): string {
1492
1293
  const words = key.split('_').filter((w) => '' !== w)
1493
1294
  .map((w) => /^[A-Z]+$/.test(w) ? w.toLowerCase() : w)
@@ -1496,12 +1297,6 @@ function camel(key: string): string {
1496
1297
  words.slice(1).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join('')
1497
1298
  }
1498
1299
 
1499
- // D3 (§4.2): a shape written twice can drift, and an alias names it
1500
- // once. Every map or list whose shape recurs in the file, and whose
1501
- // shape is REPEAT_MIN_WIDTH or wider, is reported once, at its first
1502
- // site, with the count and the other sites; the naming is the
1503
- // author's. A repeat inside a repeat is the outer one's: the walk does
1504
- // not descend into a shape it reports.
1505
1300
  function repeats(nodes: Node[], text: string, out: LintFinding[]): void {
1506
1301
  const counts = new Map<string, number>()
1507
1302
  const tally = (node: Node): void => {
@@ -1537,9 +1332,6 @@ function repeats(nodes: Node[], text: string, out: LintFinding[]): void {
1537
1332
  }
1538
1333
  }
1539
1334
 
1540
- // A node's shape: its spelling with the layout, the comments and, for
1541
- // a map, the order of its entries taken out, so that two spellings of
1542
- // one value are one shape, as they are one canon.
1543
1335
  function shape(node: Node): string {
1544
1336
  switch (node.t) {
1545
1337
  case 'map':
@@ -1581,15 +1373,6 @@ function sameDocument(root: any, after: string): boolean {
1581
1373
  return undefined === p.errors && root.canon === p.root.canon
1582
1374
  }
1583
1375
 
1584
- // The check of a lawful rewrite: the spelling before and the spelling
1585
- // after, evaluated in isolation, come to the same canon, the same
1586
- // kinds of failure, and the same outcome of generation (§7.3). Local,
1587
- // so it needs no include and no capability, and it applies whether or
1588
- // not the document as a whole evaluates. The kinds, not the count: how
1589
- // often one unresolved reference is reported depends on the order the
1590
- // meet took. Generation too, because the engine generates from more
1591
- // than the canon: a meet of maps with a nil member has refused a key
1592
- // the same map written once generates.
1593
1376
  function sameByMeet(before: string, after: string): boolean {
1594
1377
  return meetOf(before) === meetOf(after)
1595
1378
  }
@@ -1636,12 +1419,6 @@ function checkFinding(path: string | undefined, expected: string, actual: string
1636
1419
  }
1637
1420
  }
1638
1421
 
1639
- // THE TARGET'S OWN LINES ARE HELD ON LINES OF THEIR OWN (§3.14). The
1640
- // desugaring is line for line, so a line of output is known by the
1641
- // offset it begins at, and the node beginning there -- the quoted
1642
- // string the desugaring wrote -- is marked. From there on it has no
1643
- // one-line form, so every container holding it opens, and no two lines
1644
- // of the generated file are ever packed onto one.
1645
1422
  function holdOutput(nodes: Node[], at: Set<number>): void {
1646
1423
  for (const node of nodes) {
1647
1424
  if (undefined !== node.at && at.has(node.at)) {
@@ -1673,11 +1450,6 @@ function outputAt(doc: string, flags: boolean[]): Set<number> {
1673
1450
  }
1674
1451
 
1675
1452
 
1676
- // A finding's column in the TEMPLATE rather than in the document it
1677
- // carries (§3.14): the marker and its one space stand before the aontu
1678
- // on every line the resugaring writes. Every finding is on such a
1679
- // line -- the two rules point at a key or at a container, and a line of
1680
- // output is a bare string, which is neither.
1681
1453
  function shiftFindings(findings: LintFinding[], mark: string | undefined): LintFinding[] {
1682
1454
  return undefined === mark ? findings :
1683
1455
  findings.map((f) => ({ ...f, col: f.col + mark.length + 1 }))
@@ -1689,9 +1461,6 @@ function shiftFindings(findings: LintFinding[], mark: string | undefined): LintF
1689
1461
  // what `--check` and `--list` report.
1690
1462
  export function format(src: string, opts?: FormatOptions, hooks?: FormatHooks): FormatReport {
1691
1463
  const text = lf(src)
1692
- // A GENERATOR IS FORMATTED AS THE DOCUMENT IT CARRIES (§3.14): the
1693
- // template surface's two transforms stand either side of the
1694
- // formatter, and between them is what happens to any other document.
1695
1464
  const mark = opts?.template
1696
1465
  const doc = undefined === mark ? text : desugarTemplate(text, mark)
1697
1466
  const toks: Tok[] = []
@@ -1726,9 +1495,6 @@ export function format(src: string, opts?: FormatOptions, hooks?: FormatHooks):
1726
1495
  }
1727
1496
 
1728
1497
 
1729
- // ---------------------------------------------------------------------
1730
- // The unified diff of `--diff`
1731
-
1732
1498
  // A patience diff: lines unique to both sides, in order, are the
1733
1499
  // anchors, and the gaps between them recurse. Not always the shortest
1734
1500
  // edit script, but linear in space, and the same script from both
@@ -1736,11 +1502,6 @@ export function format(src: string, opts?: FormatOptions, hooks?: FormatHooks):
1736
1502
 
1737
1503
  type Edit = { op: ' ' | '-' | '+', text: string }
1738
1504
 
1739
- // The lines of a text, with a marker on the last when the text does
1740
- // not end in a newline: such a line never equals its
1741
- // newline-terminated twin, which is how the diff reports the
1742
- // difference, and the marker is rendered as diff renders it. NUL,
1743
- // which no source line ends in.
1744
1505
  const NO_NEWLINE = String.fromCharCode(0)
1745
1506
 
1746
1507
  function textLines(text: string): string[] {
@@ -1846,8 +1607,6 @@ function patience(
1846
1607
  }
1847
1608
  }
1848
1609
 
1849
- // The diff in unified format, three lines of context, the file named
1850
- // on both sides. Empty when the texts are the same.
1851
1610
  export function unifiedDiff(name: string, before: string, after: string): string {
1852
1611
  const a = textLines(before)
1853
1612
  const b = textLines(after)
@@ -1879,8 +1638,6 @@ export function unifiedDiff(name: string, before: string, after: string): string
1879
1638
  for (const [s, e] of hunks) {
1880
1639
  const from = Math.max(s - 3, 0)
1881
1640
  const to = Math.min(e + 4, edits.length)
1882
- // Everything between two hunks is context -- a change would have
1883
- // opened a hunk -- so both sides advance together.
1884
1641
  for (; next < from; next++) {
1885
1642
  ai++
1886
1643
  bi++
package/src/grammar.ts ADDED
@@ -0,0 +1,72 @@
1
+ /* Copyright (c) 2026 Richard Rodger, MIT License */
2
+
3
+ // The grammar seam, ADR-033. Twin of go/grammar.go.
4
+
5
+ import { Tabnas } from '@tabnas/parser'
6
+ import { abnf } from '@tabnas/abnf'
7
+
8
+ import { AontuContext } from './ctx'
9
+
10
+
11
+ // A constant, not a trust knob: this bounds a third party's grammar.
12
+ const PARSE_STEP_MAX = 100000
13
+
14
+ const PARSE_CHECK_EVERY = 100
15
+
16
+
17
+ const cache = new Map<string, [any, string | undefined]>()
18
+
19
+
20
+ const steps = { n: 0 }
21
+
22
+
23
+ function compileGrammar(src: string): [any, string | undefined] {
24
+ const hit = cache.get(src)
25
+ if (undefined !== hit) {
26
+ return hit
27
+ }
28
+
29
+ let out: [any, string | undefined]
30
+ try {
31
+ // Lexing off, or the engine reads `1 . 2 . 3` as `1.2.3`. The hook
32
+ // is installed here because the engine takes it at construction.
33
+ const tn = new Tabnas({
34
+ space: { lex: false },
35
+ parse: {
36
+ budget: {
37
+ checkEveryN: PARSE_CHECK_EVERY,
38
+ onCheck: () => (steps.n += PARSE_CHECK_EVERY) <= PARSE_STEP_MAX,
39
+ }
40
+ }
41
+ })
42
+ tn.use(abnf)
43
+ tn.abnf(src)
44
+ out = [tn, undefined]
45
+ }
46
+ catch (e: any) {
47
+ out = [undefined, String(e?.message ?? e).split('\n')[0]]
48
+ }
49
+
50
+ cache.set(src, out)
51
+ return out
52
+ }
53
+
54
+
55
+ function parseWith(grammar: any, text: string, _ctx: AontuContext):
56
+ [any, string | undefined] {
57
+ steps.n = 0
58
+ try {
59
+ return [grammar.parse(text), undefined]
60
+ }
61
+ catch (e: any) {
62
+ return [undefined, String(e?.message ?? e).split('\n')[0]]
63
+ }
64
+ } /* node:coverage ignore next 8 */
65
+
66
+
67
+ export {
68
+ compileGrammar,
69
+ parseWith,
70
+ PARSE_STEP_MAX,
71
+ PARSE_CHECK_EVERY,
72
+ }