sf-agentpmd 0.1.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.
- package/LICENSE +21 -0
- package/NOTICE +26 -0
- package/README.md +204 -0
- package/bin/dev.js +5 -0
- package/bin/run.js +3 -0
- package/dist/analyzer/action-references.d.ts +21 -0
- package/dist/analyzer/action-references.js +130 -0
- package/dist/analyzer/action-references.js.map +1 -0
- package/dist/analyzer/analyze.d.ts +43 -0
- package/dist/analyzer/analyze.js +222 -0
- package/dist/analyzer/analyze.js.map +1 -0
- package/dist/analyzer/apex-analyze.d.ts +14 -0
- package/dist/analyzer/apex-analyze.js +60 -0
- package/dist/analyzer/apex-analyze.js.map +1 -0
- package/dist/analyzer/apex-complexity.d.ts +27 -0
- package/dist/analyzer/apex-complexity.js +133 -0
- package/dist/analyzer/apex-complexity.js.map +1 -0
- package/dist/analyzer/apex-parse.d.ts +39 -0
- package/dist/analyzer/apex-parse.js +32 -0
- package/dist/analyzer/apex-parse.js.map +1 -0
- package/dist/analyzer/apex-resolve.d.ts +32 -0
- package/dist/analyzer/apex-resolve.js +59 -0
- package/dist/analyzer/apex-resolve.js.map +1 -0
- package/dist/analyzer/complexity.d.ts +30 -0
- package/dist/analyzer/complexity.js +126 -0
- package/dist/analyzer/complexity.js.map +1 -0
- package/dist/analyzer/parse.d.ts +51 -0
- package/dist/analyzer/parse.js +143 -0
- package/dist/analyzer/parse.js.map +1 -0
- package/dist/analyzer/project.d.ts +12 -0
- package/dist/analyzer/project.js +51 -0
- package/dist/analyzer/project.js.map +1 -0
- package/dist/analyzer/types.d.ts +76 -0
- package/dist/analyzer/types.js +2 -0
- package/dist/analyzer/types.js.map +1 -0
- package/dist/commands/agentpmd/analyze.d.ts +20 -0
- package/dist/commands/agentpmd/analyze.js +122 -0
- package/dist/commands/agentpmd/analyze.js.map +1 -0
- package/dist/commands/agentpmd/install-skill.d.ts +11 -0
- package/dist/commands/agentpmd/install-skill.js +33 -0
- package/dist/commands/agentpmd/install-skill.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/renderers/csv.d.ts +6 -0
- package/dist/renderers/csv.js +78 -0
- package/dist/renderers/csv.js.map +1 -0
- package/dist/renderers/index.d.ts +8 -0
- package/dist/renderers/index.js +25 -0
- package/dist/renderers/index.js.map +1 -0
- package/dist/renderers/markdown.d.ts +12 -0
- package/dist/renderers/markdown.js +233 -0
- package/dist/renderers/markdown.js.map +1 -0
- package/dist/renderers/options.d.ts +20 -0
- package/dist/renderers/options.js +2 -0
- package/dist/renderers/options.js.map +1 -0
- package/dist/renderers/sarif.d.ts +3 -0
- package/dist/renderers/sarif.js +131 -0
- package/dist/renderers/sarif.js.map +1 -0
- package/dist/renderers/text.d.ts +3 -0
- package/dist/renderers/text.js +243 -0
- package/dist/renderers/text.js.map +1 -0
- package/oclif.manifest.json +168 -0
- package/package.json +97 -0
- package/skill/SKILL.md +103 -0
- package/skill/references/command-structure.md +89 -0
- package/skill/references/install.md +112 -0
- package/skill/references/output-formats.md +205 -0
- package/skill/references/upgrade.md +112 -0
- package/vendor/agentscript-parser-javascript/dist/cst-node.d.ts +83 -0
- package/vendor/agentscript-parser-javascript/dist/cst-node.js +238 -0
- package/vendor/agentscript-parser-javascript/dist/errors.d.ts +34 -0
- package/vendor/agentscript-parser-javascript/dist/errors.js +74 -0
- package/vendor/agentscript-parser-javascript/dist/expressions.d.ts +36 -0
- package/vendor/agentscript-parser-javascript/dist/expressions.js +682 -0
- package/vendor/agentscript-parser-javascript/dist/highlighter.d.ts +24 -0
- package/vendor/agentscript-parser-javascript/dist/highlighter.js +260 -0
- package/vendor/agentscript-parser-javascript/dist/index.d.ts +29 -0
- package/vendor/agentscript-parser-javascript/dist/index.js +35 -0
- package/vendor/agentscript-parser-javascript/dist/lexer.d.ts +60 -0
- package/vendor/agentscript-parser-javascript/dist/lexer.js +630 -0
- package/vendor/agentscript-parser-javascript/dist/parse-mapping.d.ts +46 -0
- package/vendor/agentscript-parser-javascript/dist/parse-mapping.js +549 -0
- package/vendor/agentscript-parser-javascript/dist/parse-sequence.d.ts +10 -0
- package/vendor/agentscript-parser-javascript/dist/parse-sequence.js +118 -0
- package/vendor/agentscript-parser-javascript/dist/parse-statements.d.ts +15 -0
- package/vendor/agentscript-parser-javascript/dist/parse-statements.js +519 -0
- package/vendor/agentscript-parser-javascript/dist/parse-templates.d.ts +15 -0
- package/vendor/agentscript-parser-javascript/dist/parse-templates.js +323 -0
- package/vendor/agentscript-parser-javascript/dist/parser.d.ts +65 -0
- package/vendor/agentscript-parser-javascript/dist/parser.js +163 -0
- package/vendor/agentscript-parser-javascript/dist/recovery.d.ts +51 -0
- package/vendor/agentscript-parser-javascript/dist/recovery.js +199 -0
- package/vendor/agentscript-parser-javascript/dist/token.d.ts +58 -0
- package/vendor/agentscript-parser-javascript/dist/token.js +62 -0
- package/vendor/agentscript-parser-javascript/package.json +19 -0
- package/vendor/agentscript-types/dist/comment.d.ts +11 -0
- package/vendor/agentscript-types/dist/comment.js +10 -0
- package/vendor/agentscript-types/dist/cst.d.ts +7 -0
- package/vendor/agentscript-types/dist/cst.js +8 -0
- package/vendor/agentscript-types/dist/diagnostic.d.ts +34 -0
- package/vendor/agentscript-types/dist/diagnostic.js +23 -0
- package/vendor/agentscript-types/dist/index.d.ts +9 -0
- package/vendor/agentscript-types/dist/index.js +10 -0
- package/vendor/agentscript-types/dist/position.d.ts +11 -0
- package/vendor/agentscript-types/dist/position.js +16 -0
- package/vendor/agentscript-types/dist/syntax-node.d.ts +39 -0
- package/vendor/agentscript-types/dist/syntax-node.js +8 -0
- package/vendor/agentscript-types/package.json +15 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026, Salesforce, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Statement-parsing functions extracted from Parser class.
|
|
9
|
+
*
|
|
10
|
+
* Each function takes a ParserContext as its first parameter, following
|
|
11
|
+
* the same free-function pattern as recovery.ts and expressions.ts.
|
|
12
|
+
*
|
|
13
|
+
* Functions that call parseProcedure (which in turn calls parseStatement,
|
|
14
|
+
* which may delegate to parseTemplate) accept an optional parseTemplate
|
|
15
|
+
* callback to avoid circular dependency with parser.ts.
|
|
16
|
+
*/
|
|
17
|
+
import { isTokenKind, TokenKind } from './token.js';
|
|
18
|
+
import { CSTNode } from './cst-node.js';
|
|
19
|
+
import { makeErrorNode, tokenToAutoLeaf } from './errors.js';
|
|
20
|
+
import { makeEmptyError, addMissingTarget, makeMissing, synchronize, synchronizeRow, synchronizeRowUntilColon, consumeCommentsAndSkipNewlines, skipNewlines, isAtEnd, isTrailingCommentOnly, parseOrphanBlock, } from './recovery.js';
|
|
21
|
+
import { parseExpression, wrapExpression, parseString } from './expressions.js';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Statement detection
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export function isStatementStart(ctx) {
|
|
26
|
+
const tok = ctx.peek();
|
|
27
|
+
if (tok.kind !== TokenKind.ID)
|
|
28
|
+
return false;
|
|
29
|
+
switch (tok.text) {
|
|
30
|
+
case 'if':
|
|
31
|
+
case 'run':
|
|
32
|
+
case 'set':
|
|
33
|
+
case 'transition':
|
|
34
|
+
return true;
|
|
35
|
+
case 'with':
|
|
36
|
+
// "with" is a statement only if not followed by colon (which would make it a key)
|
|
37
|
+
return ctx.peekAt(1).kind !== TokenKind.COLON;
|
|
38
|
+
case 'available':
|
|
39
|
+
return (ctx.peekAt(1).kind === TokenKind.ID && ctx.peekAt(1).text === 'when');
|
|
40
|
+
default:
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Procedure & statement dispatch
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
export function parseProcedure(ctx, parseTemplate) {
|
|
48
|
+
const startTok = ctx.peek();
|
|
49
|
+
const node = ctx.startNode('procedure');
|
|
50
|
+
while (!isAtEnd(ctx) && ctx.peekKind() !== TokenKind.DEDENT) {
|
|
51
|
+
skipNewlines(ctx);
|
|
52
|
+
if (isAtEnd(ctx) || ctx.peekKind() === TokenKind.DEDENT)
|
|
53
|
+
break;
|
|
54
|
+
// Don't consume trailing comments that belong to the parent scope
|
|
55
|
+
// (tree-sitter parity: extras at block boundaries attach to the parent).
|
|
56
|
+
if (ctx.peekKind() === TokenKind.COMMENT && isTrailingCommentOnly(ctx)) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const stmt = parseStatement(ctx, parseTemplate);
|
|
60
|
+
if (stmt) {
|
|
61
|
+
node.appendChild(stmt);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const err = synchronize(ctx);
|
|
65
|
+
if (err) {
|
|
66
|
+
node.appendChild(err);
|
|
67
|
+
}
|
|
68
|
+
else if (!isAtEnd(ctx) && ctx.peekKind() !== TokenKind.DEDENT) {
|
|
69
|
+
ctx.consume();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// If the procedure is empty, add an ERROR node (Error 07, 34)
|
|
74
|
+
if (node.namedChildren.length === 0) {
|
|
75
|
+
node.appendChild(makeEmptyError(ctx));
|
|
76
|
+
}
|
|
77
|
+
ctx.finishNode(node, startTok);
|
|
78
|
+
return node;
|
|
79
|
+
}
|
|
80
|
+
export function parseStatement(ctx, parseTemplate) {
|
|
81
|
+
const tok = ctx.peek();
|
|
82
|
+
if (tok.kind === TokenKind.ID) {
|
|
83
|
+
switch (tok.text) {
|
|
84
|
+
case 'if':
|
|
85
|
+
return parseIfStatement(ctx, parseTemplate);
|
|
86
|
+
case 'run':
|
|
87
|
+
return parseRunStatement(ctx, parseTemplate);
|
|
88
|
+
case 'set':
|
|
89
|
+
return parseSetStatement(ctx);
|
|
90
|
+
case 'transition':
|
|
91
|
+
return parseTransitionStatement(ctx);
|
|
92
|
+
case 'with':
|
|
93
|
+
return parseWithStatement(ctx);
|
|
94
|
+
case 'available': {
|
|
95
|
+
if (ctx.peekAt(1).kind === TokenKind.ID &&
|
|
96
|
+
ctx.peekAt(1).text === 'when') {
|
|
97
|
+
return parseAvailableWhenStatement(ctx);
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case 'else':
|
|
102
|
+
case 'elif':
|
|
103
|
+
case 'for':
|
|
104
|
+
// Orphan else/elif (without if) or unsupported for → wrap in ERROR
|
|
105
|
+
return parseOrphanBlock(ctx, c => parseProcedure(c, parseTemplate));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (tok.kind === TokenKind.PIPE && parseTemplate) {
|
|
109
|
+
return parseTemplate(ctx);
|
|
110
|
+
}
|
|
111
|
+
if (tok.kind === TokenKind.COMMENT) {
|
|
112
|
+
const comment = ctx.consumeNamed('comment');
|
|
113
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
114
|
+
ctx.consume();
|
|
115
|
+
return comment;
|
|
116
|
+
}
|
|
117
|
+
// Fallback: try parsing as a bare expression (e.g., `...` inside a procedure)
|
|
118
|
+
// This keeps expressions as proper expression nodes instead of ERROR-wrapped tokens.
|
|
119
|
+
const expr = parseExpression(ctx, 0);
|
|
120
|
+
if (expr) {
|
|
121
|
+
const wrapped = wrapExpression(ctx, expr);
|
|
122
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
123
|
+
ctx.consume();
|
|
124
|
+
return wrapped;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Shared colon → procedure body
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Shared colon → procedure body sequence for if/elif/else.
|
|
133
|
+
* Consumes colon (with recovery), inline comment, extra inline tokens,
|
|
134
|
+
* then INDENT → procedure → DEDENT, and trailing NEWLINE.
|
|
135
|
+
*
|
|
136
|
+
* @param errorOnMissingBody - if true, insert ERROR when colon has no
|
|
137
|
+
* indented body (used by `if`; elif/else silently accept missing body).
|
|
138
|
+
*/
|
|
139
|
+
function parseColonAndProcedureBody(ctx, node, row, errorOnMissingBody, parseTemplate) {
|
|
140
|
+
// Colon (or recovery)
|
|
141
|
+
if (ctx.peekKind() === TokenKind.COLON) {
|
|
142
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
143
|
+
}
|
|
144
|
+
else if (errorOnMissingBody) {
|
|
145
|
+
node.appendChild(makeEmptyError(ctx));
|
|
146
|
+
}
|
|
147
|
+
// Inline comment after colon
|
|
148
|
+
if (ctx.peekKind() === TokenKind.COMMENT) {
|
|
149
|
+
node.appendChild(ctx.consumeNamed('comment'));
|
|
150
|
+
}
|
|
151
|
+
// Absorb extra inline tokens after colon on the same row
|
|
152
|
+
const inlineErr = synchronizeRow(ctx, row);
|
|
153
|
+
if (inlineErr)
|
|
154
|
+
node.appendChild(inlineErr);
|
|
155
|
+
// Consequence block
|
|
156
|
+
if (ctx.peekKind() === TokenKind.INDENT) {
|
|
157
|
+
ctx.consume();
|
|
158
|
+
const proc = parseProcedure(ctx, parseTemplate);
|
|
159
|
+
if (proc)
|
|
160
|
+
node.appendChild(proc, 'consequence');
|
|
161
|
+
consumeCommentsAndSkipNewlines(ctx, node);
|
|
162
|
+
if (ctx.peekKind() === TokenKind.DEDENT)
|
|
163
|
+
ctx.consume();
|
|
164
|
+
}
|
|
165
|
+
else if (errorOnMissingBody &&
|
|
166
|
+
(ctx.peekKind() === TokenKind.NEWLINE || ctx.isAtSyncPoint())) {
|
|
167
|
+
node.appendChild(makeEmptyError(ctx));
|
|
168
|
+
}
|
|
169
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
170
|
+
ctx.consume();
|
|
171
|
+
}
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Individual statement parsers
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
export function parseIfStatement(ctx, parseTemplate) {
|
|
176
|
+
const startTok = ctx.peek();
|
|
177
|
+
const node = ctx.startNode('if_statement');
|
|
178
|
+
ctx.addAnonymousChild(node, ctx.consume()); // if
|
|
179
|
+
// Condition
|
|
180
|
+
let condition = parseExpression(ctx, 0);
|
|
181
|
+
// Handle single `=` typo (should be `==`): wrap `=` in ERROR,
|
|
182
|
+
// build comparison_expression, then continue parsing normally
|
|
183
|
+
if (condition && ctx.peekKind() === TokenKind.EQ) {
|
|
184
|
+
const eqTok = ctx.consume(); // =
|
|
185
|
+
const right = parseExpression(ctx, 5); // parse right side above comparison
|
|
186
|
+
if (right) {
|
|
187
|
+
// Build: (comparison_expression (expr left) (ERROR =) (expr right))
|
|
188
|
+
const cmp = ctx.startNodeAt('comparison_expression', condition);
|
|
189
|
+
cmp.appendChild(wrapExpression(ctx, condition));
|
|
190
|
+
// Wrap `=` in ERROR
|
|
191
|
+
const eqChild = new CSTNode('=', ctx.source, eqTok.startOffset, eqTok.startOffset + 1, eqTok.start, eqTok.end, false);
|
|
192
|
+
const eqErr = makeErrorNode(ctx.source, [eqChild], eqTok.startOffset, eqTok.startOffset + 1, eqTok.start, eqTok.end);
|
|
193
|
+
cmp.appendChild(eqErr);
|
|
194
|
+
cmp.appendChild(wrapExpression(ctx, right));
|
|
195
|
+
cmp.finalize();
|
|
196
|
+
condition = cmp;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (condition)
|
|
200
|
+
node.appendChild(wrapExpression(ctx, condition), 'condition');
|
|
201
|
+
// Absorb extra tokens between condition and colon on the same row.
|
|
202
|
+
if (condition &&
|
|
203
|
+
ctx.peekKind() !== TokenKind.COLON &&
|
|
204
|
+
!ctx.isAtSyncPoint() &&
|
|
205
|
+
ctx.peekKind() !== TokenKind.INDENT) {
|
|
206
|
+
const condRow = startTok.start.row;
|
|
207
|
+
const err = synchronizeRowUntilColon(ctx, condRow);
|
|
208
|
+
if (err)
|
|
209
|
+
node.appendChild(err);
|
|
210
|
+
}
|
|
211
|
+
parseColonAndProcedureBody(ctx, node, startTok.start.row, true, parseTemplate);
|
|
212
|
+
// elif clauses (including misspelled 'elseif')
|
|
213
|
+
while (ctx.peekKind() === TokenKind.ID &&
|
|
214
|
+
(ctx.peek().text === 'elif' || ctx.peek().text === 'elseif')) {
|
|
215
|
+
const elif = parseElifClause(ctx, parseTemplate);
|
|
216
|
+
if (elif)
|
|
217
|
+
node.appendChild(elif, 'alternative');
|
|
218
|
+
}
|
|
219
|
+
// else clause
|
|
220
|
+
if (ctx.peekKind() === TokenKind.ID && ctx.peek().text === 'else') {
|
|
221
|
+
const elseClause = parseElseClause(ctx, parseTemplate);
|
|
222
|
+
if (elseClause)
|
|
223
|
+
node.appendChild(elseClause, 'alternative');
|
|
224
|
+
}
|
|
225
|
+
ctx.finishNode(node, startTok);
|
|
226
|
+
return node;
|
|
227
|
+
}
|
|
228
|
+
function parseElifClause(ctx, parseTemplate) {
|
|
229
|
+
const startTok = ctx.peek();
|
|
230
|
+
const node = ctx.startNode('elif_clause');
|
|
231
|
+
const kw = ctx.consume(); // elif or elseif
|
|
232
|
+
if (kw.text === 'elseif') {
|
|
233
|
+
// Wrap misspelled keyword in ERROR
|
|
234
|
+
const kwEnd = kw.startOffset + kw.text.length;
|
|
235
|
+
const leaf = tokenToAutoLeaf(kw, ctx.source, kw.startOffset);
|
|
236
|
+
const errNode = makeErrorNode(ctx.source, [leaf], kw.startOffset, kwEnd, kw.start, kw.end);
|
|
237
|
+
node.appendChild(errNode);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
ctx.addAnonymousChild(node, kw);
|
|
241
|
+
}
|
|
242
|
+
const condition = parseExpression(ctx, 0);
|
|
243
|
+
if (condition)
|
|
244
|
+
node.appendChild(wrapExpression(ctx, condition), 'condition');
|
|
245
|
+
// Absorb extra tokens between condition and colon (same as if)
|
|
246
|
+
if (condition &&
|
|
247
|
+
ctx.peekKind() !== TokenKind.COLON &&
|
|
248
|
+
!ctx.isAtSyncPoint() &&
|
|
249
|
+
ctx.peekKind() !== TokenKind.INDENT) {
|
|
250
|
+
const condRow = startTok.start.row;
|
|
251
|
+
const err = synchronizeRowUntilColon(ctx, condRow);
|
|
252
|
+
if (err)
|
|
253
|
+
node.appendChild(err);
|
|
254
|
+
}
|
|
255
|
+
parseColonAndProcedureBody(ctx, node, startTok.start.row, false, parseTemplate);
|
|
256
|
+
ctx.finishNode(node, startTok);
|
|
257
|
+
return node;
|
|
258
|
+
}
|
|
259
|
+
function parseElseClause(ctx, parseTemplate) {
|
|
260
|
+
const startTok = ctx.peek();
|
|
261
|
+
const node = ctx.startNode('else_clause');
|
|
262
|
+
ctx.addAnonymousChild(node, ctx.consume()); // else
|
|
263
|
+
parseColonAndProcedureBody(ctx, node, startTok.start.row, false, parseTemplate);
|
|
264
|
+
ctx.finishNode(node, startTok);
|
|
265
|
+
return node;
|
|
266
|
+
}
|
|
267
|
+
export function parseRunStatement(ctx, parseTemplate) {
|
|
268
|
+
const startTok = ctx.peek();
|
|
269
|
+
const node = ctx.startNode('run_statement');
|
|
270
|
+
ctx.addAnonymousChild(node, ctx.consume()); // run
|
|
271
|
+
// Target expression
|
|
272
|
+
if (!ctx.isAtSyncPoint()) {
|
|
273
|
+
const target = parseExpression(ctx, 0);
|
|
274
|
+
if (target) {
|
|
275
|
+
node.appendChild(wrapExpression(ctx, target), 'target');
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
addMissingTarget(ctx, node);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
// `run` with no target at all → insert ERROR placeholder
|
|
283
|
+
addMissingTarget(ctx, node);
|
|
284
|
+
}
|
|
285
|
+
// Optional indented block (procedure)
|
|
286
|
+
if (ctx.peekKind() === TokenKind.INDENT) {
|
|
287
|
+
ctx.consume();
|
|
288
|
+
// Comments before procedure body attach to run_statement
|
|
289
|
+
consumeCommentsAndSkipNewlines(ctx, node);
|
|
290
|
+
const proc = parseProcedure(ctx, parseTemplate);
|
|
291
|
+
if (proc) {
|
|
292
|
+
// If procedure contains an ERROR with `with` keyword (invalid with clause),
|
|
293
|
+
// attach children directly to run_statement so the dialect's
|
|
294
|
+
// error recovery can find them (it looks at run_statement.children).
|
|
295
|
+
const hasWithError = proc.namedChildren.some(c => c.isError && c.children.some(cc => cc.type === 'with'));
|
|
296
|
+
if (hasWithError) {
|
|
297
|
+
for (const child of proc.namedChildren) {
|
|
298
|
+
node.appendChild(child);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
node.appendChild(proc, 'block_value');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
consumeCommentsAndSkipNewlines(ctx, node);
|
|
306
|
+
if (ctx.peekKind() === TokenKind.DEDENT)
|
|
307
|
+
ctx.consume();
|
|
308
|
+
}
|
|
309
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
310
|
+
ctx.consume();
|
|
311
|
+
ctx.finishNode(node, startTok);
|
|
312
|
+
return node;
|
|
313
|
+
}
|
|
314
|
+
export function parseSetStatement(ctx) {
|
|
315
|
+
const startTok = ctx.peek();
|
|
316
|
+
const node = ctx.startNode('set_statement');
|
|
317
|
+
ctx.addAnonymousChild(node, ctx.consume()); // set
|
|
318
|
+
// Parse target at precedence 5 (above comparison/=) so = and == aren't consumed
|
|
319
|
+
const target = parseExpression(ctx, 5);
|
|
320
|
+
if (ctx.peekKind() === TokenKind.EQEQ) {
|
|
321
|
+
// set @var == "value" → ERROR: == instead of =
|
|
322
|
+
// Build comparison_expression(target, ==, rhs) and wrap in ERROR
|
|
323
|
+
// Don't add target to node — we're returning an ERROR node instead
|
|
324
|
+
const eqTok = ctx.consume(); // ==
|
|
325
|
+
const rhs = parseExpression(ctx, 0);
|
|
326
|
+
if (target && rhs) {
|
|
327
|
+
const cmp = ctx.startNodeAt('comparison_expression', wrapExpression(ctx, target));
|
|
328
|
+
cmp.appendChild(wrapExpression(ctx, target));
|
|
329
|
+
cmp.appendChild(new CSTNode(eqTok.text, ctx.source, eqTok.startOffset, eqTok.startOffset + 2, eqTok.start, eqTok.end, false));
|
|
330
|
+
cmp.appendChild(wrapExpression(ctx, rhs));
|
|
331
|
+
cmp.finalize();
|
|
332
|
+
const wrappedCmp = wrapExpression(ctx, cmp);
|
|
333
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
334
|
+
ctx.consume();
|
|
335
|
+
// Return ERROR instead of set_statement
|
|
336
|
+
return makeErrorNode(ctx.source, [wrappedCmp], wrappedCmp.startOffset, wrappedCmp.endOffset, wrappedCmp.startPosition, wrappedCmp.endPosition);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Add target to node only after ruling out the == error case
|
|
340
|
+
if (target)
|
|
341
|
+
node.appendChild(wrapExpression(ctx, target), 'target');
|
|
342
|
+
if (ctx.peekKind() === TokenKind.EQ) {
|
|
343
|
+
ctx.addAnonymousChild(node, ctx.consume()); // =
|
|
344
|
+
const value = parseExpression(ctx, 0);
|
|
345
|
+
if (value)
|
|
346
|
+
node.appendChild(wrapExpression(ctx, value), 'value');
|
|
347
|
+
}
|
|
348
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
349
|
+
ctx.consume();
|
|
350
|
+
ctx.finishNode(node, startTok);
|
|
351
|
+
return node;
|
|
352
|
+
}
|
|
353
|
+
export function parseTransitionStatement(ctx) {
|
|
354
|
+
const startTok = ctx.peek();
|
|
355
|
+
const node = ctx.startNode('transition_statement');
|
|
356
|
+
ctx.addAnonymousChild(node, ctx.consume()); // transition
|
|
357
|
+
// Optional with/to statement list
|
|
358
|
+
const withToList = tryParseWithToStatementList(ctx);
|
|
359
|
+
if (withToList) {
|
|
360
|
+
node.appendChild(withToList, 'with_to_statement_list');
|
|
361
|
+
}
|
|
362
|
+
else if (!ctx.isAtSyncPoint() &&
|
|
363
|
+
ctx.peekKind() !== TokenKind.NEWLINE &&
|
|
364
|
+
ctx.peekKind() !== TokenKind.EOF) {
|
|
365
|
+
// No with/to list found but there are tokens remaining on the same line —
|
|
366
|
+
// likely a missing 'to' keyword (e.g. "transition @topic.greeting").
|
|
367
|
+
// Insert a synthetic with_to_statement_list containing a to_statement
|
|
368
|
+
// with a MISSING 'to' node.
|
|
369
|
+
const listNode = ctx.startNode('with_to_statement_list');
|
|
370
|
+
const toNode = ctx.startNode('to_statement');
|
|
371
|
+
toNode.appendChild(makeMissing(ctx, 'to'));
|
|
372
|
+
const target = parseExpression(ctx, 0);
|
|
373
|
+
if (target)
|
|
374
|
+
toNode.appendChild(wrapExpression(ctx, target), 'target');
|
|
375
|
+
toNode.finalize();
|
|
376
|
+
listNode.appendChild(toNode);
|
|
377
|
+
listNode.finalize();
|
|
378
|
+
node.appendChild(listNode, 'with_to_statement_list');
|
|
379
|
+
}
|
|
380
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
381
|
+
ctx.consume();
|
|
382
|
+
ctx.finishNode(node, startTok);
|
|
383
|
+
return node;
|
|
384
|
+
}
|
|
385
|
+
export function parseWithStatement(ctx) {
|
|
386
|
+
const startTok = ctx.peek();
|
|
387
|
+
// Check if `with` is followed by a valid param (ID/STRING).
|
|
388
|
+
// If not (e.g., `with ...`), create ERROR containing `with` keyword.
|
|
389
|
+
// The remaining tokens (e.g. `...`) stay unconsumed for the caller.
|
|
390
|
+
if (ctx.peekAt(1).kind !== TokenKind.ID &&
|
|
391
|
+
ctx.peekAt(1).kind !== TokenKind.STRING) {
|
|
392
|
+
const withTok = ctx.consume();
|
|
393
|
+
const kwOffset = ctx.currentOffset();
|
|
394
|
+
const withChild = new CSTNode('with', ctx.source, kwOffset, kwOffset + 4, withTok.start, withTok.end, false);
|
|
395
|
+
return makeErrorNode(ctx.source, [withChild], kwOffset, kwOffset + 4, withTok.start, withTok.end);
|
|
396
|
+
}
|
|
397
|
+
const node = ctx.startNode('with_statement');
|
|
398
|
+
ctx.addAnonymousChild(node, ctx.consume()); // with
|
|
399
|
+
// Parse param=value pairs
|
|
400
|
+
parseWithParams(ctx, node);
|
|
401
|
+
// Inline comment on the with line (e.g. `with city=x # comment`)
|
|
402
|
+
if (ctx.peekKind() === TokenKind.COMMENT) {
|
|
403
|
+
node.appendChild(ctx.consumeNamed('comment'));
|
|
404
|
+
}
|
|
405
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
406
|
+
ctx.consume();
|
|
407
|
+
ctx.finishNode(node, startTok);
|
|
408
|
+
return node;
|
|
409
|
+
}
|
|
410
|
+
function parseWithParams(ctx, node) {
|
|
411
|
+
while (!ctx.isAtSyncPoint()) {
|
|
412
|
+
// param
|
|
413
|
+
if (ctx.peekKind() === TokenKind.ID ||
|
|
414
|
+
ctx.peekKind() === TokenKind.STRING) {
|
|
415
|
+
if (ctx.peekKind() === TokenKind.STRING) {
|
|
416
|
+
node.appendChild(parseString(ctx), 'param');
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
node.appendChild(ctx.consumeNamed('id'), 'param');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
// Not a valid param — wrap remaining tokens in ERROR inside with_statement
|
|
424
|
+
const err = synchronize(ctx);
|
|
425
|
+
if (err)
|
|
426
|
+
node.appendChild(err);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// =
|
|
430
|
+
if (ctx.peekKind() === TokenKind.EQ) {
|
|
431
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
// Missing = → insert MISSING
|
|
435
|
+
node.appendChild(makeMissing(ctx, '='));
|
|
436
|
+
}
|
|
437
|
+
// value
|
|
438
|
+
const value = parseExpression(ctx, 0);
|
|
439
|
+
if (value)
|
|
440
|
+
node.appendChild(wrapExpression(ctx, value), 'value');
|
|
441
|
+
// comma
|
|
442
|
+
if (ctx.peekKind() === TokenKind.COMMA) {
|
|
443
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
export function parseAvailableWhenStatement(ctx) {
|
|
451
|
+
const startTok = ctx.peek();
|
|
452
|
+
const node = ctx.startNode('available_when_statement');
|
|
453
|
+
ctx.addAnonymousChild(node, ctx.consume()); // available
|
|
454
|
+
ctx.addAnonymousChild(node, ctx.consume()); // when
|
|
455
|
+
const condition = parseExpression(ctx, 0);
|
|
456
|
+
if (condition)
|
|
457
|
+
node.appendChild(wrapExpression(ctx, condition), 'condition');
|
|
458
|
+
if (ctx.peekKind() === TokenKind.NEWLINE)
|
|
459
|
+
ctx.consume();
|
|
460
|
+
ctx.finishNode(node, startTok);
|
|
461
|
+
return node;
|
|
462
|
+
}
|
|
463
|
+
// ---------------------------------------------------------------------------
|
|
464
|
+
// With/To statement list
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
466
|
+
export function tryParseWithToStatementList(ctx) {
|
|
467
|
+
const tok = ctx.peek();
|
|
468
|
+
if (!isTokenKind(tok, TokenKind.ID))
|
|
469
|
+
return null;
|
|
470
|
+
if (!['with', 'to'].includes(tok.text))
|
|
471
|
+
return null;
|
|
472
|
+
const startTok = tok;
|
|
473
|
+
const node = ctx.startNode('with_to_statement_list');
|
|
474
|
+
while (!ctx.isAtSyncPoint()) {
|
|
475
|
+
if (ctx.peekKind() === TokenKind.ID && ctx.peek().text === 'with') {
|
|
476
|
+
node.appendChild(parseInlineWithStatement(ctx));
|
|
477
|
+
}
|
|
478
|
+
else if (ctx.peekKind() === TokenKind.ID && ctx.peek().text === 'to') {
|
|
479
|
+
node.appendChild(parseToStatement(ctx));
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
if (ctx.peekKind() === TokenKind.COMMA) {
|
|
485
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (node.children.length === 0)
|
|
492
|
+
return null;
|
|
493
|
+
ctx.finishNode(node, startTok);
|
|
494
|
+
return node;
|
|
495
|
+
}
|
|
496
|
+
export function parseInlineWithStatement(ctx) {
|
|
497
|
+
const startTok = ctx.peek();
|
|
498
|
+
const node = ctx.startNode('with_statement');
|
|
499
|
+
ctx.addAnonymousChild(node, ctx.consume()); // with
|
|
500
|
+
parseWithParams(ctx, node);
|
|
501
|
+
ctx.finishNode(node, startTok);
|
|
502
|
+
return node;
|
|
503
|
+
}
|
|
504
|
+
export function parseToStatement(ctx) {
|
|
505
|
+
const startTok = ctx.peek();
|
|
506
|
+
const node = ctx.startNode('to_statement');
|
|
507
|
+
ctx.addAnonymousChild(node, ctx.consume()); // to
|
|
508
|
+
const target = parseExpression(ctx, 0);
|
|
509
|
+
if (target) {
|
|
510
|
+
node.appendChild(wrapExpression(ctx, target), 'target');
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
// Missing target → ERROR
|
|
514
|
+
node.appendChild(makeEmptyError(ctx));
|
|
515
|
+
}
|
|
516
|
+
ctx.finishNode(node, startTok);
|
|
517
|
+
return node;
|
|
518
|
+
}
|
|
519
|
+
//# sourceMappingURL=parse-statements.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CSTNode } from './cst-node.js';
|
|
2
|
+
import type { ParserContext } from './parser.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse a template starting with `|`.
|
|
5
|
+
* Consumes tokens from the lexer stream, treating everything as template content
|
|
6
|
+
* except `{!...}` breaks which are parsed as template expressions.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseTemplate(ctx: ParserContext): CSTNode;
|
|
9
|
+
/**
|
|
10
|
+
* Parse a template in colinear position (after a colon on the same line).
|
|
11
|
+
* Currently identical to parseTemplate; kept as a separate entry point
|
|
12
|
+
* for semantic clarity and potential future divergence.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseTemplateAsColinear(ctx: ParserContext): CSTNode;
|
|
15
|
+
//# sourceMappingURL=parse-templates.d.ts.map
|