@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 flexiberry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * CLI Adapter
3
+ *
4
+ * Default IOAdapter implementation using Node readline + stdout.
5
+ * Handles input, output, and execution control for CLI execution.
6
+ */
7
+ import { IOAdapter, LogLevel, ExecutionCommand } from "../interpreter/interpreter.types.js";
8
+ export declare class CliAdapter implements IOAdapter {
9
+ private rl;
10
+ private commandHandler;
11
+ private readonly enableLogging;
12
+ private readonly logLevels;
13
+ private readonly enableCommands;
14
+ constructor(options?: {
15
+ /** Enable log output to stdout (default: true) */
16
+ enableLogging?: boolean;
17
+ /** Which log levels to show (default: all except debug) */
18
+ logLevels?: LogLevel[];
19
+ /** Enable keyboard commands during execution (default: false) */
20
+ enableCommands?: boolean;
21
+ });
22
+ private getReadline;
23
+ /** Prompt the user for input via stdin */
24
+ prompt(message: string): Promise<string>;
25
+ /** Ask a yes/no question */
26
+ confirm(message: string): Promise<boolean>;
27
+ /** Push a log line to stdout */
28
+ log(level: LogLevel, message: string): void;
29
+ /**
30
+ * Register a command handler.
31
+ * Called by the interpreter to wire up command sending.
32
+ */
33
+ onCommand(handler: (command: ExecutionCommand) => void): void;
34
+ /** Clean up readline interface */
35
+ dispose(): void;
36
+ private handleKeyboardInput;
37
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * CLI Adapter
3
+ *
4
+ * Default IOAdapter implementation using Node readline + stdout.
5
+ * Handles input, output, and execution control for CLI execution.
6
+ */
7
+ import * as readline from "readline";
8
+ import { ExecutionCommand } from "../interpreter/interpreter.types.js";
9
+ // ─── Log Level Icons ────────────────────────────────────────────────────────
10
+ const LEVEL_ICONS = {
11
+ info: "ℹ️ ",
12
+ warn: "⚠️ ",
13
+ error: "❌",
14
+ debug: "🔍",
15
+ step: "▶ ",
16
+ task: "📋",
17
+ api: "🌐",
18
+ };
19
+ // ─── CLI Adapter ────────────────────────────────────────────────────────────
20
+ export class CliAdapter {
21
+ rl = null;
22
+ commandHandler = null;
23
+ enableLogging;
24
+ logLevels;
25
+ enableCommands;
26
+ constructor(options = {}) {
27
+ this.enableLogging = options.enableLogging ?? true;
28
+ this.logLevels = new Set(options.logLevels ?? ["info", "warn", "error", "step", "task", "api"]);
29
+ this.enableCommands = options.enableCommands ?? false;
30
+ }
31
+ getReadline() {
32
+ if (!this.rl) {
33
+ this.rl = readline.createInterface({
34
+ input: process.stdin,
35
+ output: process.stdout,
36
+ });
37
+ // Listen for keyboard commands during execution
38
+ if (this.enableCommands) {
39
+ this.rl.on("line", (input) => {
40
+ this.handleKeyboardInput(input.trim().toLowerCase());
41
+ });
42
+ }
43
+ }
44
+ return this.rl;
45
+ }
46
+ /** Prompt the user for input via stdin */
47
+ prompt(message) {
48
+ return new Promise((resolve) => {
49
+ this.getReadline().question(`🔹 ${message}: `, (answer) => {
50
+ resolve(answer.trim());
51
+ });
52
+ });
53
+ }
54
+ /** Ask a yes/no question */
55
+ async confirm(message) {
56
+ const answer = await this.prompt(`${message} (y/n)`);
57
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
58
+ }
59
+ /** Push a log line to stdout */
60
+ log(level, message) {
61
+ if (!this.enableLogging)
62
+ return;
63
+ if (!this.logLevels.has(level))
64
+ return;
65
+ const icon = LEVEL_ICONS[level];
66
+ console.log(`${icon} ${message}`);
67
+ }
68
+ /**
69
+ * Register a command handler.
70
+ * Called by the interpreter to wire up command sending.
71
+ */
72
+ onCommand(handler) {
73
+ this.commandHandler = handler;
74
+ // Initialize readline so keyboard listening starts
75
+ if (this.enableCommands) {
76
+ this.getReadline();
77
+ console.log("─── Commands: [p]ause [c]ontinue [s]kip [t]stop [k]ill ───");
78
+ }
79
+ }
80
+ /** Clean up readline interface */
81
+ dispose() {
82
+ if (this.rl) {
83
+ this.rl.close();
84
+ this.rl = null;
85
+ }
86
+ this.commandHandler = null;
87
+ }
88
+ // ── Keyboard Input Handling ─────────────────────────────────────────────
89
+ handleKeyboardInput(input) {
90
+ if (!this.commandHandler)
91
+ return;
92
+ switch (input) {
93
+ case "p":
94
+ case "pause":
95
+ this.commandHandler(ExecutionCommand.Pause);
96
+ break;
97
+ case "c":
98
+ case "continue":
99
+ case "resume":
100
+ this.commandHandler(ExecutionCommand.Continue);
101
+ break;
102
+ case "s":
103
+ case "skip":
104
+ this.commandHandler(ExecutionCommand.Skip);
105
+ break;
106
+ case "t":
107
+ case "stop":
108
+ this.commandHandler(ExecutionCommand.Stop);
109
+ break;
110
+ case "k":
111
+ case "kill":
112
+ this.commandHandler(ExecutionCommand.Kill);
113
+ break;
114
+ default:
115
+ // Ignore unknown input
116
+ break;
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * BerryCore — Runtime Facade
3
+ *
4
+ * The single entry-point for running a .berry script programmatically.
5
+ *
6
+ * Responsibilities:
7
+ * 1. Tokenise the source code (LexerEngine)
8
+ * 2. Build the AST (AstEngine)
9
+ * 3. Execute the program (Interpreter)
10
+ * 4. Relay every interpreter event to registered outside listeners
11
+ *
12
+ * Usage (CLI consumer):
13
+ * const core = new BerryCore(source, { adapter: new CliAdapter() });
14
+ * core.on(InterpreterEvent.Completed, payload => { ... });
15
+ * await core.run();
16
+ *
17
+ * Usage (bare / no adapter):
18
+ * const core = new BerryCore(source);
19
+ * core.on(InterpreterEvent.Log, ({ message }) => console.log(message));
20
+ * await core.run();
21
+ */
22
+ import { InterpreterOptions } from "./interpreter/interpreter.js";
23
+ import { InterpreterEvent, EventListener, ExecutionCommand, ExecutionState, IOAdapter, TaskResult } from "./interpreter/interpreter.types.js";
24
+ export interface BerryCoreOptions {
25
+ /**
26
+ * IO adapter used for user-input prompts, log output, and keyboard commands.
27
+ * Pass a `CliAdapter` for terminal use, or a custom adapter for a UI/WS layer.
28
+ * When omitted, the interpreter runs silently (events are still emitted).
29
+ */
30
+ readonly adapter?: IOAdapter;
31
+ /**
32
+ * Fine-grained interpreter execution options forwarded as-is to the
33
+ * `Interpreter` constructor (timeout, continueOnError, dryRun, …).
34
+ */
35
+ readonly interpreterOptions?: Partial<InterpreterOptions>;
36
+ /**
37
+ * Optional base path used by the default link resolver to resolve relative local file paths.
38
+ * Typically the directory of the entry .berry file.
39
+ */
40
+ readonly basePath?: string;
41
+ /**
42
+ * Optional resolver for `Link` statements.
43
+ * Receives the path/url and should return the string content of that file.
44
+ * Required if the source code contains `Link` statements.
45
+ */
46
+ readonly linkResolver?: (path: string) => Promise<string>;
47
+ /**
48
+ * Optional decryption provider for `Decrypt` flagged `Var` entries.
49
+ * If omitted, base64 decryption is used by default.
50
+ */
51
+ readonly decryptionProvider?: (encrypted: string) => Promise<string> | string;
52
+ }
53
+ export declare class BerryCore {
54
+ private readonly source;
55
+ private readonly options;
56
+ /**
57
+ * The interpreter instance is created fresh on every `run()` call so that
58
+ * BerryCore remains reusable across multiple sequential runs.
59
+ */
60
+ private interpreter;
61
+ /**
62
+ * Listeners registered via `.on()` before `run()` is called.
63
+ * They are forwarded to the interpreter after it is created.
64
+ */
65
+ private readonly pendingListeners;
66
+ constructor(source: string, options?: BerryCoreOptions);
67
+ /**
68
+ * Register a listener for an interpreter lifecycle event.
69
+ *
70
+ * Listeners can be registered before or after calling `run()`.
71
+ * Listeners registered before `run()` are forwarded to the newly
72
+ * created Interpreter instance automatically.
73
+ */
74
+ on<E extends InterpreterEvent>(event: E, listener: EventListener<E>): this;
75
+ /**
76
+ * Send an execution command to the running interpreter.
77
+ * Has no effect if `run()` has not been called yet.
78
+ */
79
+ sendCommand(command: ExecutionCommand): void;
80
+ /**
81
+ * Immediately kill the running execution and clean up resources.
82
+ */
83
+ kill(): void;
84
+ /**
85
+ * Returns the current execution state, or `Idle` if not yet started.
86
+ */
87
+ getState(): ExecutionState;
88
+ private resolveLinks;
89
+ private defaultLinkResolver;
90
+ private parseData;
91
+ /**
92
+ * Execute the source code end-to-end.
93
+ *
94
+ * Pipeline:
95
+ * source → LexerEngine.tokenize() → Token[]
96
+ * → AstEngine.build() → ProgramNode
97
+ * → Interpreter.execute() → TaskResult[]
98
+ *
99
+ * Throws:
100
+ * - LexerError — if the source contains illegal characters
101
+ * - ParserError — if the source has a grammar error
102
+ * - RuntimeError — if execution encounters an unrecoverable state
103
+ *
104
+ * @param inputRow Optional data row to inject into the execution for `Input` mappings
105
+ * @returns The array of `TaskResult`s produced by the interpreter.
106
+ */
107
+ run(inputRow?: Record<string, string>): Promise<TaskResult[]>;
108
+ }
@@ -0,0 +1,258 @@
1
+ /**
2
+ * BerryCore — Runtime Facade
3
+ *
4
+ * The single entry-point for running a .berry script programmatically.
5
+ *
6
+ * Responsibilities:
7
+ * 1. Tokenise the source code (LexerEngine)
8
+ * 2. Build the AST (AstEngine)
9
+ * 3. Execute the program (Interpreter)
10
+ * 4. Relay every interpreter event to registered outside listeners
11
+ *
12
+ * Usage (CLI consumer):
13
+ * const core = new BerryCore(source, { adapter: new CliAdapter() });
14
+ * core.on(InterpreterEvent.Completed, payload => { ... });
15
+ * await core.run();
16
+ *
17
+ * Usage (bare / no adapter):
18
+ * const core = new BerryCore(source);
19
+ * core.on(InterpreterEvent.Log, ({ message }) => console.log(message));
20
+ * await core.run();
21
+ */
22
+ import { LexerEngine } from "./parser/tokenizer/reader/lexer.engine.js";
23
+ import { AstEngine } from "./parser/ast/ast.engine.js";
24
+ import { NodeType } from "./parser/ast/ast.types.js";
25
+ import { Interpreter } from "./interpreter/interpreter.js";
26
+ import { InterpreterEvent, ExecutionState, } from "./interpreter/interpreter.types.js";
27
+ // ─── BerryCore ───────────────────────────────────────────────────────────────
28
+ export class BerryCore {
29
+ source;
30
+ options;
31
+ /**
32
+ * The interpreter instance is created fresh on every `run()` call so that
33
+ * BerryCore remains reusable across multiple sequential runs.
34
+ */
35
+ interpreter = null;
36
+ /**
37
+ * Listeners registered via `.on()` before `run()` is called.
38
+ * They are forwarded to the interpreter after it is created.
39
+ */
40
+ pendingListeners = [];
41
+ // ── Constructor ──────────────────────────────────────────────────────────
42
+ constructor(source, options = {}) {
43
+ this.source = source;
44
+ this.options = options;
45
+ }
46
+ // ── Public Event API ────────────────────────────────────────────────────
47
+ /**
48
+ * Register a listener for an interpreter lifecycle event.
49
+ *
50
+ * Listeners can be registered before or after calling `run()`.
51
+ * Listeners registered before `run()` are forwarded to the newly
52
+ * created Interpreter instance automatically.
53
+ */
54
+ on(event, listener) {
55
+ if (this.interpreter) {
56
+ // Already running — wire directly
57
+ this.interpreter.on(event, listener);
58
+ }
59
+ else {
60
+ // Queue for when run() creates the interpreter
61
+ this.pendingListeners.push({
62
+ event,
63
+ listener: listener,
64
+ });
65
+ }
66
+ return this;
67
+ }
68
+ // ── Execution Control API ───────────────────────────────────────────────
69
+ /**
70
+ * Send an execution command to the running interpreter.
71
+ * Has no effect if `run()` has not been called yet.
72
+ */
73
+ sendCommand(command) {
74
+ this.interpreter?.sendCommand(command);
75
+ }
76
+ /**
77
+ * Immediately kill the running execution and clean up resources.
78
+ */
79
+ kill() {
80
+ this.interpreter?.kill();
81
+ }
82
+ /**
83
+ * Returns the current execution state, or `Idle` if not yet started.
84
+ */
85
+ getState() {
86
+ return this.interpreter?.getState() ?? ExecutionState.Idle;
87
+ }
88
+ // ── Main Run ────────────────────────────────────────────────────────────
89
+ async resolveLinks(ast, visited = new Set()) {
90
+ const resolvedBody = [];
91
+ for (const stmt of ast.body) {
92
+ if (stmt.type === NodeType.LinkStatement) {
93
+ // Prevent infinite loops in circular links
94
+ if (visited.has(stmt.path)) {
95
+ continue; // Already processed this link
96
+ }
97
+ visited.add(stmt.path);
98
+ let content;
99
+ if (this.options.linkResolver) {
100
+ content = await this.options.linkResolver(stmt.path);
101
+ }
102
+ else {
103
+ content = await this.defaultLinkResolver(stmt.path);
104
+ }
105
+ const subTokens = new LexerEngine(content).tokenize();
106
+ const subAst = new AstEngine(subTokens).build();
107
+ // Recursively resolve links in the sub-AST
108
+ const resolvedSubAst = await this.resolveLinks(subAst, visited);
109
+ // Add the resolved sub-body instead of the link statement
110
+ resolvedBody.push(...resolvedSubAst.body);
111
+ }
112
+ else {
113
+ resolvedBody.push(stmt);
114
+ }
115
+ }
116
+ return {
117
+ ...ast,
118
+ body: resolvedBody,
119
+ };
120
+ }
121
+ async defaultLinkResolver(path) {
122
+ // 1. Check if it's an HTTP URL
123
+ if (path.startsWith("http://") || path.startsWith("https://")) {
124
+ try {
125
+ const response = await fetch(path);
126
+ if (!response.ok) {
127
+ throw new Error(`Failed to fetch URL ${path}: ${response.statusText}`);
128
+ }
129
+ return await response.text();
130
+ }
131
+ catch (error) {
132
+ throw new Error(`Failed to fetch URL ${path}: ${error}`);
133
+ }
134
+ }
135
+ // 2. Check if we are in Node.js
136
+ const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
137
+ if (isNode) {
138
+ try {
139
+ // Use dynamic import to avoid bundler issues in browser
140
+ const fs = await import("fs/promises");
141
+ const pathModule = await import("path");
142
+ const resolvedPath = this.options.basePath
143
+ ? pathModule.resolve(this.options.basePath, path)
144
+ : pathModule.resolve(process.cwd(), path);
145
+ return await fs.readFile(resolvedPath, "utf-8");
146
+ }
147
+ catch (e) {
148
+ throw new Error(`Failed to read local file ${path}: ${e}`);
149
+ }
150
+ }
151
+ // 3. Fallback for browser (relative URLs)
152
+ if (typeof window !== "undefined" && typeof fetch !== "undefined") {
153
+ try {
154
+ const response = await fetch(path);
155
+ if (!response.ok) {
156
+ throw new Error(`Failed to fetch relative URL ${path}: ${response.statusText}`);
157
+ }
158
+ return await response.text();
159
+ }
160
+ catch (e) {
161
+ throw new Error(`Failed to fetch relative URL ${path}: ${e}`);
162
+ }
163
+ }
164
+ throw new Error(`Cannot resolve link '${path}': Environment does not support file reading and path is not an absolute URL.`);
165
+ }
166
+ parseData(content, path) {
167
+ if (path.endsWith('.json')) {
168
+ return JSON.parse(content);
169
+ }
170
+ // Basic CSV parser
171
+ const lines = content.split(/\r?\n/).filter(line => line.trim().length > 0);
172
+ if (lines.length === 0)
173
+ return [];
174
+ const headers = lines[0].split(',').map(h => h.trim());
175
+ const rows = [];
176
+ for (let i = 1; i < lines.length; i++) {
177
+ const values = lines[i].split(',').map(v => v.trim());
178
+ const row = {};
179
+ for (let j = 0; j < headers.length; j++) {
180
+ row[headers[j]] = values[j] ?? "";
181
+ }
182
+ rows.push(row);
183
+ }
184
+ return rows;
185
+ }
186
+ /**
187
+ * Execute the source code end-to-end.
188
+ *
189
+ * Pipeline:
190
+ * source → LexerEngine.tokenize() → Token[]
191
+ * → AstEngine.build() → ProgramNode
192
+ * → Interpreter.execute() → TaskResult[]
193
+ *
194
+ * Throws:
195
+ * - LexerError — if the source contains illegal characters
196
+ * - ParserError — if the source has a grammar error
197
+ * - RuntimeError — if execution encounters an unrecoverable state
198
+ *
199
+ * @param inputRow Optional data row to inject into the execution for `Input` mappings
200
+ * @returns The array of `TaskResult`s produced by the interpreter.
201
+ */
202
+ async run(inputRow) {
203
+ // ── Phase 1: Tokenise ──────────────────────────────────────────────
204
+ const tokens = new LexerEngine(this.source).tokenize();
205
+ // ── Phase 2: Build AST ────────────────────────────────────────────
206
+ let ast = new AstEngine(tokens).build();
207
+ // ── Phase 2.5: Resolve Links ──────────────────────────────────────
208
+ ast = await this.resolveLinks(ast);
209
+ // ── Phase 2.6: Handle Input ───────────────────────────────────────
210
+ if (!inputRow) {
211
+ const inputNode = ast.body.find(node => node.type === NodeType.InputStatement);
212
+ if (inputNode) {
213
+ let content;
214
+ if (this.options.linkResolver) {
215
+ content = await this.options.linkResolver(inputNode.path);
216
+ }
217
+ else {
218
+ content = await this.defaultLinkResolver(inputNode.path);
219
+ }
220
+ const rows = this.parseData(content, inputNode.path);
221
+ // Fire DataLoaded event to pending listeners
222
+ for (const { event, listener } of this.pendingListeners) {
223
+ if (event === InterpreterEvent.DataLoaded) {
224
+ await listener({ rows });
225
+ }
226
+ }
227
+ // Return early, adapter is expected to loop and call run(row)
228
+ return [];
229
+ }
230
+ }
231
+ // ── Phase 3: Create Interpreter ───────────────────────────────────
232
+ this.interpreter = new Interpreter(ast, {
233
+ ...this.options.interpreterOptions,
234
+ inputRow,
235
+ decryptionProvider: this.options.decryptionProvider
236
+ });
237
+ // Wire the IO adapter (if provided)
238
+ if (this.options.adapter) {
239
+ this.interpreter.setIOAdapter(this.options.adapter);
240
+ }
241
+ // Forward any listeners that were registered before run() was called
242
+ for (const { event, listener } of this.pendingListeners) {
243
+ this.interpreter.on(event, listener);
244
+ }
245
+ // ── Phase 4: Execute ──────────────────────────────────────────────
246
+ try {
247
+ return await this.interpreter.execute();
248
+ }
249
+ finally {
250
+ // Only destroy if the interpreter actually finished
251
+ const state = this.interpreter?.getState();
252
+ const isFinished = state === ExecutionState.Completed || state === ExecutionState.Killed || state === ExecutionState.Stopped;
253
+ if (!this.options.interpreterOptions?.dryRun && isFinished) {
254
+ this.interpreter = null;
255
+ }
256
+ }
257
+ }
258
+ }
@@ -0,0 +1,13 @@
1
+ import { AstEngine, Ast, ParserError } from "./parser/ast/ast.engine.js";
2
+ import { LexerEngine, LexerError } from "./parser/tokenizer/reader/lexer.engine.js";
3
+ import { BerryFormatter } from "./parser/formatter/formatter.js";
4
+ import { Interpreter } from "./interpreter/interpreter.js";
5
+ import { CliAdapter } from "./adapter/cli-adapter.js";
6
+ import { FormatUtil } from "./script/format-util.js";
7
+ import { PostmanUtil } from "./script/postman.util.js";
8
+ import { SwaggerUtil } from "./script/swagger.util.js";
9
+ import { BerryCore } from "./berry-core.js";
10
+ import { InterpreterEvent, type CompletedPayload, type TaskResult, type StepResult, type IOAdapter, ExecutionCommand, ExecutionState, ExecutionStatus } from "./interpreter/interpreter.types.js";
11
+ import { type BerryCoreOptions } from "./berry-core.js";
12
+ import { NodeType, type BaseNode, type TaskBlockNode, type StepBlockNode, type ParamsBlockNode, type CaptureBlockNode, type CheckBlockNode, type KeyValuePairNode, type ConditionNode, ProgramNode } from "./parser/ast/ast.types.js";
13
+ export { BerryCore, type BerryCoreOptions, AstEngine, Ast, ParserError, LexerEngine, LexerError, BerryFormatter, Interpreter, CliAdapter, FormatUtil, PostmanUtil, SwaggerUtil, InterpreterEvent, ExecutionCommand, ExecutionState, ExecutionStatus, NodeType, type IOAdapter, type CompletedPayload, type TaskResult, type StepResult, type BaseNode, type TaskBlockNode, type StepBlockNode, type ParamsBlockNode, type CaptureBlockNode, type CheckBlockNode, type KeyValuePairNode, type ConditionNode, type ProgramNode };
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import { AstEngine, Ast, ParserError } from "./parser/ast/ast.engine.js";
2
+ import { LexerEngine, LexerError } from "./parser/tokenizer/reader/lexer.engine.js";
3
+ import { BerryFormatter } from "./parser/formatter/formatter.js";
4
+ import { Interpreter } from "./interpreter/interpreter.js";
5
+ import { CliAdapter } from "./adapter/cli-adapter.js";
6
+ import { FormatUtil } from "./script/format-util.js";
7
+ import { PostmanUtil } from "./script/postman.util.js";
8
+ import { SwaggerUtil } from "./script/swagger.util.js";
9
+ import { BerryCore } from "./berry-core.js";
10
+ import { InterpreterEvent, ExecutionCommand, ExecutionState, ExecutionStatus, } from "./interpreter/interpreter.types.js";
11
+ import { NodeType } from "./parser/ast/ast.types.js";
12
+ export {
13
+ // ── High-level facade (recommended entry-point) ──────────────────────
14
+ BerryCore,
15
+ // ── Low-level building blocks (advanced consumers) ───────────────────
16
+ AstEngine, Ast, ParserError, LexerEngine, LexerError, BerryFormatter, Interpreter, CliAdapter, FormatUtil, PostmanUtil, SwaggerUtil,
17
+ // ── Types / enums needed by consumers ────────────────────────────────
18
+ InterpreterEvent, ExecutionCommand, ExecutionState, ExecutionStatus, NodeType };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Environment — Scoped Variable Store
3
+ *
4
+ * Maintains a chain of scopes: global → task → step.
5
+ * Each scope is a Map<string, RuntimeValue>.
6
+ * Lookup walks up the parent chain.
7
+ */
8
+ import { RuntimeValue } from "./interpreter.types.js";
9
+ export declare class Environment {
10
+ private readonly store;
11
+ private readonly parent;
12
+ constructor(parent?: Environment | null);
13
+ /**
14
+ * Declare a new variable in this scope.
15
+ * Overwrites if it already exists in this scope.
16
+ */
17
+ declare(name: string, value: RuntimeValue): void;
18
+ /**
19
+ * Look up a variable, walking up the scope chain.
20
+ * Throws VariableNotFoundError if not found in any scope.
21
+ */
22
+ lookup(name: string): RuntimeValue;
23
+ /**
24
+ * Try to look up a variable without throwing.
25
+ * Returns undefined if not found.
26
+ */
27
+ tryLookup(name: string): RuntimeValue | undefined;
28
+ /**
29
+ * Assign a value to an existing variable in the nearest scope.
30
+ * If not found, declares it in this scope.
31
+ */
32
+ assign(name: string, value: RuntimeValue): void;
33
+ /**
34
+ * Create a child scope inheriting from this environment.
35
+ */
36
+ createChild(): Environment;
37
+ /**
38
+ * Get all variables in this scope (not parents).
39
+ */
40
+ getOwnEntries(): ReadonlyMap<string, RuntimeValue>;
41
+ /**
42
+ * Get all variables including parent scopes (closest wins).
43
+ */
44
+ getAllEntries(): Map<string, RuntimeValue>;
45
+ }