aontu 0.52.1 → 0.54.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 (310) hide show
  1. package/README.md +88 -0
  2. package/bin/aontu-mcp.js +4 -0
  3. package/dist/agentsmd.d.ts +16 -0
  4. package/dist/agentsmd.js +107 -0
  5. package/dist/agentsmd.js.map +1 -0
  6. package/dist/aontu.d.ts +15 -3
  7. package/dist/aontu.js +126 -5
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +45 -1
  10. package/dist/cli.js +2845 -69
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +18 -0
  13. package/dist/ctx.js +44 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/diff.d.ts +22 -0
  16. package/dist/diff.js +141 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/err.d.ts +3 -1
  19. package/dist/err.js +45 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +13 -0
  22. package/dist/graph.js +110 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +145 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +235 -8
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +425 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +955 -110
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +343 -48
  36. package/dist/lsp.js.map +1 -1
  37. package/dist/mcp-server.d.ts +20 -0
  38. package/dist/mcp-server.js +147 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp.d.ts +42 -0
  41. package/dist/mcp.js +941 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +532 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +35 -0
  47. package/dist/mod.js +345 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +49 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +41 -0
  53. package/dist/provenance.js +336 -0
  54. package/dist/provenance.js.map +1 -0
  55. package/dist/query.d.ts +27 -0
  56. package/dist/query.js +294 -0
  57. package/dist/query.js.map +1 -0
  58. package/dist/reach.d.ts +15 -0
  59. package/dist/reach.js +168 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +23 -0
  62. package/dist/relation.js +230 -0
  63. package/dist/relation.js.map +1 -0
  64. package/dist/report-sarif.d.ts +14 -0
  65. package/dist/report-sarif.js +102 -0
  66. package/dist/report-sarif.js.map +1 -0
  67. package/dist/sig.d.ts +25 -0
  68. package/dist/sig.js +277 -0
  69. package/dist/sig.js.map +1 -0
  70. package/dist/sigdecl.d.ts +2 -0
  71. package/dist/sigdecl.js +11 -0
  72. package/dist/sigdecl.js.map +1 -0
  73. package/dist/siggate.d.ts +4 -0
  74. package/dist/siggate.js +90 -0
  75. package/dist/siggate.js.map +1 -0
  76. package/dist/site.d.ts +4 -0
  77. package/dist/site.js +31 -0
  78. package/dist/site.js.map +1 -1
  79. package/dist/std.d.ts +1 -0
  80. package/dist/std.js +129 -0
  81. package/dist/std.js.map +1 -0
  82. package/dist/subsume.d.ts +39 -0
  83. package/dist/subsume.js +573 -0
  84. package/dist/subsume.js.map +1 -0
  85. package/dist/trim.d.ts +19 -0
  86. package/dist/trim.js +155 -0
  87. package/dist/trim.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/dist/type.d.ts +17 -1
  90. package/dist/type.js.map +1 -1
  91. package/dist/unify.d.ts +2 -1
  92. package/dist/unify.js +234 -38
  93. package/dist/unify.js.map +1 -1
  94. package/dist/utility.d.ts +8 -1
  95. package/dist/utility.js +68 -1
  96. package/dist/utility.js.map +1 -1
  97. package/dist/val/AggFuncVal.d.ts +44 -0
  98. package/dist/val/AggFuncVal.js +364 -0
  99. package/dist/val/AggFuncVal.js.map +1 -0
  100. package/dist/val/ArithFuncVal.d.ts +31 -0
  101. package/dist/val/ArithFuncVal.js +62 -0
  102. package/dist/val/ArithFuncVal.js.map +1 -0
  103. package/dist/val/BagVal.d.ts +7 -0
  104. package/dist/val/BagVal.js +150 -11
  105. package/dist/val/BagVal.js.map +1 -1
  106. package/dist/val/CloseFuncVal.js +9 -1
  107. package/dist/val/CloseFuncVal.js.map +1 -1
  108. package/dist/val/ConjunctVal.d.ts +1 -1
  109. package/dist/val/ConjunctVal.js +19 -0
  110. package/dist/val/ConjunctVal.js.map +1 -1
  111. package/dist/val/ConstraintVal.d.ts +7 -1
  112. package/dist/val/ConstraintVal.js +523 -45
  113. package/dist/val/ConstraintVal.js.map +1 -1
  114. package/dist/val/ContainerKindVal.d.ts +35 -0
  115. package/dist/val/ContainerKindVal.js +99 -0
  116. package/dist/val/ContainerKindVal.js.map +1 -0
  117. package/dist/val/CopyFuncVal.d.ts +1 -2
  118. package/dist/val/CopyFuncVal.js.map +1 -1
  119. package/dist/val/Decimal.d.ts +1 -0
  120. package/dist/val/Decimal.js +13 -0
  121. package/dist/val/Decimal.js.map +1 -1
  122. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  123. package/dist/val/DeprecateFuncVal.js +47 -0
  124. package/dist/val/DeprecateFuncVal.js.map +1 -0
  125. package/dist/val/DisjunctVal.d.ts +1 -2
  126. package/dist/val/DisjunctVal.js +269 -53
  127. package/dist/val/DisjunctVal.js.map +1 -1
  128. package/dist/val/EachFuncVal.d.ts +15 -0
  129. package/dist/val/EachFuncVal.js +75 -0
  130. package/dist/val/EachFuncVal.js.map +1 -0
  131. package/dist/val/ExpectVal.js +62 -4
  132. package/dist/val/ExpectVal.js.map +1 -1
  133. package/dist/val/FeatureVal.js +1 -1
  134. package/dist/val/FeatureVal.js.map +1 -1
  135. package/dist/val/FilterFuncVal.d.ts +15 -0
  136. package/dist/val/FilterFuncVal.js +91 -0
  137. package/dist/val/FilterFuncVal.js.map +1 -0
  138. package/dist/val/FuncBaseVal.d.ts +7 -1
  139. package/dist/val/FuncBaseVal.js +183 -2
  140. package/dist/val/FuncBaseVal.js.map +1 -1
  141. package/dist/val/GraphAtomVal.d.ts +39 -0
  142. package/dist/val/GraphAtomVal.js +184 -0
  143. package/dist/val/GraphAtomVal.js.map +1 -0
  144. package/dist/val/HideFuncVal.js.map +1 -1
  145. package/dist/val/JunctionVal.js +24 -1
  146. package/dist/val/JunctionVal.js.map +1 -1
  147. package/dist/val/KeyFuncVal.d.ts +1 -1
  148. package/dist/val/KeyFuncVal.js +38 -30
  149. package/dist/val/KeyFuncVal.js.map +1 -1
  150. package/dist/val/ListVal.js +94 -14
  151. package/dist/val/ListVal.js.map +1 -1
  152. package/dist/val/LowerFuncVal.js.map +1 -1
  153. package/dist/val/MapVal.d.ts +1 -0
  154. package/dist/val/MapVal.js +146 -14
  155. package/dist/val/MapVal.js.map +1 -1
  156. package/dist/val/MatchFuncVal.d.ts +15 -0
  157. package/dist/val/MatchFuncVal.js +107 -0
  158. package/dist/val/MatchFuncVal.js.map +1 -0
  159. package/dist/val/MoveFuncVal.js.map +1 -1
  160. package/dist/val/NilVal.js +24 -0
  161. package/dist/val/NilVal.js.map +1 -1
  162. package/dist/val/OpBaseVal.d.ts +1 -1
  163. package/dist/val/OpBaseVal.js +19 -1
  164. package/dist/val/OpBaseVal.js.map +1 -1
  165. package/dist/val/OpenFuncVal.js +4 -1
  166. package/dist/val/OpenFuncVal.js.map +1 -1
  167. package/dist/val/PackFuncVal.d.ts +15 -0
  168. package/dist/val/PackFuncVal.js +108 -0
  169. package/dist/val/PackFuncVal.js.map +1 -0
  170. package/dist/val/PathFuncVal.d.ts +2 -2
  171. package/dist/val/PathFuncVal.js +75 -16
  172. package/dist/val/PathFuncVal.js.map +1 -1
  173. package/dist/val/PathVal.d.ts +25 -0
  174. package/dist/val/PathVal.js +150 -0
  175. package/dist/val/PathVal.js.map +1 -0
  176. package/dist/val/PlaceVal.d.ts +13 -0
  177. package/dist/val/PlaceVal.js +131 -0
  178. package/dist/val/PlaceVal.js.map +1 -0
  179. package/dist/val/PlusOpVal.d.ts +2 -1
  180. package/dist/val/PlusOpVal.js +61 -37
  181. package/dist/val/PlusOpVal.js.map +1 -1
  182. package/dist/val/PrefFuncVal.js.map +1 -1
  183. package/dist/val/PrefVal.d.ts +4 -2
  184. package/dist/val/PrefVal.js +223 -41
  185. package/dist/val/PrefVal.js.map +1 -1
  186. package/dist/val/RecurseVal.d.ts +19 -0
  187. package/dist/val/RecurseVal.js +217 -0
  188. package/dist/val/RecurseVal.js.map +1 -0
  189. package/dist/val/RefVal.d.ts +3 -2
  190. package/dist/val/RefVal.js +181 -66
  191. package/dist/val/RefVal.js.map +1 -1
  192. package/dist/val/ReferFuncVal.d.ts +59 -0
  193. package/dist/val/ReferFuncVal.js +604 -0
  194. package/dist/val/ReferFuncVal.js.map +1 -0
  195. package/dist/val/ScalarKindVal.d.ts +4 -3
  196. package/dist/val/ScalarKindVal.js +12 -12
  197. package/dist/val/ScalarKindVal.js.map +1 -1
  198. package/dist/val/SuperFuncVal.d.ts +4 -2
  199. package/dist/val/SuperFuncVal.js +118 -14
  200. package/dist/val/SuperFuncVal.js.map +1 -1
  201. package/dist/val/TopVal.d.ts +1 -1
  202. package/dist/val/TopVal.js.map +1 -1
  203. package/dist/val/TypeFuncVal.js.map +1 -1
  204. package/dist/val/UpperFuncVal.js.map +1 -1
  205. package/dist/val/Val.d.ts +7 -1
  206. package/dist/val/Val.js +170 -2
  207. package/dist/val/Val.js.map +1 -1
  208. package/dist/val/VarVal.js.map +1 -1
  209. package/dist/val/arith.d.ts +6 -0
  210. package/dist/val/arith.js +173 -0
  211. package/dist/val/arith.js.map +1 -0
  212. package/dist/vet.d.ts +45 -0
  213. package/dist/vet.js +802 -0
  214. package/dist/vet.js.map +1 -0
  215. package/dist/view.d.ts +85 -0
  216. package/dist/view.js +1948 -0
  217. package/dist/view.js.map +1 -0
  218. package/dist/walk.d.ts +2 -0
  219. package/dist/walk.js +91 -0
  220. package/dist/walk.js.map +1 -0
  221. package/grammar/aontu.gbnf +138 -0
  222. package/grammar/aontu.lark +118 -0
  223. package/grammar/aontu.tmLanguage.json +184 -0
  224. package/package.json +30 -8
  225. package/skill/SKILL.md +37 -0
  226. package/skill/error-codes.md +62 -0
  227. package/skill/examples.md +99 -0
  228. package/skill/grammar-card.md +56 -0
  229. package/src/agentsmd.ts +135 -0
  230. package/src/aontu.ts +166 -5
  231. package/src/cli.ts +3324 -72
  232. package/src/ctx.ts +93 -1
  233. package/src/diff.ts +198 -0
  234. package/src/err.ts +50 -8
  235. package/src/graph.ts +185 -0
  236. package/src/hcanon.ts +167 -0
  237. package/src/hints.ts +291 -8
  238. package/src/jsonschema.ts +552 -0
  239. package/src/lang.ts +1049 -110
  240. package/src/lsp.ts +351 -49
  241. package/src/mcp-server.ts +187 -0
  242. package/src/mcp.ts +1130 -0
  243. package/src/mod-tool.ts +718 -0
  244. package/src/mod.ts +453 -0
  245. package/src/patch.ts +628 -0
  246. package/src/provenance.ts +437 -0
  247. package/src/query.ts +381 -0
  248. package/src/reach.ts +213 -0
  249. package/src/relation.ts +298 -0
  250. package/src/report-sarif.ts +137 -0
  251. package/src/sig.ts +345 -0
  252. package/src/sigdecl.ts +11 -0
  253. package/src/siggate.ts +144 -0
  254. package/src/site.ts +36 -1
  255. package/src/std.ts +131 -0
  256. package/src/subsume.ts +739 -0
  257. package/src/trim.ts +195 -0
  258. package/src/tsconfig.json +10 -4
  259. package/src/type.ts +51 -2
  260. package/src/unify.ts +253 -38
  261. package/src/utility.ts +79 -1
  262. package/src/val/AggFuncVal.ts +546 -0
  263. package/src/val/ArithFuncVal.ts +108 -0
  264. package/src/val/BagVal.ts +158 -12
  265. package/src/val/CloseFuncVal.ts +9 -1
  266. package/src/val/ConjunctVal.ts +20 -0
  267. package/src/val/ConstraintVal.ts +576 -48
  268. package/src/val/ContainerKindVal.ts +158 -0
  269. package/src/val/CopyFuncVal.ts +0 -1
  270. package/src/val/Decimal.ts +15 -0
  271. package/src/val/DeprecateFuncVal.ts +84 -0
  272. package/src/val/DisjunctVal.ts +282 -59
  273. package/src/val/EachFuncVal.ts +133 -0
  274. package/src/val/ExpectVal.ts +65 -8
  275. package/src/val/FeatureVal.ts +1 -1
  276. package/src/val/FilterFuncVal.ts +154 -0
  277. package/src/val/FuncBaseVal.ts +206 -2
  278. package/src/val/GraphAtomVal.ts +264 -0
  279. package/src/val/HideFuncVal.ts +0 -2
  280. package/src/val/JunctionVal.ts +24 -1
  281. package/src/val/KeyFuncVal.ts +39 -35
  282. package/src/val/ListVal.ts +101 -15
  283. package/src/val/LowerFuncVal.ts +0 -1
  284. package/src/val/MapVal.ts +153 -14
  285. package/src/val/MatchFuncVal.ts +176 -0
  286. package/src/val/MoveFuncVal.ts +0 -2
  287. package/src/val/NilVal.ts +25 -0
  288. package/src/val/OpBaseVal.ts +20 -1
  289. package/src/val/OpenFuncVal.ts +4 -2
  290. package/src/val/PackFuncVal.ts +175 -0
  291. package/src/val/PathFuncVal.ts +107 -20
  292. package/src/val/PathVal.ts +221 -0
  293. package/src/val/PlaceVal.ts +193 -0
  294. package/src/val/PlusOpVal.ts +67 -39
  295. package/src/val/PrefFuncVal.ts +0 -1
  296. package/src/val/PrefVal.ts +251 -60
  297. package/src/val/RecurseVal.ts +285 -0
  298. package/src/val/RefVal.ts +183 -78
  299. package/src/val/ReferFuncVal.ts +732 -0
  300. package/src/val/ScalarKindVal.ts +12 -13
  301. package/src/val/SuperFuncVal.ts +137 -13
  302. package/src/val/TopVal.ts +1 -2
  303. package/src/val/TypeFuncVal.ts +0 -2
  304. package/src/val/UpperFuncVal.ts +0 -1
  305. package/src/val/Val.ts +216 -4
  306. package/src/val/VarVal.ts +0 -1
  307. package/src/val/arith.ts +319 -0
  308. package/src/vet.ts +1025 -0
  309. package/src/view.ts +2563 -0
  310. package/src/walk.ts +99 -0
@@ -0,0 +1,175 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // GENERATION: `pack(data, tmpl)` (G8 phase 1,
4
+ // docs/capability-review/g8-generation.md). One child of the result map
5
+ // per child of `data`, each of them `tmpl` cloned at the destination.
6
+ //
7
+ // names: [web, auth, billing]
8
+ // deploy: pack($.names, {
9
+ // image: "acme/" + key() + ":1.4.2"
10
+ // replicas: *2 | integer
11
+ // })
12
+ //
13
+ // WHY A FUNCTION AND NOT SYNTAX. A comprehension would need keywords,
14
+ // a scoping rule, and string interpolation for the computed name; the
15
+ // language already has functions, spread templates, and `key()`, and
16
+ // `pack` is those three composed. A call is also DATA -- a named node
17
+ // in the tree -- so a document that generates stays as analysable as
18
+ // one that does not.
19
+ //
20
+ // KEYS ARE DATA, NOT POSITION. For a list, the strings themselves are
21
+ // the keys (a non-string element is refused: `pack_key`); for a map,
22
+ // its keys. Terraform's `count` keyed by index is the counter-example
23
+ // the review names: reordering the data churns every downstream child.
24
+ // Duplicate keys are not an error -- the colliding children unify,
25
+ // exactly as duplicate source keys already merge.
26
+ //
27
+ // TOTALITY. `pack` iterates a finite, settled bag and cannot call
28
+ // itself: the number of children it can produce is fixed by data that
29
+ // already exists. Nothing here can recurse, which is the guarantee the
30
+ // combinators exist to keep.
31
+
32
+ import type {
33
+ Val,
34
+ ValSpec,
35
+ } from '../type'
36
+
37
+ import {
38
+ AontuContext,
39
+ } from '../ctx'
40
+
41
+ import { unite } from '../unify'
42
+ import { makeNilErr } from '../err'
43
+ import { MapVal } from './MapVal'
44
+ import { FuncBaseVal } from './FuncBaseVal'
45
+ import { repathInstance } from './Val'
46
+ import { fillPlace } from './PlaceVal'
47
+
48
+
49
+ // The keys a data bag names, in the order the result must carry them,
50
+ // or a code naming what is wrong with it. Shared with `each`, which
51
+ // asks the same question of the same argument and answers it with the
52
+ // values rather than the keys.
53
+ function dataKeys(data: Val | undefined): string[] | string {
54
+ const d: any = data
55
+
56
+ if (true === d?.isMap) {
57
+ return Object.keys(d.peg)
58
+ }
59
+
60
+ if (true === d?.isList) {
61
+ const out: string[] = []
62
+ for (const el of d.peg as Val[]) {
63
+ const e: any = el
64
+ // A key is a NAME, and only a string is one. A number would
65
+ // key by position under another spelling, which is the failure
66
+ // mode the data-keyed rule exists to refuse.
67
+ if (true !== e?.isScalar || 'string' !== typeof e.peg) {
68
+ return 'pack_key'
69
+ }
70
+ out.push(e.peg)
71
+ }
72
+ return out
73
+ }
74
+
75
+ return 'pack_data'
76
+ }
77
+
78
+
79
+ class PackFuncVal extends FuncBaseVal {
80
+ isPackFunc = true
81
+
82
+ // THE STAGING RULE (G8 phase 0, see AontuContext.settle). A
83
+ // generator's data argument is not settled merely by being `done`
84
+ // once: a sibling conjunct, an include or a spread can still merge
85
+ // keys into it, and children generated from the half-merged bag
86
+ // would be missing. It fires on the settle pass and only then.
87
+ staged = true
88
+
89
+ constructor(
90
+ spec: ValSpec,
91
+ ctx?: AontuContext
92
+ ) {
93
+ super(spec, ctx)
94
+ }
95
+
96
+
97
+
98
+ funcname() {
99
+ return 'pack'
100
+ }
101
+
102
+
103
+ // The TEMPLATE IS NOT AN ARGUMENT TO DRIVE. Driving it would resolve
104
+ // its `key()` at the CALL SITE -- the one position the template is
105
+ // never used at -- and freeze whatever else in it is path-dependent
106
+ // there too. The data argument is driven by `unify` below instead,
107
+ // one argument by hand rather than all of them by the base.
108
+ prepare(_ctx: AontuContext, _args: Val[]) {
109
+ return null
110
+ }
111
+
112
+
113
+ unify(peer: Val, ctx: AontuContext): Val {
114
+ // ONE argument is driven: the data. The template is not (see
115
+ // prepare above), and driveStagedArgs answers whether the data has
116
+ // settled -- the other half of "ready to fire".
117
+ const ready = this.driveStagedArgs(ctx, 1)
118
+
119
+ if (!ready || !ctx.settle) {
120
+ return this.residuate(peer, ctx)
121
+ }
122
+
123
+ return super.unify(peer, ctx)
124
+ }
125
+
126
+
127
+ resolve(ctx: AontuContext, args: Val[]) {
128
+ const keys = dataKeys(args?.[0])
129
+ if ('string' === typeof keys) {
130
+ return makeNilErr(ctx, keys, this)
131
+ }
132
+
133
+ // Arity is checked at parse (funcArity), so both arguments are here.
134
+ const tmpl: Val = args[1]
135
+ const peg: Record<string, Val> = {}
136
+
137
+ const src: any = args[0]
138
+
139
+ for (const key of keys) {
140
+ const keyctx = ctx.descend(key)
141
+ // THE PLACEHOLDER BINDS THE SOURCE CHILD (G8 phase 3): inside a
142
+ // generator's template `_` is the datum this child is being made
143
+ // FROM. For a map that is the child's value; for a list of names
144
+ // it is the name, which is also the key -- so `_` and `key()`
145
+ // agree there, and differ the moment the data is a map.
146
+ const source: Val = true === src?.isMap ? src.peg[key] : src.peg[keys.indexOf(key)]
147
+ // CLONED, never shared. A spread may share a template that holds
148
+ // nothing path-dependent (MapVal.spreadClone), because a spread
149
+ // CONSTRAINS a child that exists; a generator's template IS the
150
+ // child, and a child is a position. Sharing left every generated
151
+ // child pointing at the template's own parse-time location, which
152
+ // is the position the template is never used at -- visible as the
153
+ // site an error inside a generated child reports.
154
+ //
155
+ // A FULL INSTANCE, to the leaves (`dup`, ADR-005): a bare clone
156
+ // shares the inner structure of any call, preference or
157
+ // operation in the template, so the first child's resolution of
158
+ // a shared key()/ref answered for every child (BUGS.md §8, §9).
159
+ const inst = tmpl.clone(keyctx, { dup: true })
160
+ repathInstance(inst, inst.path)
161
+ const child = fillPlace(inst, source, keyctx)
162
+ peg[key] = undefined === peg[key] ? child :
163
+ unite(keyctx, peg[key], child, 'pack')
164
+ }
165
+
166
+ return new MapVal({ peg }, ctx)
167
+ }
168
+
169
+ } /* node:coverage ignore next 7 */
170
+
171
+
172
+ export {
173
+ dataKeys,
174
+ PackFuncVal,
175
+ }
@@ -1,5 +1,31 @@
1
1
  /* Copyright (c) 2021-2025 Richard Rodger, MIT License */
2
2
 
3
+ // THE PATH CONSTRUCTOR (docs/design/PATHS.0.md). `path(p)` CAPTURES
4
+ // `p` -- the one non-strict argument position in the language: every
5
+ // other call reads its argument's value, this one reads its spelling.
6
+ // `path($.a.b)` is the address `$.a.b` as a first-class value, not
7
+ // the value found there; a plain reference (`$.a.b` alone) stays the
8
+ // embedding it has always been.
9
+ //
10
+ // The capture happens in prepare(), which receives the argument
11
+ // BEFORE the driving loop resolves it -- the same interception this
12
+ // function always used, now kept instead of released: the RefVal's
13
+ // spelling is read off its segments and the RefVal itself is never
14
+ // driven, so no pass ever holds a resolvable reference here.
15
+ //
16
+ // `path()` with no argument is the path KIND (PathKindVal): the
17
+ // vacuous constructor call is the kind, as `{}`-the-unit is to maps
18
+ // -- see ContainerKindVal for the other half of that convention.
19
+ //
20
+ // A string argument is read by the ADDRESS grammar, not resolved as a
21
+ // segment: `path("$.a")` is the same capture `path($.a)` is, and text
22
+ // with no anchor is RELATIVE (`path("a.b")` is `path(.a.b)`, the
23
+ // address the raw spelling captures). A COMPUTED argument -- an
24
+ // expression, a reference to a string -- is the one shape that does
25
+ // evaluate: the driven result converts by the same grammar at
26
+ // resolve, which is what makes an address buildable (`refer() &
27
+ // path("$.customers." + key())`) while a bare string still never IS
28
+ // one (ADR-016): the conversion happens only inside this call.
3
29
 
4
30
  import type {
5
31
  Val,
@@ -12,9 +38,41 @@ import {
12
38
 
13
39
  import { makeNilErr } from '../err'
14
40
 
15
- import { NilVal } from '../val/NilVal'
16
- import { RefVal } from '../val/RefVal'
17
41
  import { FuncBaseVal } from './FuncBaseVal'
42
+ import { PathVal, PathKindVal, parseAddress, textAddress } from './PathVal'
43
+
44
+
45
+ // The address a reference SPELLS, or undefined when its segments
46
+ // cannot spell one (a variable segment, a parent step after the first
47
+ // named segment). Leading `.` entries in a relative ref's peg are
48
+ // parent steps; the spelling is the same grammar refer reads, so one
49
+ // address parser stays the single gate.
50
+ function captureSpelling(rv: any): string | undefined {
51
+ const parts: string[] = []
52
+ let up = 0
53
+ let lead = true
54
+ for (const p of rv.peg) {
55
+ if ('string' !== typeof p) {
56
+ return undefined
57
+ }
58
+ if ('.' === p) {
59
+ if (!lead) {
60
+ return undefined
61
+ }
62
+ up++
63
+ }
64
+ else {
65
+ lead = false
66
+ parts.push(p)
67
+ }
68
+ }
69
+ if (0 === parts.length || (rv.absolute && 0 < up)) {
70
+ return undefined
71
+ }
72
+ return rv.absolute ?
73
+ '$.' + parts.join('.') :
74
+ '.'.repeat(up + 1) + parts.join('.')
75
+ }
18
76
 
19
77
 
20
78
  class PathFuncVal extends FuncBaseVal {
@@ -42,37 +100,66 @@ class PathFuncVal extends FuncBaseVal {
42
100
 
43
101
 
44
102
  prepare(ctx: AontuContext, args: Val[]) {
45
- let arg = args[0]
46
-
47
103
  if (0 === this.prepared) {
48
- // A missing argument (`path()`) must produce an invalid-arg error
49
- // value, as the Go port does -- reading .isScalar off nothing threw
50
- // a TypeError that the unifier could only report as an opaque
51
- // internal error. Same discipline breach, and same fix, as the one
52
- // UpperFuncVal records; path() was missed in that sweep.
104
+ this.prepared++
105
+
106
+ const arg: any = args[0]
107
+
108
+ // The kind form: no argument to capture.
53
109
  if (null == arg) {
54
- arg = makeNilErr(ctx, 'invalid-arg', this)
110
+ return []
55
111
  }
56
- else if (arg.isScalar) {
57
- arg = this.place(new RefVal({ peg: [arg], absolute: false }))
112
+
113
+ // The captured spelling, from a reference's segments or from a
114
+ // string literal read as address text. Both go through
115
+ // parseAddress, so what capture admits and what refer reads
116
+ // cannot drift. Anything else -- an expression, a reference to
117
+ // a string -- is left for the driving loop, and resolve
118
+ // converts the driven result below.
119
+ let spelling: string | undefined
120
+ if (true === arg.isRef) {
121
+ spelling = captureSpelling(arg)
58
122
  }
59
- else if (!arg.isRef) {
60
- arg = makeNilErr(ctx, 'invalid-arg', this)
123
+ else if (true === arg.isScalar && 'string' === typeof arg.peg) {
124
+ spelling = textAddress(arg.peg)
125
+ }
126
+ else {
127
+ return args
61
128
  }
62
- }
63
129
 
64
- args[0] = arg
130
+ if (undefined === spelling || undefined === parseAddress(spelling)) {
131
+ return [makeNilErr(ctx, 'path_address', this, arg)]
132
+ }
65
133
 
66
- this.prepared++
134
+ return [new PathVal({ peg: spelling }, ctx)]
135
+ }
67
136
 
68
137
  return args
69
138
  }
70
139
 
71
140
 
72
141
  resolve(ctx: AontuContext, args: Val[]) {
73
- let out = args[0] ?? makeNilErr(ctx, 'arg', this)
74
-
75
- return out
142
+ if (0 === args.length) {
143
+ const out = new PathKindVal({}, ctx)
144
+ out.site = this.site
145
+ out.path = this.path
146
+ return out
147
+ }
148
+ const arg: any = args[0]
149
+ if (true === arg.isPath || true === arg.isNil) {
150
+ return arg
151
+ }
152
+ // The COMPUTED argument, driven by the loop above: a string
153
+ // converts by the address grammar, exactly as a literal does at
154
+ // capture; anything else was never a path expression.
155
+ if (true === arg.isScalar && 'string' === typeof arg.peg) {
156
+ const spelling = textAddress(arg.peg)
157
+ if (undefined === parseAddress(spelling)) {
158
+ return makeNilErr(ctx, 'path_address', this, arg)
159
+ }
160
+ return new PathVal({ peg: spelling }, ctx)
161
+ }
162
+ return makeNilErr(ctx, 'invalid-arg', this)
76
163
  }
77
164
 
78
165
  } /* node:coverage ignore next 6 */
@@ -0,0 +1,221 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // FIRST-CLASS PATHS (docs/design/PATHS.0.md). A PathVal is the value
4
+ // `path(p)` captures: a tree address as DATA -- the spelling, never
5
+ // the resolution. It is a scalar whose peg is the address string in
6
+ // exactly the grammar `refer` reads (`$.a.b` from the root, `.b` from
7
+ // the sibling scope, one more leading dot per parent step), which is
8
+ // what lets a captured path meet the checking machinery unchanged.
9
+ //
10
+ // Meets are SYNTACTIC, by the PREFIX rule (amended, ADR-016): two
11
+ // path values meet when one spells a prefix of the other -- same
12
+ // anchor, the shorter's segments opening the longer's -- and the
13
+ // result is the LONGER: a path can always be told more precisely.
14
+ // Incomparable spellings refuse as any two unequal scalars do.
15
+ // Resolving during a meet would make the meet depend on the value's
16
+ // position, which is the property the staging machinery exists to
17
+ // quarantine -- resolution stays the business of `refer`, `rel` and
18
+ // the graph.
19
+ //
20
+ // The kind sits UNDER string (ScalarKindVal.KIND_PARENT), so `string`
21
+ // admits a path value and the string constraints keep working; a
22
+ // plain string LITERAL and a path value refuse each other, exactly as
23
+ // the number tower's leaves do. A bare string is NEVER a path
24
+ // (ADR-016): `path("...")` -- the call's own string argument -- is
25
+ // the one conversion the language has.
26
+
27
+ import type {
28
+ Val,
29
+ ValSpec,
30
+ } from '../type'
31
+
32
+ import {
33
+ AontuContext,
34
+ } from '../ctx'
35
+
36
+ import { makeNilErr } from '../err'
37
+
38
+ import { propagateMarks } from '../utility'
39
+
40
+ import { ScalarVal } from './ScalarVal'
41
+ import { ScalarKindVal, Path } from './ScalarKindVal'
42
+
43
+
44
+
45
+ // A segment of a tree path: a map key or a list index. The same
46
+ // grammar the rest of the engine spells keys with, and a leading digit
47
+ // is legitimate because a list index is one.
48
+ const ADDR_SEGMENT = /^[A-Za-z0-9_-]+$/
49
+
50
+ export type Address = {
51
+ // Anchored at the document root (`$.a.b`) rather than at the link's
52
+ // own position (`.a.b`).
53
+ absolute: boolean
54
+ // Parent steps, for a relative address that climbs (`..a` is one).
55
+ up: number
56
+ // The written segments, below the anchor.
57
+ parts: string[]
58
+ }
59
+
60
+
61
+ // The address a string spells, or undefined when it does not spell
62
+ // one. An address is a TREE PATH, in exactly the two spellings a
63
+ // reference uses: `$.services.auth` from the root, `.auth` from the
64
+ // link's own sibling scope. The tree is the only namespace -- which is
65
+ // what makes a model instantiable more than once, each instance
66
+ // resolving its relative links inside itself (ADR-014).
67
+ export function parseAddress(s: string): Address | undefined {
68
+ if ('$' === s) {
69
+ // The whole document is not a relation's target: an address must
70
+ // name something with a position to be written back into.
71
+ return undefined
72
+ }
73
+ if (s.startsWith('$.')) {
74
+ const parts = s.slice(2).split('.')
75
+ for (const seg of parts) {
76
+ if (!ADDR_SEGMENT.test(seg)) {
77
+ return undefined
78
+ }
79
+ }
80
+ return { absolute: true, up: 0, parts }
81
+ }
82
+ if (!s.startsWith('.')) {
83
+ return undefined
84
+ }
85
+ // A relative address: the leading dot anchors it at the sibling
86
+ // scope, and every FURTHER leading dot is one step up from there --
87
+ // the same reduction a relative reference's `.` segments perform.
88
+ let up = 0
89
+ let rest = s.slice(1)
90
+ while (rest.startsWith('.')) {
91
+ up++
92
+ rest = rest.slice(1)
93
+ }
94
+ if ('' === rest) {
95
+ return undefined
96
+ }
97
+ const parts = rest.split('.')
98
+ for (const seg of parts) {
99
+ if (!ADDR_SEGMENT.test(seg)) {
100
+ return undefined
101
+ }
102
+ }
103
+ return { absolute: false, up, parts }
104
+ }
105
+
106
+
107
+ // The spelling string TEXT converts by, inside a `path(...)` call:
108
+ // text that carries no anchor is RELATIVE (`"a.b"` is the address
109
+ // `.a.b`), matching the raw form (`path(a.b)` captures `.a.b`). Only
110
+ // the anchor is supplied -- the result still has to parse, so
111
+ // malformed text (`""`, `"a..b"`, a bad `$` spelling) refuses as
112
+ // before. The prefix is not applied to text that claims an anchor:
113
+ // `"$x"` is a broken absolute address, not a relative one.
114
+ export function textAddress(s: string): string {
115
+ return ('$' === s[0] || '.' === s[0]) ? s : '.' + s
116
+ }
117
+
118
+
119
+ // The LONGER of two addresses when one spells a prefix of the other
120
+ // (docs/design/PATHS.0.md, amended): same anchor -- absolute or the
121
+ // same number of parent steps -- and the shorter's segments open the
122
+ // longer's. The meet of two path values, and of a refer's address
123
+ // with a later path peer: a path can always be told more precisely,
124
+ // and the more precise spelling is the result. Undefined when the two
125
+ // are not comparable, which refuses as any two unequal scalars do.
126
+ // Both arguments must already be valid addresses: every caller hands
127
+ // over a PathVal peg or a refer addrsrc, and both are validated at
128
+ // capture or conversion -- the same trust `unify`'s own address arm
129
+ // extends (`parseAddress(p.peg) as Address`).
130
+ export function prefixMeet(a: string, b: string): string | undefined {
131
+ const pa = parseAddress(a) as Address
132
+ const pb = parseAddress(b) as Address
133
+ if (pa.absolute !== pb.absolute || pa.up !== pb.up) {
134
+ return undefined
135
+ }
136
+ const short = pa.parts.length <= pb.parts.length ? pa : pb
137
+ const long = short === pa ? pb : pa
138
+ for (let i = 0; i < short.parts.length; i++) {
139
+ if (short.parts[i] !== long.parts[i]) {
140
+ return undefined
141
+ }
142
+ }
143
+ return short === pa ? b : a
144
+ }
145
+
146
+
147
+
148
+ class PathVal extends ScalarVal {
149
+ isPath = true
150
+
151
+ constructor(
152
+ spec: ValSpec,
153
+ ctx?: AontuContext
154
+ ) {
155
+ super({ peg: spec.peg, kind: Path }, ctx)
156
+ }
157
+
158
+ // Two path values meet by the PREFIX rule (ADR-016): the longer
159
+ // when one opens the other, refusal otherwise. Exactly equal pegs
160
+ // are absorbed by unite's fast path before this runs, so the arm
161
+ // sees the unequal pairs; the winner carries both sides' marks, as
162
+ // the equal-scalar arm has always ratcheted them.
163
+ unify(peer: Val, ctx: AontuContext): Val {
164
+ const p: any = peer
165
+ if (true === p.isPath) {
166
+ const merged = prefixMeet(this.peg, p.peg)
167
+ if (undefined === merged) {
168
+ return makeNilErr(ctx, 'scalar_value', this, peer)
169
+ }
170
+ const out = merged === this.peg ? this : p
171
+ const other = out === this ? p : this
172
+ propagateMarks(other, out)
173
+ return out
174
+ }
175
+ return super.unify(peer, ctx)
176
+ }
177
+
178
+ // Reparses to the same VALUE: the call form is the literal syntax
179
+ // for this kind, so canon renders it back. The peg is already the
180
+ // address grammar, which the argument grammar also accepts.
181
+ get canon() {
182
+ return 'path(' + this.peg + ')'
183
+ }
184
+
185
+ // The super() ladder lifts a path value to its own kind, and the
186
+ // kind must render as `path()` -- the bare word `path` is an
187
+ // ordinary string. ScalarVal.superior would mint the plain
188
+ // ScalarKindVal, whose canon is the bare word.
189
+ superior() {
190
+ return this.place(new PathKindVal({}))
191
+ }
192
+
193
+ } /* node:coverage ignore next 4 */
194
+
195
+
196
+ // The path KIND, `path()`: admits every path value and defaults to
197
+ // nothing, as `string` does. It does NOT promote (ADR-016): a bare
198
+ // string meeting the kind refuses through the generic kind ladder,
199
+ // exactly as `integer & "x"` does -- `path("...")` is the one string
200
+ // conversion, and it happens at the call.
201
+ class PathKindVal extends ScalarKindVal {
202
+ isPathKind = true
203
+
204
+ constructor(
205
+ spec: ValSpec,
206
+ ctx?: AontuContext
207
+ ) {
208
+ super({ ...spec, peg: Path }, ctx)
209
+ }
210
+
211
+ get canon() {
212
+ return 'path()'
213
+ }
214
+
215
+ } /* node:coverage ignore next 6 */
216
+
217
+
218
+ export {
219
+ PathVal,
220
+ PathKindVal,
221
+ }