@malloydata/malloy 0.0.385 → 0.0.387

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 (37) hide show
  1. package/dist/internal.d.ts +2 -0
  2. package/dist/internal.js +3 -1
  3. package/dist/lang/prettify/binary-chain.d.ts +3 -0
  4. package/dist/lang/prettify/binary-chain.js +65 -0
  5. package/dist/lang/prettify/block-body.d.ts +4 -0
  6. package/dist/lang/prettify/block-body.js +87 -0
  7. package/dist/lang/prettify/error-listener.d.ts +6 -0
  8. package/dist/lang/prettify/error-listener.js +19 -0
  9. package/dist/lang/prettify/field-properties.d.ts +3 -0
  10. package/dist/lang/prettify/field-properties.js +57 -0
  11. package/dist/lang/prettify/formatter.d.ts +17 -0
  12. package/dist/lang/prettify/formatter.js +150 -0
  13. package/dist/lang/prettify/import-select.d.ts +3 -0
  14. package/dist/lang/prettify/import-select.js +88 -0
  15. package/dist/lang/prettify/index.d.ts +19 -0
  16. package/dist/lang/prettify/index.js +163 -0
  17. package/dist/lang/prettify/inline-renderer.d.ts +3 -0
  18. package/dist/lang/prettify/inline-renderer.js +101 -0
  19. package/dist/lang/prettify/leaf.d.ts +9 -0
  20. package/dist/lang/prettify/leaf.js +340 -0
  21. package/dist/lang/prettify/out.d.ts +12 -0
  22. package/dist/lang/prettify/out.js +74 -0
  23. package/dist/lang/prettify/pick-case.d.ts +5 -0
  24. package/dist/lang/prettify/pick-case.js +222 -0
  25. package/dist/lang/prettify/rules.d.ts +13 -0
  26. package/dist/lang/prettify/rules.js +111 -0
  27. package/dist/lang/prettify/sections.d.ts +4 -0
  28. package/dist/lang/prettify/sections.js +380 -0
  29. package/dist/lang/prettify/tokens.d.ts +13 -0
  30. package/dist/lang/prettify/tokens.js +185 -0
  31. package/dist/lang/prettify/types.d.ts +9 -0
  32. package/dist/lang/prettify/types.js +7 -0
  33. package/dist/lang/run-malloy-parser.d.ts +23 -0
  34. package/dist/lang/run-malloy-parser.js +32 -8
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/package.json +4 -4
@@ -0,0 +1,380 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * RULE: SECTION-STATEMENT — formatSectionStatement / formatSectionList
7
+ *
8
+ * A `keyword: items` block. The statement context wraps:
9
+ * <tags?> <ACCESS_LABEL?> <KEYWORD> <listCtx>
10
+ * We walk children: tags + access-label go through the normal dispatcher
11
+ * (so annotations are preserved); the KEYWORD is emitted manually; the
12
+ * list context dispatches to formatSectionList.
13
+ *
14
+ * formatSectionList rule (locked in with the user):
15
+ * - All bare items + total fits ≤ LINE_BUDGET → inline `kw: a, b, c`.
16
+ * - Single item that fits (even if it has `is`) → inline. Items containing
17
+ * a `{...}` body with more than one section statement are excluded —
18
+ * view bodies don't read on one line regardless of length.
19
+ * - Single is-item that doesn't fit inline → keep keyword and item on the
20
+ * same line (`nest: name is { …wrapped body… }`); the body's `{...}`
21
+ * wraps internally. Annotated items still take the keyword-on-own-line
22
+ * form so the annotation lands above its item.
23
+ * - Otherwise → wrapped: keyword on its own line; items at +1 indent;
24
+ * bare items flow-fill ≤ LINE_BUDGET, comma-separated intra-line,
25
+ * no trailing commas; `is` items each on own line; annotated items
26
+ * each on own line, annotation on the line above.
27
+ *
28
+ * After the last item, trailing comments that sit between the section and
29
+ * the enclosing `}` are also emitted at the inner indent — they belong to
30
+ * the section the user just wrote, not the parent block.
31
+ */
32
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ var desc = Object.getOwnPropertyDescriptor(m, k);
35
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
36
+ desc = { enumerable: true, get: function() { return m[k]; } };
37
+ }
38
+ Object.defineProperty(o, k2, desc);
39
+ }) : (function(o, m, k, k2) {
40
+ if (k2 === undefined) k2 = k;
41
+ o[k2] = m[k];
42
+ }));
43
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
44
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
45
+ }) : function(o, v) {
46
+ o["default"] = v;
47
+ });
48
+ var __importStar = (this && this.__importStar) || (function () {
49
+ var ownKeys = function(o) {
50
+ ownKeys = Object.getOwnPropertyNames || function (o) {
51
+ var ar = [];
52
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
53
+ return ar;
54
+ };
55
+ return ownKeys(o);
56
+ };
57
+ return function (mod) {
58
+ if (mod && mod.__esModule) return mod;
59
+ var result = {};
60
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
61
+ __setModuleDefault(result, mod);
62
+ return result;
63
+ };
64
+ })();
65
+ Object.defineProperty(exports, "__esModule", { value: true });
66
+ exports.formatSectionStatement = formatSectionStatement;
67
+ const antlr4ts_1 = require("antlr4ts");
68
+ const tree_1 = require("antlr4ts/tree");
69
+ const parser = __importStar(require("../lib/Malloy/MalloyParser"));
70
+ const tokens_1 = require("./tokens");
71
+ const leaf_1 = require("./leaf");
72
+ const inline_renderer_1 = require("./inline-renderer");
73
+ function formatSectionStatement(f, stmt, rule) {
74
+ var _a;
75
+ const keywordTok = findKeyword(stmt, rule.keywordTypes);
76
+ const listCtx = rule.list(stmt);
77
+ if (!keywordTok || !listCtx) {
78
+ (0, leaf_1.formatTokenRange)(f, stmt._start.tokenIndex, stmt._stop.tokenIndex);
79
+ return;
80
+ }
81
+ for (let i = 0; i < stmt.childCount; i++) {
82
+ const c = stmt.getChild(i);
83
+ const isKeywordChild = (c instanceof tree_1.TerminalNode && c.symbol === keywordTok) ||
84
+ (c instanceof antlr4ts_1.ParserRuleContext && childContainsToken(c, keywordTok));
85
+ if (isKeywordChild) {
86
+ (0, leaf_1.flushHiddenBefore)(f, keywordTok.tokenIndex);
87
+ if (f.o.indent > 0)
88
+ f.o.nl();
89
+ else
90
+ (0, leaf_1.startStatementLine)(f);
91
+ f.o.text(((_a = keywordTok.text) !== null && _a !== void 0 ? _a : '').replace(/\s+/g, ''));
92
+ (0, leaf_1.note)(f, keywordTok.type, keywordTok.tokenIndex, keywordTok);
93
+ continue;
94
+ }
95
+ if (c === listCtx) {
96
+ const items = listItems(listCtx, rule.itemKind);
97
+ if (items.length > 0)
98
+ formatSectionList(f, items, rule.itemKind);
99
+ continue;
100
+ }
101
+ f.format(c);
102
+ }
103
+ }
104
+ function childContainsToken(node, tok) {
105
+ for (let i = 0; i < node.childCount; i++) {
106
+ const c = node.getChild(i);
107
+ if (c instanceof tree_1.TerminalNode && c.symbol === tok)
108
+ return true;
109
+ }
110
+ return false;
111
+ }
112
+ function findKeyword(node, types) {
113
+ // Direct terminal children — fast path, the common case.
114
+ for (let i = 0; i < node.childCount; i++) {
115
+ const c = node.getChild(i);
116
+ if (c instanceof tree_1.TerminalNode && types.includes(c.symbol.type))
117
+ return c.symbol;
118
+ }
119
+ // Fallback: descend one level. Some rules wrap the keyword in a small
120
+ // nested rule (e.g. includeItem → accessLabelProp → INTERNAL).
121
+ for (let i = 0; i < node.childCount; i++) {
122
+ const c = node.getChild(i);
123
+ if (c instanceof antlr4ts_1.ParserRuleContext) {
124
+ for (let j = 0; j < c.childCount; j++) {
125
+ const g = c.getChild(j);
126
+ if (g instanceof tree_1.TerminalNode && types.includes(g.symbol.type))
127
+ return g.symbol;
128
+ }
129
+ }
130
+ }
131
+ return undefined;
132
+ }
133
+ function listItems(listCtx, itemKind) {
134
+ // Type predicate (`c is ParserRuleContext`) so callers don't need a cast.
135
+ const matches = (c) => {
136
+ if (!(c instanceof antlr4ts_1.ParserRuleContext))
137
+ return false;
138
+ switch (itemKind) {
139
+ case 'fieldEntry':
140
+ return c instanceof parser.QueryFieldEntryContext;
141
+ case 'nestEntry':
142
+ return c instanceof parser.NestEntryContext;
143
+ case 'fieldDef':
144
+ return c instanceof parser.FieldDefContext;
145
+ case 'fieldName':
146
+ return c instanceof parser.FieldNameContext;
147
+ case 'collectionMember':
148
+ return c instanceof parser.CollectionMemberContext;
149
+ case 'orderBySpec':
150
+ return c instanceof parser.OrderBySpecContext;
151
+ case 'fieldExpr':
152
+ return c instanceof parser.FieldExprContext;
153
+ case 'joinDef':
154
+ return c instanceof parser.JoinDefContext;
155
+ case 'includeField':
156
+ return c instanceof parser.IncludeFieldContext;
157
+ case 'indexElement':
158
+ return c instanceof parser.IndexElementContext;
159
+ }
160
+ };
161
+ const out = [];
162
+ for (let i = 0; i < listCtx.childCount; i++) {
163
+ const c = listCtx.getChild(i);
164
+ if (matches(c))
165
+ out.push(c);
166
+ }
167
+ return out;
168
+ }
169
+ function classifyItem(f, ctx, itemKind) {
170
+ // joinDef items always wrap onto their own line. They use `with` / `on`
171
+ // instead of `is`, but they're structurally one-per-line like is-items.
172
+ let hasIs = itemKind === 'joinDef';
173
+ let hasAnnotation = false;
174
+ for (let i = ctx._start.tokenIndex; i <= ctx._stop.tokenIndex; i++) {
175
+ const t = f.tokens[i];
176
+ if (t.type === tokens_1.L.IS)
177
+ hasIs = true;
178
+ if (t.type === tokens_1.L.ANNOTATION ||
179
+ t.type === tokens_1.L.DOC_ANNOTATION ||
180
+ t.type === tokens_1.L.BLOCK_ANNOTATION_BEGIN ||
181
+ t.type === tokens_1.L.DOC_BLOCK_ANNOTATION_BEGIN)
182
+ hasAnnotation = true;
183
+ }
184
+ return { ctx, hasIs, hasAnnotation };
185
+ }
186
+ function formatSectionList(f, items, itemKind) {
187
+ const itemInfos = items.map(it => classifyItem(f, it, itemKind));
188
+ const noAnnotations = itemInfos.every(info => !info.hasAnnotation);
189
+ const allBare = itemInfos.every(info => !info.hasIs && !info.hasAnnotation);
190
+ const firstItem = items[0];
191
+ const lastItem = items[items.length - 1];
192
+ // Inline candidate: no annotations, no hidden-channel comments anywhere in
193
+ // the items' span (renderItemInline drops them), AND either all bare or
194
+ // exactly one item. Items containing a `{...}` body with multiple inner
195
+ // statements are also excluded — collapsing a view body onto one line is
196
+ // hostile to read regardless of length.
197
+ const itemsHaveComments = (0, leaf_1.hasCommentsInRange)(f, firstItem._start.tokenIndex, lastItem._stop.tokenIndex);
198
+ const itemsHaveMultiStatementBody = itemInfos.some(info => hasMultiStatementCurlyBody(f, info.ctx));
199
+ const inlineEligible = noAnnotations &&
200
+ !itemsHaveComments &&
201
+ !itemsHaveMultiStatementBody &&
202
+ (allBare || items.length === 1);
203
+ if (inlineEligible) {
204
+ const renderedItems = itemInfos.map(info => (0, inline_renderer_1.renderItemInline)(f, info.ctx));
205
+ const inlineBody = renderedItems.join(', ');
206
+ const candidateLen = f.o.lineLengthSoFar() + 1 /* space */ + inlineBody.length;
207
+ if (candidateLen <= tokens_1.LINE_BUDGET) {
208
+ f.o.text(' ');
209
+ f.o.text(inlineBody);
210
+ (0, leaf_1.note)(f, lastItem._stop.type, lastItem._stop.tokenIndex, lastItem._stop);
211
+ return;
212
+ }
213
+ }
214
+ // Single is-item that doesn't fit inline: keep the keyword on the same
215
+ // line as the item (`nest: name is { …wrapped body… }`) instead of
216
+ // breaking before the name. The body's `{...}` will wrap on its own.
217
+ // Annotated items still need the keyword-on-own-line form so the
218
+ // annotation can land between them.
219
+ if (items.length === 1 &&
220
+ itemInfos[0].hasIs &&
221
+ !itemInfos[0].hasAnnotation &&
222
+ !itemsHaveComments) {
223
+ f.o.text(' ');
224
+ f.format(itemInfos[0].ctx);
225
+ f.lastEmittedType = lastItem._stop.type;
226
+ return;
227
+ }
228
+ // Wrapped form. Two paths:
229
+ // - No comments anywhere: original flow-fill — bare items pack into lines
230
+ // at LINE_BUDGET, `is`/annotated items each get their own line.
231
+ // - Comments anywhere in the items' span: every item emits on its own
232
+ // line via f.format(), which goes through emitVisibleToken /
233
+ // flushHiddenBefore and handles trailing-vs-leading attachment for
234
+ // hidden-channel tokens correctly. Flow-fill density is sacrificed for
235
+ // comment correctness.
236
+ f.o.indent++;
237
+ f.o.nl();
238
+ if (itemsHaveComments) {
239
+ f.lastEmittedIdx = firstItem._start.tokenIndex - 1;
240
+ for (let k = 0; k < itemInfos.length; k++) {
241
+ const info = itemInfos[k];
242
+ // flushHiddenBefore handles between-item comments and advances
243
+ // lastEmittedIdx so they aren't re-emitted by f.format(item)'s own first
244
+ // emit.
245
+ (0, leaf_1.flushHiddenBefore)(f, info.ctx._start.tokenIndex);
246
+ f.o.nl();
247
+ f.format(info.ctx);
248
+ }
249
+ flushSameLineTail(f, lastItem._stop);
250
+ f.o.indent--;
251
+ // Update lastEmittedType for leading-action decisions, but DO NOT reset
252
+ // lastEmittedIdx — flushSameLineTail may have advanced it past tail
253
+ // comments, and rolling it back would let the parent re-emit them.
254
+ f.lastEmittedType = lastItem._stop.type;
255
+ return;
256
+ }
257
+ // No-comments fast path: flow-fill packing as before.
258
+ let curBare = '';
259
+ const flushBare = () => {
260
+ if (curBare.length > 0) {
261
+ f.o.text(curBare);
262
+ f.o.nl();
263
+ curBare = '';
264
+ }
265
+ };
266
+ for (const info of itemInfos) {
267
+ if (info.hasIs || info.hasAnnotation) {
268
+ flushBare();
269
+ (0, leaf_1.flushHiddenBefore)(f, info.ctx._start.tokenIndex);
270
+ f.format(info.ctx);
271
+ f.o.nl();
272
+ }
273
+ else {
274
+ const itemText = (0, inline_renderer_1.renderItemInline)(f, info.ctx);
275
+ const indentChars = f.o.indent * tokens_1.INDENT_STR.length;
276
+ const sepLen = curBare.length > 0 ? 2 : 0; // ", "
277
+ const projected = indentChars + curBare.length + sepLen + itemText.length;
278
+ if (curBare.length > 0 && projected > tokens_1.LINE_BUDGET) {
279
+ flushBare();
280
+ curBare = itemText;
281
+ }
282
+ else {
283
+ curBare = curBare.length > 0 ? curBare + ', ' + itemText : itemText;
284
+ }
285
+ }
286
+ }
287
+ flushBare();
288
+ flushSameLineTail(f, lastItem._stop);
289
+ f.o.indent--;
290
+ // See comment in the with-comments branch: keep the advanced
291
+ // lastEmittedIdx so tail comments aren't re-emitted by the parent.
292
+ f.lastEmittedType = lastItem._stop.type;
293
+ }
294
+ // Does any `{...}` block inside this item contain more than one section
295
+ // keyword (group_by:, aggregate:, where:, …) at its top level? Used to gate
296
+ // the section-list inline form: a view body with multiple statements never
297
+ // reads well on a single line, even when it fits. Single-statement bodies
298
+ // like `{ where: x = 1 }` may still inline.
299
+ function hasMultiStatementCurlyBody(f, ctx) {
300
+ const fromIdx = ctx._start.tokenIndex;
301
+ const toIdx = ctx._stop.tokenIndex;
302
+ for (let i = fromIdx; i <= toIdx; i++) {
303
+ if (f.tokens[i].type !== tokens_1.L.OCURLY)
304
+ continue;
305
+ const close = (0, tokens_1.findMatching)(f.tokens, i, tokens_1.L.OCURLY, tokens_1.L.CCURLY);
306
+ let count = 0;
307
+ let depth = 0;
308
+ for (let j = i + 1; j < close; j++) {
309
+ const t = f.tokens[j];
310
+ if (t.type === tokens_1.L.OCURLY || t.type === tokens_1.L.OPAREN || t.type === tokens_1.L.OBRACK) {
311
+ depth++;
312
+ }
313
+ else if (t.type === tokens_1.L.CCURLY ||
314
+ t.type === tokens_1.L.CPAREN ||
315
+ t.type === tokens_1.L.CBRACK) {
316
+ depth--;
317
+ }
318
+ else if (depth === 0 && tokens_1.SECTION_TOKENS.has(t.type)) {
319
+ count++;
320
+ if (count > 1)
321
+ return true;
322
+ }
323
+ }
324
+ i = close;
325
+ }
326
+ return false;
327
+ }
328
+ // After the last item of a wrapped section list, flush trailing comments
329
+ // belonging to the section. There are two cases:
330
+ //
331
+ // 1. Same-line tail: a comment on the SAME source line as the last item.
332
+ // Always belongs to the last item; emit at the section's inner indent.
333
+ //
334
+ // 2. Different-line trailing comments that sit between the last item and
335
+ // the closing `}` of the enclosing block (no other statement follows).
336
+ // These visually belong to the section the user just wrote, not the
337
+ // block. Emit them at the inner indent so they stay associated with
338
+ // the section. If a real statement follows the comments, leave them
339
+ // for the parent — they're leading comments for that statement.
340
+ function flushSameLineTail(f, lastTok) {
341
+ const lastEndLine = (0, tokens_1.endLineOf)(lastTok);
342
+ let j = lastTok.tokenIndex + 1;
343
+ // Phase 1: same-line tail comments.
344
+ while (j < f.tokens.length) {
345
+ const t = f.tokens[j];
346
+ if (t.channel !== antlr4ts_1.Token.HIDDEN_CHANNEL)
347
+ break;
348
+ if (t.line !== lastEndLine)
349
+ break;
350
+ j++;
351
+ }
352
+ if (j > lastTok.tokenIndex + 1) {
353
+ // Same-line comments: drop the wrapping loop's per-item newline so
354
+ // emitHiddenToken's same-line branch reattaches the comment correctly
355
+ // (and adds a trailing newline back for EOL comments). Otherwise a
356
+ // re-parse sees a different-line comment, breaking idempotence.
357
+ f.o.trimTrailingNewlines();
358
+ (0, leaf_1.flushHiddenBefore)(f, j);
359
+ }
360
+ // Phase 2: own-line comments before the next visible token. If the next
361
+ // visible token is a closing `}`, the comments visually belong to this
362
+ // section — emit them at the inner indent. Otherwise leave them for the
363
+ // parent (they're leading comments for whatever follows).
364
+ let k = j;
365
+ let trailingHidden = 0;
366
+ while (k < f.tokens.length) {
367
+ const t = f.tokens[k];
368
+ if (t.channel !== antlr4ts_1.Token.HIDDEN_CHANNEL)
369
+ break;
370
+ trailingHidden++;
371
+ k++;
372
+ }
373
+ if (trailingHidden > 0 && k < f.tokens.length) {
374
+ const next = f.tokens[k];
375
+ if (next.type === tokens_1.L.CCURLY) {
376
+ (0, leaf_1.flushHiddenBefore)(f, k);
377
+ }
378
+ }
379
+ }
380
+ //# sourceMappingURL=sections.js.map
@@ -0,0 +1,13 @@
1
+ import type { Token } from 'antlr4ts';
2
+ import { MalloyLexer } from '../lib/Malloy/MalloyLexer';
3
+ export declare const L: typeof MalloyLexer;
4
+ export declare const LINE_BUDGET = 100;
5
+ export declare const INDENT_STR = " ";
6
+ export declare const SECTION_TOKENS: Set<number>;
7
+ export declare const TOP_LEVEL_STARTERS: Set<number>;
8
+ export declare const CALL_HUG_AFTER: Set<number>;
9
+ export declare const BINARY_OPS: Set<number>;
10
+ export type LeadingAction = 'glue' | 'hug' | 'space';
11
+ export declare function leadingAction(prevType: number | null, nextType: number): LeadingAction;
12
+ export declare function endLineOf(t: Token): number;
13
+ export declare function findMatching(tokens: Token[], startIdx: number, beginType: number, endType: number): number;
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * Token classification, layout config, and small token utilities used by the
7
+ * prettifier's leaf walker and rule formatters.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.BINARY_OPS = exports.CALL_HUG_AFTER = exports.TOP_LEVEL_STARTERS = exports.SECTION_TOKENS = exports.INDENT_STR = exports.LINE_BUDGET = exports.L = void 0;
11
+ exports.leadingAction = leadingAction;
12
+ exports.endLineOf = endLineOf;
13
+ exports.findMatching = findMatching;
14
+ const MalloyLexer_1 = require("../lib/Malloy/MalloyLexer");
15
+ exports.L = MalloyLexer_1.MalloyLexer;
16
+ // ---------- Global formatting config ----------
17
+ exports.LINE_BUDGET = 100;
18
+ exports.INDENT_STR = ' '; // two spaces per indent level
19
+ // ---------- Token classification ----------
20
+ // Section keywords that introduce a `keyword: items` block (or a single value
21
+ // for some). Used by the leaf walker as a fallback newline rule when no
22
+ // explicit section-statement handler caught us.
23
+ exports.SECTION_TOKENS = new Set([
24
+ exports.L.ACCEPT,
25
+ exports.L.AGGREGATE,
26
+ exports.L.CALCULATE,
27
+ exports.L.CALCULATION,
28
+ exports.L.CONNECTION,
29
+ exports.L.DECLARE,
30
+ exports.L.DIMENSION,
31
+ exports.L.DRILL,
32
+ exports.L.EXCEPT,
33
+ exports.L.EXTENDQ,
34
+ exports.L.GROUP_BY,
35
+ exports.L.GROUPED_BY,
36
+ exports.L.HAVING,
37
+ exports.L.INDEX,
38
+ exports.L.INTERNAL,
39
+ exports.L.JOIN_CROSS,
40
+ exports.L.JOIN_ONE,
41
+ exports.L.JOIN_MANY,
42
+ exports.L.LIMIT,
43
+ exports.L.MEASURE,
44
+ exports.L.NEST,
45
+ exports.L.ORDER_BY,
46
+ exports.L.PARTITION_BY,
47
+ exports.L.PRIMARY_KEY,
48
+ exports.L.PRIVATE,
49
+ exports.L.PROJECT,
50
+ exports.L.PUBLIC,
51
+ exports.L.QUERY,
52
+ exports.L.RENAME,
53
+ exports.L.RUN,
54
+ exports.L.SAMPLE,
55
+ exports.L.SELECT,
56
+ exports.L.SOURCE,
57
+ exports.L.TYPE,
58
+ exports.L.TOP,
59
+ exports.L.WHERE,
60
+ exports.L.VIEW,
61
+ exports.L.TIMEZONE,
62
+ ]);
63
+ // Top-level statement starters. At column 0 each gets a blank line before it
64
+ // when introducing a new statement (subject to the same-kind-no-blank rule).
65
+ exports.TOP_LEVEL_STARTERS = new Set([
66
+ exports.L.SOURCE,
67
+ exports.L.QUERY,
68
+ exports.L.RUN,
69
+ exports.L.IMPORT,
70
+ ]);
71
+ // Token types after which an immediately following `(` or `[` is a call /
72
+ // subscript and should hug (no leading space). Anything else (binary ops,
73
+ // IS/AS/EXTEND/ON/WHEN/PICK/etc.) gets a space — the `(` is grouping.
74
+ exports.CALL_HUG_AFTER = new Set([
75
+ exports.L.IDENTIFIER,
76
+ exports.L.CPAREN,
77
+ exports.L.CBRACK,
78
+ // Aggregate / built-in callable keywords commonly used as function names.
79
+ exports.L.COUNT,
80
+ exports.L.SUM,
81
+ exports.L.AVG,
82
+ exports.L.MIN,
83
+ exports.L.MAX,
84
+ exports.L.TABLE,
85
+ exports.L.SQL,
86
+ exports.L.COMPOSE,
87
+ exports.L.CAST,
88
+ exports.L.NOW,
89
+ exports.L.LAST,
90
+ // Ungrouped / level-modifier function-style calls.
91
+ exports.L.ALL,
92
+ exports.L.EXCLUDE,
93
+ // Timeframe truncation keywords used as functions: year(x), month(x), …
94
+ exports.L.YEAR,
95
+ exports.L.QUARTER,
96
+ exports.L.MONTH,
97
+ exports.L.WEEK,
98
+ exports.L.DAY,
99
+ exports.L.HOUR,
100
+ exports.L.MINUTE,
101
+ exports.L.SECOND,
102
+ // Type-cast keyword names: timestamp(x), date(x), number(x), string(x), …
103
+ exports.L.TIMESTAMP,
104
+ exports.L.TIMESTAMPTZ,
105
+ exports.L.DATE,
106
+ exports.L.NUMBER,
107
+ exports.L.STRING,
108
+ exports.L.BOOLEAN,
109
+ exports.L.JSON,
110
+ ]);
111
+ // Binary operators that get spaces on both sides at the leaf level.
112
+ // (Chain wrapping for and/or/??/+/- happens at parse-tree level — see
113
+ // formatBinaryChain.)
114
+ exports.BINARY_OPS = new Set([
115
+ exports.L.PLUS,
116
+ exports.L.MINUS,
117
+ exports.L.STAR,
118
+ exports.L.SLASH,
119
+ exports.L.PERCENT,
120
+ exports.L.STARSTAR,
121
+ exports.L.EQ,
122
+ exports.L.NE,
123
+ exports.L.LT,
124
+ exports.L.GT,
125
+ exports.L.LTE,
126
+ exports.L.GTE,
127
+ exports.L.AND,
128
+ exports.L.OR,
129
+ exports.L.MATCH,
130
+ exports.L.NOT_MATCH,
131
+ exports.L.ARROW,
132
+ exports.L.FAT_ARROW,
133
+ exports.L.BAR,
134
+ exports.L.AMPER,
135
+ ]);
136
+ function leadingAction(prevType, nextType) {
137
+ if (nextType === exports.L.DOT ||
138
+ nextType === exports.L.COMMA ||
139
+ nextType === exports.L.SEMI ||
140
+ nextType === exports.L.COLON ||
141
+ nextType === exports.L.TRIPLECOLON ||
142
+ nextType === exports.L.EXCLAM ||
143
+ nextType === exports.L.CPAREN ||
144
+ nextType === exports.L.CBRACK) {
145
+ return 'glue';
146
+ }
147
+ // After the `!` cast operator (`epoch_ms!timestamp(x)`), the next token
148
+ // glues to it like the `.` operator does.
149
+ if (prevType === exports.L.EXCLAM)
150
+ return 'glue';
151
+ if ((nextType === exports.L.OPAREN || nextType === exports.L.OBRACK) &&
152
+ prevType !== null &&
153
+ exports.CALL_HUG_AFTER.has(prevType)) {
154
+ return 'hug';
155
+ }
156
+ return 'space';
157
+ }
158
+ // ---------- Token utilities ----------
159
+ function endLineOf(t) {
160
+ var _a, _b;
161
+ const text = (_a = t.text) !== null && _a !== void 0 ? _a : '';
162
+ const newlines = ((_b = text.match(/\n/g)) !== null && _b !== void 0 ? _b : []).length;
163
+ return t.line + newlines;
164
+ }
165
+ // Find the index of the closing token that matches the opener at startIdx.
166
+ // Counts nested begin/end pairs of the same types.
167
+ //
168
+ // Precondition: must not be called inside an SQL string region — depth
169
+ // tracking only knows about the begin/end token types passed in, not about
170
+ // embedded SQL. In practice we never call this inside an SQL block (we skip
171
+ // past them).
172
+ function findMatching(tokens, startIdx, beginType, endType) {
173
+ let depth = 1;
174
+ for (let j = startIdx + 1; j < tokens.length; j++) {
175
+ if (tokens[j].type === beginType)
176
+ depth++;
177
+ else if (tokens[j].type === endType) {
178
+ depth--;
179
+ if (depth === 0)
180
+ return j;
181
+ }
182
+ }
183
+ return tokens.length - 1;
184
+ }
185
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1,9 @@
1
+ export interface PrettifyError {
2
+ message: string;
3
+ line: number;
4
+ column: number;
5
+ }
6
+ export interface PrettifyResult {
7
+ result: string;
8
+ errors: PrettifyError[];
9
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=types.js.map
@@ -1,3 +1,26 @@
1
+ import { CommonTokenStream } from 'antlr4ts';
2
+ import type { ANTLRErrorListener, Token, CodePointCharStream } from 'antlr4ts';
3
+ import { MalloyParser } from './lib/Malloy/MalloyParser';
1
4
  import type { MessageLogger } from './parse-log';
2
5
  import type { ParseInfo, SourceInfo } from './utils';
6
+ export interface MalloyParserSetupOptions {
7
+ lexerErrorListener?: ANTLRErrorListener<any>;
8
+ parserErrorListener?: ANTLRErrorListener<Token>;
9
+ }
10
+ /**
11
+ * Build a Malloy lexer/token-stream/parser triplet ready to invoke a grammar
12
+ * rule on `code`. Centralises the lexer fix (HandlesOverpoppingLexer for
13
+ * unmatched `}%`) and the project's MalloyErrorStrategy so callers can't
14
+ * accidentally diverge on those.
15
+ *
16
+ * The token stream is NOT pre-filled. Callers that need all tokens up front
17
+ * (e.g. the prettifier's leaf walker) should call `tokenStream.fill()` before
18
+ * iterating; callers that only need parser-driven access can leave it alone
19
+ * — the parser pulls tokens as it consumes the rule.
20
+ */
21
+ export declare function makeMalloyParser(code: string, options?: MalloyParserSetupOptions): {
22
+ inputStream: CodePointCharStream;
23
+ tokenStream: CommonTokenStream;
24
+ parser: MalloyParser;
25
+ };
3
26
  export declare function runMalloyParser(code: string, sourceURL: string, sourceInfo: SourceInfo, logger: MessageLogger, grammarRule?: string): ParseInfo;
@@ -6,6 +6,7 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.makeMalloyParser = makeMalloyParser;
9
10
  exports.runMalloyParser = runMalloyParser;
10
11
  const antlr4ts_1 = require("antlr4ts");
11
12
  const MalloyLexer_1 = require("./lib/Malloy/MalloyLexer");
@@ -24,24 +25,47 @@ class HandlesOverpoppingLexer extends MalloyLexer_1.MalloyLexer {
24
25
  return super.popMode();
25
26
  }
26
27
  }
27
- function runMalloyParser(code, sourceURL, sourceInfo, logger, grammarRule = 'malloyDocument') {
28
+ /**
29
+ * Build a Malloy lexer/token-stream/parser triplet ready to invoke a grammar
30
+ * rule on `code`. Centralises the lexer fix (HandlesOverpoppingLexer for
31
+ * unmatched `}%`) and the project's MalloyErrorStrategy so callers can't
32
+ * accidentally diverge on those.
33
+ *
34
+ * The token stream is NOT pre-filled. Callers that need all tokens up front
35
+ * (e.g. the prettifier's leaf walker) should call `tokenStream.fill()` before
36
+ * iterating; callers that only need parser-driven access can leave it alone
37
+ * — the parser pulls tokens as it consumes the rule.
38
+ */
39
+ function makeMalloyParser(code, options = {}) {
28
40
  const inputStream = antlr4ts_1.CharStreams.fromString(code);
29
41
  const lexer = new HandlesOverpoppingLexer(inputStream);
42
+ if (options.lexerErrorListener) {
43
+ lexer.removeErrorListeners();
44
+ lexer.addErrorListener(options.lexerErrorListener);
45
+ }
30
46
  const tokenStream = new antlr4ts_1.CommonTokenStream(lexer);
31
- const malloyParser = new MalloyParser_1.MalloyParser(tokenStream);
32
- malloyParser.removeErrorListeners();
33
- malloyParser.addErrorListener(new malloy_parser_error_listener_1.MalloyParserErrorListener(logger, sourceURL, sourceInfo));
34
- malloyParser.errorHandler = new malloy_error_strategy_1.MalloyErrorStrategy();
47
+ const parser = new MalloyParser_1.MalloyParser(tokenStream);
48
+ if (options.parserErrorListener) {
49
+ parser.removeErrorListeners();
50
+ parser.addErrorListener(options.parserErrorListener);
51
+ }
52
+ parser.errorHandler = new malloy_error_strategy_1.MalloyErrorStrategy();
53
+ return { inputStream, tokenStream, parser };
54
+ }
55
+ function runMalloyParser(code, sourceURL, sourceInfo, logger, grammarRule = 'malloyDocument') {
56
+ const { inputStream, tokenStream, parser } = makeMalloyParser(code, {
57
+ parserErrorListener: new malloy_parser_error_listener_1.MalloyParserErrorListener(logger, sourceURL, sourceInfo),
58
+ });
35
59
  // Admitted code smell here, testing likes to parse from an arbitrary
36
60
  // node and this is the simplest way to allow that.
37
61
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- const parseFunc = malloyParser[grammarRule];
62
+ const parseFunc = parser[grammarRule];
39
63
  if (!parseFunc) {
40
64
  throw new Error(`No such parse rule as ${grammarRule}`);
41
65
  }
42
66
  return {
43
- root: parseFunc.call(malloyParser),
44
- tokenStream: tokenStream,
67
+ root: parseFunc.call(parser),
68
+ tokenStream,
45
69
  sourceStream: inputStream,
46
70
  sourceInfo,
47
71
  sourceURL,