@flexiberry/berrycore 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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapter/cli-adapter.d.ts +37 -0
  3. package/dist/adapter/cli-adapter.js +119 -0
  4. package/dist/berry-core.d.ts +108 -0
  5. package/dist/berry-core.js +258 -0
  6. package/dist/index.d.ts +13 -0
  7. package/dist/index.js +18 -0
  8. package/dist/interpreter/environment.d.ts +45 -0
  9. package/dist/interpreter/environment.js +96 -0
  10. package/dist/interpreter/errors.d.ts +16 -0
  11. package/dist/interpreter/errors.js +27 -0
  12. package/dist/interpreter/interpreter.d.ts +111 -0
  13. package/dist/interpreter/interpreter.js +682 -0
  14. package/dist/interpreter/interpreter.types.d.ts +182 -0
  15. package/dist/interpreter/interpreter.types.js +73 -0
  16. package/dist/parser/ast/ast.engine.d.ts +103 -0
  17. package/dist/parser/ast/ast.engine.js +526 -0
  18. package/dist/parser/ast/ast.types.d.ts +242 -0
  19. package/dist/parser/ast/ast.types.js +37 -0
  20. package/dist/parser/formatter/formatter.d.ts +44 -0
  21. package/dist/parser/formatter/formatter.js +214 -0
  22. package/dist/parser/tokenizer/reader/grammer/api.grammer.d.ts +2 -0
  23. package/dist/parser/tokenizer/reader/grammer/api.grammer.js +102 -0
  24. package/dist/parser/tokenizer/reader/grammer/capture.grammer.d.ts +2 -0
  25. package/dist/parser/tokenizer/reader/grammer/capture.grammer.js +21 -0
  26. package/dist/parser/tokenizer/reader/grammer/check.grammer.d.ts +2 -0
  27. package/dist/parser/tokenizer/reader/grammer/check.grammer.js +21 -0
  28. package/dist/parser/tokenizer/reader/grammer/comment.grammer.d.ts +2 -0
  29. package/dist/parser/tokenizer/reader/grammer/comment.grammer.js +13 -0
  30. package/dist/parser/tokenizer/reader/grammer/conditions.grammer.d.ts +2 -0
  31. package/dist/parser/tokenizer/reader/grammer/conditions.grammer.js +68 -0
  32. package/dist/parser/tokenizer/reader/grammer/input.grammer.d.ts +2 -0
  33. package/dist/parser/tokenizer/reader/grammer/input.grammer.js +17 -0
  34. package/dist/parser/tokenizer/reader/grammer/keyvalue.grammer.d.ts +2 -0
  35. package/dist/parser/tokenizer/reader/grammer/keyvalue.grammer.js +240 -0
  36. package/dist/parser/tokenizer/reader/grammer/link.grammer.d.ts +2 -0
  37. package/dist/parser/tokenizer/reader/grammer/link.grammer.js +17 -0
  38. package/dist/parser/tokenizer/reader/grammer/params.grammer.d.ts +2 -0
  39. package/dist/parser/tokenizer/reader/grammer/params.grammer.js +21 -0
  40. package/dist/parser/tokenizer/reader/grammer/step.grammer.d.ts +2 -0
  41. package/dist/parser/tokenizer/reader/grammer/step.grammer.js +25 -0
  42. package/dist/parser/tokenizer/reader/grammer/task.grammer.d.ts +2 -0
  43. package/dist/parser/tokenizer/reader/grammer/task.grammer.js +17 -0
  44. package/dist/parser/tokenizer/reader/grammer/var.grammer.d.ts +2 -0
  45. package/dist/parser/tokenizer/reader/grammer/var.grammer.js +47 -0
  46. package/dist/parser/tokenizer/reader/lexer.engine.d.ts +43 -0
  47. package/dist/parser/tokenizer/reader/lexer.engine.js +178 -0
  48. package/dist/parser/tokenizer/reader/lexer.types.d.ts +18 -0
  49. package/dist/parser/tokenizer/reader/lexer.types.js +1 -0
  50. package/dist/parser/tokenizer/token.d.ts +13 -0
  51. package/dist/parser/tokenizer/token.js +13 -0
  52. package/dist/parser/tokenizer/tokenType.d.ts +58 -0
  53. package/dist/parser/tokenizer/tokenType.js +64 -0
  54. package/dist/script/format-util.d.ts +33 -0
  55. package/dist/script/format-util.js +94 -0
  56. package/dist/script/postman.util.d.ts +88 -0
  57. package/dist/script/postman.util.js +176 -0
  58. package/dist/script/swagger.util.d.ts +80 -0
  59. package/dist/script/swagger.util.js +202 -0
  60. package/dist/util/store-util.d.ts +5 -0
  61. package/dist/util/store-util.js +22 -0
  62. package/package.json +25 -0
  63. package/readme.md +107 -0
@@ -0,0 +1,526 @@
1
+ /**
2
+ * AST Engine — Recursive Descent Parser
3
+ *
4
+ * Consumes Token[] from the LexerEngine and produces a ProgramNode AST.
5
+ * Each parse* method corresponds to one grammar rule from reader_v2.
6
+ *
7
+ * Architecture rules enforced:
8
+ * - Parser ONLY produces AST — no runtime execution
9
+ * - Deterministic recursive descent
10
+ * - Throws syntax errors with line/column info
11
+ */
12
+ import { LexerEngine } from "../tokenizer/reader/lexer.engine.js";
13
+ import { TokenType } from "../tokenizer/tokenType.js";
14
+ import { NodeType, } from "./ast.types.js";
15
+ // ─── Parser Errors ──────────────────────────────────────────────────────────
16
+ export class ParserError extends Error {
17
+ line;
18
+ column;
19
+ constructor(message, line, column) {
20
+ super(`[ParserError at ${line}:${column}] ${message}`);
21
+ this.line = line;
22
+ this.column = column;
23
+ this.name = "ParserError";
24
+ }
25
+ }
26
+ export class Ast {
27
+ static parse(code) {
28
+ const lexer = new LexerEngine(code);
29
+ const tokens = lexer.tokenize();
30
+ const engine = new AstEngine(tokens);
31
+ return engine.build();
32
+ }
33
+ }
34
+ // ─── Parser Engine ──────────────────────────────────────────────────────────
35
+ export class AstEngine {
36
+ tokens;
37
+ cursor = 0;
38
+ constructor(tokens) {
39
+ this.tokens = tokens;
40
+ }
41
+ // ── Public API ──────────────────────────────────────────────────────────
42
+ /** Entry point: parse entire token stream into ProgramNode */
43
+ build() {
44
+ const body = [];
45
+ while (!this.isEof()) {
46
+ // Skip comments at top-level but collect them as nodes
47
+ if (this.check(TokenType.Comment)) {
48
+ body.push(this.parseComment());
49
+ continue;
50
+ }
51
+ const statement = this.parseStatement();
52
+ if (statement) {
53
+ body.push(statement);
54
+ }
55
+ }
56
+ return {
57
+ type: NodeType.Program,
58
+ position: { line: 0, column: 0 },
59
+ body,
60
+ };
61
+ }
62
+ // ── Statement Dispatch ──────────────────────────────────────────────────
63
+ parseStatement() {
64
+ if (this.check(TokenType.Var))
65
+ return this.parseVarDeclaration();
66
+ if (this.check(TokenType.Link))
67
+ return this.parseLinkStatement();
68
+ if (this.check(TokenType.Input))
69
+ return this.parseInputStatement();
70
+ if (this.check(TokenType.Api))
71
+ return this.parseApiBlock();
72
+ if (this.check(TokenType.Task))
73
+ return this.parseTaskBlock();
74
+ if (this.check(TokenType.Step))
75
+ return this.parseStepBlock();
76
+ if (this.check(TokenType.Params))
77
+ return this.parseParamsBlock();
78
+ if (this.check(TokenType.Capture))
79
+ return this.parseCaptureBlock();
80
+ if (this.check(TokenType.Check))
81
+ return this.parseCheckBlock();
82
+ if (this.check(TokenType.Comment))
83
+ return this.parseComment();
84
+ // Skip unrecognized tokens to avoid infinite loop
85
+ this.advance();
86
+ return null;
87
+ }
88
+ // ── Var Declaration ─────────────────────────────────────────────────────
89
+ /**
90
+ * Grammar: Var ((@)(pointed) (title))? keyValueLoop
91
+ * Tokens: Var, optional Pointer+Pointed+Title, then key-value pairs
92
+ */
93
+ parseVarDeclaration() {
94
+ const varToken = this.expect(TokenType.Var, "Expected 'Var' keyword");
95
+ const position = this.positionOf(varToken);
96
+ let pointer = null;
97
+ let title = null;
98
+ // Check for pointer reference: @ followed by pointed name
99
+ if (this.check(TokenType.Pointer)) {
100
+ pointer = this.parsePointerReference();
101
+ }
102
+ // Check for title
103
+ if (this.check(TokenType.Title)) {
104
+ title = this.advance().value;
105
+ }
106
+ // Parse key-value entries
107
+ const entries = this.parseKeyValuePairs();
108
+ return {
109
+ type: NodeType.VarDeclaration,
110
+ position,
111
+ title,
112
+ pointer,
113
+ entries,
114
+ };
115
+ }
116
+ parsePointerReference() {
117
+ const pointerToken = this.expect(TokenType.Pointer, "Expected '@' pointer");
118
+ const position = this.positionOf(pointerToken);
119
+ const pointedToken = this.expect(TokenType.Pointed, "Expected pointed name after '@'");
120
+ return {
121
+ type: NodeType.PointerReference,
122
+ position,
123
+ symbol: pointerToken.value,
124
+ target: pointedToken.value,
125
+ };
126
+ }
127
+ // ── Link Statement ──────────────────────────────────────────────────────
128
+ /**
129
+ * Grammar: Link <path>
130
+ * Tokens: Link, LinkPath
131
+ */
132
+ parseLinkStatement() {
133
+ const linkToken = this.expect(TokenType.Link, "Expected 'Link' keyword");
134
+ const position = this.positionOf(linkToken);
135
+ const pathToken = this.expect(TokenType.LinkPath, "Expected path after 'Link'");
136
+ return {
137
+ type: NodeType.LinkStatement,
138
+ position,
139
+ path: pathToken.value,
140
+ };
141
+ }
142
+ // ── Input Statement ──────────────────────────────────────────────────────
143
+ /**
144
+ * Grammar: Input <path>
145
+ * Tokens: Input, InputPath
146
+ */
147
+ parseInputStatement() {
148
+ const inputToken = this.expect(TokenType.Input, "Expected 'Input' keyword");
149
+ const position = this.positionOf(inputToken);
150
+ const pathToken = this.expect(TokenType.InputPath, "Expected path after 'Input'");
151
+ return {
152
+ type: NodeType.InputStatement,
153
+ position,
154
+ path: pathToken.value,
155
+ };
156
+ }
157
+ // ── API Block ───────────────────────────────────────────────────────────
158
+ /**
159
+ * Grammar: Api (GET|POST|..)? #identifier title?
160
+ * Url value
161
+ * Header keyValueLoop
162
+ * Body type `content`
163
+ */
164
+ parseApiBlock() {
165
+ const apiToken = this.expect(TokenType.Api, "Expected 'Api' keyword");
166
+ const position = this.positionOf(apiToken);
167
+ // Optional method
168
+ let method = null;
169
+ if (this.check(TokenType.ApiMethod)) {
170
+ method = this.advance().value;
171
+ }
172
+ // Hash + Identifier (API name)
173
+ if (this.check(TokenType.Hash)) {
174
+ this.advance(); // consume '#'
175
+ }
176
+ const nameToken = this.expect(TokenType.Identifier, "Expected API identifier after '#'");
177
+ const name = nameToken.value;
178
+ // Optional title
179
+ let title = null;
180
+ if (this.check(TokenType.Title)) {
181
+ title = this.advance().value;
182
+ }
183
+ // Parse sub-blocks: Url, Header, Body (in any order)
184
+ let url = null;
185
+ let headers = null;
186
+ let body = null;
187
+ while (this.check(TokenType.Url) ||
188
+ this.check(TokenType.Header) ||
189
+ this.check(TokenType.Body)) {
190
+ if (this.check(TokenType.Url)) {
191
+ url = this.parseUrlStatement();
192
+ }
193
+ else if (this.check(TokenType.Header)) {
194
+ headers = this.parseHeaderBlock();
195
+ }
196
+ else if (this.check(TokenType.Body)) {
197
+ body = this.parseBodyBlock();
198
+ }
199
+ }
200
+ return {
201
+ type: NodeType.ApiBlock,
202
+ position,
203
+ method,
204
+ name,
205
+ title,
206
+ url,
207
+ headers,
208
+ body,
209
+ };
210
+ }
211
+ parseUrlStatement() {
212
+ const urlToken = this.expect(TokenType.Url, "Expected 'Url' keyword");
213
+ const position = this.positionOf(urlToken);
214
+ const valueToken = this.expect(TokenType.Value, "Expected URL value after 'Url'");
215
+ return {
216
+ type: NodeType.UrlStatement,
217
+ position,
218
+ value: valueToken.value,
219
+ };
220
+ }
221
+ parseHeaderBlock() {
222
+ const headerToken = this.expect(TokenType.Header, "Expected 'Header' keyword");
223
+ const position = this.positionOf(headerToken);
224
+ const entries = this.parseKeyValuePairs();
225
+ return {
226
+ type: NodeType.HeaderBlock,
227
+ position,
228
+ entries,
229
+ };
230
+ }
231
+ parseBodyBlock() {
232
+ const bodyToken = this.expect(TokenType.Body, "Expected 'Body' keyword");
233
+ const position = this.positionOf(bodyToken);
234
+ const bodyTypeToken = this.expect(TokenType.BodyType, "Expected body type (e.g. 'json') after 'Body'");
235
+ // Expect backtick-wrapped content
236
+ let content = "";
237
+ if (this.check(TokenType.Backtick)) {
238
+ this.advance(); // opening backtick
239
+ if (this.check(TokenType.Scalar)) {
240
+ content = this.advance().value;
241
+ }
242
+ if (this.check(TokenType.Backtick)) {
243
+ this.advance(); // closing backtick
244
+ }
245
+ }
246
+ return {
247
+ type: NodeType.BodyBlock,
248
+ position,
249
+ bodyType: bodyTypeToken.value,
250
+ content,
251
+ };
252
+ }
253
+ // ── Task Block ──────────────────────────────────────────────────────────
254
+ /**
255
+ * Grammar: Task title?
256
+ * Tokens: Task, Title
257
+ */
258
+ parseTaskBlock() {
259
+ const taskToken = this.expect(TokenType.Task, "Expected 'Task' keyword");
260
+ const position = this.positionOf(taskToken);
261
+ let title = null;
262
+ if (this.check(TokenType.Title)) {
263
+ title = this.advance().value;
264
+ }
265
+ // Collect Step blocks that follow this Task
266
+ const steps = [];
267
+ while (this.check(TokenType.Step)) {
268
+ steps.push(this.parseStepBlock());
269
+ }
270
+ return {
271
+ type: NodeType.TaskBlock,
272
+ position,
273
+ title,
274
+ steps,
275
+ };
276
+ }
277
+ // ── Step Block ──────────────────────────────────────────────────────────
278
+ /**
279
+ * Grammar: Step Call Api identifier
280
+ * Tokens: Step, Call, Api, Identifier
281
+ */
282
+ parseStepBlock() {
283
+ const stepToken = this.expect(TokenType.Step, "Expected 'Step' keyword");
284
+ const position = this.positionOf(stepToken);
285
+ const callToken = this.expect(TokenType.Call, "Expected 'Call' after 'Step'");
286
+ const apiToken = this.expect(TokenType.Api, "Expected 'Api' after 'Call'");
287
+ const nameToken = this.expect(TokenType.Identifier, "Expected API name after 'Api'");
288
+ // Parse optional sub-blocks: Params, Capture, Check
289
+ let params = null;
290
+ let capture = null;
291
+ let check = null;
292
+ while (this.check(TokenType.Params) ||
293
+ this.check(TokenType.Capture) ||
294
+ this.check(TokenType.Check)) {
295
+ if (this.check(TokenType.Params)) {
296
+ params = this.parseParamsBlock();
297
+ }
298
+ else if (this.check(TokenType.Capture)) {
299
+ capture = this.parseCaptureBlock();
300
+ }
301
+ else if (this.check(TokenType.Check)) {
302
+ check = this.parseCheckBlock();
303
+ }
304
+ }
305
+ return {
306
+ type: NodeType.StepBlock,
307
+ position,
308
+ callType: callToken.value,
309
+ targetType: apiToken.value,
310
+ targetName: nameToken.value,
311
+ params,
312
+ capture,
313
+ check,
314
+ };
315
+ }
316
+ // ── Params Block ────────────────────────────────────────────────────────
317
+ /**
318
+ * Grammar: Params keyValueLoop
319
+ * Tokens: Params, then key-value pairs
320
+ */
321
+ parseParamsBlock() {
322
+ const paramsToken = this.expect(TokenType.Params, "Expected 'Params' keyword");
323
+ const position = this.positionOf(paramsToken);
324
+ const entries = this.parseKeyValuePairs();
325
+ return {
326
+ type: NodeType.ParamsBlock,
327
+ position,
328
+ entries,
329
+ };
330
+ }
331
+ // ── Capture Block ───────────────────────────────────────────────────────
332
+ /**
333
+ * Grammar: Capture keyValueLoop
334
+ * Tokens: Capture, then key-value pairs
335
+ */
336
+ parseCaptureBlock() {
337
+ const captureToken = this.expect(TokenType.Capture, "Expected 'Capture' keyword");
338
+ const position = this.positionOf(captureToken);
339
+ const entries = this.parseKeyValuePairs();
340
+ return {
341
+ type: NodeType.CaptureBlock,
342
+ position,
343
+ entries,
344
+ };
345
+ }
346
+ // ── Check Block ─────────────────────────────────────────────────────────
347
+ /**
348
+ * Grammar: Check conditionLoop
349
+ * Tokens: Check, then condition list
350
+ */
351
+ parseCheckBlock() {
352
+ const checkToken = this.expect(TokenType.Check, "Expected 'Check' keyword");
353
+ const position = this.positionOf(checkToken);
354
+ const conditions = this.parseConditions();
355
+ return {
356
+ type: NodeType.CheckBlock,
357
+ position,
358
+ conditions,
359
+ };
360
+ }
361
+ // ── Key-Value Pairs ─────────────────────────────────────────────────────
362
+ /**
363
+ * Grammar: (- key: value)*
364
+ * Loops while Hyphen tokens appear
365
+ */
366
+ parseKeyValuePairs() {
367
+ const entries = [];
368
+ while (this.check(TokenType.Hyphen)) {
369
+ const hyphenToken = this.advance(); // consume '-'
370
+ const position = this.positionOf(hyphenToken);
371
+ let key = "";
372
+ let isKeyQuoted = false;
373
+ let value = "";
374
+ let isValueQuoted = false;
375
+ let isMultiline = false;
376
+ // Key: quoted or unquoted
377
+ if (this.check(TokenType.Quote)) {
378
+ isKeyQuoted = true;
379
+ this.advance(); // opening quote
380
+ if (this.check(TokenType.Identifier)) {
381
+ key = this.advance().value;
382
+ }
383
+ if (this.check(TokenType.Quote)) {
384
+ this.advance(); // closing quote
385
+ }
386
+ }
387
+ else if (this.check(TokenType.Identifier)) {
388
+ key = this.advance().value;
389
+ }
390
+ // Colon separator
391
+ if (this.check(TokenType.Colon)) {
392
+ this.advance();
393
+ }
394
+ // Value: quoted string, multiline backtick, or unquoted identifier
395
+ if (this.check(TokenType.Quote)) {
396
+ isValueQuoted = true;
397
+ this.advance(); // opening quote
398
+ if (this.check(TokenType.Scalar)) {
399
+ value = this.advance().value;
400
+ }
401
+ if (this.check(TokenType.Quote)) {
402
+ this.advance(); // closing quote
403
+ }
404
+ }
405
+ else if (this.check(TokenType.Backtick)) {
406
+ isMultiline = true;
407
+ this.advance(); // opening backtick
408
+ if (this.check(TokenType.Scalar)) {
409
+ value = this.advance().value;
410
+ }
411
+ if (this.check(TokenType.Backtick)) {
412
+ this.advance(); // closing backtick
413
+ }
414
+ }
415
+ else if (this.check(TokenType.Identifier)) {
416
+ value = this.advance().value;
417
+ }
418
+ let isEncrypted = false;
419
+ if (this.check(TokenType.Decrypt)) {
420
+ this.advance(); // consume 'Decrypt'
421
+ isEncrypted = true;
422
+ }
423
+ entries.push({
424
+ type: NodeType.KeyValuePair,
425
+ position,
426
+ key,
427
+ value,
428
+ isKeyQuoted,
429
+ isValueQuoted,
430
+ isMultiline,
431
+ isEncrypted,
432
+ });
433
+ }
434
+ return entries;
435
+ }
436
+ // ── Conditions ──────────────────────────────────────────────────────────
437
+ /**
438
+ * Grammar: (- lhs operator rhs (OR lhs operator rhs)*)*
439
+ * Loops while Hyphen tokens appear
440
+ */
441
+ parseConditions() {
442
+ const conditions = [];
443
+ while (this.check(TokenType.Hyphen)) {
444
+ const hyphenToken = this.advance(); // consume '-'
445
+ const position = this.positionOf(hyphenToken);
446
+ const lhs = this.expect(TokenType.Lhs, "Expected LHS in condition").value;
447
+ const operator = this.expect(TokenType.Operator, "Expected operator in condition").value;
448
+ const rhs = this.expect(TokenType.Rhs, "Expected RHS in condition").value;
449
+ // Parse OR chains
450
+ const orConditions = [];
451
+ while (this.check(TokenType.Or)) {
452
+ const orToken = this.advance(); // consume 'OR'
453
+ const orPos = this.positionOf(orToken);
454
+ const orLhs = this.expect(TokenType.Lhs, "Expected LHS after 'OR'").value;
455
+ const orOperator = this.expect(TokenType.Operator, "Expected operator after OR LHS").value;
456
+ const orRhs = this.expect(TokenType.Rhs, "Expected RHS after OR operator").value;
457
+ orConditions.push({
458
+ type: NodeType.BinaryExpression,
459
+ position: orPos,
460
+ lhs: orLhs,
461
+ operator: orOperator,
462
+ rhs: orRhs,
463
+ });
464
+ }
465
+ conditions.push({
466
+ type: NodeType.Condition,
467
+ position,
468
+ lhs,
469
+ operator,
470
+ rhs,
471
+ orConditions,
472
+ });
473
+ }
474
+ return conditions;
475
+ }
476
+ // ── Comment ─────────────────────────────────────────────────────────────
477
+ parseComment() {
478
+ const commentToken = this.expect(TokenType.Comment, "Expected comment text");
479
+ return {
480
+ type: NodeType.Comment,
481
+ position: this.positionOf(commentToken),
482
+ text: commentToken.value,
483
+ };
484
+ }
485
+ // ── Token Helpers ─────────────────────────────────────────────────────
486
+ /** Check if we've reached EOF */
487
+ isEof() {
488
+ return (this.cursor >= this.tokens.length ||
489
+ this.tokens[this.cursor].type === TokenType.Eof);
490
+ }
491
+ /** Peek at current token without consuming */
492
+ peek() {
493
+ return this.tokens[this.cursor];
494
+ }
495
+ /** Check if current token matches expected type */
496
+ check(type) {
497
+ if (this.isEof())
498
+ return false;
499
+ return this.peek().type === type;
500
+ }
501
+ /** Consume current token and return it */
502
+ advance() {
503
+ const token = this.tokens[this.cursor];
504
+ this.cursor++;
505
+ return token;
506
+ }
507
+ /** Expect a specific token type or throw a ParserError */
508
+ expect(type, errorMessage) {
509
+ if (this.isEof()) {
510
+ const lastToken = this.tokens[this.tokens.length - 1];
511
+ throw new ParserError(`${errorMessage} — unexpected end of input`, lastToken?.position.line ?? 0, lastToken?.position.start ?? 0);
512
+ }
513
+ const token = this.peek();
514
+ if (token.type !== type) {
515
+ throw new ParserError(`${errorMessage} — got '${TokenType[token.type]}' (${token.value})`, token.position.line ?? 0, token.position.start);
516
+ }
517
+ return this.advance();
518
+ }
519
+ /** Extract NodePosition from a Token */
520
+ positionOf(token) {
521
+ return {
522
+ line: token.position.line ?? 0,
523
+ column: token.position.start,
524
+ };
525
+ }
526
+ }