@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,163 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * ============================================================================
7
+ * Malloy pretty-printer (experimental, /internal export — no stability promise)
8
+ * ============================================================================
9
+ *
10
+ * Architecture
11
+ * ------------
12
+ * 1. Lex + parse the input.
13
+ * 2. Walk the parse tree from `Formatter.format(node)`. The dispatcher routes
14
+ * each rule context to a per-rule free function in a sibling module;
15
+ * everything unhandled recurses to children, eventually reaching terminal
16
+ * tokens that emit through `emitVisibleToken` (the leaf).
17
+ * 3. The leaf walker handles per-token spacing/indentation/comments. It is
18
+ * v1's behaviour and is also the fallback when parsing fails.
19
+ *
20
+ * File layout
21
+ * -----------
22
+ * - ./out — Out buffer (indent, newlines, single-space coalescing).
23
+ * - ./tokens — LINE_BUDGET, INDENT_STR; classification sets
24
+ * (SECTION_TOKENS, BINARY_OPS, CALL_HUG_AFTER, etc.);
25
+ * findMatching, endLineOf.
26
+ * - ./rules — SECTION_STATEMENT_RULES + STATEMENT_KIND_BY_CTX.
27
+ * A maintainer adding a new section keyword lands here.
28
+ * - ./error-listener — CollectingErrorListener.
29
+ * - ./types — PrettifyError, PrettifyResult.
30
+ * - ./formatter — Formatter class (state + format() dispatcher).
31
+ * - ./leaf — emitVisibleToken + per-token state mutators
32
+ * (note, flushHiddenBefore, startStatementLine, …)
33
+ * and the small read-only helpers used by rule
34
+ * formatters (approxInlineSpan, hasCommentsInRange,
35
+ * formatTokenRange).
36
+ * - ./inline-renderer — renderItemInline (flat-string form for budget
37
+ * measurement and column alignment).
38
+ * - ./block-body — formatBlockBody, formatTopLevel.
39
+ * - ./sections — formatSectionStatement / formatSectionList.
40
+ * - ./field-properties — formatFieldProperties (postfix `{…}`).
41
+ * - ./pick-case — formatPickStatement, formatCaseStatement.
42
+ * - ./binary-chain — formatBinaryChain.
43
+ * - ./index (this) — prettify() entry point + type re-exports.
44
+ *
45
+ * Decisions worth knowing
46
+ * -----------------------
47
+ * - Comparison operators (`=`, `!=`, `<`, `>`, …) are kept glued to their
48
+ * operands. We only break chains at and/or/??/+/-. Justification: LHS/RHS
49
+ * of a comparison reads as one unit; breaking inside is more confusing
50
+ * than the line being long.
51
+ * - SQL strings (`"""…"""`, including `%{…}` malloy interpolations) and
52
+ * block annotations (`#" … "`) are emitted verbatim from source. We don't
53
+ * own a SQL formatter; annotation indentation is significant.
54
+ * - `;` is the compact-inline statement separator. Wrapped form drops it
55
+ * (newlines do the job); inline form keeps it.
56
+ * - `,` in section-list bare flow: intra-line yes, end-of-line never.
57
+ * - Single-arg function calls don't wrap (no point — nowhere useful to break).
58
+ * - `(` hugs only after a known-callable token (CALL_HUG_AFTER); after `is`,
59
+ * `as`, `extend`, `on`, `when`, etc. the `(` is grouping and gets a space.
60
+ * CALL_HUG_AFTER includes the keyword-named built-ins that are commonly
61
+ * used as functions: ALL, EXCLUDE, the timeframe truncation keywords
62
+ * (YEAR/MONTH/DAY/…), and the cast-target type names
63
+ * (TIMESTAMP/DATE/NUMBER/STRING/BOOLEAN/JSON).
64
+ * - `!` is the cast operator (`epoch_ms!timestamp(x)`); it glues to both
65
+ * sides like `.` does.
66
+ * - Empty `{}` collapses inline (`extend {}`, not `extend {\n}`).
67
+ * - `import {a, b, c} from 'x'` formats as a flat list when it fits on the
68
+ * line; otherwise one item per line at +1 indent.
69
+ * - `view:` definitions in a block body always have a blank line before
70
+ * each one (after the first), even when no blank was in the source. The
71
+ * same-kind-no-blank rule still applies to other statement kinds.
72
+ * - `{...}` bodies that contain more than one section statement never
73
+ * collapse onto a single line, even when they would fit. Reading two
74
+ * `group_by:` clauses jammed on one line is hostile.
75
+ * - Single is-item section lists keep the keyword and item on the same
76
+ * line (`nest: name is { … }`), so the body wraps naturally instead of
77
+ * forcing a `nest:\n name is {` opener split.
78
+ * - join_one / join_many / join_cross multi-item lists always wrap one
79
+ * item per line — items use `with`/`on` instead of `is` but are
80
+ * structurally is-like.
81
+ * - Trailing comments between the last item of a section list and the
82
+ * enclosing `}` are emitted at the section's inner indent, so they
83
+ * stay associated with the section the user wrote them in.
84
+ *
85
+ * Adding a new section-statement
86
+ * ------------------------------
87
+ * Add a row to SECTION_STATEMENT_RULES in ./rules with the rule's context
88
+ * class, the keyword token type(s), the list-context accessor, and the
89
+ * item-kind tag. Add a corresponding entry to listItems() in ./sections.
90
+ *
91
+ * Note: section keywords NOT in the table fall through to the leaf walker
92
+ * (which produces correct-but-plain output). Add a row only when the default
93
+ * isn't good enough — flow-fill, alignment, or annotation handling.
94
+ *
95
+ * Inter-token spacing
96
+ * -------------------
97
+ * The classifier `leadingAction(prev, next)` in ./tokens is the single
98
+ * source of truth for what separator (glue, hug, or coalescing space) goes
99
+ * between two adjacent tokens. Both walkers — emitVisibleToken in ./leaf
100
+ * and renderItemInline in ./inline-renderer — consult it, so a change to
101
+ * `leadingAction` (e.g. adding a token type that hugs `(`) takes effect in
102
+ * both paths automatically. Walker-specific concerns (newlines, indent,
103
+ * paren-wrap decisions, compact-inline `, ` / `; `) live in the walker.
104
+ */
105
+ Object.defineProperty(exports, "__esModule", { value: true });
106
+ exports.prettify = prettify;
107
+ const antlr4ts_1 = require("antlr4ts");
108
+ const run_malloy_parser_1 = require("../run-malloy-parser");
109
+ const error_listener_1 = require("./error-listener");
110
+ const formatter_1 = require("./formatter");
111
+ const leaf_1 = require("./leaf");
112
+ /**
113
+ * Pretty-print a Malloy source string.
114
+ *
115
+ * **Experimental — this API may vanish or change at any time without notice.**
116
+ * It is exposed only via `@malloydata/malloy/internal` and is not covered by
117
+ * any compatibility commitment. Do not depend on it from anything you can't
118
+ * fix in a single PR.
119
+ *
120
+ * Parses the input, walks the parse tree, and emits a reformatted string.
121
+ *
122
+ * `errors` surfaces parse errors only (lexer + parser). Semantic / compile
123
+ * errors aren't checked here. If `errors.length > 0` you have a bigger problem
124
+ * than formatting — output is best-effort and not guaranteed to round-trip.
125
+ *
126
+ * @experimental
127
+ */
128
+ function prettify(src) {
129
+ const lexerErrors = new error_listener_1.CollectingErrorListener();
130
+ const parserErrors = new error_listener_1.CollectingErrorListener();
131
+ const { tokenStream, parser: malloyParser } = (0, run_malloy_parser_1.makeMalloyParser)(src, {
132
+ lexerErrorListener: lexerErrors,
133
+ parserErrorListener: parserErrors,
134
+ });
135
+ tokenStream.fill();
136
+ const tokens = tokenStream.getTokens();
137
+ let root = null;
138
+ try {
139
+ root = malloyParser.malloyDocument();
140
+ }
141
+ catch {
142
+ root = null;
143
+ }
144
+ const f = new formatter_1.Formatter(src, tokens);
145
+ if (root) {
146
+ f.format(root);
147
+ }
148
+ else {
149
+ // Parse failed. Fall back to leaf-only emission so we still produce
150
+ // something reasonable.
151
+ for (let i = 0; i < tokens.length; i++) {
152
+ const t = tokens[i];
153
+ if (t.channel !== antlr4ts_1.Token.HIDDEN_CHANNEL && t.type !== antlr4ts_1.Token.EOF) {
154
+ (0, leaf_1.emitVisibleToken)(f, t, i);
155
+ }
156
+ }
157
+ }
158
+ return {
159
+ result: f.o.toString(),
160
+ errors: [...lexerErrors.errors, ...parserErrors.errors],
161
+ };
162
+ }
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import type { ParserRuleContext } from 'antlr4ts';
2
+ import type { Formatter } from './formatter';
3
+ export declare function renderItemInline(f: Formatter, ctx: ParserRuleContext): string;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * renderItemInline — flat-string form of a parse-rule's token range.
7
+ *
8
+ * Used by:
9
+ * - section-list inline measurement and bare-item flow-fill
10
+ * - postfix `{…}` inline form
11
+ * - pick / case alignment (rendering values and conditions to strings)
12
+ *
13
+ * Inter-token spacing comes from `leadingAction` in ./tokens, the same
14
+ * classifier the leaf walker (./leaf) consults. Both walkers therefore agree
15
+ * on what spacing goes between any two adjacent tokens; inline measurement
16
+ * predicts actual emission.
17
+ *
18
+ * Walker-specific divergence:
19
+ * - This produces a flat string (no newlines, no indentation).
20
+ * - SEMI emits `; ` (compact-inline form), not a newline.
21
+ * - COMMA emits `, ` (intra-line form), not a newline.
22
+ * - The space-coalescing skip-list omits `\n` (we never emit one) but is
23
+ * otherwise the same as Out.space.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.renderItemInline = renderItemInline;
27
+ const antlr4ts_1 = require("antlr4ts");
28
+ const tokens_1 = require("./tokens");
29
+ function renderItemInline(f, ctx) {
30
+ var _a;
31
+ let buf = '';
32
+ let lastType = null;
33
+ // Coalescing space: skip if buffer is empty or last char already provides
34
+ // separation. Mirrors Out.space() (./out) minus the newline check, since
35
+ // this renderer never emits a newline.
36
+ const space = () => {
37
+ if (buf.length === 0)
38
+ return;
39
+ const last = buf[buf.length - 1];
40
+ if (last === ' ' || last === '(' || last === '[' || last === '.')
41
+ return;
42
+ buf += ' ';
43
+ };
44
+ const trim = () => {
45
+ buf = buf.replace(/ +$/, '');
46
+ };
47
+ for (let i = ctx._start.tokenIndex; i <= ctx._stop.tokenIndex; i++) {
48
+ const t = f.tokens[i];
49
+ if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
50
+ continue;
51
+ const text = (_a = t.text) !== null && _a !== void 0 ? _a : '';
52
+ if (t.type === tokens_1.L.SQL_BEGIN) {
53
+ const endIdx = (0, tokens_1.findMatching)(f.tokens, i, tokens_1.L.SQL_BEGIN, tokens_1.L.SQL_END);
54
+ const stop = f.tokens[endIdx].stopIndex;
55
+ space();
56
+ buf += f.src.substring(t.startIndex, stop + 1);
57
+ i = endIdx;
58
+ lastType = tokens_1.L.SQL_END;
59
+ continue;
60
+ }
61
+ if (t.type === tokens_1.L.CCURLY) {
62
+ // `{}` empty: no inner space. `{ x }`: both inner spaces.
63
+ if (buf.length > 0 && !buf.endsWith('{') && !buf.endsWith(' '))
64
+ buf += ' ';
65
+ else
66
+ trim();
67
+ buf += '}';
68
+ lastType = t.type;
69
+ continue;
70
+ }
71
+ // Walker-specific divergence: COMMA and SEMI use compact-inline form
72
+ // (`, ` and `; `). The leaf walker (./leaf) emits newlines for these in
73
+ // wrapped contexts.
74
+ if (t.type === tokens_1.L.COMMA) {
75
+ trim();
76
+ buf += ', ';
77
+ lastType = t.type;
78
+ continue;
79
+ }
80
+ if (t.type === tokens_1.L.SEMI) {
81
+ trim();
82
+ buf += '; ';
83
+ lastType = t.type;
84
+ continue;
85
+ }
86
+ // Everything else: classifier-driven leading separator + text + (if a
87
+ // binary op) trailing space. Same shape as the leaf walker's default.
88
+ const action = (0, tokens_1.leadingAction)(lastType, t.type);
89
+ if (action === 'glue')
90
+ trim();
91
+ else if (action === 'space')
92
+ space();
93
+ // 'hug' — emit nothing before
94
+ buf += text;
95
+ if (tokens_1.BINARY_OPS.has(t.type))
96
+ buf += ' ';
97
+ lastType = t.type;
98
+ }
99
+ return buf;
100
+ }
101
+ //# sourceMappingURL=inline-renderer.js.map
@@ -0,0 +1,9 @@
1
+ import { Token } from 'antlr4ts';
2
+ import type { Formatter } from './formatter';
3
+ export declare function note(f: Formatter, tokenType: number, idx: number, endTok: Token): void;
4
+ export declare function flushHiddenBefore(f: Formatter, idx: number): void;
5
+ export declare function startStatementLine(f: Formatter): void;
6
+ export declare function approxInlineSpan(f: Formatter, fromIdx: number, toIdx: number): number;
7
+ export declare function hasCommentsInRange(f: Formatter, fromIdx: number, toIdx: number): boolean;
8
+ export declare function emitVisibleToken(f: Formatter, t: Token, idx: number): void;
9
+ export declare function formatTokenRange(f: Formatter, fromIdx: number, toIdx: number): void;
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * Leaf walker: per-token spacing, structural punctuation, comment placement,
7
+ * and the small helpers the rule formatters use to look at adjacent tokens.
8
+ *
9
+ * Per-rule formatters call into here via:
10
+ * - emitVisibleToken — the leaf
11
+ * - flushHiddenBefore — emit pending comments before a target index
12
+ * - note — atomic per-token state update
13
+ * - startStatementLine — newline (consuming `needBlank`) for stmt start
14
+ * - approxInlineSpan — line-budget overestimate for a token range
15
+ * - hasCommentsInRange — comment-presence check (gates inline form)
16
+ * - formatTokenRange — emit a span via the leaf walker
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.note = note;
20
+ exports.flushHiddenBefore = flushHiddenBefore;
21
+ exports.startStatementLine = startStatementLine;
22
+ exports.approxInlineSpan = approxInlineSpan;
23
+ exports.hasCommentsInRange = hasCommentsInRange;
24
+ exports.emitVisibleToken = emitVisibleToken;
25
+ exports.formatTokenRange = formatTokenRange;
26
+ const antlr4ts_1 = require("antlr4ts");
27
+ const tokens_1 = require("./tokens");
28
+ // Update per-token state after emitting a token (or a token-like span).
29
+ function note(f, tokenType, idx, endTok) {
30
+ f.lastEmittedType = tokenType;
31
+ f.prevTokenEndLine = (0, tokens_1.endLineOf)(endTok);
32
+ f.lastEmittedIdx = idx;
33
+ }
34
+ // Emit any hidden-channel tokens (comments) sitting between f.lastEmittedIdx
35
+ // and idx-1, advancing f.lastEmittedIdx so they are not re-emitted by a later
36
+ // call. Visible tokens in the same range (e.g. commas the section-list rule
37
+ // chose to drop) are skipped.
38
+ function flushHiddenBefore(f, idx) {
39
+ if (idx <= f.lastEmittedIdx + 1)
40
+ return;
41
+ for (let j = f.lastEmittedIdx + 1; j < idx; j++) {
42
+ const t = f.tokens[j];
43
+ if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL) {
44
+ emitHiddenToken(f, t);
45
+ }
46
+ }
47
+ f.lastEmittedIdx = idx - 1;
48
+ }
49
+ // Hidden-channel tokens (comments). Trailing comments (same line as previous
50
+ // token) attach with a space; leading comments (own line) start a fresh line.
51
+ function emitHiddenToken(f, t) {
52
+ var _a;
53
+ const text = (_a = t.text) !== null && _a !== void 0 ? _a : '';
54
+ const sameLine = f.prevTokenEndLine !== 0 && t.line === f.prevTokenEndLine;
55
+ if (t.type === tokens_1.L.COMMENT_TO_EOL) {
56
+ if (sameLine) {
57
+ f.o.space();
58
+ f.o.text(text.replace(/\s+$/, ''));
59
+ f.o.nl();
60
+ }
61
+ else {
62
+ if (f.o.indent === 0)
63
+ startStatementLine(f);
64
+ else
65
+ f.o.nl();
66
+ f.o.text(text.replace(/\s+$/, ''));
67
+ f.o.nl();
68
+ }
69
+ }
70
+ else if (t.type === tokens_1.L.BLOCK_COMMENT) {
71
+ if (sameLine) {
72
+ f.o.space();
73
+ f.o.text(text);
74
+ f.o.space();
75
+ }
76
+ else {
77
+ if (f.o.indent === 0)
78
+ startStatementLine(f);
79
+ else
80
+ f.o.nl();
81
+ f.o.text(text);
82
+ f.o.nl();
83
+ }
84
+ }
85
+ f.prevTokenEndLine = (0, tokens_1.endLineOf)(t);
86
+ }
87
+ // Either consume `needBlank` (emit blank line) or just newline.
88
+ function startStatementLine(f) {
89
+ if (f.needBlank) {
90
+ f.o.blank();
91
+ f.needBlank = false;
92
+ }
93
+ else {
94
+ f.o.nl();
95
+ }
96
+ }
97
+ // Approximate length of the inline form of tokens [fromIdx, toIdx]: sum of
98
+ // visible token text + 1 char between adjacent tokens. Used for budget checks
99
+ // (paren wrap, pickStatement wrap, etc.). It's an overestimate for dotted-paths
100
+ // and similar (`a.b` is 3 chars, our estimate is 5) but the direction is
101
+ // conservative — we wrap a touch sooner than strictly needed.
102
+ function approxInlineSpan(f, fromIdx, toIdx) {
103
+ var _a;
104
+ let len = 0;
105
+ let prev = -1;
106
+ for (let i = fromIdx; i <= toIdx; i++) {
107
+ const t = f.tokens[i];
108
+ if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
109
+ continue;
110
+ if (t.type === antlr4ts_1.Token.EOF)
111
+ continue;
112
+ if (prev >= 0)
113
+ len += 1;
114
+ len += ((_a = t.text) !== null && _a !== void 0 ? _a : '').length;
115
+ prev = i;
116
+ }
117
+ return len;
118
+ }
119
+ // Are there any hidden-channel tokens (comments) in [fromIdx, toIdx]? Used to
120
+ // gate inline-form candidates: any path that goes through `renderItemInline`
121
+ // strips comments, so candidates with comments must fall back to wrapped or
122
+ // broken form where the leaf walker preserves them.
123
+ function hasCommentsInRange(f, fromIdx, toIdx) {
124
+ for (let i = fromIdx; i <= toIdx; i++) {
125
+ if (f.tokens[i].channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
126
+ return true;
127
+ }
128
+ return false;
129
+ }
130
+ // Index of the next non-hidden, non-EOF token strictly after `idx`, or -1.
131
+ function nextVisibleAfter(f, idx) {
132
+ for (let j = idx + 1; j < f.tokens.length; j++) {
133
+ const t = f.tokens[j];
134
+ if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
135
+ continue;
136
+ if (t.type === antlr4ts_1.Token.EOF)
137
+ return -1;
138
+ return j;
139
+ }
140
+ return -1;
141
+ }
142
+ // Does the paren-pair at [openIdx, closeIdx] have any COMMA at its own depth?
143
+ // (Used to distinguish "function call with multiple args" from "single-arg
144
+ // call" / "empty parens".)
145
+ function hasCommaAtDepth1(f, openIdx, closeIdx) {
146
+ let depth = 0;
147
+ for (let i = openIdx + 1; i < closeIdx; i++) {
148
+ const t = f.tokens[i];
149
+ if (t.channel === antlr4ts_1.Token.HIDDEN_CHANNEL)
150
+ continue;
151
+ if (t.type === tokens_1.L.OPAREN || t.type === tokens_1.L.OBRACK || t.type === tokens_1.L.OCURLY)
152
+ depth++;
153
+ else if (t.type === tokens_1.L.CPAREN || t.type === tokens_1.L.CBRACK || t.type === tokens_1.L.CCURLY)
154
+ depth--;
155
+ else if (t.type === tokens_1.L.COMMA && depth === 0)
156
+ return true;
157
+ }
158
+ return false;
159
+ }
160
+ // The big switch. Each branch ends with `note(...)`.
161
+ function emitVisibleToken(f, t, idx) {
162
+ var _a, _b;
163
+ if (idx <= f.lastEmittedIdx)
164
+ return; // already emitted (e.g. SQL block range)
165
+ flushHiddenBefore(f, idx);
166
+ const text = (_a = t.text) !== null && _a !== void 0 ? _a : '';
167
+ // ---- Verbatim regions: SQL strings and block annotations ----
168
+ // We don't own a SQL formatter. Annotation indentation is significant.
169
+ if (t.type === tokens_1.L.SQL_BEGIN) {
170
+ const endIdx = (0, tokens_1.findMatching)(f.tokens, idx, tokens_1.L.SQL_BEGIN, tokens_1.L.SQL_END);
171
+ const stop = f.tokens[endIdx].stopIndex;
172
+ f.o.space();
173
+ f.o.text(f.src.substring(t.startIndex, stop + 1));
174
+ note(f, tokens_1.L.SQL_END, endIdx, f.tokens[endIdx]);
175
+ return;
176
+ }
177
+ if (t.type === tokens_1.L.BLOCK_ANNOTATION_BEGIN ||
178
+ t.type === tokens_1.L.DOC_BLOCK_ANNOTATION_BEGIN) {
179
+ const endIdx = (0, tokens_1.findMatching)(f.tokens, idx, t.type, tokens_1.L.BLOCK_ANNOTATION_END);
180
+ const stop = f.tokens[endIdx].stopIndex;
181
+ if (f.o.indent === 0)
182
+ startStatementLine(f);
183
+ else
184
+ f.o.nl();
185
+ f.o.text(f.src.substring(t.startIndex, stop + 1));
186
+ f.o.nl();
187
+ note(f, tokens_1.L.BLOCK_ANNOTATION_END, endIdx, f.tokens[endIdx]);
188
+ return;
189
+ }
190
+ // ---- Single-line annotations on their own line ----
191
+ if (t.type === tokens_1.L.ANNOTATION || t.type === tokens_1.L.DOC_ANNOTATION) {
192
+ if (f.o.indent === 0)
193
+ startStatementLine(f);
194
+ else
195
+ f.o.nl();
196
+ f.o.text(text.replace(/\s+$/, ''));
197
+ f.o.nl();
198
+ note(f, t.type, idx, t);
199
+ return;
200
+ }
201
+ // ---- Curly braces: indent in/out around block bodies ----
202
+ if (t.type === tokens_1.L.OCURLY) {
203
+ f.o.space();
204
+ f.o.text('{');
205
+ // Empty `{}`: peek the next visible token. If it's the matching close
206
+ // AND nothing hidden sits between them (no comments to preserve), emit
207
+ // inline so we get `extend {}` not `extend {\n}`. With a comment in the
208
+ // gap (`extend { /* keep */ }`), fall through to the wrapping form so
209
+ // the leaf walker's comment placement runs.
210
+ const nextVisible = nextVisibleAfter(f, idx);
211
+ if (nextVisible !== -1 &&
212
+ f.tokens[nextVisible].type === tokens_1.L.CCURLY &&
213
+ !hasCommentsInRange(f, idx + 1, nextVisible - 1)) {
214
+ f.o.text('}');
215
+ if (f.o.indent === 0)
216
+ f.needBlank = true;
217
+ note(f, tokens_1.L.CCURLY, nextVisible, f.tokens[nextVisible]);
218
+ return;
219
+ }
220
+ f.o.indent++;
221
+ f.o.nl();
222
+ note(f, t.type, idx, t);
223
+ return;
224
+ }
225
+ if (t.type === tokens_1.L.CCURLY) {
226
+ f.o.indent = Math.max(0, f.o.indent - 1);
227
+ f.o.nl();
228
+ f.o.text('}');
229
+ if (f.o.indent === 0)
230
+ f.needBlank = true;
231
+ note(f, t.type, idx, t);
232
+ return;
233
+ }
234
+ // ---- Statement separators ----
235
+ // `;` in wrapped form is dropped — newlines do the job. (Inline `;` appears
236
+ // via renderItemInline, not here.)
237
+ if (t.type === tokens_1.L.SEMI) {
238
+ f.o.trimTrailingSpace();
239
+ f.o.nl();
240
+ if (f.o.indent === 0)
241
+ f.needBlank = true;
242
+ note(f, t.type, idx, t);
243
+ return;
244
+ }
245
+ // ---- Commas ----
246
+ // At top level (parenDepth==0) → newline. Inside parens that the wrap logic
247
+ // flagged as multi-line → newline. Otherwise inline (just space).
248
+ if (t.type === tokens_1.L.COMMA) {
249
+ f.o.trimTrailingSpace();
250
+ f.o.text(',');
251
+ const innerBreaks = f.parenBreaks.length > 0 && f.parenBreaks[f.parenBreaks.length - 1];
252
+ if (f.parenDepth === 0 || innerBreaks)
253
+ f.o.nl();
254
+ note(f, t.type, idx, t);
255
+ return;
256
+ }
257
+ // ---- Open paren / bracket: decide call-hug vs grouping, decide wrap ----
258
+ if (t.type === tokens_1.L.OPAREN || t.type === tokens_1.L.OBRACK) {
259
+ const action = (0, tokens_1.leadingAction)(f.lastEmittedType, t.type);
260
+ if (action === 'space')
261
+ f.o.space();
262
+ f.o.text(text);
263
+ // Decide whether the contents will exceed the line budget when laid out
264
+ // inline. Break only if there's somewhere useful to break:
265
+ // - call/subscript parens (action='hug'): must have ≥ 2 args (commas at
266
+ // this depth);
267
+ // - grouping parens (action='space'): any overflow — content's own
268
+ // rules will wrap.
269
+ const closeType = t.type === tokens_1.L.OPAREN ? tokens_1.L.CPAREN : tokens_1.L.CBRACK;
270
+ const matchIdx = (0, tokens_1.findMatching)(f.tokens, idx, t.type, closeType);
271
+ const inlineLen = approxInlineSpan(f, idx, matchIdx);
272
+ const wouldOverflow = f.o.lineLengthSoFar() + inlineLen > tokens_1.LINE_BUDGET;
273
+ const hasArgCommas = hasCommaAtDepth1(f, idx, matchIdx);
274
+ const isCall = action === 'hug';
275
+ const willBreak = wouldOverflow && (hasArgCommas || !isCall);
276
+ f.parenBreaks.push(willBreak);
277
+ f.parenDepth++;
278
+ if (willBreak) {
279
+ f.o.indent++;
280
+ f.o.nl();
281
+ }
282
+ note(f, t.type, idx, t);
283
+ return;
284
+ }
285
+ if (t.type === tokens_1.L.CPAREN || t.type === tokens_1.L.CBRACK) {
286
+ const wasBreak = (_b = f.parenBreaks.pop()) !== null && _b !== void 0 ? _b : false;
287
+ if (wasBreak) {
288
+ f.o.indent = Math.max(0, f.o.indent - 1);
289
+ f.o.nl();
290
+ }
291
+ else {
292
+ f.o.trimTrailingSpace();
293
+ }
294
+ f.o.text(text);
295
+ f.parenDepth = Math.max(0, f.parenDepth - 1);
296
+ note(f, t.type, idx, t);
297
+ return;
298
+ }
299
+ // ---- Section keyword fallback (no explicit handler took it) ----
300
+ // Inside a brace block, force a fresh line. Keeps v1-style readable output
301
+ // even for sections we don't yet handle.
302
+ if (tokens_1.SECTION_TOKENS.has(t.type) && f.o.indent > 0) {
303
+ f.o.nl();
304
+ f.o.text(text.replace(/\s+/g, ''));
305
+ note(f, t.type, idx, t);
306
+ return;
307
+ }
308
+ // ---- Top-level statement starter ----
309
+ if (tokens_1.TOP_LEVEL_STARTERS.has(t.type) &&
310
+ f.o.indent === 0 &&
311
+ f.parenDepth === 0) {
312
+ startStatementLine(f);
313
+ f.o.text(text.replace(/\s+/g, ''));
314
+ note(f, t.type, idx, t);
315
+ return;
316
+ }
317
+ // ---- Default: identifier / literal / keyword / DOT / COLON / TRIPLECOLON
318
+ // / binary op. Leading separator from the classifier; binary ops also
319
+ // get a trailing space.
320
+ const action = (0, tokens_1.leadingAction)(f.lastEmittedType, t.type);
321
+ if (action === 'glue')
322
+ f.o.trimTrailingSpace();
323
+ else if (action === 'space')
324
+ f.o.space();
325
+ // 'hug' — emit nothing before
326
+ f.o.text(text);
327
+ if (tokens_1.BINARY_OPS.has(t.type))
328
+ f.o.space();
329
+ note(f, t.type, idx, t);
330
+ }
331
+ // Emit each visible token in [fromIdx, toIdx] via the leaf walker.
332
+ function formatTokenRange(f, fromIdx, toIdx) {
333
+ for (let i = fromIdx; i <= toIdx; i++) {
334
+ const t = f.tokens[i];
335
+ if (t.channel !== antlr4ts_1.Token.HIDDEN_CHANNEL && t.type !== antlr4ts_1.Token.EOF) {
336
+ emitVisibleToken(f, t, i);
337
+ }
338
+ }
339
+ }
340
+ //# sourceMappingURL=leaf.js.map
@@ -0,0 +1,12 @@
1
+ export declare class Out {
2
+ buf: string;
3
+ indent: number;
4
+ text(s: string): void;
5
+ space(): void;
6
+ nl(): void;
7
+ blank(): void;
8
+ trimTrailingSpace(): void;
9
+ trimTrailingNewlines(): void;
10
+ lineLengthSoFar(): number;
11
+ toString(): string;
12
+ }