aontu 0.57.0 → 0.59.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 (132) hide show
  1. package/README.md +2 -2
  2. package/dist/agentsmd.js +1 -1
  3. package/dist/alias.d.ts +3 -0
  4. package/dist/alias.js +59 -0
  5. package/dist/alias.js.map +1 -0
  6. package/dist/aontu.d.ts +4 -2
  7. package/dist/aontu.js +12 -3
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +3 -1
  10. package/dist/cli.js +486 -4
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +4 -0
  13. package/dist/ctx.js +3 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/err.js +6 -3
  16. package/dist/err.js.map +1 -1
  17. package/dist/format.js +8 -2
  18. package/dist/format.js.map +1 -1
  19. package/dist/hints.js +40 -9
  20. package/dist/hints.js.map +1 -1
  21. package/dist/lang.js +409 -58
  22. package/dist/lang.js.map +1 -1
  23. package/dist/lower.d.ts +20 -0
  24. package/dist/lower.js +575 -0
  25. package/dist/lower.js.map +1 -0
  26. package/dist/lsp.d.ts +1 -1
  27. package/dist/lsp.js +1 -1
  28. package/dist/lsp.js.map +1 -1
  29. package/dist/mcp-server.js +1 -1
  30. package/dist/mcp.d.ts +1 -0
  31. package/dist/mcp.js +40 -3
  32. package/dist/mcp.js.map +1 -1
  33. package/dist/render.d.ts +53 -0
  34. package/dist/render.js +542 -0
  35. package/dist/render.js.map +1 -0
  36. package/dist/sigdecl.js +1 -1
  37. package/dist/sigdecl.js.map +1 -1
  38. package/dist/std.d.ts +2 -0
  39. package/dist/std.js +498 -2
  40. package/dist/std.js.map +1 -1
  41. package/dist/template.d.ts +5 -0
  42. package/dist/template.js +257 -0
  43. package/dist/template.js.map +1 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/dist/type.d.ts +2 -0
  46. package/dist/type.js.map +1 -1
  47. package/dist/unify.js +43 -0
  48. package/dist/unify.js.map +1 -1
  49. package/dist/val/AggFuncVal.d.ts +1 -1
  50. package/dist/val/AggFuncVal.js +10 -21
  51. package/dist/val/AggFuncVal.js.map +1 -1
  52. package/dist/val/BagVal.js +1 -1
  53. package/dist/val/BagVal.js.map +1 -1
  54. package/dist/val/ConstraintVal.js +1 -1
  55. package/dist/val/DisjunctVal.js +33 -2
  56. package/dist/val/DisjunctVal.js.map +1 -1
  57. package/dist/val/EachFuncVal.d.ts +1 -1
  58. package/dist/val/EachFuncVal.js +8 -13
  59. package/dist/val/EachFuncVal.js.map +1 -1
  60. package/dist/val/EmitFuncVal.d.ts +23 -4
  61. package/dist/val/EmitFuncVal.js +298 -28
  62. package/dist/val/EmitFuncVal.js.map +1 -1
  63. package/dist/val/FilterFuncVal.js +8 -4
  64. package/dist/val/FilterFuncVal.js.map +1 -1
  65. package/dist/val/FormFuncVal.d.ts +14 -0
  66. package/dist/val/FormFuncVal.js +55 -0
  67. package/dist/val/FormFuncVal.js.map +1 -0
  68. package/dist/val/FuncBaseVal.js +36 -13
  69. package/dist/val/FuncBaseVal.js.map +1 -1
  70. package/dist/val/ListVal.js +9 -1
  71. package/dist/val/ListVal.js.map +1 -1
  72. package/dist/val/MapVal.d.ts +2 -1
  73. package/dist/val/MapVal.js +2 -1
  74. package/dist/val/MapVal.js.map +1 -1
  75. package/dist/val/PackFuncVal.d.ts +1 -1
  76. package/dist/val/PackFuncVal.js +22 -18
  77. package/dist/val/PackFuncVal.js.map +1 -1
  78. package/dist/val/PlaceVal.js +7 -2
  79. package/dist/val/PlaceVal.js.map +1 -1
  80. package/dist/val/RefVal.d.ts +3 -0
  81. package/dist/val/RefVal.js +181 -18
  82. package/dist/val/RefVal.js.map +1 -1
  83. package/dist/val/Val.d.ts +8 -1
  84. package/dist/val/Val.js +39 -1
  85. package/dist/val/Val.js.map +1 -1
  86. package/dist/val/members.d.ts +9 -0
  87. package/dist/val/members.js +52 -0
  88. package/dist/val/members.js.map +1 -0
  89. package/grammar/aontu.abnf +1 -1
  90. package/grammar/aontu.gbnf +1 -1
  91. package/grammar/aontu.lark +1 -1
  92. package/grammar/aontu.tmLanguage.json +1 -1
  93. package/package.json +1 -1
  94. package/skill/SKILL.md +4 -4
  95. package/skill/error-codes.md +1 -1
  96. package/skill/examples.md +1 -1
  97. package/skill/grammar-card.md +1 -1
  98. package/src/agentsmd.ts +1 -1
  99. package/src/alias.ts +112 -0
  100. package/src/aontu.ts +20 -2
  101. package/src/cli.ts +528 -4
  102. package/src/ctx.ts +18 -0
  103. package/src/err.ts +6 -3
  104. package/src/format.ts +12 -2
  105. package/src/hints.ts +47 -9
  106. package/src/lang.ts +453 -63
  107. package/src/lower.ts +636 -0
  108. package/src/lsp.ts +1 -1
  109. package/src/mcp-server.ts +1 -1
  110. package/src/mcp.ts +43 -4
  111. package/src/render.ts +727 -0
  112. package/src/sigdecl.ts +1 -1
  113. package/src/std.ts +506 -1
  114. package/src/template.ts +291 -0
  115. package/src/type.ts +10 -1
  116. package/src/unify.ts +47 -0
  117. package/src/val/AggFuncVal.ts +10 -21
  118. package/src/val/BagVal.ts +1 -1
  119. package/src/val/ConstraintVal.ts +1 -1
  120. package/src/val/DisjunctVal.ts +33 -2
  121. package/src/val/EachFuncVal.ts +8 -16
  122. package/src/val/EmitFuncVal.ts +376 -39
  123. package/src/val/FilterFuncVal.ts +11 -4
  124. package/src/val/FormFuncVal.ts +119 -0
  125. package/src/val/FuncBaseVal.ts +36 -13
  126. package/src/val/ListVal.ts +9 -1
  127. package/src/val/MapVal.ts +3 -2
  128. package/src/val/PackFuncVal.ts +23 -19
  129. package/src/val/PlaceVal.ts +7 -2
  130. package/src/val/RefVal.ts +193 -20
  131. package/src/val/Val.ts +75 -4
  132. package/src/val/members.ts +86 -0
package/src/lower.ts ADDED
@@ -0,0 +1,636 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // THE DECLARATION LOWERING (docs/design/RENDER.0.md D5, D6, P5): the
4
+ // one place in the renderer where "a new language is data" does not
5
+ // hold. A `%record` becoming `export interface X {` or `type X struct
6
+ // {` is a per-language-family function here, selected by the profile's
7
+ // `lowering` field and parameterised by the profile's data -- the
8
+ // indent, the comment forms, the string quote and its escape table, the
9
+ // identifier rules and the type forms. Two families ship, `typescript`
10
+ // and `go`; a third language with declarations is a change here, in
11
+ // both ports (go/lower.go is the twin, function for function).
12
+ //
13
+ // Everything produced is PIECES -- lines at a depth, blanks -- that the
14
+ // fragment fold then turns into bytes, so a lowered declaration and a
15
+ // fragment take the same path to the file: one indent rule, one
16
+ // terminator rule, nothing trimmed.
17
+ //
18
+ // THE PARITY GUARDS (G9 section 3), each a rule both ports keep: case
19
+ // conversion is ASCII-only, and a code point at or above U+0080 rides
20
+ // into the current word verbatim, never split and never converted;
21
+ // string escaping is one pass, per code point, through the profile's
22
+ // table, keyed by decimal code point, with a control character the
23
+ // table does not name spelled as a \u escape; a reserved word is
24
+ // matched by exact equality on the converted name; parenthesisation is
25
+ // by `prec`/`childPrec`; numbers go through `exactJSON`; nothing here
26
+ // sorts, and nothing iterates a map.
27
+
28
+ import { exactJSON } from './exactjson'
29
+ import type { RenderLoss } from './render'
30
+
31
+
32
+ // The lowering's view of one unit: the merged profile, the family it
33
+ // names, and the report the losses go to.
34
+ export type LowerCtx = {
35
+ profile: any
36
+ family: string
37
+ unit: string
38
+ lossy: RenderLoss[]
39
+ }
40
+
41
+
42
+ function ln(at: number, text: string): any {
43
+ return { k: 'line', at, of: [text] }
44
+ }
45
+
46
+ const BLANK = { k: 'blank' }
47
+
48
+
49
+ // --- identifiers -------------------------------------------------------
50
+
51
+ function isUpper(c: string): boolean {
52
+ return 'A' <= c && c <= 'Z'
53
+ }
54
+
55
+ function isLower(c: string): boolean {
56
+ return 'a' <= c && c <= 'z'
57
+ }
58
+
59
+ function isDigit(c: string): boolean {
60
+ return '0' <= c && c <= '9'
61
+ }
62
+
63
+ function isASCII(c: string): boolean {
64
+ return (c.codePointAt(0) as number) < 0x80
65
+ }
66
+
67
+ // THE ASCII WORD SPLITTER. Words break at `_`, `-` and space, at a
68
+ // lower-to-upper boundary (`creditLimit`), at a letter-to-digit
69
+ // boundary in either direction (`utf8String`), and before the last
70
+ // capital of a capital run that a lower case letter follows
71
+ // (`HTTPServer` is HTTP, Server). A code point at or above U+0080
72
+ // rides into the current word and never starts, ends or converts one.
73
+ export function splitWords(name: string): string[] {
74
+ const words: string[] = []
75
+ let cur = ''
76
+ const chars = Array.from(name)
77
+ for (let i = 0; i < chars.length; i++) {
78
+ const c = chars[i]
79
+ if ('_' === c || '-' === c || ' ' === c) {
80
+ if ('' !== cur) {
81
+ words.push(cur)
82
+ }
83
+ cur = ''
84
+ continue
85
+ }
86
+ if ('' !== cur) {
87
+ const prev = chars[i - 1]
88
+ const next = chars[i + 1]
89
+ const boundary = isASCII(c) && isASCII(prev) && (
90
+ (isUpper(c) && (isLower(prev) || isDigit(prev))) ||
91
+ (isDigit(c) !== isDigit(prev)) ||
92
+ (isUpper(c) && isUpper(prev) && undefined !== next && isLower(next)))
93
+ if (boundary) {
94
+ words.push(cur)
95
+ cur = ''
96
+ }
97
+ }
98
+ cur += c
99
+ }
100
+ if ('' !== cur) {
101
+ words.push(cur)
102
+ }
103
+ return words
104
+ }
105
+
106
+ function lowerASCII(s: string): string {
107
+ return Array.from(s).map((c) =>
108
+ isUpper(c) ? String.fromCharCode(c.charCodeAt(0) + 32) : c).join('')
109
+ }
110
+
111
+ function upperASCII(s: string): string {
112
+ return Array.from(s).map((c) =>
113
+ isLower(c) ? String.fromCharCode(c.charCodeAt(0) - 32) : c).join('')
114
+ }
115
+
116
+ // A word capitalised: the acronym set wins, so `id` is `ID` under a
117
+ // profile that lists it and `Id` under one that does not.
118
+ function capitalise(word: string, acronyms: string[]): string {
119
+ const low = lowerASCII(word)
120
+ for (const a of acronyms) {
121
+ if (lowerASCII(a) === low) {
122
+ return a
123
+ }
124
+ }
125
+ const chars = Array.from(low)
126
+ return upperASCII(chars[0]) + chars.slice(1).join('')
127
+ }
128
+
129
+ // THE CASE STYLES of aontu:profile's %case, over the words.
130
+ export function caseName(name: string, style: string, acronyms: string[]): string {
131
+ if ('as-is' === style) {
132
+ return name
133
+ }
134
+ const words = splitWords(name)
135
+ if (0 === words.length) {
136
+ return name
137
+ }
138
+ if ('snake' === style) {
139
+ return words.map(lowerASCII).join('_')
140
+ }
141
+ if ('screaming' === style) {
142
+ return words.map(upperASCII).join('_')
143
+ }
144
+ if ('kebab' === style) {
145
+ return words.map(lowerASCII).join('-')
146
+ }
147
+ const caps = words.map((w) => capitalise(w, acronyms))
148
+ if ('pascal' === style) {
149
+ return caps.join('')
150
+ }
151
+ // camel: the first word lower, and never an acronym.
152
+ return lowerASCII(words[0]) + caps.slice(1).join('')
153
+ }
154
+
155
+ // An identifier in a ROLE (record, field, enum, member, const, func,
156
+ // param, alias): the profile's case style for the role, then -- for
157
+ // the names that become bare identifiers, declarations and parameters
158
+ // -- the reserved-word rule on the converted name, exact equality, so
159
+ // Go's `Type` is not `type`. A reserved name is renamed with a trailing
160
+ // underscore and the report says so, since code that named the
161
+ // original will not compile against the rename. A field is a property,
162
+ // and a property may spell what its target's string form admits.
163
+ export function ident(
164
+ name: string, role: string, ctx: LowerCtx, path: string, bare: boolean
165
+ ): string {
166
+ const rules = ctx.profile.ident ?? {}
167
+ const style: string = rules.case?.[role] ?? 'as-is'
168
+ const acronyms: string[] = rules.acronyms ?? []
169
+ let out = caseName(name, style, acronyms)
170
+ if (bare && (rules.reserved ?? []).includes(out)) {
171
+ ctx.lossy.push({
172
+ unit: ctx.unit, path, tier: 1, construct: 'reserved',
173
+ reason: out + ' is reserved in ' + ctx.profile.lang + '; renamed ' + out + '_',
174
+ })
175
+ out += '_'
176
+ }
177
+ return out
178
+ }
179
+
180
+
181
+ // --- literals ----------------------------------------------------------
182
+
183
+ function hex4(n: number): string {
184
+ return '\\u' + n.toString(16).padStart(4, '0')
185
+ }
186
+
187
+ // A string literal: the profile's quote, and one pass over the code
188
+ // points through its escape table. A control character the table does
189
+ // not name is a \u escape; so are the quote and the backslash when the
190
+ // table is silent about them; everything else, non-ASCII and astral
191
+ // included, is verbatim.
192
+ export function quote(s: string, profile: any): string {
193
+ const q: string = profile.str?.quote ?? '"'
194
+ const table: any = profile.str?.escape ?? {}
195
+ let out = q
196
+ for (const c of Array.from(s)) {
197
+ const cp = c.codePointAt(0) as number
198
+ const esc = table[String(cp)]
199
+ if ('string' === typeof esc) {
200
+ out += esc
201
+ }
202
+ else if (cp < 0x20 || 0x7f === cp || c === q || '\\' === c) {
203
+ out += hex4(cp)
204
+ }
205
+ else {
206
+ out += c
207
+ }
208
+ }
209
+ return out + q
210
+ }
211
+
212
+ // A literal value as the target spells it: a string quoted, a number
213
+ // through exactJSON (no second number formatter), a boolean as is, and
214
+ // null as the family's null.
215
+ export function literal(v: any, ctx: LowerCtx): string {
216
+ if ('string' === typeof v) {
217
+ return quote(v, ctx.profile)
218
+ }
219
+ if (null === v) {
220
+ return 'go' === ctx.family ? 'nil' : 'null'
221
+ }
222
+ if ('boolean' === typeof v) {
223
+ return v ? 'true' : 'false'
224
+ }
225
+ return exactJSON(v)
226
+ }
227
+
228
+
229
+ // --- types -------------------------------------------------------------
230
+
231
+ type Expr = { text: string, prec: number }
232
+
233
+ function form(ctx: LowerCtx, name: string): any {
234
+ return ctx.profile.types?.[name] ?? { open: '', close: '', prec: 9, childPrec: 0 }
235
+ }
236
+
237
+ // An inner expression under a form, in parens when its precedence is
238
+ // below the form's childPrec: TypeScript's `(string | null)[]`.
239
+ //
240
+ // A FORM ALWAYS CARRIES ITS childPrec, so there is nothing to fall back
241
+ // to: the profile vocabulary declares it `*0`, so every type form of a
242
+ // profile met with `aontu:profile` has one, and `form()`'s own fallback
243
+ // spells it. The default lives in the model, which is the point of
244
+ // putting it there.
245
+ function under(inner: Expr, f: any): string {
246
+ return inner.prec < f.childPrec ? '(' + inner.text + ')' : inner.text
247
+ }
248
+
249
+ function loss(ctx: LowerCtx, path: string, construct: string, reason: string): void {
250
+ ctx.lossy.push({ unit: ctx.unit, path, tier: 1, construct, reason })
251
+ }
252
+
253
+ // The primitive kind a literal set shares, for a family with no
254
+ // literal type: strings, integers, floats, booleans, null, or any.
255
+ function litPrim(of: any[]): string {
256
+ const kinds = of.map((v) => null === v ? 'null' : typeof v)
257
+ const first = kinds[0]
258
+ if (!kinds.every((k) => k === first)) {
259
+ return 'any'
260
+ }
261
+ if ('number' === first) {
262
+ return of.every((v) => Number.isInteger(v)) ? 'int' : 'float'
263
+ }
264
+ return 'string' === first ? 'string' : 'boolean' === first ? 'bool' : 'null'
265
+ }
266
+
267
+ function prim(ctx: LowerCtx, name: string): string {
268
+ return ctx.profile.types?.prim?.[name] ?? name
269
+ }
270
+
271
+ // THE TYPE EXPRESSION, from the profile's type forms (D6): an atom has
272
+ // prec 9; a form is `open + inner + close`, its precedence its own, its
273
+ // inner parenthesised below childPrec. A `text` leaf is verbatim
274
+ // target syntax and tier 3, as a text declaration is.
275
+ export function typeExpr(t: any, ctx: LowerCtx, path: string): Expr {
276
+ const k: string = t.k
277
+ if ('prim' === k) {
278
+ return { text: prim(ctx, t.prim), prec: 9 }
279
+ }
280
+ if ('ref' === k) {
281
+ return { text: ident(t.name, 'record', ctx, path, false), prec: 9 }
282
+ }
283
+ if ('text' === k) {
284
+ ctx.lossy.push({
285
+ unit: ctx.unit, path, tier: 3, construct: 'text',
286
+ reason: 'verbatim ' + t.lang + ': the renderer checks nothing in it',
287
+ })
288
+ return { text: t.text, prec: 9 }
289
+ }
290
+ if ('list' === k) {
291
+ const f = form(ctx, 'list')
292
+ return { text: f.open + under(typeExpr(t.of, ctx, path + '.of'), f) + f.close, prec: f.prec }
293
+ }
294
+ if ('map' === k) {
295
+ const f = form(ctx, 'map')
296
+ const key = under(typeExpr(t.key, ctx, path + '.key'), f)
297
+ const of = under(typeExpr(t.of, ctx, path + '.of'), f)
298
+ // The two families spell a map differently between the key and
299
+ // the value: `map[K]V` and `Record<K, V>`.
300
+ const sep = 'go' === ctx.family ? ']' : ', '
301
+ return { text: f.open + key + sep + of + f.close, prec: f.prec }
302
+ }
303
+ if ('opt' === k) {
304
+ const f = form(ctx, 'opt')
305
+ return { text: f.open + under(typeExpr(t.of, ctx, path + '.of'), f) + f.close, prec: f.prec }
306
+ }
307
+ if ('union' === k) {
308
+ if ('go' === ctx.family) {
309
+ loss(ctx, path, 'union', 'go has no union type: any')
310
+ return { text: prim(ctx, 'any'), prec: 9 }
311
+ }
312
+ const f = form(ctx, 'union')
313
+ const members = t.of.map((m: any, i: number) =>
314
+ under(typeExpr(m, ctx, path + '.of.' + i), f))
315
+ return { text: f.open + members.join(' | ') + f.close, prec: f.prec }
316
+ }
317
+ // lit
318
+ if ('go' === ctx.family) {
319
+ const p = litPrim(t.of)
320
+ loss(ctx, path, 'lit', 'go has no literal type: ' + p)
321
+ return { text: prim(ctx, p), prec: 9 }
322
+ }
323
+ const f = form(ctx, 'lit')
324
+ const lits = t.of.map((v: any) => literal(v, ctx))
325
+ return { text: f.open + lits.join(' | ') + f.close, prec: f.prec }
326
+ }
327
+
328
+
329
+ // --- comments ----------------------------------------------------------
330
+
331
+ // A doc comment as pieces at a depth: the profile's doc form -- open,
332
+ // a prefix per line, close -- and the line form when a profile has no
333
+ // doc form. A deprecation is the last line, in the family's spelling.
334
+ function doc(d: any, at: number, ctx: LowerCtx): any[] {
335
+ if (null == d) {
336
+ return []
337
+ }
338
+ const forms = ctx.profile.comment ?? {}
339
+ const f = forms.doc ?? forms.line ?? { prefix: '// ' }
340
+ const prefix: string = f.prefix ?? ''
341
+ const lines: string[] = String(d.text).split('\n')
342
+ if (null != d.deprecated) {
343
+ const dep = d.deprecated
344
+ const note = [dep.msg, dep.use ? 'use ' + dep.use : '', dep.since ? 'since ' + dep.since : '']
345
+ .filter((s: string) => s).join('; ')
346
+ lines.push('go' === ctx.family ? 'Deprecated: ' + note : '@deprecated ' + note)
347
+ }
348
+ const out: any[] = []
349
+ if (null != f.open) {
350
+ out.push(ln(at, f.open))
351
+ }
352
+ for (const l of lines) {
353
+ out.push(ln(at, (prefix + l).replace(/[ \t]+$/, '')))
354
+ }
355
+ if (null != f.close) {
356
+ out.push(ln(at, f.close))
357
+ }
358
+ return out
359
+ }
360
+
361
+ // Every check a target's type system cannot enforce is tier-1 loss,
362
+ // one entry per check, addressed at the check.
363
+ function checks(list: any[] | undefined, path: string, ctx: LowerCtx): void {
364
+ (list ?? []).forEach((c: any, i: number) => {
365
+ loss(ctx, path + '.check.' + i, 'check',
366
+ 'the check ' + c.c + ' is not enforced by the ' + ctx.profile.lang + ' type system')
367
+ })
368
+ }
369
+
370
+
371
+ // --- the unit header ---------------------------------------------------
372
+
373
+ // The path from one unit's directory to another unit, for a TypeScript
374
+ // import: `./other` beside, `../shared/x` across, the extension gone.
375
+ function relImport(from: string, to: string): string {
376
+ const a = from.split('/').slice(0, -1)
377
+ const b = to.split('/')
378
+ const file = (b.pop() as string).replace(/\.[^.]*$/, '')
379
+ let i = 0
380
+ while (i < a.length && i < b.length && a[i] === b[i]) {
381
+ i++
382
+ }
383
+ const up = a.slice(i).map(() => '..')
384
+ const parts = [...up, ...b.slice(i), file]
385
+ return (0 === up.length ? './' : '') + parts.join('/')
386
+ }
387
+
388
+ // Imports derived from `{k:"ref", unit}` nodes, in order of first
389
+ // appearance, grouped by unit -- TypeScript only: a Go reference names
390
+ // a type in the same package.
391
+ function derivedImports(unit: any): any[] {
392
+ const groups: any[] = []
393
+ const visit = (t: any): void => {
394
+ if (null == t || 'object' !== typeof t) {
395
+ return
396
+ }
397
+ if (Array.isArray(t)) {
398
+ t.forEach(visit)
399
+ return
400
+ }
401
+ if ('ref' === t.k && 'string' === typeof t.unit) {
402
+ let g = groups.find((x) => x.from === t.unit)
403
+ if (undefined === g) {
404
+ g = { from: t.unit, names: [] }
405
+ groups.push(g)
406
+ }
407
+ if (!g.names.includes(t.name)) {
408
+ g.names.push(t.name)
409
+ }
410
+ return
411
+ }
412
+ for (const key of ['type', 'of', 'key', 'returns', 'fields', 'params']) {
413
+ visit(t[key])
414
+ }
415
+ }
416
+ visit(unit.decls)
417
+ return groups
418
+ }
419
+
420
+ // The banner from `code.source`, the package clause, and the imports:
421
+ // what stands before the declarations, each part followed by a blank.
422
+ export function lowerHeader(unit: any, source: any, ctx: LowerCtx): any[] {
423
+ const parts: any[][] = []
424
+ const banner: string | undefined = ctx.profile.banner
425
+ if (null != banner && null != source?.path) {
426
+ const prefix: string = ctx.profile.comment?.line?.prefix ?? ''
427
+ parts.push([prefix + banner.split('{path}').join(source.path)
428
+ .split('{hash}').join(source.hash ?? '')])
429
+ }
430
+ if ('go' === ctx.family && 'string' === typeof unit.pkg) {
431
+ parts.push(['package ' + unit.pkg])
432
+ }
433
+ const imports: any[] = [...(unit.imports ?? [])]
434
+ if ('typescript' === ctx.family) {
435
+ for (const g of derivedImports(unit)) {
436
+ imports.push({
437
+ from: relImport(unit.path, g.from),
438
+ names: g.names.map((n: string) => ident(n, 'record', ctx, '', false)),
439
+ })
440
+ }
441
+ }
442
+ if (0 < imports.length) {
443
+ const lines: any[] = []
444
+ if ('go' === ctx.family) {
445
+ const spell = (im: any) =>
446
+ (null == im.alias ? '' : im.alias + ' ') + quote(im.from, ctx.profile)
447
+ if (1 === imports.length) {
448
+ lines.push('import ' + spell(imports[0]))
449
+ }
450
+ else {
451
+ lines.push('import (')
452
+ for (const im of imports) {
453
+ lines.push(ln(1, spell(im)))
454
+ }
455
+ lines.push(')')
456
+ }
457
+ }
458
+ else {
459
+ for (const im of imports) {
460
+ const from = quote(im.from, ctx.profile)
461
+ if (Array.isArray(im.names) && 0 < im.names.length) {
462
+ lines.push('import { ' + im.names.join(', ') + ' } from ' + from + ';')
463
+ }
464
+ else if (null != im.alias) {
465
+ lines.push('import * as ' + im.alias + ' from ' + from + ';')
466
+ }
467
+ else {
468
+ lines.push('import ' + from + ';')
469
+ }
470
+ }
471
+ }
472
+ parts.push(lines)
473
+ }
474
+ // The parts, a blank between each two; the fold puts the blank
475
+ // after the last when a declaration follows.
476
+ const out: any[] = []
477
+ parts.forEach((part, i) => {
478
+ if (0 < i) {
479
+ out.push(BLANK)
480
+ }
481
+ out.push(...part)
482
+ })
483
+ return out
484
+ }
485
+
486
+
487
+ // --- declarations ------------------------------------------------------
488
+
489
+ // A body fragment's pieces, one level deeper: a bare string is a line
490
+ // at depth 1, a line or a raw keeps its shape at `at + 1`, a blank is
491
+ // a blank.
492
+ function nest(pieces: any[]): any[] {
493
+ return pieces.map((p) => 'string' === typeof p ? ln(1, p)
494
+ : 'blank' === p.k ? p : { ...p, at: (p.at ?? 0) + 1 })
495
+ }
496
+
497
+ function params(list: any[], ctx: LowerCtx, path: string): string {
498
+ return list.map((p: any, i: number) => {
499
+ const ppath = path + '.params.' + i
500
+ const name = ident(p.name, 'param', ctx, ppath + '.name', true)
501
+ const type = typeExpr(p.type, ctx, ppath + '.type').text
502
+ if ('go' === ctx.family) {
503
+ if (undefined !== p.default) {
504
+ loss(ctx, ppath + '.default', 'default',
505
+ 'go has no default parameter: the default of ' + p.name + ' is dropped')
506
+ }
507
+ return name + ' ' + type
508
+ }
509
+ return name + ': ' + type +
510
+ (undefined === p.default ? '' : ' = ' + literal(p.default, ctx))
511
+ }).join(', ')
512
+ }
513
+
514
+ // The lowering of one declaration to pieces. A `frag` and a `text`
515
+ // never arrive here: the fold takes those itself.
516
+ export function lowerDecl(decl: any, path: string, ctx: LowerCtx): any[] {
517
+ const go = 'go' === ctx.family
518
+ const out: any[] = []
519
+ const k: string = decl.k
520
+
521
+ if ('record' === k) {
522
+ const name = ident(decl.name, 'record', ctx, path + '.name', true)
523
+ out.push(...doc(decl.doc, 0, ctx))
524
+ out.push(go ? 'type ' + name + ' struct {' : 'export interface ' + name + ' {')
525
+ decl.fields.forEach((f: any, i: number) => {
526
+ const fpath = path + '.fields.' + i
527
+ out.push(...doc(f.doc, 1, ctx))
528
+ const fname = ident(f.name, 'field', ctx, fpath + '.name', false)
529
+ const type = typeExpr(f.type, ctx, fpath + '.type')
530
+ checks(f.check, fpath, ctx)
531
+ const opt = true === f.optional
532
+ if (go) {
533
+ const star = opt && 'opt' !== f.type.k ? '*' : ''
534
+ out.push(ln(1, fname + ' ' + star + type.text +
535
+ ' `json:"' + f.name + (opt ? ',omitempty' : '') + '"`'))
536
+ }
537
+ else {
538
+ out.push(ln(1, fname + (opt ? '?' : '') + ': ' + type.text + ';'))
539
+ }
540
+ if (undefined !== f.default) {
541
+ loss(ctx, fpath + '.default', 'default', 'a field default is not expressed by a ' +
542
+ (go ? 'go struct' : 'typescript interface'))
543
+ }
544
+ })
545
+ if (true === decl.open) {
546
+ if (go) {
547
+ loss(ctx, path + '.open', 'open', 'go has no open struct: extra keys are dropped')
548
+ }
549
+ else {
550
+ out.push(ln(1, '[key: string]: unknown;'))
551
+ }
552
+ }
553
+ checks(decl.check, path, ctx)
554
+ out.push('}')
555
+ return out
556
+ }
557
+
558
+ if ('enum' === k) {
559
+ const name = ident(decl.name, 'enum', ctx, path + '.name', true)
560
+ out.push(...doc(decl.doc, 0, ctx))
561
+ const members: any[] = decl.members
562
+ if (go) {
563
+ const numeric = 0 < members.length &&
564
+ members.every((m: any) => 'number' === typeof m.value)
565
+ out.push('type ' + name + ' ' + prim(ctx, numeric ? 'int' : 'string'))
566
+ out.push(BLANK, 'const (')
567
+ members.forEach((m: any, i: number) => {
568
+ out.push(...doc(m.doc, 1, ctx))
569
+ const mname = name + ident(m.name, 'member', ctx, path + '.members.' + i + '.name', false)
570
+ const value = undefined === m.value ? quote(m.name, ctx.profile) : literal(m.value, ctx)
571
+ out.push(ln(1, mname + ' ' + name + ' = ' + value))
572
+ })
573
+ out.push(')')
574
+ }
575
+ else {
576
+ out.push('export enum ' + name + ' {')
577
+ members.forEach((m: any, i: number) => {
578
+ out.push(...doc(m.doc, 1, ctx))
579
+ const mname = ident(m.name, 'member', ctx, path + '.members.' + i + '.name', false)
580
+ out.push(ln(1, mname + (undefined === m.value ? '' : ' = ' + literal(m.value, ctx)) + ','))
581
+ })
582
+ out.push('}')
583
+ }
584
+ return out
585
+ }
586
+
587
+ if ('alias' === k) {
588
+ const name = ident(decl.name, 'alias', ctx, path + '.name', true)
589
+ out.push(...doc(decl.doc, 0, ctx))
590
+ const type = typeExpr(decl.type, ctx, path + '.type').text
591
+ checks(decl.check, path, ctx)
592
+ out.push(go ? 'type ' + name + ' ' + type : 'export type ' + name + ' = ' + type + ';')
593
+ return out
594
+ }
595
+
596
+ if ('const' === k) {
597
+ const name = ident(decl.name, 'const', ctx, path + '.name', true)
598
+ out.push(...doc(decl.doc, 0, ctx))
599
+ const type = null == decl.type ? undefined : typeExpr(decl.type, ctx, path + '.type').text
600
+ const value = literal(decl.value, ctx)
601
+ out.push(go
602
+ ? 'const ' + name + (undefined === type ? '' : ' ' + type) + ' = ' + value
603
+ : 'export const ' + name + (undefined === type ? '' : ': ' + type) + ' = ' + value + ';')
604
+ return out
605
+ }
606
+
607
+ // func
608
+ const name = ident(decl.name, 'func', ctx, path + '.name', true)
609
+ out.push(...doc(decl.doc, 0, ctx))
610
+ const sig = params(decl.params, ctx, path)
611
+ const returns = null == decl.returns ? undefined : typeExpr(decl.returns, ctx, path + '.returns').text
612
+ const abstract = 'abstract' === decl.body.k
613
+ if (go) {
614
+ out.push('func ' + name + '(' + sig + ')' + (undefined === returns ? '' : ' ' + returns) + ' {')
615
+ if (abstract) {
616
+ loss(ctx, path + '.body', 'abstract', 'go has no abstract function: the body panics')
617
+ out.push(ln(1, 'panic(' + quote('abstract', ctx.profile) + ')'))
618
+ }
619
+ else {
620
+ out.push(...nest(decl.body.of))
621
+ }
622
+ out.push('}')
623
+ }
624
+ else {
625
+ const ret = ': ' + (returns ?? 'void')
626
+ if (abstract) {
627
+ out.push('export declare function ' + name + '(' + sig + ')' + ret + ';')
628
+ }
629
+ else {
630
+ out.push('export function ' + name + '(' + sig + ')' + ret + ' {')
631
+ out.push(...nest(decl.body.of))
632
+ out.push('}')
633
+ }
634
+ }
635
+ return out
636
+ }
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', 'greatest',
531
+ 'filter', 'form', '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/mcp-server.ts CHANGED
@@ -30,7 +30,7 @@ import type { McpRequest, McpResponse } from './mcp'
30
30
  import { VERSION } from './aontu'
31
31
 
32
32
 
33
- const USAGE = 'aontu mcp - Aontu MCP server (stdio, NDJSON JSON-RPC)\n' +
33
+ const USAGE = 'aontu mcp - aontu MCP server (stdio, NDJSON JSON-RPC)\n' +
34
34
  '\n' +
35
35
  ' aontu mcp [--root <dir>]\n' +
36
36
  '\n' +