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,682 @@
|
|
|
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
|
+
* Pratt expression parser for AgentScript.
|
|
9
|
+
*
|
|
10
|
+
* Precedence levels (matching grammar.js):
|
|
11
|
+
* 0: ternary (X if C else Y) — right-associative
|
|
12
|
+
* 1: or
|
|
13
|
+
* 2: and
|
|
14
|
+
* 3: not (prefix)
|
|
15
|
+
* 4: ==, !=, <, >, <=, >=, is, is not, = (comparison)
|
|
16
|
+
* 5: +, - (binary)
|
|
17
|
+
* 6: *, /
|
|
18
|
+
* 7: +, - (unary prefix)
|
|
19
|
+
* 8: call, member, subscript (postfix)
|
|
20
|
+
* 9: parenthesized (atomic)
|
|
21
|
+
*/
|
|
22
|
+
import { TokenKind } from './token.js';
|
|
23
|
+
import { CSTNode } from './cst-node.js';
|
|
24
|
+
// Hoisted constants — avoid per-call allocation
|
|
25
|
+
// Must match ESCAPE_TABLE in @agentscript/language (packages/language/src/core/string-escapes.ts)
|
|
26
|
+
const VALID_ESCAPES = new Set(['"', "'", '\\', 'n', 'r', 't', '0']);
|
|
27
|
+
const KEY_STOP_KEYWORDS = new Set([
|
|
28
|
+
'if',
|
|
29
|
+
'elif',
|
|
30
|
+
'else',
|
|
31
|
+
'run',
|
|
32
|
+
'set',
|
|
33
|
+
'with',
|
|
34
|
+
'to',
|
|
35
|
+
'transition',
|
|
36
|
+
'available',
|
|
37
|
+
'and',
|
|
38
|
+
'or',
|
|
39
|
+
'not',
|
|
40
|
+
'is',
|
|
41
|
+
'True',
|
|
42
|
+
'False',
|
|
43
|
+
'None',
|
|
44
|
+
'mutable',
|
|
45
|
+
'linked',
|
|
46
|
+
'empty',
|
|
47
|
+
]);
|
|
48
|
+
/** Create a MISSING id wrapped in atom → expression at the current parser position. */
|
|
49
|
+
function makeMissingArgument(ctx) {
|
|
50
|
+
const offset = ctx.peekOffset();
|
|
51
|
+
const pos = ctx.peek().start;
|
|
52
|
+
const missingId = new CSTNode('id', ctx.source, offset, offset, pos, pos, true, false, true);
|
|
53
|
+
const atom = new CSTNode('atom', ctx.source, offset, offset, pos, pos);
|
|
54
|
+
atom.appendChild(missingId);
|
|
55
|
+
const expr = new CSTNode('expression', ctx.source, offset, offset, pos, pos);
|
|
56
|
+
expr.appendChild(atom);
|
|
57
|
+
return expr;
|
|
58
|
+
}
|
|
59
|
+
/** Create an empty ERROR node at the current parser position. */
|
|
60
|
+
function makeEmptyError(ctx) {
|
|
61
|
+
const tok = ctx.peek();
|
|
62
|
+
const offset = ctx.peekOffset();
|
|
63
|
+
return new CSTNode('ERROR', ctx.source, offset, offset, tok.start, tok.start, true, true);
|
|
64
|
+
}
|
|
65
|
+
export function parseExpression(ctx, minPrec = 0) {
|
|
66
|
+
let left = parsePrefix(ctx);
|
|
67
|
+
if (!left)
|
|
68
|
+
return null;
|
|
69
|
+
while (true) {
|
|
70
|
+
// Fast path: sync points (NEWLINE/DEDENT/EOF) are never infix operators.
|
|
71
|
+
// This avoids the infixPrecedence() lookup in the most common case for mappings.
|
|
72
|
+
const nextKind = ctx.peekKind();
|
|
73
|
+
if (nextKind === TokenKind.NEWLINE ||
|
|
74
|
+
nextKind === TokenKind.DEDENT ||
|
|
75
|
+
nextKind === TokenKind.EOF)
|
|
76
|
+
break;
|
|
77
|
+
const prec = infixPrecedence(ctx);
|
|
78
|
+
if (prec < minPrec)
|
|
79
|
+
break;
|
|
80
|
+
const result = parseInfix(ctx, left, prec);
|
|
81
|
+
if (!result)
|
|
82
|
+
break;
|
|
83
|
+
left = result;
|
|
84
|
+
}
|
|
85
|
+
return left;
|
|
86
|
+
}
|
|
87
|
+
function parsePrefix(ctx) {
|
|
88
|
+
const tok = ctx.peek();
|
|
89
|
+
// not (precedence 3)
|
|
90
|
+
if (tok.kind === TokenKind.ID && tok.text === 'not') {
|
|
91
|
+
return parseUnary(ctx, 'not', 3);
|
|
92
|
+
}
|
|
93
|
+
// Unary + / - (precedence 7)
|
|
94
|
+
if (tok.kind === TokenKind.PLUS || tok.kind === TokenKind.MINUS) {
|
|
95
|
+
const op = tok.text;
|
|
96
|
+
return parseUnary(ctx, op, 7);
|
|
97
|
+
}
|
|
98
|
+
// Spread *expr (precedence 7)
|
|
99
|
+
if (tok.kind === TokenKind.STAR) {
|
|
100
|
+
return parseSpread(ctx);
|
|
101
|
+
}
|
|
102
|
+
// Parenthesized expression
|
|
103
|
+
if (tok.kind === TokenKind.LPAREN) {
|
|
104
|
+
return parseParenthesized(ctx);
|
|
105
|
+
}
|
|
106
|
+
// Atom
|
|
107
|
+
return parseAtom(ctx);
|
|
108
|
+
}
|
|
109
|
+
function parseUnary(ctx, _op, prec) {
|
|
110
|
+
const startTok = ctx.peek();
|
|
111
|
+
const node = ctx.startNode('unary_expression');
|
|
112
|
+
ctx.addAnonymousChild(node, ctx.consume()); // operator
|
|
113
|
+
const operand = parseExpression(ctx, prec + 1);
|
|
114
|
+
if (operand) {
|
|
115
|
+
node.appendChild(wrapExpression(ctx, operand));
|
|
116
|
+
}
|
|
117
|
+
ctx.finishNode(node, startTok);
|
|
118
|
+
return node;
|
|
119
|
+
}
|
|
120
|
+
function parseSpread(ctx) {
|
|
121
|
+
const startTok = ctx.peek();
|
|
122
|
+
const node = ctx.startNode('spread_expression');
|
|
123
|
+
ctx.addAnonymousChild(node, ctx.consume()); // *
|
|
124
|
+
// Bind at precedence 8 (same as postfix call/member/subscript) so
|
|
125
|
+
// *@variables.x parses as *(variables.x), not (*variables).x
|
|
126
|
+
const operand = parseExpression(ctx, 8);
|
|
127
|
+
if (operand) {
|
|
128
|
+
node.appendChild(wrapExpression(ctx, operand), 'expression');
|
|
129
|
+
}
|
|
130
|
+
ctx.finishNode(node, startTok);
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
function parseParenthesized(ctx) {
|
|
134
|
+
const startTok = ctx.peek();
|
|
135
|
+
const node = ctx.startNode('parenthesized_expression');
|
|
136
|
+
ctx.addAnonymousChild(node, ctx.consume()); // (
|
|
137
|
+
const expr = parseExpression(ctx, 0);
|
|
138
|
+
if (expr) {
|
|
139
|
+
node.appendChild(wrapExpression(ctx, expr), 'expression');
|
|
140
|
+
}
|
|
141
|
+
else if (ctx.peekKind() === TokenKind.RPAREN) {
|
|
142
|
+
// Empty parens () → insert MISSING id
|
|
143
|
+
node.appendChild(makeMissingArgument(ctx), 'expression');
|
|
144
|
+
}
|
|
145
|
+
if (ctx.peekKind() === TokenKind.RPAREN) {
|
|
146
|
+
ctx.addAnonymousChild(node, ctx.consume()); // )
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Unclosed paren → add ERROR node
|
|
150
|
+
node.appendChild(makeEmptyError(ctx));
|
|
151
|
+
}
|
|
152
|
+
ctx.finishNode(node, startTok);
|
|
153
|
+
return node;
|
|
154
|
+
}
|
|
155
|
+
function parseAtom(ctx) {
|
|
156
|
+
const tok = ctx.peek();
|
|
157
|
+
// Boolean / None constants
|
|
158
|
+
if (tok.kind === TokenKind.ID &&
|
|
159
|
+
(tok.text === 'True' || tok.text === 'False' || tok.text === 'None')) {
|
|
160
|
+
const node = ctx.startNode('atom');
|
|
161
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
162
|
+
ctx.finishNode(node, tok);
|
|
163
|
+
return node;
|
|
164
|
+
}
|
|
165
|
+
// empty keyword
|
|
166
|
+
if (tok.kind === TokenKind.ID && tok.text === 'empty') {
|
|
167
|
+
const node = ctx.startNode('empty_keyword');
|
|
168
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
169
|
+
ctx.finishNode(node, tok);
|
|
170
|
+
return node;
|
|
171
|
+
}
|
|
172
|
+
// @id
|
|
173
|
+
if (tok.kind === TokenKind.AT) {
|
|
174
|
+
return parseAtId(ctx);
|
|
175
|
+
}
|
|
176
|
+
// id
|
|
177
|
+
if (tok.kind === TokenKind.ID) {
|
|
178
|
+
return ctx.consumeNamed('id');
|
|
179
|
+
}
|
|
180
|
+
// number
|
|
181
|
+
if (tok.kind === TokenKind.NUMBER) {
|
|
182
|
+
return ctx.consumeNamed('number');
|
|
183
|
+
}
|
|
184
|
+
// datetime
|
|
185
|
+
if (tok.kind === TokenKind.DATETIME) {
|
|
186
|
+
return ctx.consumeNamed('datetime_literal');
|
|
187
|
+
}
|
|
188
|
+
// string
|
|
189
|
+
if (tok.kind === TokenKind.STRING) {
|
|
190
|
+
return parseString(ctx);
|
|
191
|
+
}
|
|
192
|
+
// ellipsis
|
|
193
|
+
if (tok.kind === TokenKind.ELLIPSIS) {
|
|
194
|
+
return ctx.consumeNamed('ellipsis');
|
|
195
|
+
}
|
|
196
|
+
// list [...]
|
|
197
|
+
if (tok.kind === TokenKind.LBRACKET) {
|
|
198
|
+
return parseList(ctx);
|
|
199
|
+
}
|
|
200
|
+
// dictionary {...}
|
|
201
|
+
if (tok.kind === TokenKind.LBRACE) {
|
|
202
|
+
return parseDictionary(ctx);
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
function parseAtId(ctx) {
|
|
207
|
+
const startTok = ctx.peek();
|
|
208
|
+
const node = ctx.startNode('at_id');
|
|
209
|
+
ctx.addAnonymousChild(node, ctx.consume()); // @
|
|
210
|
+
if (ctx.peekKind() === TokenKind.ID) {
|
|
211
|
+
node.appendChild(ctx.consumeNamed('id'));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// @ with no identifier → ERROR
|
|
215
|
+
node.appendChild(makeEmptyError(ctx));
|
|
216
|
+
}
|
|
217
|
+
ctx.finishNode(node, startTok);
|
|
218
|
+
return node;
|
|
219
|
+
}
|
|
220
|
+
export function parseString(ctx) {
|
|
221
|
+
const tok = ctx.peek();
|
|
222
|
+
const startTok = tok;
|
|
223
|
+
const node = ctx.startNode('string');
|
|
224
|
+
// The lexer gives us the whole string as one token.
|
|
225
|
+
// We need to break it into children: opening quote, string_content/escape_sequence, closing quote.
|
|
226
|
+
const text = tok.text;
|
|
227
|
+
const tokenOffset = ctx.peekOffset();
|
|
228
|
+
ctx.consume(); // consume the full string token
|
|
229
|
+
const baseRow = startTok.start.row;
|
|
230
|
+
const baseCol = startTok.start.column;
|
|
231
|
+
// Opening quote
|
|
232
|
+
node.appendChild(new CSTNode('"', ctx.source, tokenOffset, tokenOffset + 1, { row: baseRow, column: baseCol }, { row: baseRow, column: baseCol + 1 }, false));
|
|
233
|
+
// Parse content between quotes
|
|
234
|
+
let i = 1; // skip opening "
|
|
235
|
+
const quoteChar = text[0]; // " or '
|
|
236
|
+
const hasClosingQuote = text.length > 1 && text[text.length - 1] === quoteChar;
|
|
237
|
+
const contentEnd = hasClosingQuote ? text.length - 1 : text.length;
|
|
238
|
+
let contentStart = i;
|
|
239
|
+
while (i < contentEnd) {
|
|
240
|
+
if (text[i] === '\\' &&
|
|
241
|
+
i + 1 < contentEnd &&
|
|
242
|
+
VALID_ESCAPES.has(text[i + 1])) {
|
|
243
|
+
// Emit any accumulated content before the escape
|
|
244
|
+
if (i > contentStart) {
|
|
245
|
+
node.appendChild(new CSTNode('string_content', ctx.source, tokenOffset + contentStart, tokenOffset + i, { row: baseRow, column: baseCol + contentStart }, { row: baseRow, column: baseCol + i }));
|
|
246
|
+
}
|
|
247
|
+
// Emit escape sequence
|
|
248
|
+
const escLen = 2;
|
|
249
|
+
node.appendChild(new CSTNode('escape_sequence', ctx.source, tokenOffset + i, tokenOffset + i + escLen, { row: baseRow, column: baseCol + i }, { row: baseRow, column: baseCol + i + escLen }));
|
|
250
|
+
i += escLen;
|
|
251
|
+
contentStart = i;
|
|
252
|
+
}
|
|
253
|
+
else if (text[i] === '\\' &&
|
|
254
|
+
i + 1 < contentEnd &&
|
|
255
|
+
!VALID_ESCAPES.has(text[i + 1])) {
|
|
256
|
+
// Invalid escape sequence — emit accumulated content, then ERROR
|
|
257
|
+
if (i > contentStart) {
|
|
258
|
+
node.appendChild(new CSTNode('string_content', ctx.source, tokenOffset + contentStart, tokenOffset + i, { row: baseRow, column: baseCol + contentStart }, { row: baseRow, column: baseCol + i }));
|
|
259
|
+
}
|
|
260
|
+
// Find the extent of the invalid escape: \x followed by remaining word chars
|
|
261
|
+
const escStart = i;
|
|
262
|
+
i += 2; // skip \ and the invalid char
|
|
263
|
+
while (i < contentEnd && /[a-zA-Z0-9_]/.test(text[i])) {
|
|
264
|
+
i++;
|
|
265
|
+
}
|
|
266
|
+
const errNode = new CSTNode('ERROR', ctx.source, tokenOffset + escStart, tokenOffset + i, { row: baseRow, column: baseCol + escStart }, { row: baseRow, column: baseCol + i }, true, true);
|
|
267
|
+
node.appendChild(errNode);
|
|
268
|
+
contentStart = i;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
i++;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Emit remaining content
|
|
275
|
+
if (i > contentStart) {
|
|
276
|
+
node.appendChild(new CSTNode('string_content', ctx.source, tokenOffset + contentStart, tokenOffset + i, { row: baseRow, column: baseCol + contentStart }, { row: baseRow, column: baseCol + i }));
|
|
277
|
+
}
|
|
278
|
+
// Closing quote
|
|
279
|
+
if (hasClosingQuote) {
|
|
280
|
+
node.appendChild(new CSTNode(quoteChar, ctx.source, tokenOffset + text.length - 1, tokenOffset + text.length, { row: baseRow, column: baseCol + text.length - 1 }, { row: baseRow, column: baseCol + text.length }, false));
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// Unclosed string → MISSING closing quote
|
|
284
|
+
// Position at end of string content (where the quote should have been),
|
|
285
|
+
// not at the next token which may be on the next line.
|
|
286
|
+
const missingOffset = tokenOffset + text.length;
|
|
287
|
+
const missingPos = { row: baseRow, column: baseCol + text.length };
|
|
288
|
+
node.appendChild(new CSTNode(quoteChar, ctx.source, missingOffset, missingOffset, missingPos, missingPos, false, false, true));
|
|
289
|
+
}
|
|
290
|
+
ctx.finishNode(node, startTok);
|
|
291
|
+
return node;
|
|
292
|
+
}
|
|
293
|
+
function parseList(ctx) {
|
|
294
|
+
const startTok = ctx.peek();
|
|
295
|
+
const node = ctx.startNode('list');
|
|
296
|
+
ctx.addAnonymousChild(node, ctx.consume()); // [
|
|
297
|
+
// Lists can span multiple lines — skip whitespace tokens inside [...]
|
|
298
|
+
let _listIndentDepth = 0;
|
|
299
|
+
while (ctx.peekKind() !== TokenKind.RBRACKET &&
|
|
300
|
+
ctx.peekKind() !== TokenKind.EOF) {
|
|
301
|
+
if (ctx.peekKind() === TokenKind.NEWLINE) {
|
|
302
|
+
ctx.consume();
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (ctx.peekKind() === TokenKind.INDENT) {
|
|
306
|
+
_listIndentDepth++;
|
|
307
|
+
ctx.consume();
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (ctx.peekKind() === TokenKind.DEDENT) {
|
|
311
|
+
_listIndentDepth--;
|
|
312
|
+
ctx.consume();
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const expr = parseExpression(ctx, 0);
|
|
316
|
+
if (expr) {
|
|
317
|
+
node.appendChild(wrapExpression(ctx, expr));
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
if (ctx.peekKind() === TokenKind.COMMA) {
|
|
323
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// Skip whitespace tokens to find the closing ]
|
|
330
|
+
while (ctx.peekKind() === TokenKind.NEWLINE ||
|
|
331
|
+
ctx.peekKind() === TokenKind.INDENT ||
|
|
332
|
+
ctx.peekKind() === TokenKind.DEDENT) {
|
|
333
|
+
ctx.consume();
|
|
334
|
+
}
|
|
335
|
+
if (ctx.peekKind() === TokenKind.RBRACKET) {
|
|
336
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
node.appendChild(makeEmptyError(ctx));
|
|
340
|
+
}
|
|
341
|
+
ctx.finishNode(node, startTok);
|
|
342
|
+
return node;
|
|
343
|
+
}
|
|
344
|
+
function parseDictionary(ctx) {
|
|
345
|
+
const startTok = ctx.peek();
|
|
346
|
+
const node = ctx.startNode('dictionary');
|
|
347
|
+
ctx.addAnonymousChild(node, ctx.consume()); // {
|
|
348
|
+
// Dictionaries can span multiple lines
|
|
349
|
+
while (ctx.peekKind() !== TokenKind.RBRACE &&
|
|
350
|
+
ctx.peekKind() !== TokenKind.EOF) {
|
|
351
|
+
if (ctx.peekKind() === TokenKind.NEWLINE ||
|
|
352
|
+
ctx.peekKind() === TokenKind.INDENT ||
|
|
353
|
+
ctx.peekKind() === TokenKind.DEDENT) {
|
|
354
|
+
ctx.consume();
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const pair = parseDictionaryPair(ctx);
|
|
358
|
+
if (pair) {
|
|
359
|
+
node.appendChild(pair);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
if (ctx.peekKind() === TokenKind.COMMA) {
|
|
365
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (ctx.peekKind() === TokenKind.RBRACE) {
|
|
372
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
node.appendChild(makeEmptyError(ctx));
|
|
376
|
+
}
|
|
377
|
+
ctx.finishNode(node, startTok);
|
|
378
|
+
return node;
|
|
379
|
+
}
|
|
380
|
+
function parseDictionaryPair(ctx) {
|
|
381
|
+
const startTok = ctx.peek();
|
|
382
|
+
if (!isKeyStart(ctx))
|
|
383
|
+
return null;
|
|
384
|
+
const node = ctx.startNode('dictionary_pair');
|
|
385
|
+
const key = parseKey(ctx);
|
|
386
|
+
if (key)
|
|
387
|
+
node.appendChild(key, 'key');
|
|
388
|
+
if (ctx.peekKind() === TokenKind.COLON) {
|
|
389
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
390
|
+
}
|
|
391
|
+
const value = parseExpression(ctx, 0);
|
|
392
|
+
if (value)
|
|
393
|
+
node.appendChild(wrapExpression(ctx, value), 'value');
|
|
394
|
+
ctx.finishNode(node, startTok);
|
|
395
|
+
return node;
|
|
396
|
+
}
|
|
397
|
+
// --- Infix parsing ---
|
|
398
|
+
// Precedence lookup tables (O(1) instead of 20+ if-statements)
|
|
399
|
+
const INFIX_PREC_BY_KIND = new Map([
|
|
400
|
+
[TokenKind.LPAREN, 8],
|
|
401
|
+
[TokenKind.DOT, 8],
|
|
402
|
+
[TokenKind.LBRACKET, 8],
|
|
403
|
+
[TokenKind.EQEQ, 4],
|
|
404
|
+
[TokenKind.NEQ, 4],
|
|
405
|
+
[TokenKind.LT, 4],
|
|
406
|
+
[TokenKind.GT, 4],
|
|
407
|
+
[TokenKind.LTE, 4],
|
|
408
|
+
[TokenKind.GTE, 4],
|
|
409
|
+
[TokenKind.PLUS, 5],
|
|
410
|
+
[TokenKind.MINUS, 5],
|
|
411
|
+
[TokenKind.STAR, 6],
|
|
412
|
+
[TokenKind.SLASH, 6],
|
|
413
|
+
]);
|
|
414
|
+
const INFIX_KEYWORD_PREC = new Map([
|
|
415
|
+
['if', 0],
|
|
416
|
+
['or', 1],
|
|
417
|
+
['and', 2],
|
|
418
|
+
['is', 4],
|
|
419
|
+
]);
|
|
420
|
+
function infixPrecedence(ctx) {
|
|
421
|
+
const tok = ctx.peek();
|
|
422
|
+
if (tok.kind === TokenKind.ID)
|
|
423
|
+
return INFIX_KEYWORD_PREC.get(tok.text) ?? -2;
|
|
424
|
+
return INFIX_PREC_BY_KIND.get(tok.kind) ?? -2;
|
|
425
|
+
}
|
|
426
|
+
function parseInfix(ctx, left, prec) {
|
|
427
|
+
const tok = ctx.peek();
|
|
428
|
+
// Call expression: expr(args)
|
|
429
|
+
if (tok.kind === TokenKind.LPAREN && prec === 8) {
|
|
430
|
+
return parseCall(ctx, left);
|
|
431
|
+
}
|
|
432
|
+
// Member expression: expr.id
|
|
433
|
+
if (tok.kind === TokenKind.DOT && prec === 8) {
|
|
434
|
+
return parseMember(ctx, left);
|
|
435
|
+
}
|
|
436
|
+
// Subscript expression: expr[expr]
|
|
437
|
+
if (tok.kind === TokenKind.LBRACKET && prec === 8) {
|
|
438
|
+
return parseSubscript(ctx, left);
|
|
439
|
+
}
|
|
440
|
+
// Ternary: consequence if condition else alternative
|
|
441
|
+
if (tok.kind === TokenKind.ID && tok.text === 'if') {
|
|
442
|
+
return parseTernary(ctx, left);
|
|
443
|
+
}
|
|
444
|
+
// "is not" compound operator
|
|
445
|
+
if (tok.kind === TokenKind.ID && tok.text === 'is') {
|
|
446
|
+
return parseIsExpression(ctx, left);
|
|
447
|
+
}
|
|
448
|
+
// Binary / comparison
|
|
449
|
+
return parseBinaryOrComparison(ctx, left, prec);
|
|
450
|
+
}
|
|
451
|
+
function parseCall(ctx, func) {
|
|
452
|
+
const startTok = ctx.peek();
|
|
453
|
+
const node = ctx.startNodeAt('call_expression', func);
|
|
454
|
+
node.appendChild(wrapExpression(ctx, func), 'function');
|
|
455
|
+
ctx.addAnonymousChild(node, ctx.consume()); // (
|
|
456
|
+
while (ctx.peekKind() !== TokenKind.RPAREN && !ctx.isAtSyncPoint()) {
|
|
457
|
+
const arg = parseExpression(ctx, 0);
|
|
458
|
+
if (arg) {
|
|
459
|
+
node.appendChild(wrapExpression(ctx, arg), 'argument');
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
if (ctx.peekKind() === TokenKind.COMMA) {
|
|
465
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
466
|
+
// Trailing comma: if `)` follows, insert MISSING id argument
|
|
467
|
+
if (ctx.peekKind() === TokenKind.RPAREN) {
|
|
468
|
+
node.appendChild(makeMissingArgument(ctx), 'argument');
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (ctx.peekKind() === TokenKind.RPAREN) {
|
|
477
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
node.appendChild(makeEmptyError(ctx));
|
|
481
|
+
}
|
|
482
|
+
ctx.finishNode(node, startTok);
|
|
483
|
+
return node;
|
|
484
|
+
}
|
|
485
|
+
function parseMember(ctx, object) {
|
|
486
|
+
const startTok = ctx.peek();
|
|
487
|
+
const node = ctx.startNodeAt('member_expression', object);
|
|
488
|
+
node.appendChild(wrapExpression(ctx, object));
|
|
489
|
+
ctx.addAnonymousChild(node, ctx.consume()); // .
|
|
490
|
+
if (ctx.peekKind() === TokenKind.ID) {
|
|
491
|
+
node.appendChild(ctx.consumeNamed('id'));
|
|
492
|
+
}
|
|
493
|
+
else if (ctx.peekKind() === TokenKind.NUMBER) {
|
|
494
|
+
// Error 19: member access with number like @var.123
|
|
495
|
+
const numNode = ctx.consumeNamed('number');
|
|
496
|
+
const errNode = new CSTNode('ERROR', ctx.source, numNode.startOffset, numNode.endOffset, numNode.startPosition, numNode.endPosition, true, true);
|
|
497
|
+
errNode.appendChild(numNode);
|
|
498
|
+
node.appendChild(errNode);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
// Trailing dot with nothing after → ERROR
|
|
502
|
+
node.appendChild(makeEmptyError(ctx));
|
|
503
|
+
}
|
|
504
|
+
ctx.finishNode(node, startTok);
|
|
505
|
+
return node;
|
|
506
|
+
}
|
|
507
|
+
function parseSubscript(ctx, object) {
|
|
508
|
+
const startTok = ctx.peek();
|
|
509
|
+
const node = ctx.startNodeAt('subscript_expression', object);
|
|
510
|
+
node.appendChild(wrapExpression(ctx, object));
|
|
511
|
+
ctx.addAnonymousChild(node, ctx.consume()); // [
|
|
512
|
+
const index = parseExpression(ctx, 0);
|
|
513
|
+
if (index) {
|
|
514
|
+
node.appendChild(wrapExpression(ctx, index));
|
|
515
|
+
}
|
|
516
|
+
if (ctx.peekKind() === TokenKind.RBRACKET) {
|
|
517
|
+
ctx.addAnonymousChild(node, ctx.consume());
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
node.appendChild(makeEmptyError(ctx));
|
|
521
|
+
}
|
|
522
|
+
ctx.finishNode(node, startTok);
|
|
523
|
+
return node;
|
|
524
|
+
}
|
|
525
|
+
function parseTernary(ctx, consequence) {
|
|
526
|
+
const startTok = ctx.peek();
|
|
527
|
+
const node = ctx.startNodeAt('ternary_expression', consequence);
|
|
528
|
+
node.appendChild(wrapExpression(ctx, consequence), 'consequence');
|
|
529
|
+
ctx.addAnonymousChild(node, ctx.consume()); // if
|
|
530
|
+
const condition = parseExpression(ctx, 1); // above 'or'
|
|
531
|
+
if (condition) {
|
|
532
|
+
node.appendChild(wrapExpression(ctx, condition), 'condition');
|
|
533
|
+
}
|
|
534
|
+
if (ctx.peekKind() === TokenKind.ID && ctx.peek().text === 'else') {
|
|
535
|
+
ctx.addAnonymousChild(node, ctx.consume()); // else
|
|
536
|
+
const alt = parseExpression(ctx, 0); // right-associative: parse at 0
|
|
537
|
+
if (alt) {
|
|
538
|
+
node.appendChild(wrapExpression(ctx, alt), 'alternative');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
// Incomplete ternary: "a if condition" without else → ERROR
|
|
543
|
+
node.appendChild(makeEmptyError(ctx));
|
|
544
|
+
}
|
|
545
|
+
ctx.finishNode(node, startTok);
|
|
546
|
+
return node;
|
|
547
|
+
}
|
|
548
|
+
function parseIsExpression(ctx, left) {
|
|
549
|
+
const startTok = ctx.peek();
|
|
550
|
+
// Check for "is not"
|
|
551
|
+
const isNot = ctx.peekAt(1).kind === TokenKind.ID && ctx.peekAt(1).text === 'not';
|
|
552
|
+
const nodeType = 'comparison_expression';
|
|
553
|
+
const node = ctx.startNodeAt(nodeType, left);
|
|
554
|
+
node.appendChild(wrapExpression(ctx, left));
|
|
555
|
+
ctx.addAnonymousChild(node, ctx.consume()); // is
|
|
556
|
+
if (isNot) {
|
|
557
|
+
ctx.addAnonymousChild(node, ctx.consume()); // not
|
|
558
|
+
}
|
|
559
|
+
const right = parseExpression(ctx, 5); // above binary +/-
|
|
560
|
+
if (right) {
|
|
561
|
+
node.appendChild(wrapExpression(ctx, right));
|
|
562
|
+
}
|
|
563
|
+
ctx.finishNode(node, startTok);
|
|
564
|
+
return node;
|
|
565
|
+
}
|
|
566
|
+
function parseBinaryOrComparison(ctx, left, prec) {
|
|
567
|
+
const tok = ctx.peek();
|
|
568
|
+
const startTok = tok;
|
|
569
|
+
// Determine if this is a comparison or binary expression
|
|
570
|
+
const isComparison = tok.kind === TokenKind.EQEQ ||
|
|
571
|
+
tok.kind === TokenKind.NEQ ||
|
|
572
|
+
tok.kind === TokenKind.LT ||
|
|
573
|
+
tok.kind === TokenKind.GT ||
|
|
574
|
+
tok.kind === TokenKind.LTE ||
|
|
575
|
+
tok.kind === TokenKind.GTE ||
|
|
576
|
+
tok.kind === TokenKind.EQ;
|
|
577
|
+
const nodeType = isComparison ? 'comparison_expression' : 'binary_expression';
|
|
578
|
+
const node = ctx.startNodeAt(nodeType, left);
|
|
579
|
+
node.appendChild(wrapExpression(ctx, left));
|
|
580
|
+
ctx.addAnonymousChild(node, ctx.consume()); // operator
|
|
581
|
+
const right = parseExpression(ctx, prec + 1); // left-associative
|
|
582
|
+
if (right) {
|
|
583
|
+
node.appendChild(wrapExpression(ctx, right));
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
// Incomplete binary/comparison: `3 +` or `@var ==` with no right operand
|
|
587
|
+
node.appendChild(makeEmptyError(ctx));
|
|
588
|
+
}
|
|
589
|
+
ctx.finishNode(node, startTok);
|
|
590
|
+
return node;
|
|
591
|
+
}
|
|
592
|
+
// --- Helpers ---
|
|
593
|
+
/**
|
|
594
|
+
* Types that are already wrapped or structural — should NOT get an expression wrapper.
|
|
595
|
+
* Everything else produced by expression parsing gets wrapped.
|
|
596
|
+
*/
|
|
597
|
+
const SKIP_WRAP_TYPES = new Set(['expression', 'ERROR']);
|
|
598
|
+
/**
|
|
599
|
+
* Leaf/literal types that need an intermediate `atom` wrapper before the `expression` wrapper.
|
|
600
|
+
*/
|
|
601
|
+
export const ATOM_TYPES = new Set([
|
|
602
|
+
'id',
|
|
603
|
+
'number',
|
|
604
|
+
'string',
|
|
605
|
+
'datetime_literal',
|
|
606
|
+
'at_id',
|
|
607
|
+
'list',
|
|
608
|
+
'dictionary',
|
|
609
|
+
'ellipsis',
|
|
610
|
+
]);
|
|
611
|
+
/**
|
|
612
|
+
* Wrap an expression in an `expression` supertype node if it isn't already one.
|
|
613
|
+
* Tree-sitter wraps most expression children in an (expression ...) wrapper.
|
|
614
|
+
*/
|
|
615
|
+
export function wrapExpression(ctx, inner) {
|
|
616
|
+
if (SKIP_WRAP_TYPES.has(inner.type)) {
|
|
617
|
+
return inner;
|
|
618
|
+
}
|
|
619
|
+
// For atoms, wrap in atom first then expression
|
|
620
|
+
let wrapped = inner;
|
|
621
|
+
if (ATOM_TYPES.has(inner.type)) {
|
|
622
|
+
const atom = new CSTNode('atom', ctx.source, inner.startOffset, inner.endOffset, inner.startPosition, inner.endPosition);
|
|
623
|
+
atom.appendChild(inner);
|
|
624
|
+
wrapped = atom;
|
|
625
|
+
}
|
|
626
|
+
// Now wrap in expression
|
|
627
|
+
const expr = new CSTNode('expression', ctx.source, wrapped.startOffset, wrapped.endOffset, wrapped.startPosition, wrapped.endPosition);
|
|
628
|
+
expr.appendChild(wrapped);
|
|
629
|
+
return expr;
|
|
630
|
+
}
|
|
631
|
+
export function isKeyStart(ctx) {
|
|
632
|
+
const tok = ctx.peek();
|
|
633
|
+
return isKeyTokenStart(tok.kind);
|
|
634
|
+
}
|
|
635
|
+
/** Can this token kind begin a key? (ID, STRING, or NUMBER for digit-prefixed keys like `3var`) */
|
|
636
|
+
export function isKeyTokenStart(kind) {
|
|
637
|
+
return (kind === TokenKind.ID ||
|
|
638
|
+
kind === TokenKind.STRING ||
|
|
639
|
+
kind === TokenKind.NUMBER);
|
|
640
|
+
}
|
|
641
|
+
/** Can this token kind appear within a multi-part key? (key-start tokens plus MINUS/DOT for `my-var`, `a.b`) */
|
|
642
|
+
export function isKeyTokenContinuation(kind) {
|
|
643
|
+
return (isKeyTokenStart(kind) || kind === TokenKind.MINUS || kind === TokenKind.DOT);
|
|
644
|
+
}
|
|
645
|
+
export function parseKey(ctx) {
|
|
646
|
+
if (!isKeyStart(ctx))
|
|
647
|
+
return null;
|
|
648
|
+
const startTok = ctx.peek();
|
|
649
|
+
const node = ctx.startNode('key');
|
|
650
|
+
// First name — may be a number (digit-starting key like "3var")
|
|
651
|
+
if (ctx.peekKind() === TokenKind.NUMBER) {
|
|
652
|
+
// Digit-starting key: wrap number in ERROR
|
|
653
|
+
const numNode = ctx.consumeNamed('number');
|
|
654
|
+
const errNode = new CSTNode('ERROR', ctx.source, numNode.startOffset, numNode.endOffset, numNode.startPosition, numNode.endPosition, true, true);
|
|
655
|
+
node.appendChild(errNode);
|
|
656
|
+
// Consume the ID part if present
|
|
657
|
+
if (ctx.peekKind() === TokenKind.ID) {
|
|
658
|
+
node.appendChild(ctx.consumeNamed('id'));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
else if (ctx.peekKind() === TokenKind.STRING) {
|
|
662
|
+
node.appendChild(parseString(ctx));
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
node.appendChild(ctx.consumeNamed('id'));
|
|
666
|
+
}
|
|
667
|
+
// Optional second name (two-word keys like "topic greeting")
|
|
668
|
+
// The second word must be on the same line with exactly immediate adjacency
|
|
669
|
+
// (the grammar uses token.immediate(' '))
|
|
670
|
+
if (ctx.peekKind() === TokenKind.ID &&
|
|
671
|
+
!ctx.isAtSyncPoint() &&
|
|
672
|
+
ctx.peek().start.row === startTok.start.row) {
|
|
673
|
+
// Check if this could be a keyword that starts a value/statement
|
|
674
|
+
const nextText = ctx.peek().text;
|
|
675
|
+
if (!KEY_STOP_KEYWORDS.has(nextText)) {
|
|
676
|
+
node.appendChild(ctx.consumeNamed('id'));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
ctx.finishNode(node, startTok);
|
|
680
|
+
return node;
|
|
681
|
+
}
|
|
682
|
+
//# sourceMappingURL=expressions.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CST-walk syntax highlighter for AgentScript.
|
|
3
|
+
*
|
|
4
|
+
* Produces QueryCapture[] matching the tree-sitter highlights.scm rules.
|
|
5
|
+
* Replaces tree-sitter's Query engine with a direct CST walk.
|
|
6
|
+
*/
|
|
7
|
+
import type { CSTNode } from './cst-node.js';
|
|
8
|
+
export interface HighlightCapture {
|
|
9
|
+
name: string;
|
|
10
|
+
text: string;
|
|
11
|
+
startRow: number;
|
|
12
|
+
startCol: number;
|
|
13
|
+
endRow: number;
|
|
14
|
+
endCol: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Walk the CST and produce highlight captures matching highlights.scm.
|
|
18
|
+
*
|
|
19
|
+
* Tree-sitter query priority: later patterns override earlier ones.
|
|
20
|
+
* We replicate this by first assigning generic captures, then overriding
|
|
21
|
+
* with contextual ones (e.g., id → variable, then key > id → property).
|
|
22
|
+
*/
|
|
23
|
+
export declare function highlight(root: CSTNode): HighlightCapture[];
|
|
24
|
+
//# sourceMappingURL=highlighter.d.ts.map
|