@ldclabs/kip-lang 0.3.1 → 2.0.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 (64) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -44
  4. package/dist/ast.d.ts +519 -144
  5. package/dist/ast.d.ts.map +1 -1
  6. package/dist/budget.d.ts +37 -0
  7. package/dist/budget.d.ts.map +1 -0
  8. package/dist/budget.js +105 -0
  9. package/dist/budget.js.map +1 -0
  10. package/dist/diagnostics.d.ts +8 -2
  11. package/dist/diagnostics.d.ts.map +1 -1
  12. package/dist/diagnostics.js +32 -3
  13. package/dist/diagnostics.js.map +1 -1
  14. package/dist/errors.d.ts +29 -0
  15. package/dist/errors.d.ts.map +1 -0
  16. package/dist/errors.js +27 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/exec-ast.d.ts +679 -0
  19. package/dist/exec-ast.d.ts.map +1 -0
  20. package/dist/exec-ast.js +24 -0
  21. package/dist/exec-ast.js.map +1 -0
  22. package/dist/formatter.d.ts.map +1 -1
  23. package/dist/formatter.js +870 -479
  24. package/dist/formatter.js.map +1 -1
  25. package/dist/index.d.ts +9 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +6 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/lexer.d.ts.map +1 -1
  30. package/dist/lexer.js +46 -33
  31. package/dist/lexer.js.map +1 -1
  32. package/dist/lower.d.ts +17 -0
  33. package/dist/lower.d.ts.map +1 -0
  34. package/dist/lower.js +1566 -0
  35. package/dist/lower.js.map +1 -0
  36. package/dist/parser.d.ts.map +1 -1
  37. package/dist/parser.js +2476 -1152
  38. package/dist/parser.js.map +1 -1
  39. package/dist/semantics.d.ts +11 -7
  40. package/dist/semantics.d.ts.map +1 -1
  41. package/dist/semantics.js +295 -180
  42. package/dist/semantics.js.map +1 -1
  43. package/dist/token.d.ts +130 -40
  44. package/dist/token.d.ts.map +1 -1
  45. package/dist/token.js +264 -83
  46. package/dist/token.js.map +1 -1
  47. package/dist/version.d.ts +13 -0
  48. package/dist/version.d.ts.map +1 -0
  49. package/dist/version.js +13 -0
  50. package/dist/version.js.map +1 -0
  51. package/package.json +36 -6
  52. package/src/ast.ts +914 -0
  53. package/src/budget.ts +108 -0
  54. package/src/diagnostics.ts +182 -0
  55. package/src/errors.ts +42 -0
  56. package/src/exec-ast.ts +614 -0
  57. package/src/formatter.ts +1339 -0
  58. package/src/index.ts +226 -0
  59. package/src/lexer.ts +459 -0
  60. package/src/lower.ts +2011 -0
  61. package/src/parser.ts +3506 -0
  62. package/src/semantics.ts +392 -0
  63. package/src/token.ts +408 -0
  64. package/src/version.ts +13 -0
package/dist/formatter.js CHANGED
@@ -38,7 +38,6 @@ class Formatter {
38
38
  this.commentIdx++;
39
39
  }
40
40
  }
41
- /** Emit all remaining comments */
42
41
  emitRemainingComments() {
43
42
  while (this.commentIdx < this.comments.length) {
44
43
  this.writeIndent();
@@ -69,650 +68,1045 @@ class Formatter {
69
68
  // Separate statements with exactly one blank line.
70
69
  if (i > 0)
71
70
  this.newline();
72
- // Emit comments that appear before this statement
73
71
  this.emitCommentsBefore(stmt.range.start.line);
74
72
  this.formatStatement(stmt);
75
73
  }
76
- // Trailing comments after last statement
77
74
  this.emitRemainingComments();
78
75
  return this.output.trimEnd() + '\n';
79
76
  }
80
77
  // ────────────────────────────────────────────────────────────────────
81
- // Statements
78
+ // Statement dispatch
82
79
  // ────────────────────────────────────────────────────────────────────
83
80
  formatStatement(stmt) {
84
81
  switch (stmt.kind) {
85
82
  case 'FindStatement':
86
- return this.formatFind(stmt);
87
- case 'UpsertStatement':
88
- return this.formatUpsert(stmt);
89
- case 'UpdateStatement':
90
- return this.formatUpdate(stmt);
91
- case 'MergeStatement':
92
- return this.formatMerge(stmt);
93
- case 'DeleteStatement':
94
- return this.formatDelete(stmt);
83
+ this.formatFind(stmt);
84
+ break;
85
+ case 'MutateStatement':
86
+ this.formatMutate(stmt);
87
+ break;
95
88
  case 'DescribeStatement':
96
- return this.formatDescribe(stmt);
89
+ this.formatDescribe(stmt);
90
+ break;
91
+ case 'ListStatement':
92
+ this.formatList(stmt);
93
+ break;
97
94
  case 'SearchStatement':
98
- return this.formatSearch(stmt);
99
- case 'ExportStatement':
100
- return this.formatExport(stmt);
95
+ this.formatSearch(stmt);
96
+ break;
97
+ case 'VerifyStatement':
98
+ this.formatVerify(stmt);
99
+ break;
100
+ case 'ValidateStatement':
101
+ this.formatValidate(stmt);
102
+ break;
103
+ case 'PreviewStatement':
104
+ this.formatPreview(stmt);
105
+ break;
106
+ case 'HistoryStatement':
107
+ this.formatHistory(stmt);
108
+ break;
109
+ case 'ChangesStatement':
110
+ this.formatChanges(stmt);
111
+ break;
112
+ case 'SnapshotStatement':
113
+ this.formatSnapshot(stmt);
114
+ break;
115
+ case 'ExportCapsuleStatement':
116
+ this.formatExport(stmt);
117
+ break;
118
+ default:
119
+ this.formatMutationClause(stmt);
120
+ }
121
+ }
122
+ formatMutationClause(stmt) {
123
+ switch (stmt.kind) {
124
+ case 'CreateConceptStatement':
125
+ this.formatCreateConcept(stmt);
126
+ break;
127
+ case 'UpsertConceptStatement':
128
+ this.formatUpsertConcept(stmt);
129
+ break;
130
+ case 'EnsurePropositionStatement':
131
+ this.formatEnsureProposition(stmt);
132
+ break;
133
+ case 'AssertStatement':
134
+ this.formatAssert(stmt);
135
+ break;
136
+ case 'CreateEvidenceStatement':
137
+ this.formatRecordCreate('EVIDENCE', stmt);
138
+ break;
139
+ case 'CreateAssertionStatement':
140
+ this.formatRecordCreate('ASSERTION', stmt);
141
+ break;
142
+ case 'CreateActivityStatement':
143
+ this.formatRecordCreate('ACTIVITY', stmt);
144
+ break;
145
+ case 'UpdateStatement':
146
+ this.formatUpdate(stmt);
147
+ break;
148
+ case 'RetractAssertionStatement':
149
+ this.formatRetract(stmt);
150
+ break;
151
+ case 'SupersedeAssertionStatement':
152
+ this.formatSupersede(stmt);
153
+ break;
154
+ case 'CorrectEvidenceStatement':
155
+ this.formatCorrect(stmt);
156
+ break;
157
+ case 'TransitionActivityStatement':
158
+ this.formatTransition(stmt);
159
+ break;
160
+ case 'SetRetentionStatement':
161
+ this.formatSetRetention(stmt);
162
+ break;
163
+ case 'ArchiveStatement':
164
+ this.formatRemoval('ARCHIVE', stmt);
165
+ break;
166
+ case 'TombstoneStatement':
167
+ this.formatRemoval('TOMBSTONE', stmt);
168
+ break;
169
+ case 'PurgeStatement':
170
+ this.formatPurge(stmt);
171
+ break;
172
+ case 'MergeConceptStatement':
173
+ this.formatMerge(stmt);
174
+ break;
101
175
  }
102
176
  }
103
- // ── FIND ───────────────────────────────────────────────────────────
177
+ // ────────────────────────────────────────────────────────────────────
178
+ // KQL
179
+ // ────────────────────────────────────────────────────────────────────
104
180
  formatFind(stmt) {
105
181
  this.writeIndent();
106
182
  this.write('FIND(');
107
- if (stmt.projections.length <= 2 &&
108
- this.allSimpleExpressions(stmt.projections)) {
109
- // Inline
110
- this.write(stmt.projections.map((p) => this.exprToString(p, 0)).join(', '));
111
- }
112
- else {
183
+ this.write(stmt.projections.map((p) => this.expr(p)).join(', '));
184
+ this.write(')');
185
+ this.newline();
186
+ this.formatWhere(stmt.where, 'WHERE');
187
+ if (stmt.asOf) {
188
+ this.writeIndent();
189
+ this.write(this.asOfToString(stmt.asOf));
113
190
  this.newline();
114
- this.indentLevel++;
115
- for (let i = 0; i < stmt.projections.length; i++) {
116
- this.writeIndent();
117
- this.write(this.exprToString(stmt.projections[i], 0));
118
- if (i < stmt.projections.length - 1) {
119
- this.write(',');
120
- }
121
- this.newline();
122
- }
123
- this.indentLevel--;
191
+ }
192
+ if (stmt.forTime) {
124
193
  this.writeIndent();
194
+ this.write(`FOR TIME ${this.scalar(stmt.forTime.value)}`);
195
+ this.newline();
125
196
  }
126
- this.write(')');
127
- this.newline();
128
- if (stmt.where) {
129
- this.formatWhere(stmt.where);
197
+ if (stmt.epistemic) {
198
+ this.writeIndent();
199
+ this.write('WITH EPISTEMIC ');
200
+ this.formatObjectBlock(stmt.epistemic.options, false);
201
+ this.newline();
130
202
  }
131
- if (stmt.orderBy) {
203
+ if (stmt.orderBy)
132
204
  this.formatOrderBy(stmt.orderBy);
133
- }
134
- if (stmt.limit) {
205
+ if (stmt.limit)
135
206
  this.formatLimit(stmt.limit);
136
- }
137
- if (stmt.cursor) {
207
+ if (stmt.cursor)
138
208
  this.formatCursor(stmt.cursor);
139
- }
140
209
  }
141
- // ── UPSERT ─────────────────────────────────────────────────────────
142
- formatUpsert(stmt) {
210
+ formatOrderBy(clause) {
143
211
  this.writeIndent();
144
- this.write('UPSERT {');
212
+ this.write('ORDER BY ');
213
+ this.write(clause.items
214
+ .map((item) => {
215
+ const expr = this.expr(item.expression);
216
+ return item.direction ? `${expr} ${item.direction}` : expr;
217
+ })
218
+ .join(', '));
145
219
  this.newline();
146
- this.indentLevel++;
147
- for (let i = 0; i < stmt.blocks.length; i++) {
148
- const block = stmt.blocks[i];
149
- if (i > 0)
150
- this.newline();
151
- // Emit comments between blocks
152
- this.emitCommentsBefore(block.range.start.line);
153
- this.formatUpsertBlock(block);
154
- }
155
- this.indentLevel--;
220
+ }
221
+ formatLimit(clause) {
156
222
  this.writeIndent();
157
- this.write('}');
223
+ this.write(`LIMIT ${this.scalar(clause.value)}`);
158
224
  this.newline();
159
- if (stmt.metadata) {
160
- this.formatWithMetadata(stmt.metadata);
161
- }
162
- }
163
- formatUpsertBlock(block) {
164
- if (block.kind === 'ConceptBlock') {
165
- this.formatConceptBlock(block);
166
- }
167
- else {
168
- this.formatPropositionBlockDef(block);
169
- }
170
225
  }
171
- formatConceptBlock(block) {
226
+ formatCursor(clause) {
172
227
  this.writeIndent();
173
- this.write(`CONCEPT ${block.handle} {`);
228
+ this.write(`CURSOR ${this.scalar(clause.value)}`);
174
229
  this.newline();
175
- this.indentLevel++;
176
- // Matcher: {type: "...", name: "..."}
177
- this.emitCommentsBefore(block.matcher.range.start.line);
230
+ }
231
+ asOfToString(clause) {
232
+ return `AS OF ${clause.basis} ${this.scalar(clause.value)}`;
233
+ }
234
+ // ────────────────────────────────────────────────────────────────────
235
+ // WHERE
236
+ // ────────────────────────────────────────────────────────────────────
237
+ formatWhere(clause, keyword) {
178
238
  this.writeIndent();
179
- this.write('{');
180
- this.write(block.matcher.entries
181
- .map((e) => `${this.keyToString(e)}: ${this.exprToString(e.value, 0)}`)
182
- .join(', '));
183
- this.write('}');
239
+ this.write(`${keyword} {`);
184
240
  this.newline();
185
- if (block.expectVersion) {
186
- this.emitCommentsBefore(block.expectVersion.range.start.line);
187
- this.formatExpectVersion(block.expectVersion);
188
- }
189
- if (block.setAttributes) {
190
- this.emitCommentsBefore(block.setAttributes.range.start.line);
191
- this.formatSetAttributes(block.setAttributes);
192
- }
193
- if (block.setPropositions) {
194
- this.emitCommentsBefore(block.setPropositions.range.start.line);
195
- this.formatSetPropositions(block.setPropositions);
241
+ this.indentLevel++;
242
+ for (const pattern of clause.patterns) {
243
+ this.emitCommentsBefore(pattern.range.start.line);
244
+ this.formatWherePattern(pattern);
196
245
  }
246
+ this.emitCommentsBefore(clause.range.end.line);
197
247
  this.indentLevel--;
198
248
  this.writeIndent();
199
249
  this.write('}');
200
250
  this.newline();
201
- if (block.metadata) {
202
- this.formatWithMetadata(block.metadata);
203
- }
204
251
  }
205
- formatPropositionBlockDef(block) {
252
+ formatNestedBlock(keyword, patterns, endLine) {
206
253
  this.writeIndent();
207
- this.write('PROPOSITION');
208
- if (block.handle)
209
- this.write(` ${block.handle}`);
210
- this.write(' {');
254
+ this.write(`${keyword} {`);
211
255
  this.newline();
212
256
  this.indentLevel++;
213
- this.writeIndent();
214
- this.write(this.propositionBlockPatternToString(block));
215
- this.newline();
216
- if (block.expectVersion) {
217
- this.emitCommentsBefore(block.expectVersion.range.start.line);
218
- this.formatExpectVersion(block.expectVersion);
219
- }
220
- if (block.setAttributes) {
221
- this.emitCommentsBefore(block.setAttributes.range.start.line);
222
- this.formatSetAttributes(block.setAttributes);
257
+ for (const pattern of patterns) {
258
+ this.emitCommentsBefore(pattern.range.start.line);
259
+ this.formatWherePattern(pattern);
223
260
  }
261
+ this.emitCommentsBefore(endLine);
224
262
  this.indentLevel--;
225
263
  this.writeIndent();
226
264
  this.write('}');
227
265
  this.newline();
228
- if (block.metadata) {
229
- this.formatWithMetadata(block.metadata);
230
- }
231
266
  }
232
- formatSetAttributes(sa) {
233
- this.formatEntryBlock('SET ATTRIBUTES', sa.entries, sa.range, {
234
- inlineMax: 3,
235
- sort: this.opts.sortAttributes
236
- });
237
- }
238
- formatSetMetadata(sm) {
239
- this.formatEntryBlock('SET METADATA', sm.entries, sm.range, {
240
- inlineMax: 3,
241
- sort: false
242
- });
267
+ formatWherePattern(pattern) {
268
+ switch (pattern.kind) {
269
+ case 'ConceptPattern':
270
+ this.writeIndent();
271
+ this.write(pattern.variable.name);
272
+ if (pattern.explicit)
273
+ this.write(' CONCEPT');
274
+ this.write(' ');
275
+ this.write(this.objectPatternToString(pattern.matcher));
276
+ this.newline();
277
+ break;
278
+ case 'PropositionPattern':
279
+ this.writeIndent();
280
+ if (pattern.variable)
281
+ this.write(`${pattern.variable.name} `);
282
+ if (pattern.explicit)
283
+ this.write('PROPOSITION ');
284
+ this.write(this.tupleToString(pattern.tuple));
285
+ this.newline();
286
+ break;
287
+ case 'AssertionPattern':
288
+ case 'EvidencePattern':
289
+ case 'ActivityPattern': {
290
+ const keyword = {
291
+ AssertionPattern: 'ASSERTION',
292
+ EvidencePattern: 'EVIDENCE',
293
+ ActivityPattern: 'ACTIVITY'
294
+ }[pattern.kind];
295
+ this.writeIndent();
296
+ this.write(`${pattern.variable.name} ${keyword} `);
297
+ this.write(this.objectPatternToString(pattern.matcher));
298
+ this.newline();
299
+ break;
300
+ }
301
+ case 'StructuralPattern':
302
+ this.writeIndent();
303
+ if (pattern.variable)
304
+ this.write(`${pattern.variable.name} `);
305
+ this.write(`STRUCTURAL (${this.term(pattern.subject)}, ${this.symbol(pattern.field)}, ${this.term(pattern.object)})`);
306
+ this.newline();
307
+ break;
308
+ case 'BeliefPattern':
309
+ this.writeIndent();
310
+ this.write(`${pattern.variable.name} BELIEF (`);
311
+ if (pattern.proposition) {
312
+ this.write(pattern.proposition.name);
313
+ }
314
+ else if (pattern.propositionId) {
315
+ this.write(`id: ${this.scalar(pattern.propositionId)}`);
316
+ }
317
+ else {
318
+ this.write(`${this.term(pattern.subject)}, ${this.predAtom(pattern.predicate)}, ${this.term(pattern.object)}`);
319
+ }
320
+ this.write(')');
321
+ this.newline();
322
+ break;
323
+ case 'BeliefSlotPattern':
324
+ this.writeIndent();
325
+ this.write(`${pattern.variable.name} BELIEF SLOT (${this.term(pattern.subject)}, ${this.predAtom(pattern.predicate)})`);
326
+ this.newline();
327
+ break;
328
+ case 'FilterClause':
329
+ this.writeIndent();
330
+ this.write(`FILTER(${this.expr(pattern.expression)})`);
331
+ this.newline();
332
+ break;
333
+ case 'NotClause':
334
+ this.formatNestedBlock('NOT', pattern.patterns, pattern.range.end.line);
335
+ break;
336
+ case 'OptionalClause':
337
+ this.formatNestedBlock('OPTIONAL', pattern.patterns, pattern.range.end.line);
338
+ break;
339
+ case 'UnionClause':
340
+ this.formatNestedBlock('UNION', pattern.patterns, pattern.range.end.line);
341
+ break;
342
+ }
243
343
  }
244
- /**
245
- * Render a `HEADER { ... }` key/value block. Collapses to a single line when
246
- * all values are primitive, there are few of them, and no comment sits inside
247
- * the block; otherwise emits one entry per line, preserving interior comments
248
- * at their source position. Sorting is skipped whenever interior comments are
249
- * present, since reordering would detach comments from their keys.
250
- */
251
- formatEntryBlock(header, entries, range, opts) {
344
+ // ────────────────────────────────────────────────────────────────────
345
+ // KML
346
+ // ────────────────────────────────────────────────────────────────────
347
+ formatMutate(stmt) {
252
348
  this.writeIndent();
253
- this.write(`${header} {`);
254
- if (entries.length === 0) {
255
- this.write('}');
256
- this.newline();
257
- return;
258
- }
259
- const hasComments = this.hasPendingCommentInRange(range.start.line, range.end.line);
260
- const ordered = opts.sort && !hasComments ? this.sortObjectEntries(entries) : entries;
261
- const allSimple = ordered.every((e) => this.isSimpleValue(e.value));
262
- if (allSimple && ordered.length <= opts.inlineMax && !hasComments) {
263
- this.write(' ');
264
- this.write(ordered
265
- .map((e) => `${this.keyToString(e)}: ${this.exprToString(e.value, 0)}`)
266
- .join(', '));
267
- this.write(' }');
268
- this.newline();
269
- return;
270
- }
349
+ this.write('MUTATE {');
271
350
  this.newline();
272
351
  this.indentLevel++;
273
- for (let i = 0; i < ordered.length; i++) {
274
- this.emitCommentsBefore(ordered[i].range.start.line);
275
- this.formatObjectEntry(ordered[i]);
276
- if (i < ordered.length - 1) {
277
- this.write(',');
278
- }
279
- this.newline();
352
+ for (let i = 0; i < stmt.clauses.length; i++) {
353
+ const clause = stmt.clauses[i];
354
+ if (i > 0)
355
+ this.newline();
356
+ this.emitCommentsBefore(clause.range.start.line);
357
+ this.formatMutationClause(clause);
280
358
  }
281
- this.emitCommentsBefore(range.end.line);
359
+ this.emitCommentsBefore(stmt.range.end.line);
282
360
  this.indentLevel--;
283
361
  this.writeIndent();
284
362
  this.write('}');
285
363
  this.newline();
286
364
  }
287
- formatExpectVersion(ev) {
288
- this.writeIndent();
289
- this.write(`EXPECT VERSION ${this.numberOrParameterValueToString(ev.value)}`);
290
- this.newline();
291
- }
292
- formatSetPropositions(sp) {
365
+ /**
366
+ * A body clause together with the printer for it. Bodies (CREATE / UPSERT
367
+ * CONCEPT, CREATE EVIDENCE|ASSERTION|ACTIVITY) hold their clauses in typed
368
+ * slots, so the source order has to be recovered from the ranges before
369
+ * printing — a comment written above a clause must come out above that
370
+ * same clause, and the comment cursor only moves forward.
371
+ */
372
+ formatBody(header, endLine, clauses) {
293
373
  this.writeIndent();
294
- this.write('SET PROPOSITIONS {');
374
+ this.write(`${header} {`);
295
375
  this.newline();
296
376
  this.indentLevel++;
297
- for (const item of sp.items) {
298
- this.emitCommentsBefore(item.range.start.line);
299
- this.formatPropositionItem(item);
377
+ const ordered = [...clauses].sort((a, b) => a.node.range.start.line - b.node.range.start.line ||
378
+ a.node.range.start.column - b.node.range.start.column);
379
+ for (const clause of ordered) {
380
+ this.emitCommentsBefore(clause.node.range.start.line);
381
+ clause.print();
300
382
  }
301
- this.emitCommentsBefore(sp.range.end.line);
383
+ // Comments after the last clause still belong inside the braces.
384
+ this.emitCommentsBefore(endLine);
302
385
  this.indentLevel--;
303
386
  this.writeIndent();
304
387
  this.write('}');
305
388
  this.newline();
306
389
  }
307
- formatPropositionItem(item) {
308
- this.writeIndent();
309
- this.write(`("${this.escapeString(item.predicate)}", ${this.endpointToString(item.target)})`);
310
- if (item.metadata) {
311
- this.write(' ');
312
- this.write('WITH METADATA { ');
313
- this.write(item.metadata.entries
314
- .map((e) => `${this.keyToString(e)}: ${this.exprToString(e.value, 0)}`)
315
- .join(', '));
316
- this.write(' }');
390
+ formatCreateConcept(stmt) {
391
+ const clauses = [];
392
+ if (stmt.type) {
393
+ const node = stmt.type;
394
+ clauses.push({
395
+ node,
396
+ print: () => {
397
+ this.writeIndent();
398
+ this.write(`TYPE ${this.symbol(node.value)}`);
399
+ this.newline();
400
+ }
401
+ });
317
402
  }
318
- this.newline();
403
+ if (stmt.clientKey) {
404
+ const node = stmt.clientKey;
405
+ clauses.push({
406
+ node,
407
+ print: () => {
408
+ this.writeIndent();
409
+ this.write(`CLIENT KEY ${this.scalar(node.value)}`);
410
+ this.newline();
411
+ }
412
+ });
413
+ }
414
+ if (stmt.name) {
415
+ const node = stmt.name;
416
+ clauses.push({
417
+ node,
418
+ print: () => {
419
+ this.writeIndent();
420
+ this.write(`NAME ${this.scalar(node.value)}`);
421
+ this.newline();
422
+ }
423
+ });
424
+ }
425
+ if (stmt.setFields) {
426
+ const node = stmt.setFields;
427
+ clauses.push({
428
+ node,
429
+ print: () => this.formatAssignmentClause('SET FIELDS', node.assignments, false)
430
+ });
431
+ }
432
+ if (stmt.setAttributes) {
433
+ const node = stmt.setAttributes;
434
+ clauses.push({
435
+ node,
436
+ print: () => this.formatAssignmentClause('SET ATTRIBUTES', node.assignments, this.opts.sortAttributes)
437
+ });
438
+ }
439
+ for (const facet of stmt.setFacets) {
440
+ clauses.push({ node: facet, print: () => this.formatFacet(facet) });
441
+ }
442
+ if (stmt.setStructural) {
443
+ const node = stmt.setStructural;
444
+ clauses.push({ node, print: () => this.formatStructural(node) });
445
+ }
446
+ this.formatBody(`CREATE CONCEPT ${stmt.handle.name}`, stmt.range.end.line, clauses);
447
+ }
448
+ formatUpsertConcept(stmt) {
449
+ const clauses = [];
450
+ if (stmt.match) {
451
+ const node = stmt.match;
452
+ clauses.push({
453
+ node,
454
+ print: () => {
455
+ this.writeIndent();
456
+ this.write('MATCH ');
457
+ this.write(this.objectPatternToString(node.pattern));
458
+ this.newline();
459
+ }
460
+ });
461
+ }
462
+ if (stmt.expectVersion) {
463
+ const node = stmt.expectVersion;
464
+ clauses.push({
465
+ node,
466
+ print: () => {
467
+ this.writeIndent();
468
+ this.write(`EXPECT VERSION ${this.scalar(node.value)}`);
469
+ this.newline();
470
+ }
471
+ });
472
+ }
473
+ if (stmt.setFields) {
474
+ const node = stmt.setFields;
475
+ clauses.push({
476
+ node,
477
+ print: () => this.formatAssignmentClause('SET FIELDS', node.assignments, false)
478
+ });
479
+ }
480
+ if (stmt.setAttributes) {
481
+ const node = stmt.setAttributes;
482
+ clauses.push({
483
+ node,
484
+ print: () => this.formatAssignmentClause('SET ATTRIBUTES', node.assignments, this.opts.sortAttributes)
485
+ });
486
+ }
487
+ for (const facet of stmt.setFacets) {
488
+ clauses.push({ node: facet, print: () => this.formatFacet(facet) });
489
+ }
490
+ if (stmt.unsetAttributes) {
491
+ const node = stmt.unsetAttributes;
492
+ clauses.push({ node, print: () => this.formatUnsetAttributes(node) });
493
+ }
494
+ for (const facet of stmt.unsetFacets) {
495
+ clauses.push({ node: facet, print: () => this.formatUnsetFacet(facet) });
496
+ }
497
+ if (stmt.setStructural) {
498
+ const node = stmt.setStructural;
499
+ clauses.push({ node, print: () => this.formatStructural(node) });
500
+ }
501
+ if (stmt.unsetStructural) {
502
+ const node = stmt.unsetStructural;
503
+ clauses.push({ node, print: () => this.formatUnsetStructural(node) });
504
+ }
505
+ this.formatBody(`UPSERT CONCEPT ${stmt.handle.name}`, stmt.range.end.line, clauses);
319
506
  }
320
- formatWithMetadata(wm) {
507
+ formatRecordCreate(keyword, stmt) {
508
+ const clauses = [];
509
+ if (stmt.clientKey) {
510
+ const node = stmt.clientKey;
511
+ clauses.push({
512
+ node,
513
+ print: () => {
514
+ this.writeIndent();
515
+ this.write(`CLIENT KEY ${this.scalar(node.value)}`);
516
+ this.newline();
517
+ }
518
+ });
519
+ }
520
+ if (stmt.setFields) {
521
+ const node = stmt.setFields;
522
+ clauses.push({
523
+ node,
524
+ print: () => this.formatAssignmentClause('SET FIELDS', node.assignments, false)
525
+ });
526
+ }
527
+ for (const facet of stmt.setFacets) {
528
+ clauses.push({ node: facet, print: () => this.formatFacet(facet) });
529
+ }
530
+ if (stmt.setStructural) {
531
+ const node = stmt.setStructural;
532
+ clauses.push({ node, print: () => this.formatStructural(node) });
533
+ }
534
+ this.formatBody(`CREATE ${keyword} ${stmt.handle.name}`, stmt.range.end.line, clauses);
535
+ }
536
+ formatEnsureProposition(stmt) {
321
537
  this.writeIndent();
322
- this.write('WITH METADATA {');
323
- if (wm.entries.length === 0) {
324
- this.write('}');
325
- this.newline();
326
- return;
538
+ this.write('ENSURE PROPOSITION ');
539
+ if (stmt.handle)
540
+ this.write(`${stmt.handle.name} `);
541
+ this.write(this.tupleToString(stmt.tuple));
542
+ if (stmt.expectVersion) {
543
+ this.write(` EXPECT VERSION ${this.scalar(stmt.expectVersion.value)}`);
327
544
  }
328
- // Metadata blocks are conventionally one entry per line.
329
545
  this.newline();
330
- this.indentLevel++;
331
- for (let i = 0; i < wm.entries.length; i++) {
332
- this.emitCommentsBefore(wm.entries[i].range.start.line);
333
- this.formatObjectEntry(wm.entries[i]);
334
- if (i < wm.entries.length - 1) {
335
- this.write(',');
336
- }
546
+ }
547
+ formatAssert(stmt) {
548
+ this.writeIndent();
549
+ this.write('ASSERT ');
550
+ if (stmt.handle)
551
+ this.write(`${stmt.handle.name} `);
552
+ this.write(this.tupleToString(stmt.tuple));
553
+ this.write(' ');
554
+ this.formatObjectBlock(stmt.assignments, false);
555
+ if (stmt.superseding) {
337
556
  this.newline();
557
+ this.indentLevel++;
558
+ this.writeIndent();
559
+ this.write(`SUPERSEDING ${this.targetRef(stmt.superseding)}`);
560
+ this.indentLevel--;
338
561
  }
339
- this.emitCommentsBefore(wm.range.end.line);
340
- this.indentLevel--;
341
- this.writeIndent();
342
- this.write('}');
343
562
  this.newline();
344
563
  }
345
- // ── UPDATE ─────────────────────────────────────────────────────────
346
564
  formatUpdate(stmt) {
347
565
  this.writeIndent();
348
- this.write(`UPDATE ${stmt.target}`);
566
+ this.write(`UPDATE ${this.targetRef(stmt.target)}`);
349
567
  this.newline();
350
- if (stmt.setAttributes) {
351
- this.formatSetAttributes(stmt.setAttributes);
568
+ if (stmt.expectVersion) {
569
+ this.emitCommentsBefore(stmt.expectVersion.range.start.line);
570
+ this.writeIndent();
571
+ this.write(`EXPECT VERSION ${this.scalar(stmt.expectVersion.value)}`);
572
+ this.newline();
573
+ }
574
+ for (const action of stmt.actions) {
575
+ this.emitCommentsBefore(action.range.start.line);
576
+ this.formatUpdateAction(action);
352
577
  }
353
- if (stmt.setMetadata) {
354
- this.formatSetMetadata(stmt.setMetadata);
578
+ if (stmt.where) {
579
+ this.emitCommentsBefore(stmt.where.range.start.line);
580
+ this.formatWhere(stmt.where, 'WHERE');
355
581
  }
356
- this.formatWhere(stmt.where);
357
582
  if (stmt.limit) {
583
+ this.emitCommentsBefore(stmt.limit.range.start.line);
358
584
  this.formatLimit(stmt.limit);
359
585
  }
360
586
  }
361
- // ── MERGE ──────────────────────────────────────────────────────────
362
- formatMerge(stmt) {
363
- this.writeIndent();
364
- this.write(`MERGE CONCEPT ${stmt.source} INTO ${stmt.target}`);
365
- this.newline();
366
- this.formatWhere(stmt.where);
367
- }
368
- // ── DELETE ─────────────────────────────────────────────────────────
369
- formatDelete(stmt) {
370
- this.writeIndent();
371
- if (stmt.deleteType === 'ATTRIBUTES' || stmt.deleteType === 'METADATA') {
372
- this.write(`DELETE ${stmt.deleteType} {`);
373
- if (stmt.keys) {
374
- this.write(stmt.keys.map((k) => `"${this.escapeString(k)}"`).join(', '));
375
- }
376
- this.write(`} FROM ${stmt.target}`);
377
- }
378
- else if (stmt.deleteType === 'PROPOSITIONS') {
379
- this.write(`DELETE PROPOSITIONS ${stmt.target}`);
380
- }
381
- else {
382
- this.write(`DELETE CONCEPT ${stmt.target} DETACH`);
383
- }
384
- this.newline();
385
- this.formatWhere(stmt.where);
386
- }
387
- // ── DESCRIBE ───────────────────────────────────────────────────────
388
- formatDescribe(stmt) {
389
- this.writeIndent();
390
- switch (stmt.describeType) {
391
- case 'PRIMER':
392
- this.write('DESCRIBE PRIMER');
587
+ formatUpdateAction(action) {
588
+ switch (action.kind) {
589
+ case 'SetFieldsClause':
590
+ this.formatAssignmentClause('SET FIELDS', action.assignments, false);
591
+ break;
592
+ case 'SetAttributesClause':
593
+ this.formatAssignmentClause('SET ATTRIBUTES', action.assignments, this.opts.sortAttributes);
393
594
  break;
394
- case 'DOMAINS':
395
- this.write('DESCRIBE DOMAINS');
595
+ case 'SetFacetClause':
596
+ this.formatFacet(action);
396
597
  break;
397
- case 'CONCEPT_TYPES':
398
- this.write('DESCRIBE CONCEPT TYPES');
598
+ case 'UnsetAttributesClause':
599
+ this.formatUnsetAttributes(action);
399
600
  break;
400
- case 'CONCEPT_TYPE':
401
- this.write(`DESCRIBE CONCEPT TYPE ${this.quotedOrParameterValueToString(stmt.typeNameValue, stmt.typeName ?? '')}`);
601
+ case 'UnsetFacetClause':
602
+ this.formatUnsetFacet(action);
402
603
  break;
403
- case 'PROPOSITION_TYPES':
404
- this.write('DESCRIBE PROPOSITION TYPES');
604
+ case 'SetStructuralClause':
605
+ this.formatStructural(action);
405
606
  break;
406
- case 'PROPOSITION_TYPE':
407
- this.write(`DESCRIBE PROPOSITION TYPE ${this.quotedOrParameterValueToString(stmt.typeNameValue, stmt.typeName ?? '')}`);
607
+ case 'UnsetStructuralClause':
608
+ this.formatUnsetStructural(action);
408
609
  break;
409
610
  }
410
- if (stmt.limit) {
411
- this.write(` LIMIT ${this.limitValueToString(stmt.limit)}`);
611
+ }
612
+ formatRetract(stmt) {
613
+ this.writeIndent();
614
+ this.write(`RETRACT ASSERTION ${this.targetRef(stmt.target)}`);
615
+ if (!stmt.where && !stmt.limit && stmt.expectState) {
616
+ this.write(` EXPECT STATE ${this.scalar(stmt.expectState.value)}`);
617
+ this.newline();
618
+ return;
412
619
  }
413
- if (stmt.cursor) {
414
- this.write(` CURSOR ${this.cursorValueToString(stmt.cursor)}`);
620
+ this.newline();
621
+ if (stmt.where)
622
+ this.formatWhere(stmt.where, 'WHERE');
623
+ if (stmt.limit)
624
+ this.formatLimit(stmt.limit);
625
+ if (stmt.expectState) {
626
+ this.writeIndent();
627
+ this.write(`EXPECT STATE ${this.scalar(stmt.expectState.value)}`);
628
+ this.newline();
415
629
  }
416
630
  }
417
- // ── SEARCH ─────────────────────────────────────────────────────────
418
- formatSearch(stmt) {
631
+ formatSupersede(stmt) {
419
632
  this.writeIndent();
420
- this.write(`SEARCH ${stmt.searchTarget} ${this.quotedOrParameterValueToString(stmt.termValue, stmt.term)}`);
421
- if (stmt.withTypeValue || stmt.withType !== undefined) {
422
- this.write(` WITH TYPE ${this.quotedOrParameterValueToString(stmt.withTypeValue, stmt.withType ?? '')}`);
633
+ this.write(`SUPERSEDE ASSERTION ${this.targetRef(stmt.target)} BY ${this.targetRef(stmt.by)}`);
634
+ if (stmt.expectState) {
635
+ this.write(` EXPECT STATE ${this.scalar(stmt.expectState.value)}`);
423
636
  }
424
- if (stmt.modeValue || stmt.mode !== undefined) {
425
- this.write(` MODE ${this.quotedOrParameterValueToString(stmt.modeValue, stmt.mode ?? '')}`);
637
+ this.newline();
638
+ }
639
+ formatCorrect(stmt) {
640
+ this.writeIndent();
641
+ this.write(`CORRECT EVIDENCE ${this.targetRef(stmt.target)} BY ${this.targetRef(stmt.by)}`);
642
+ if (stmt.expectState) {
643
+ this.write(` EXPECT STATE ${this.scalar(stmt.expectState.value)}`);
426
644
  }
427
- if (stmt.threshold) {
428
- this.write(` THRESHOLD ${this.thresholdValueToString(stmt.threshold)}`);
645
+ this.newline();
646
+ }
647
+ formatTransition(stmt) {
648
+ this.writeIndent();
649
+ this.write(`TRANSITION ACTIVITY ${this.targetRef(stmt.target)} TO ${this.scalar(stmt.to)}`);
650
+ this.newline();
651
+ if (stmt.finalize.length > 0) {
652
+ this.indentLevel++;
653
+ for (const clause of stmt.finalize) {
654
+ this.emitCommentsBefore(clause.range.start.line);
655
+ if (clause.kind === 'SetFieldsClause') {
656
+ this.formatAssignmentClause('SET FIELDS', clause.assignments, false);
657
+ }
658
+ else {
659
+ this.formatStructural(clause);
660
+ }
661
+ }
662
+ this.indentLevel--;
429
663
  }
430
- if (stmt.limit) {
431
- this.write(` LIMIT ${this.limitValueToString(stmt.limit)}`);
664
+ if (stmt.expectState) {
665
+ this.indentLevel++;
666
+ this.emitCommentsBefore(stmt.expectState.range.start.line);
667
+ this.writeIndent();
668
+ this.write(`EXPECT STATE ${this.scalar(stmt.expectState.value)}`);
669
+ this.newline();
670
+ this.indentLevel--;
432
671
  }
433
672
  }
434
- // ── EXPORT ─────────────────────────────────────────────────────────
435
- formatExport(stmt) {
673
+ formatSetRetention(stmt) {
436
674
  this.writeIndent();
437
- this.write(`EXPORT ${stmt.target}`);
675
+ this.write(`SET RETENTION ${this.targetRef(stmt.target)} `);
676
+ this.formatObjectBlock(stmt.assignments, false);
438
677
  this.newline();
439
- this.formatWhere(stmt.where);
440
- if (stmt.limit) {
678
+ if (stmt.where)
679
+ this.formatWhere(stmt.where, 'WHERE');
680
+ if (stmt.limit)
441
681
  this.formatLimit(stmt.limit);
682
+ if (stmt.expectVersion) {
683
+ this.writeIndent();
684
+ this.write(`EXPECT VERSION ${this.scalar(stmt.expectVersion.value)}`);
685
+ this.newline();
442
686
  }
443
687
  }
444
- // ── WHERE ──────────────────────────────────────────────────────────
445
- formatWhere(where) {
688
+ formatRemoval(keyword, stmt) {
446
689
  this.writeIndent();
447
- this.write('WHERE {');
690
+ this.write(`${keyword} ${this.targetRef(stmt.target)}`);
691
+ if (!stmt.where && !stmt.limit) {
692
+ if (stmt.expectState) {
693
+ this.write(` EXPECT STATE ${this.scalar(stmt.expectState.value)}`);
694
+ }
695
+ this.newline();
696
+ return;
697
+ }
448
698
  this.newline();
699
+ if (stmt.where)
700
+ this.formatWhere(stmt.where, 'WHERE');
701
+ if (stmt.limit)
702
+ this.formatLimit(stmt.limit);
703
+ if (stmt.expectState) {
704
+ this.writeIndent();
705
+ this.write(`EXPECT STATE ${this.scalar(stmt.expectState.value)}`);
706
+ this.newline();
707
+ }
708
+ }
709
+ formatPurge(stmt) {
710
+ this.writeIndent();
711
+ this.write(`PURGE ${this.targetRef(stmt.target)}`);
712
+ this.newline();
713
+ if (stmt.where)
714
+ this.formatWhere(stmt.where, 'WHERE');
715
+ if (stmt.limit)
716
+ this.formatLimit(stmt.limit);
449
717
  this.indentLevel++;
450
- for (const p of where.patterns) {
451
- this.formatWherePattern(p);
718
+ if (stmt.referencePolicy) {
719
+ this.writeIndent();
720
+ this.write(`REFERENCE POLICY ${this.scalar(stmt.referencePolicy)}`);
721
+ this.newline();
452
722
  }
453
- this.emitCommentsBefore(where.range.end.line);
454
- this.indentLevel--;
455
723
  this.writeIndent();
456
- this.write('}');
724
+ this.write(`CONFIRM ${stmt.confirm.value}`);
457
725
  this.newline();
726
+ this.indentLevel--;
458
727
  }
459
- formatWherePattern(p) {
460
- this.emitCommentsBefore(p.range.start.line);
461
- switch (p.kind) {
462
- case 'ConceptPattern':
463
- return this.formatConceptPattern(p);
464
- case 'PropositionPattern':
465
- return this.formatPropositionPattern(p);
466
- case 'FilterClause':
467
- return this.formatFilterClause(p);
468
- case 'NotClause':
469
- return this.formatNotClause(p);
470
- case 'OptionalClause':
471
- return this.formatOptionalClause(p);
472
- case 'UnionClause':
473
- return this.formatUnionClause(p);
728
+ formatMerge(stmt) {
729
+ this.writeIndent();
730
+ this.write(`MERGE CONCEPT ${this.targetRef(stmt.source)} INTO ${this.targetRef(stmt.into)}`);
731
+ this.newline();
732
+ if (stmt.where)
733
+ this.formatWhere(stmt.where, 'WHERE');
734
+ if (stmt.expectVersion) {
735
+ this.writeIndent();
736
+ this.write(`EXPECT VERSION ${this.scalar(stmt.expectVersion.value)}`);
737
+ this.newline();
474
738
  }
475
739
  }
476
- formatConceptPattern(p) {
740
+ // ────────────────────────────────────────────────────────────────────
741
+ // KML clause bodies
742
+ // ────────────────────────────────────────────────────────────────────
743
+ formatAssignmentClause(keyword, assignments, sort) {
477
744
  this.writeIndent();
478
- if (p.variable)
479
- this.write(`${p.variable} `);
480
- this.write('{');
481
- this.write(p.matcher.entries
482
- .map((e) => `${this.keyToString(e)}: ${this.exprToString(e.value, 0)}`)
483
- .join(', '));
484
- this.write('}');
745
+ this.write(`${keyword} `);
746
+ this.formatObjectBlock(assignments, sort);
485
747
  this.newline();
486
748
  }
487
- formatPropositionPattern(p) {
749
+ formatFacet(clause) {
488
750
  this.writeIndent();
489
- if (p.variable)
490
- this.write(`${p.variable} `);
491
- this.write(this.propositionPatternToString(p));
751
+ this.write(`SET FACET ${this.symbol(clause.facet)} `);
752
+ this.formatObjectBlock(clause.assignments, false);
492
753
  this.newline();
493
754
  }
494
- formatFilterClause(f) {
755
+ formatUnsetAttributes(clause) {
495
756
  this.writeIndent();
496
- this.write(`FILTER(${this.exprToString(f.expression, 0)})`);
757
+ this.write(`UNSET ATTRIBUTES { ${clause.fields.map((f) => this.fieldName(f.name, f.isQuoted)).join(', ')} }`);
497
758
  this.newline();
498
759
  }
499
- formatNotClause(n) {
500
- this.formatBlockClause('NOT', n.patterns, n.range.end.line);
501
- }
502
- formatOptionalClause(o) {
503
- this.formatBlockClause('OPTIONAL', o.patterns, o.range.end.line);
504
- }
505
- formatUnionClause(u) {
506
- this.formatBlockClause('UNION', u.patterns, u.range.end.line);
760
+ formatUnsetFacet(clause) {
761
+ this.writeIndent();
762
+ this.write(`UNSET FACET ${this.symbol(clause.facet)} { ${clause.fields.map((f) => this.fieldName(f.name, f.isQuoted)).join(', ')} }`);
763
+ this.newline();
507
764
  }
508
- /** Shared rendering for NOT / OPTIONAL / UNION blocks (consistent styling). */
509
- formatBlockClause(keyword, patterns, endLine) {
765
+ formatStructural(clause) {
510
766
  this.writeIndent();
511
- this.write(`${keyword} {`);
767
+ this.write('SET STRUCTURAL {');
512
768
  this.newline();
513
769
  this.indentLevel++;
514
- for (const p of patterns) {
515
- this.formatWherePattern(p);
770
+ for (const assignment of clause.assignments) {
771
+ this.emitCommentsBefore(assignment.range.start.line);
772
+ this.writeIndent();
773
+ this.write(`(${this.symbol(assignment.field)}, ${this.expr(assignment.value)})`);
774
+ if (assignment.options) {
775
+ this.write(` ${this.objectLiteralToString(assignment.options)}`);
776
+ }
777
+ this.newline();
516
778
  }
517
- this.emitCommentsBefore(endLine);
779
+ this.emitCommentsBefore(clause.range.end.line);
518
780
  this.indentLevel--;
519
781
  this.writeIndent();
520
782
  this.write('}');
521
783
  this.newline();
522
784
  }
523
- formatOrderBy(ob) {
785
+ formatUnsetStructural(clause) {
524
786
  this.writeIndent();
525
- const keys = ob.keys && ob.keys.length > 0
526
- ? ob.keys
527
- : [{ expression: ob.expression, direction: ob.direction }];
528
- this.write(`ORDER BY ${keys
529
- .map((key) => `${this.exprToString(key.expression, 0)} ${key.direction}`)
530
- .join(', ')}`);
787
+ this.write('UNSET STRUCTURAL {');
788
+ this.newline();
789
+ this.indentLevel++;
790
+ for (const removal of clause.removals) {
791
+ this.emitCommentsBefore(removal.range.start.line);
792
+ this.writeIndent();
793
+ this.write(`(${this.symbol(removal.field)}, ${this.expr(removal.value)})`);
794
+ this.newline();
795
+ }
796
+ this.emitCommentsBefore(clause.range.end.line);
797
+ this.indentLevel--;
798
+ this.writeIndent();
799
+ this.write('}');
531
800
  this.newline();
532
801
  }
533
- formatLimit(lim) {
802
+ // ────────────────────────────────────────────────────────────────────
803
+ // META
804
+ // ────────────────────────────────────────────────────────────────────
805
+ formatDescribe(stmt) {
534
806
  this.writeIndent();
535
- this.write(`LIMIT ${this.limitValueToString(lim)}`);
807
+ const words = {
808
+ PRIMER: 'PRIMER',
809
+ PROTOCOL: 'PROTOCOL',
810
+ EXECUTION_CONTEXT: 'EXECUTION CONTEXT',
811
+ CAPABILITIES: 'CAPABILITIES',
812
+ SPACE: 'SPACE',
813
+ SCHEMA_ENVIRONMENT: 'SCHEMA ENVIRONMENT',
814
+ PACKAGE: 'PACKAGE',
815
+ TYPE: 'TYPE',
816
+ PREDICATE: 'PREDICATE',
817
+ FACET: 'FACET',
818
+ STRUCTURAL_FIELD: 'STRUCTURAL FIELD',
819
+ COMPATIBILITY: 'COMPATIBILITY',
820
+ ERROR: 'ERROR',
821
+ TRANSACTION: 'TRANSACTION',
822
+ TRANSACTION_BY_IDEMPOTENCY_KEY: 'TRANSACTION BY IDEMPOTENCY KEY',
823
+ SNAPSHOT: 'SNAPSHOT',
824
+ CAPSULE: 'CAPSULE',
825
+ EPISTEMIC_POLICY: 'EPISTEMIC POLICY',
826
+ PROJECTION_CAPABILITY: 'PROJECTION CAPABILITY',
827
+ TRUST: 'TRUST',
828
+ ACCESS: 'ACCESS'
829
+ };
830
+ this.write(`DESCRIBE ${words[stmt.target]}`);
831
+ if (stmt.target === 'COMPATIBILITY' && stmt.from && stmt.to) {
832
+ this.write(` FROM ${this.scalar(stmt.from)} TO ${this.scalar(stmt.to)}`);
833
+ }
834
+ else if (stmt.value) {
835
+ this.write(` ${this.scalar(stmt.value)}`);
836
+ }
837
+ if (stmt.mode)
838
+ this.write(` MODE ${this.scalar(stmt.mode)}`);
839
+ if (stmt.asOf)
840
+ this.write(` ${this.asOfToString(stmt.asOf)}`);
841
+ if (stmt.with)
842
+ this.write(` WITH ${this.objectLiteralToString(stmt.with)}`);
536
843
  this.newline();
537
844
  }
538
- formatCursor(cur) {
845
+ formatList(stmt) {
539
846
  this.writeIndent();
540
- this.write(`CURSOR ${this.cursorValueToString(cur)}`);
847
+ const words = {
848
+ SPACES: 'SPACES',
849
+ SCHEMA_PACKAGES: 'SCHEMA PACKAGES',
850
+ TYPES: 'TYPES',
851
+ PREDICATES: 'PREDICATES',
852
+ FACETS: 'FACETS',
853
+ STRUCTURAL_FIELDS: 'STRUCTURAL FIELDS',
854
+ EPISTEMIC_POLICIES: 'EPISTEMIC POLICIES'
855
+ };
856
+ this.write(`LIST ${words[stmt.target]}`);
857
+ if (stmt.status)
858
+ this.write(` STATUS ${this.scalar(stmt.status)}`);
859
+ if (stmt.limit)
860
+ this.write(` LIMIT ${this.scalar(stmt.limit.value)}`);
861
+ if (stmt.cursor)
862
+ this.write(` CURSOR ${this.scalar(stmt.cursor.value)}`);
541
863
  this.newline();
542
864
  }
543
- // ────────────────────────────────────────────────────────────────────
544
- // Expression → string (depth-aware for nested indentation)
545
- // ────────────────────────────────────────────────────────────────────
546
- exprToString(expr, depth) {
547
- switch (expr.kind) {
548
- case 'VariableRef':
549
- return expr.name;
550
- case 'ParameterRef':
551
- return expr.name;
552
- case 'DotExpression':
553
- return `${this.exprToString(expr.object, depth)}.${expr.property}`;
554
- case 'StringLiteral':
555
- return expr.value;
556
- case 'NumberLiteral':
557
- return expr.raw;
558
- case 'BooleanLiteral':
559
- return String(expr.value);
560
- case 'NullLiteral':
561
- return 'null';
562
- case 'ArrayLiteral':
563
- return this.arrayToString(expr, depth);
564
- case 'ObjectLiteral':
565
- return this.objectToString(expr, depth);
566
- case 'BinaryExpression':
567
- return `${this.exprToString(expr.left, depth)} ${expr.operator} ${this.exprToString(expr.right, depth)}`;
568
- case 'UnaryExpression':
569
- return `${expr.operator}${this.exprToString(expr.operand, depth)}`;
570
- case 'FunctionCallExpr':
571
- if (expr.name === 'DISTINCT' && expr.args.length === 1) {
572
- return `DISTINCT ${this.exprToString(expr.args[0], depth)}`;
573
- }
574
- return `${expr.name}(${expr.args.map((a) => this.exprToString(a, depth)).join(', ')})`;
865
+ formatSearch(stmt) {
866
+ this.writeIndent();
867
+ this.write(`SEARCH ${stmt.searchKind} ${this.scalar(stmt.term)}`);
868
+ if (stmt.withType)
869
+ this.write(` WITH TYPE ${this.scalar(stmt.withType)}`);
870
+ if (stmt.withPredicate) {
871
+ this.write(` WITH PREDICATE ${this.scalar(stmt.withPredicate)}`);
575
872
  }
873
+ if (stmt.mode)
874
+ this.write(` MODE ${this.scalar(stmt.mode)}`);
875
+ if (stmt.threshold)
876
+ this.write(` THRESHOLD ${this.scalar(stmt.threshold)}`);
877
+ if (stmt.asOfSeq)
878
+ this.write(` AS OF SEQ ${this.scalar(stmt.asOfSeq)}`);
879
+ if (stmt.limit)
880
+ this.write(` LIMIT ${this.scalar(stmt.limit.value)}`);
881
+ if (stmt.cursor)
882
+ this.write(` CURSOR ${this.scalar(stmt.cursor.value)}`);
883
+ this.newline();
884
+ }
885
+ formatVerify(stmt) {
886
+ const words = {
887
+ CAPSULE: 'CAPSULE',
888
+ SCHEMA_PACKAGE: 'SCHEMA PACKAGE',
889
+ RECEIPT: 'RECEIPT',
890
+ BLOB: 'BLOB',
891
+ CHECKPOINT: 'CHECKPOINT'
892
+ };
893
+ this.writeIndent();
894
+ this.write(`VERIFY ${words[stmt.target]} ${this.scalar(stmt.value)}`);
895
+ this.newline();
576
896
  }
577
- arrayToString(arr, depth) {
578
- if (arr.elements.length === 0)
579
- return '[]';
580
- const inner = arr.elements.map((e) => this.exprToString(e, depth + 1));
581
- const singleLine = `[${inner.join(', ')}]`;
582
- if (singleLine.length <= 80 && !inner.some((s) => s.includes('\n'))) {
583
- return singleLine;
897
+ formatValidate(stmt) {
898
+ const words = {
899
+ KQL: 'KQL',
900
+ KML: 'KML',
901
+ CAPSULE: 'CAPSULE',
902
+ SCHEMA_PACKAGE: 'SCHEMA PACKAGE',
903
+ IMPORT_PLAN: 'IMPORT PLAN'
904
+ };
905
+ this.writeIndent();
906
+ this.write(`VALIDATE ${words[stmt.target]} ${this.scalar(stmt.value)}`);
907
+ if (stmt.options) {
908
+ this.write(` WITH ${this.objectLiteralToString(stmt.options)}`);
584
909
  }
585
- const baseIndent = this.indentAt(this.indentLevel + depth);
586
- const innerIndent = this.indentAt(this.indentLevel + depth + 1);
587
- return `[\n${inner.map((s) => `${innerIndent}${s}`).join(',\n')}\n${baseIndent}]`;
910
+ this.newline();
588
911
  }
589
- objectToString(obj, depth) {
590
- if (obj.entries.length === 0)
591
- return '{}';
592
- const entries = obj.entries.map((e) => `${this.keyToString(e)}: ${this.exprToString(e.value, depth + 1)}`);
593
- const singleLine = `{${entries.join(', ')}}`;
594
- if (singleLine.length <= 80 && !entries.some((s) => s.includes('\n'))) {
595
- return singleLine;
912
+ formatPreview(stmt) {
913
+ this.writeIndent();
914
+ if (stmt.target === 'KML') {
915
+ this.write(`PREVIEW KML ${this.scalar(stmt.value)}`);
916
+ }
917
+ else {
918
+ this.write(`PREVIEW IMPORT CAPSULE ${this.scalar(stmt.value)} INTO ${this.scalar(stmt.into)}`);
596
919
  }
597
- const baseIndent = this.indentAt(this.indentLevel + depth);
598
- const innerIndent = this.indentAt(this.indentLevel + depth + 1);
599
- return `{\n${entries.map((s) => `${innerIndent}${s}`).join(',\n')}\n${baseIndent}}`;
920
+ this.newline();
921
+ }
922
+ formatHistory(stmt) {
923
+ this.writeIndent();
924
+ this.write(`HISTORY ${stmt.target}`);
925
+ if (stmt.value)
926
+ this.write(` ${this.scalar(stmt.value)}`);
927
+ if (stmt.fromSeq)
928
+ this.write(` FROM SEQ ${this.scalar(stmt.fromSeq)}`);
929
+ if (stmt.toSeq)
930
+ this.write(` TO SEQ ${this.scalar(stmt.toSeq)}`);
931
+ if (stmt.limit)
932
+ this.write(` LIMIT ${this.scalar(stmt.limit.value)}`);
933
+ if (stmt.cursor)
934
+ this.write(` CURSOR ${this.scalar(stmt.cursor.value)}`);
935
+ this.newline();
936
+ }
937
+ formatChanges(stmt) {
938
+ this.writeIndent();
939
+ const keyword = stmt.mode === 'SINCE' ? 'SINCE' : 'AFTER SEQ';
940
+ this.write(`CHANGES ${keyword} ${this.scalar(stmt.value)}`);
941
+ if (stmt.limit)
942
+ this.write(` LIMIT ${this.scalar(stmt.limit.value)}`);
943
+ this.newline();
600
944
  }
601
- formatObjectEntry(entry) {
945
+ formatSnapshot(stmt) {
602
946
  this.writeIndent();
603
- const valStr = this.exprToString(entry.value, 0);
604
- this.write(`${this.keyToString(entry)}: ${valStr}`);
947
+ this.write('SNAPSHOT');
948
+ if (stmt.asOf)
949
+ this.write(` ${this.asOfToString(stmt.asOf)}`);
950
+ this.newline();
951
+ }
952
+ formatExport(stmt) {
953
+ this.writeIndent();
954
+ this.write(`EXPORT CAPSULE ${this.targetRef(stmt.target)}`);
955
+ this.newline();
956
+ this.formatWhere(stmt.where, 'WHERE');
957
+ if (stmt.options) {
958
+ this.writeIndent();
959
+ this.write(`WITH ${this.objectLiteralToString(stmt.options)}`);
960
+ this.newline();
961
+ }
962
+ if (stmt.asOf) {
963
+ this.writeIndent();
964
+ this.write(this.asOfToString(stmt.asOf));
965
+ this.newline();
966
+ }
605
967
  }
606
968
  // ────────────────────────────────────────────────────────────────────
607
- // Helpers
969
+ // Objects
608
970
  // ────────────────────────────────────────────────────────────────────
609
- keyToString(entry) {
610
- if (entry.isQuoted) {
611
- return `"${this.escapeString(entry.key)}"`;
971
+ /**
972
+ * Emits `{...}` on one line when it is short and comment-free, otherwise
973
+ * one entry per line. A block holding a comment always stays multi-line, so
974
+ * the comment keeps the key it was written against.
975
+ */
976
+ formatObjectBlock(object, sort) {
977
+ const entries = object.entries;
978
+ if (entries.length === 0) {
979
+ this.write('{}');
980
+ return;
612
981
  }
613
- return entry.key;
614
- }
615
- endpointToString(ep) {
616
- switch (ep.kind) {
617
- case 'VariableRef':
618
- return ep.name;
619
- case 'ConceptPattern': {
620
- const entries = ep.matcher.entries
621
- .map((e) => `${this.keyToString(e)}: ${this.exprToString(e.value, 0)}`)
622
- .join(', ');
623
- const prefix = ep.variable ? `${ep.variable} ` : '';
624
- return `${prefix}{${entries}}`;
625
- }
626
- case 'PropositionPattern': {
627
- const prefix = ep.variable ? `${ep.variable} ` : '';
628
- return `${prefix}${this.propositionPatternToString(ep)}`;
629
- }
630
- default:
631
- return '?unknown';
982
+ const hasComments = this.hasPendingCommentInRange(object.range.start.line, object.range.end.line);
983
+ const inline = this.objectLiteralToString(object, sort);
984
+ if (!hasComments &&
985
+ inline.length + this.indentLevel * this.opts.indentSize <= 78 &&
986
+ !inline.includes('\n')) {
987
+ this.write(inline);
988
+ return;
632
989
  }
633
- }
634
- propositionBlockPatternToString(block) {
635
- if (block.id) {
636
- return `(id: ${this.idValueToString(block.id)})`;
990
+ const ordered = sort && !hasComments ? this.sortObjectEntries(entries) : entries;
991
+ this.write('{');
992
+ this.newline();
993
+ this.indentLevel++;
994
+ for (let i = 0; i < ordered.length; i++) {
995
+ const entry = ordered[i];
996
+ this.emitCommentsBefore(entry.range.start.line);
997
+ this.writeIndent();
998
+ this.write(`${this.fieldName(entry.key, entry.isQuoted)}: ${this.expr(entry.value)}`);
999
+ if (i < ordered.length - 1)
1000
+ this.write(',');
1001
+ this.newline();
637
1002
  }
638
- return `(${this.endpointToString(block.subject)}, ${this.predicateToString(block.predicate)}, ${this.endpointToString(block.object)})`;
1003
+ this.emitCommentsBefore(object.range.end.line);
1004
+ this.indentLevel--;
1005
+ this.writeIndent();
1006
+ this.write('}');
639
1007
  }
640
- propositionPatternToString(pattern) {
641
- if (pattern.id) {
642
- return `(id: ${this.idValueToString(pattern.id)})`;
643
- }
644
- return `(${this.endpointToString(pattern.subject)}, ${this.predicateToString(pattern.predicate)}, ${this.endpointToString(pattern.object)})`;
1008
+ objectLiteralToString(object, sort = false) {
1009
+ if (object.entries.length === 0)
1010
+ return '{}';
1011
+ const entries = sort ? this.sortObjectEntries(object.entries) : object.entries;
1012
+ const inner = entries
1013
+ .map((e) => `${this.fieldName(e.key, e.isQuoted)}: ${this.expr(e.value)}`)
1014
+ .join(', ');
1015
+ return `{${inner}}`;
645
1016
  }
646
- idValueToString(id) {
647
- if (!id)
648
- return '""';
649
- if (id.kind === 'StringLiteral')
650
- return id.value;
651
- return id.name;
1017
+ objectPatternToString(pattern) {
1018
+ if (pattern.members.length === 0)
1019
+ return '{}';
1020
+ const inner = pattern.members
1021
+ .map((e) => `${this.fieldName(e.key, e.isQuoted)}: ${this.expr(e.value)}`)
1022
+ .join(', ');
1023
+ return `{${inner}}`;
652
1024
  }
653
- predicateToString(pred) {
654
- if (pred.kind === 'PredicateVariable') {
655
- return pred.name;
656
- }
657
- if (pred.kind === 'PredicateLiteral') {
658
- let s = `"${this.escapeString(pred.value)}"`;
659
- if (pred.hopRange) {
660
- if (pred.hopRange.max === undefined) {
661
- s += `{${pred.hopRange.min},}`;
662
- }
663
- else if (pred.hopRange.max === pred.hopRange.min) {
664
- s += `{${pred.hopRange.min}}`;
665
- }
666
- else {
667
- s += `{${pred.hopRange.min},${pred.hopRange.max}}`;
668
- }
669
- }
670
- return s;
671
- }
672
- // Alternation
673
- return pred.predicates.map((p) => this.predicateToString(p)).join(' | ');
1025
+ sortObjectEntries(entries) {
1026
+ // Alphabetical by key (attributes are an unordered map in KIP).
1027
+ return [...entries].sort((a, b) => a.key.localeCompare(b.key));
674
1028
  }
675
- limitValueToString(lim) {
676
- return this.numberOrParameterValueToString(lim.value);
1029
+ fieldName(key, isQuoted) {
1030
+ return isQuoted ? `"${this.escapeString(key)}"` : key;
677
1031
  }
678
- thresholdValueToString(threshold) {
679
- return this.numberOrParameterValueToString(threshold.value);
1032
+ // ────────────────────────────────────────────────────────────────────
1033
+ // Terms and expressions
1034
+ // ────────────────────────────────────────────────────────────────────
1035
+ tupleToString(tuple) {
1036
+ if (tuple.id)
1037
+ return `(id: ${this.scalar(tuple.id)})`;
1038
+ return `(${this.term(tuple.subject)}, ${this.predicate(tuple.predicate)}, ${this.term(tuple.object)})`;
680
1039
  }
681
- numberOrParameterValueToString(value) {
682
- if (value.kind === 'NumberLiteral')
683
- return value.raw;
684
- return value.name;
1040
+ term(term) {
1041
+ if (term.kind === 'ObjectPattern')
1042
+ return this.objectPatternToString(term);
1043
+ if (term.kind === 'PropositionTuple')
1044
+ return this.tupleToString(term);
1045
+ return this.expr(term);
685
1046
  }
686
- cursorValueToString(cur) {
687
- if (cur.value.kind === 'StringLiteral')
688
- return cur.value.value;
689
- return cur.value.name;
1047
+ predicate(expr) {
1048
+ return expr.atoms
1049
+ .map((atom) => {
1050
+ const base = this.predAtom(atom.atom);
1051
+ if (!atom.quantifier)
1052
+ return base;
1053
+ const q = atom.quantifier;
1054
+ if (!q.hasComma)
1055
+ return `${base}{${q.min}}`;
1056
+ return q.max === undefined
1057
+ ? `${base}{${q.min},}`
1058
+ : `${base}{${q.min},${q.max}}`;
1059
+ })
1060
+ .join(' | ');
690
1061
  }
691
- quotedOrParameterValueToString(value, fallback) {
692
- if (value?.kind === 'StringLiteral')
693
- return value.value;
694
- if (value?.kind === 'ParameterRef')
695
- return value.name;
696
- return fallback.startsWith(':')
697
- ? fallback
698
- : `"${this.escapeString(fallback)}"`;
1062
+ predAtom(atom) {
1063
+ return this.expr(atom);
699
1064
  }
700
- sortObjectEntries(entries) {
701
- // Alphabetical by key (attributes are an unordered map in KIP).
702
- return [...entries].sort((a, b) => a.key.localeCompare(b.key));
1065
+ symbol(symbol) {
1066
+ return symbol.kind === 'ParameterRef' ? symbol.name : symbol.value;
703
1067
  }
704
- isSimpleValue(expr) {
705
- return (expr.kind === 'StringLiteral' ||
706
- expr.kind === 'NumberLiteral' ||
707
- expr.kind === 'BooleanLiteral' ||
708
- expr.kind === 'NullLiteral' ||
709
- expr.kind === 'VariableRef' ||
710
- expr.kind === 'ParameterRef');
1068
+ scalar(value) {
1069
+ return this.expr(value);
711
1070
  }
712
- allSimpleExpressions(exprs) {
713
- return exprs.every((e) => e.kind === 'VariableRef' ||
714
- e.kind === 'DotExpression' ||
715
- e.kind === 'FunctionCallExpr');
1071
+ targetRef(ref) {
1072
+ return this.expr(ref);
1073
+ }
1074
+ expr(expr) {
1075
+ switch (expr.kind) {
1076
+ case 'StringLiteral':
1077
+ return expr.value;
1078
+ case 'NumberLiteral':
1079
+ return expr.raw;
1080
+ case 'BooleanLiteral':
1081
+ return expr.value ? 'true' : 'false';
1082
+ case 'NullLiteral':
1083
+ return 'null';
1084
+ case 'VariableRef':
1085
+ return expr.name;
1086
+ case 'ParameterRef':
1087
+ return expr.name;
1088
+ case 'FieldAccess':
1089
+ return (expr.base.name +
1090
+ expr.steps
1091
+ .map((step) => step.kind === 'DotStep' ? `.${step.name}` : `[${step.key.value}]`)
1092
+ .join(''));
1093
+ case 'FunctionCallExpr':
1094
+ return `${expr.name.toUpperCase()}(${expr.args.map((a) => this.expr(a)).join(', ')})`;
1095
+ case 'AggregateExpr':
1096
+ return `${expr.name}(${expr.distinct ? 'DISTINCT ' : ''}${this.expr(expr.argument)})`;
1097
+ case 'BinaryExpression':
1098
+ return `${this.expr(expr.left)} ${expr.operator} ${this.expr(expr.right)}`;
1099
+ case 'UnaryExpression':
1100
+ return `${expr.operator}${this.expr(expr.operand)}`;
1101
+ case 'ArrayLiteral':
1102
+ return `[${expr.elements.map((e) => this.expr(e)).join(', ')}]`;
1103
+ case 'ObjectLiteral':
1104
+ return this.objectLiteralToString(expr);
1105
+ case 'ObjectPattern':
1106
+ return this.objectPatternToString(expr);
1107
+ case 'PropositionTuple':
1108
+ return this.tupleToString(expr);
1109
+ }
716
1110
  }
717
1111
  escapeString(s) {
718
1112
  return s
@@ -722,9 +1116,6 @@ class Formatter {
722
1116
  .replace(/\t/g, '\\t')
723
1117
  .replace(/\r/g, '\\r');
724
1118
  }
725
- indentAt(level) {
726
- return ' '.repeat(level * this.opts.indentSize);
727
- }
728
1119
  indent() {
729
1120
  return ' '.repeat(this.indentLevel * this.opts.indentSize);
730
1121
  }