@rong/agentscript 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/CHANGELOG.md +22 -0
- package/INSTALL.md +92 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/ast/constants.js +1 -0
- package/dist/ast/format.js +41 -0
- package/dist/ast/types.js +1 -0
- package/dist/bin/agentscript.js +234 -0
- package/dist/bin/input.js +19 -0
- package/dist/bin/repl.js +290 -0
- package/dist/index.js +26 -0
- package/dist/parser/errors.js +8 -0
- package/dist/parser/parser.js +661 -0
- package/dist/parser/tokenizer.js +246 -0
- package/dist/providers/llm/anthropic.js +36 -0
- package/dist/providers/llm/index.js +3 -0
- package/dist/providers/llm/ollama.js +19 -0
- package/dist/providers/llm/openai.js +31 -0
- package/dist/providers/llm/protocol.js +45 -0
- package/dist/providers/llm/shared.js +147 -0
- package/dist/providers/llm/types.js +1 -0
- package/dist/providers/llm/uri.js +24 -0
- package/dist/providers/memory/file.js +44 -0
- package/dist/providers/memory/host.js +66 -0
- package/dist/providers/memory/index.js +1 -0
- package/dist/providers/memory/shared.js +56 -0
- package/dist/providers/memory/sqlite.js +98 -0
- package/dist/providers/mock/index.js +32 -0
- package/dist/providers/tools/env.js +11 -0
- package/dist/providers/tools/file.js +99 -0
- package/dist/providers/tools/host.js +34 -0
- package/dist/providers/tools/http.js +40 -0
- package/dist/providers/tools/index.js +2 -0
- package/dist/providers/tools/scheme.js +16 -0
- package/dist/providers/tools/shared.js +92 -0
- package/dist/providers/tools/shell.js +80 -0
- package/dist/runtime/context.js +160 -0
- package/dist/runtime/errors.js +14 -0
- package/dist/runtime/evaluator.js +276 -0
- package/dist/runtime/generate.js +175 -0
- package/dist/runtime/guards.js +39 -0
- package/dist/runtime/input.js +38 -0
- package/dist/runtime/interpreter.js +314 -0
- package/dist/runtime/json.js +59 -0
- package/dist/runtime/loader.js +146 -0
- package/dist/runtime/scope.js +47 -0
- package/dist/runtime/shape.js +132 -0
- package/dist/runtime/trace.js +54 -0
- package/dist/runtime/truth.js +13 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/uri.js +10 -0
- package/dist/semantic/analyzer.js +519 -0
- package/dist/semantic/diagnostics.js +16 -0
- package/dist/utils/assert.js +3 -0
- package/docs/cn/context-engineering.md +389 -0
- package/docs/cn/language.md +478 -0
- package/docs/design-history/v0-design.md +365 -0
- package/docs/design-history/v0-implement.md +274 -0
- package/docs/design-history/v1-design.md +323 -0
- package/docs/design-history/v1-implement.md +267 -0
- package/docs/design-history/v2-design.md +387 -0
- package/docs/design-history/v2-implement.md +399 -0
- package/docs/en/context-engineering.md +332 -0
- package/docs/en/language.md +478 -0
- package/examples/changelog.as +29 -0
- package/examples/extract.as +29 -0
- package/examples/review.as +38 -0
- package/examples/summarize.as +28 -0
- package/examples/translate.as +33 -0
- package/package.json +59 -0
- package/tutorials/cli.as +22 -0
- package/tutorials/helloworld.as +14 -0
- package/tutorials/memory.as +19 -0
- package/tutorials/plan-execute.as +155 -0
- package/tutorials/react.as +98 -0
- package/tutorials/repl.as +31 -0
- package/tutorials/self-improve.as +60 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import { ParseError } from "./errors.js";
|
|
2
|
+
import { SHAPE_TYPE_NAMES } from "../ast/constants.js";
|
|
3
|
+
import { tokenize } from "./tokenizer.js";
|
|
4
|
+
export function parse(source) {
|
|
5
|
+
return new Parser(tokenize(source)).parseProgram();
|
|
6
|
+
}
|
|
7
|
+
const IMPORT_RESOURCE_KINDS = new Set(["tool", "llm", "file", "agent", "memory"]);
|
|
8
|
+
const CONFIG_KEYS = new Set(["model", "role", "description"]);
|
|
9
|
+
const ANONYMOUS_MAIN_AGENT = "__main_agent";
|
|
10
|
+
const ANONYMOUS_MAIN_FUNC = "__main";
|
|
11
|
+
class Parser {
|
|
12
|
+
tokens;
|
|
13
|
+
current = 0;
|
|
14
|
+
constructor(tokens) {
|
|
15
|
+
this.tokens = tokens;
|
|
16
|
+
}
|
|
17
|
+
parseProgram() {
|
|
18
|
+
const start = this.peek().range.start;
|
|
19
|
+
const imports = [];
|
|
20
|
+
const agents = [];
|
|
21
|
+
while (!this.isAtEnd()) {
|
|
22
|
+
if (this.check("import")) {
|
|
23
|
+
imports.push(this.parseImport());
|
|
24
|
+
}
|
|
25
|
+
else if (this.check("agent") || this.check("main")) {
|
|
26
|
+
agents.push(this.parseAgent());
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
throw this.error(`Expected 'import', 'agent', or 'main agent'`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
kind: "Program",
|
|
34
|
+
imports,
|
|
35
|
+
agents,
|
|
36
|
+
range: { start, end: this.previous().range.end }
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
parseImport() {
|
|
40
|
+
const start = this.consume("import").range.start;
|
|
41
|
+
const resourceToken = this.consumeIdentifier("Expected import resource kind");
|
|
42
|
+
const resourceKind = resourceToken.value;
|
|
43
|
+
if (!isImportResourceKind(resourceKind)) {
|
|
44
|
+
throw new ParseError("Expected import resource kind 'tool', 'llm', 'file', 'agent', or 'memory'", resourceToken.range.start);
|
|
45
|
+
}
|
|
46
|
+
const name = this.consumeIdentifier(`Expected ${resourceKind} name`).value;
|
|
47
|
+
this.consume("from");
|
|
48
|
+
const uri = this.consumeKind("string", `Expected ${resourceKind} URI`).value;
|
|
49
|
+
return {
|
|
50
|
+
kind: "ImportDecl",
|
|
51
|
+
resourceKind,
|
|
52
|
+
name,
|
|
53
|
+
uri,
|
|
54
|
+
range: { start, end: this.previous().range.end }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
parseAgent() {
|
|
58
|
+
const mainToken = this.match("main") ? this.previous() : undefined;
|
|
59
|
+
const start = (mainToken ?? this.peek()).range.start;
|
|
60
|
+
const isMain = Boolean(mainToken);
|
|
61
|
+
this.consume("agent");
|
|
62
|
+
const name = this.check("{") ? ANONYMOUS_MAIN_AGENT : this.consumeIdentifier("Expected agent name").value;
|
|
63
|
+
if (!isMain && name === ANONYMOUS_MAIN_AGENT) {
|
|
64
|
+
throw this.error("Only main agent can omit its name");
|
|
65
|
+
}
|
|
66
|
+
this.consume("{");
|
|
67
|
+
const config = [];
|
|
68
|
+
const functions = [];
|
|
69
|
+
while (!this.check("}") && !this.isAtEnd()) {
|
|
70
|
+
if (this.isConfigKey(this.peek().value)) {
|
|
71
|
+
config.push(this.parseConfigDecl());
|
|
72
|
+
}
|
|
73
|
+
else if (this.check("func") || this.check("main")) {
|
|
74
|
+
functions.push(this.parseFunc());
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
throw this.error("Expected configuration declaration or function declaration");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
this.consume("}");
|
|
81
|
+
return {
|
|
82
|
+
kind: "AgentDecl",
|
|
83
|
+
name,
|
|
84
|
+
isMain,
|
|
85
|
+
config,
|
|
86
|
+
functions,
|
|
87
|
+
range: { start, end: this.previous().range.end }
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
parseConfigDecl() {
|
|
91
|
+
const key = this.consumeConfigKey();
|
|
92
|
+
const value = this.parseConfigValue(key.value);
|
|
93
|
+
return {
|
|
94
|
+
kind: "ConfigDecl",
|
|
95
|
+
key: key.value,
|
|
96
|
+
value,
|
|
97
|
+
range: { start: key.range.start, end: value.range.end }
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
parseConfigStmt() {
|
|
101
|
+
const key = this.consumeConfigKey();
|
|
102
|
+
const value = this.parseConfigValue(key.value);
|
|
103
|
+
return {
|
|
104
|
+
kind: "ConfigStmt",
|
|
105
|
+
key: key.value,
|
|
106
|
+
value,
|
|
107
|
+
range: { start: key.range.start, end: value.range.end }
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
parseFunc() {
|
|
111
|
+
const mainToken = this.match("main") ? this.previous() : undefined;
|
|
112
|
+
const start = (mainToken ?? this.peek()).range.start;
|
|
113
|
+
const isMain = Boolean(mainToken);
|
|
114
|
+
this.consume("func");
|
|
115
|
+
const name = this.check("(") ? ANONYMOUS_MAIN_FUNC : this.consumeIdentifier("Expected function name").value;
|
|
116
|
+
if (!isMain && name === ANONYMOUS_MAIN_FUNC) {
|
|
117
|
+
throw this.error("Only main func can omit its name");
|
|
118
|
+
}
|
|
119
|
+
this.consume("(");
|
|
120
|
+
const params = this.parseCommaSeparatedUntil(")", () => this.parseFuncParam());
|
|
121
|
+
this.consume(")");
|
|
122
|
+
const body = this.parseBlock();
|
|
123
|
+
return {
|
|
124
|
+
kind: "FuncDecl",
|
|
125
|
+
name,
|
|
126
|
+
isMain,
|
|
127
|
+
params,
|
|
128
|
+
body,
|
|
129
|
+
range: { start, end: this.previous().range.end }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
parseFuncParam() {
|
|
133
|
+
const token = this.consumeIdentifier("Expected parameter name");
|
|
134
|
+
const shape = this.check("{") ? this.parseShapeObject() : undefined;
|
|
135
|
+
return {
|
|
136
|
+
kind: "FuncParam",
|
|
137
|
+
name: token.value,
|
|
138
|
+
shape,
|
|
139
|
+
range: { start: token.range.start, end: (shape ?? token).range.end }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
parseBlock() {
|
|
143
|
+
this.consume("{");
|
|
144
|
+
const statements = [];
|
|
145
|
+
while (!this.check("}") && !this.isAtEnd()) {
|
|
146
|
+
statements.push(this.parseStatement());
|
|
147
|
+
}
|
|
148
|
+
this.consume("}");
|
|
149
|
+
return statements;
|
|
150
|
+
}
|
|
151
|
+
parseStatement() {
|
|
152
|
+
if (this.isConfigKey(this.peek().value)) {
|
|
153
|
+
return this.parseConfigStmt();
|
|
154
|
+
}
|
|
155
|
+
if (this.check("use")) {
|
|
156
|
+
return this.parseUse();
|
|
157
|
+
}
|
|
158
|
+
if (this.check("return")) {
|
|
159
|
+
return this.parseReturn();
|
|
160
|
+
}
|
|
161
|
+
if (this.check("repeat")) {
|
|
162
|
+
return this.parseRepeat();
|
|
163
|
+
}
|
|
164
|
+
if (this.check("for")) {
|
|
165
|
+
return this.parseForIn();
|
|
166
|
+
}
|
|
167
|
+
if (this.check("loop")) {
|
|
168
|
+
return this.parseLoop();
|
|
169
|
+
}
|
|
170
|
+
if (this.check("if")) {
|
|
171
|
+
return this.parseIf();
|
|
172
|
+
}
|
|
173
|
+
return this.parseAssignmentOrExpressionStatement();
|
|
174
|
+
}
|
|
175
|
+
parseUse() {
|
|
176
|
+
const start = this.consume("use").range.start;
|
|
177
|
+
const value = this.parseLogicalOr();
|
|
178
|
+
const budget = this.match("<") ? this.parseBudget() : undefined;
|
|
179
|
+
return {
|
|
180
|
+
kind: "UseStmt",
|
|
181
|
+
value,
|
|
182
|
+
budget,
|
|
183
|
+
range: { start, end: this.previous().range.end }
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
parseConfigValue(key) {
|
|
187
|
+
if (key === "model") {
|
|
188
|
+
return this.parsePostfix();
|
|
189
|
+
}
|
|
190
|
+
return this.parseExpression();
|
|
191
|
+
}
|
|
192
|
+
parseReturn() {
|
|
193
|
+
const start = this.consume("return").range.start;
|
|
194
|
+
const value = this.parseExpression();
|
|
195
|
+
return {
|
|
196
|
+
kind: "ReturnStmt",
|
|
197
|
+
value,
|
|
198
|
+
range: { start, end: value.range.end }
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
parseRepeat() {
|
|
202
|
+
const start = this.consume("repeat").range.start;
|
|
203
|
+
this.consume("*");
|
|
204
|
+
const maxAttempts = this.parsePositiveInteger("Expected repeat count");
|
|
205
|
+
const body = this.parseBlock();
|
|
206
|
+
return {
|
|
207
|
+
kind: "RepeatStmt",
|
|
208
|
+
maxAttempts,
|
|
209
|
+
body,
|
|
210
|
+
range: { start, end: this.previous().range.end }
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
parseForIn() {
|
|
214
|
+
const start = this.consume("for").range.start;
|
|
215
|
+
const item = this.consumeIdentifier("Expected for item name");
|
|
216
|
+
this.consume("in");
|
|
217
|
+
const iterable = this.parseLogicalOr();
|
|
218
|
+
this.consume("<");
|
|
219
|
+
const maxIterations = this.parsePositiveInteger("Expected for iteration count");
|
|
220
|
+
const body = this.parseBlock();
|
|
221
|
+
return {
|
|
222
|
+
kind: "ForInStmt",
|
|
223
|
+
itemName: item.value,
|
|
224
|
+
itemRange: item.range,
|
|
225
|
+
iterable,
|
|
226
|
+
maxIterations,
|
|
227
|
+
body,
|
|
228
|
+
range: { start, end: this.previous().range.end }
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
parseLoop() {
|
|
232
|
+
const start = this.consume("loop").range.start;
|
|
233
|
+
this.consume("until");
|
|
234
|
+
const condition = this.parseLogicalOr();
|
|
235
|
+
this.consume("<");
|
|
236
|
+
const maxIterations = this.parsePositiveInteger("Expected loop iteration count");
|
|
237
|
+
const body = this.parseBlock();
|
|
238
|
+
return {
|
|
239
|
+
kind: "LoopUntilStmt",
|
|
240
|
+
condition,
|
|
241
|
+
maxIterations,
|
|
242
|
+
body,
|
|
243
|
+
range: { start, end: this.previous().range.end }
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
parseIf() {
|
|
247
|
+
const start = this.consume("if").range.start;
|
|
248
|
+
const condition = this.parseExpression();
|
|
249
|
+
const thenBody = this.parseBlock();
|
|
250
|
+
const elseBody = this.match("else") ? this.parseBlock() : undefined;
|
|
251
|
+
return {
|
|
252
|
+
kind: "IfStmt",
|
|
253
|
+
condition,
|
|
254
|
+
thenBody,
|
|
255
|
+
elseBody,
|
|
256
|
+
range: { start, end: this.previous().range.end }
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
parseAssignmentOrExpressionStatement() {
|
|
260
|
+
const expr = this.parseExpression();
|
|
261
|
+
if (this.match("=")) {
|
|
262
|
+
if (expr.kind !== "IdentifierExpr" && expr.kind !== "MemberExpr") {
|
|
263
|
+
throw new ParseError("Assignment target must be an identifier or member expression", expr.range.start);
|
|
264
|
+
}
|
|
265
|
+
const value = this.parseExpression();
|
|
266
|
+
return {
|
|
267
|
+
kind: "AssignStmt",
|
|
268
|
+
target: expr,
|
|
269
|
+
value,
|
|
270
|
+
range: { start: expr.range.start, end: value.range.end }
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
kind: "ExprStmt",
|
|
275
|
+
expr,
|
|
276
|
+
range: expr.range
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
parseExpression() {
|
|
280
|
+
return this.parseLogicalOr();
|
|
281
|
+
}
|
|
282
|
+
parseLogicalOr() {
|
|
283
|
+
return this.parseBinaryExpression(() => this.parseLogicalAnd(), "or");
|
|
284
|
+
}
|
|
285
|
+
parseLogicalAnd() {
|
|
286
|
+
return this.parseBinaryExpression(() => this.parseEquality(), "and");
|
|
287
|
+
}
|
|
288
|
+
parseEquality() {
|
|
289
|
+
return this.parseBinaryExpression(() => this.parseUnary(), "==", "!=");
|
|
290
|
+
}
|
|
291
|
+
parseBinaryExpression(parseOperand, ...operators) {
|
|
292
|
+
let expr = parseOperand();
|
|
293
|
+
while (this.matchAny(operators)) {
|
|
294
|
+
const operator = this.previous().value;
|
|
295
|
+
const right = parseOperand();
|
|
296
|
+
expr = {
|
|
297
|
+
kind: "BinaryExpr",
|
|
298
|
+
operator,
|
|
299
|
+
left: expr,
|
|
300
|
+
right,
|
|
301
|
+
range: { start: expr.range.start, end: right.range.end }
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return expr;
|
|
305
|
+
}
|
|
306
|
+
parseUnary() {
|
|
307
|
+
if (this.match("not")) {
|
|
308
|
+
const start = this.previous().range.start;
|
|
309
|
+
const value = this.parseUnary();
|
|
310
|
+
return {
|
|
311
|
+
kind: "UnaryExpr",
|
|
312
|
+
operator: "not",
|
|
313
|
+
value,
|
|
314
|
+
range: { start, end: value.range.end }
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return this.parsePostfix();
|
|
318
|
+
}
|
|
319
|
+
parsePostfix() {
|
|
320
|
+
let expr = this.parsePrimary();
|
|
321
|
+
while (true) {
|
|
322
|
+
if (this.match(".")) {
|
|
323
|
+
const property = this.consumeIdentifier("Expected property name").value;
|
|
324
|
+
expr = {
|
|
325
|
+
kind: "MemberExpr",
|
|
326
|
+
object: expr,
|
|
327
|
+
property,
|
|
328
|
+
range: { start: expr.range.start, end: this.previous().range.end }
|
|
329
|
+
};
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (this.match("(")) {
|
|
333
|
+
const args = this.parseCommaSeparatedUntil(")", () => this.parseExpression());
|
|
334
|
+
this.consume(")");
|
|
335
|
+
expr = {
|
|
336
|
+
kind: "CallExpr",
|
|
337
|
+
callee: expr,
|
|
338
|
+
args,
|
|
339
|
+
range: { start: expr.range.start, end: this.previous().range.end }
|
|
340
|
+
};
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (this.match("[")) {
|
|
344
|
+
const index = this.parseExpression();
|
|
345
|
+
this.consume("]");
|
|
346
|
+
expr = {
|
|
347
|
+
kind: "IndexExpr",
|
|
348
|
+
object: expr,
|
|
349
|
+
index,
|
|
350
|
+
range: { start: expr.range.start, end: this.previous().range.end }
|
|
351
|
+
};
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
return expr;
|
|
357
|
+
}
|
|
358
|
+
parsePrimary() {
|
|
359
|
+
const token = this.peek();
|
|
360
|
+
if (this.check("generate")) {
|
|
361
|
+
return this.parseGenerate();
|
|
362
|
+
}
|
|
363
|
+
if (this.matchKind("string")) {
|
|
364
|
+
return {
|
|
365
|
+
kind: "StringExpr",
|
|
366
|
+
value: token.value,
|
|
367
|
+
range: token.range
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (this.matchKind("number")) {
|
|
371
|
+
return {
|
|
372
|
+
kind: "NumberExpr",
|
|
373
|
+
value: Number.parseFloat(token.value),
|
|
374
|
+
raw: token.value,
|
|
375
|
+
range: token.range
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (this.match("true") || this.match("false")) {
|
|
379
|
+
return {
|
|
380
|
+
kind: "BooleanExpr",
|
|
381
|
+
value: token.value === "true",
|
|
382
|
+
range: token.range
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (this.match("none")) {
|
|
386
|
+
return {
|
|
387
|
+
kind: "NullExpr",
|
|
388
|
+
range: token.range
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (this.check("{")) {
|
|
392
|
+
return this.parseObject();
|
|
393
|
+
}
|
|
394
|
+
if (this.check("[")) {
|
|
395
|
+
return this.parseList();
|
|
396
|
+
}
|
|
397
|
+
if (this.matchKind("identifier") || this.matchKind("keyword")) {
|
|
398
|
+
return {
|
|
399
|
+
kind: "IdentifierExpr",
|
|
400
|
+
name: token.value,
|
|
401
|
+
range: token.range
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
throw this.error("Expected expression");
|
|
405
|
+
}
|
|
406
|
+
parseGenerate() {
|
|
407
|
+
const start = this.consume("generate").range.start;
|
|
408
|
+
this.consume("(");
|
|
409
|
+
const options = this.parseGenerateOptions();
|
|
410
|
+
this.consume(")");
|
|
411
|
+
this.consume("{");
|
|
412
|
+
this.consume("return");
|
|
413
|
+
const returnShape = this.parseShapeObject();
|
|
414
|
+
this.consume("}");
|
|
415
|
+
return {
|
|
416
|
+
kind: "GenerateExpr",
|
|
417
|
+
options,
|
|
418
|
+
returnShape,
|
|
419
|
+
range: { start, end: this.previous().range.end }
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
parseGenerateOptions() {
|
|
423
|
+
const start = this.consume("{").range.start;
|
|
424
|
+
const properties = [];
|
|
425
|
+
let input;
|
|
426
|
+
let attempts;
|
|
427
|
+
let limit;
|
|
428
|
+
let debug;
|
|
429
|
+
while (!this.check("}") && !this.isAtEnd()) {
|
|
430
|
+
const propStart = this.peek().range.start;
|
|
431
|
+
const key = this.consumeObjectKey();
|
|
432
|
+
this.consume(":");
|
|
433
|
+
let value;
|
|
434
|
+
if (key === "limit") {
|
|
435
|
+
const token = this.consumeKind("number", "Expected generate limit");
|
|
436
|
+
limit = this.parseBudgetToken(token);
|
|
437
|
+
value = {
|
|
438
|
+
kind: "NumberExpr",
|
|
439
|
+
value: Number.parseFloat(token.value),
|
|
440
|
+
raw: token.value,
|
|
441
|
+
range: token.range
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
value = this.parseExpression();
|
|
446
|
+
}
|
|
447
|
+
properties.push({
|
|
448
|
+
kind: "ObjectProperty",
|
|
449
|
+
key,
|
|
450
|
+
value,
|
|
451
|
+
range: { start: propStart, end: value.range.end }
|
|
452
|
+
});
|
|
453
|
+
if (key === "input") {
|
|
454
|
+
input = value;
|
|
455
|
+
}
|
|
456
|
+
else if (key === "attempts" && value.kind === "NumberExpr") {
|
|
457
|
+
attempts = value;
|
|
458
|
+
}
|
|
459
|
+
else if (key === "debug" && value.kind === "BooleanExpr") {
|
|
460
|
+
debug = value;
|
|
461
|
+
}
|
|
462
|
+
this.match(",");
|
|
463
|
+
}
|
|
464
|
+
this.consume("}");
|
|
465
|
+
return {
|
|
466
|
+
kind: "GenerateOptionsExpr",
|
|
467
|
+
properties,
|
|
468
|
+
input,
|
|
469
|
+
attempts,
|
|
470
|
+
limit,
|
|
471
|
+
debug,
|
|
472
|
+
range: { start, end: this.previous().range.end }
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
parseObject() {
|
|
476
|
+
const start = this.consume("{").range.start;
|
|
477
|
+
const properties = [];
|
|
478
|
+
while (!this.check("}") && !this.isAtEnd()) {
|
|
479
|
+
const propStart = this.peek().range.start;
|
|
480
|
+
const key = this.consumeObjectKey();
|
|
481
|
+
this.consume(":");
|
|
482
|
+
const value = this.parseExpression();
|
|
483
|
+
properties.push({
|
|
484
|
+
kind: "ObjectProperty",
|
|
485
|
+
key,
|
|
486
|
+
value,
|
|
487
|
+
range: { start: propStart, end: value.range.end }
|
|
488
|
+
});
|
|
489
|
+
this.match(",");
|
|
490
|
+
}
|
|
491
|
+
this.consume("}");
|
|
492
|
+
return {
|
|
493
|
+
kind: "ObjectExpr",
|
|
494
|
+
properties,
|
|
495
|
+
range: { start, end: this.previous().range.end }
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
parseList() {
|
|
499
|
+
const start = this.consume("[").range.start;
|
|
500
|
+
const items = this.parseCommaSeparatedUntil("]", () => this.parseExpression());
|
|
501
|
+
this.consume("]");
|
|
502
|
+
return {
|
|
503
|
+
kind: "ListExpr",
|
|
504
|
+
items,
|
|
505
|
+
range: { start, end: this.previous().range.end }
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
parseShapeObject() {
|
|
509
|
+
const start = this.consume("{").range.start;
|
|
510
|
+
const fields = this.parseCommaSeparatedUntil("}", () => this.parseShapeField());
|
|
511
|
+
this.consume("}");
|
|
512
|
+
return {
|
|
513
|
+
kind: "ShapeObjectExpr",
|
|
514
|
+
fields,
|
|
515
|
+
range: { start, end: this.previous().range.end }
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
parseShapeField() {
|
|
519
|
+
const start = this.peek().range.start;
|
|
520
|
+
const name = this.consumeIdentifier("Expected shape field name").value;
|
|
521
|
+
const type = this.parseShapeType();
|
|
522
|
+
return {
|
|
523
|
+
kind: "ShapeField",
|
|
524
|
+
name,
|
|
525
|
+
type,
|
|
526
|
+
range: { start, end: type.range.end }
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
parseShapeType() {
|
|
530
|
+
const start = this.peek().range.start;
|
|
531
|
+
const name = this.consumeIdentifier("Expected shape type").value;
|
|
532
|
+
if (name === "list" && this.match("[")) {
|
|
533
|
+
const itemType = this.parseShapeType();
|
|
534
|
+
this.consume("]");
|
|
535
|
+
return {
|
|
536
|
+
kind: "ListShapeType",
|
|
537
|
+
itemType,
|
|
538
|
+
range: { start, end: this.previous().range.end }
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (!SHAPE_TYPE_NAMES.has(name)) {
|
|
542
|
+
throw new ParseError(`Unsupported shape type '${name}'`, { ...start });
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
kind: "NamedShapeType",
|
|
546
|
+
name: name,
|
|
547
|
+
range: { start, end: this.previous().range.end }
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
parseBudget() {
|
|
551
|
+
return this.parseBudgetToken(this.consumeKind("number", "Expected budget amount"));
|
|
552
|
+
}
|
|
553
|
+
parseBudgetToken(token) {
|
|
554
|
+
const raw = token.value;
|
|
555
|
+
const match = /^(\d+(?:\.\d+)?)([A-Za-z]+)?$/.exec(raw);
|
|
556
|
+
if (!match) {
|
|
557
|
+
throw this.error(`Invalid budget '${raw}'`);
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
amount: Number.parseFloat(match[1] ?? raw),
|
|
561
|
+
unit: match[2]
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
parsePositiveInteger(message) {
|
|
565
|
+
const raw = this.consumeKind("number", message).value;
|
|
566
|
+
if (!/^\d+$/.test(raw)) {
|
|
567
|
+
throw this.error(message);
|
|
568
|
+
}
|
|
569
|
+
return Number.parseInt(raw, 10);
|
|
570
|
+
}
|
|
571
|
+
consumeObjectKey() {
|
|
572
|
+
if (this.matchKind("identifier") || this.matchKind("keyword") || this.matchKind("string")) {
|
|
573
|
+
return this.previous().value;
|
|
574
|
+
}
|
|
575
|
+
throw this.error("Expected object key");
|
|
576
|
+
}
|
|
577
|
+
consumeConfigKey() {
|
|
578
|
+
const token = this.consumeIdentifier("Expected configuration key");
|
|
579
|
+
if (!this.isConfigKey(token.value)) {
|
|
580
|
+
throw this.error("Expected configuration key");
|
|
581
|
+
}
|
|
582
|
+
return token;
|
|
583
|
+
}
|
|
584
|
+
consumeIdentifier(message) {
|
|
585
|
+
if (this.matchKind("identifier") || this.matchKind("keyword")) {
|
|
586
|
+
return this.previous();
|
|
587
|
+
}
|
|
588
|
+
throw this.error(message);
|
|
589
|
+
}
|
|
590
|
+
consumeKind(kind, message) {
|
|
591
|
+
if (this.matchKind(kind)) {
|
|
592
|
+
return this.previous();
|
|
593
|
+
}
|
|
594
|
+
throw this.error(message);
|
|
595
|
+
}
|
|
596
|
+
consume(value) {
|
|
597
|
+
if (this.match(value)) {
|
|
598
|
+
return this.previous();
|
|
599
|
+
}
|
|
600
|
+
throw this.error(`Expected '${value}'`);
|
|
601
|
+
}
|
|
602
|
+
match(value) {
|
|
603
|
+
if (!this.check(value)) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
this.advance();
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
matchAny(values) {
|
|
610
|
+
if (!values.some((value) => this.check(value))) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
this.advance();
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
matchKind(kind) {
|
|
617
|
+
if (!this.checkKind(kind)) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
this.advance();
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
check(value) {
|
|
624
|
+
return this.peek().value === value;
|
|
625
|
+
}
|
|
626
|
+
checkKind(kind) {
|
|
627
|
+
return this.peek().kind === kind;
|
|
628
|
+
}
|
|
629
|
+
parseCommaSeparatedUntil(terminator, parseItem) {
|
|
630
|
+
const items = [];
|
|
631
|
+
while (!this.check(terminator) && !this.isAtEnd()) {
|
|
632
|
+
items.push(parseItem());
|
|
633
|
+
this.match(",");
|
|
634
|
+
}
|
|
635
|
+
return items;
|
|
636
|
+
}
|
|
637
|
+
advance() {
|
|
638
|
+
if (!this.isAtEnd()) {
|
|
639
|
+
this.current += 1;
|
|
640
|
+
}
|
|
641
|
+
return this.previous();
|
|
642
|
+
}
|
|
643
|
+
isAtEnd() {
|
|
644
|
+
return this.peek().kind === "eof";
|
|
645
|
+
}
|
|
646
|
+
peek() {
|
|
647
|
+
return this.tokens[this.current];
|
|
648
|
+
}
|
|
649
|
+
previous() {
|
|
650
|
+
return this.tokens[this.current - 1] ?? this.tokens[0];
|
|
651
|
+
}
|
|
652
|
+
error(message) {
|
|
653
|
+
return new ParseError(message, this.peek().range.start);
|
|
654
|
+
}
|
|
655
|
+
isConfigKey(value) {
|
|
656
|
+
return CONFIG_KEYS.has(value);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function isImportResourceKind(value) {
|
|
660
|
+
return IMPORT_RESOURCE_KINDS.has(value);
|
|
661
|
+
}
|