@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,176 @@
1
+ import * as fs from "fs";
2
+ export class PostmanUtil {
3
+ /**
4
+ * Convert a Postman collection file (JSON) to Berry API code
5
+ * @param filePath Path to the Postman collection JSON file
6
+ */
7
+ static convertFromPostmanFile(filePath) {
8
+ let content;
9
+ try {
10
+ content = fs.readFileSync(filePath, "utf-8");
11
+ }
12
+ catch (e) {
13
+ throw new Error(`Failed to read file: ${e}`);
14
+ }
15
+ let collection;
16
+ try {
17
+ collection = JSON.parse(content);
18
+ }
19
+ catch (e) {
20
+ throw new Error(`Invalid JSON in Postman file`);
21
+ }
22
+ if (!collection.item) {
23
+ throw new Error("No items found in Postman collection");
24
+ }
25
+ const lines = [];
26
+ // Recursively process items
27
+ this.processItems(collection.item, lines);
28
+ return lines.join("\n").trim() + "\n";
29
+ }
30
+ /**
31
+ * Internal recursive processor for Postman items
32
+ */
33
+ static processItems(items, lines, parentName = "") {
34
+ for (const item of items) {
35
+ if (item.item) {
36
+ // It's a folder/group
37
+ this.processItems(item.item, lines, item.name);
38
+ continue;
39
+ }
40
+ if (!item.request)
41
+ continue;
42
+ const req = item.request;
43
+ const name = item.name || parentName || "api";
44
+ const method = (req.method || "GET").toUpperCase();
45
+ // 1. URL Resolution
46
+ let url = this.buildUrl(req.url);
47
+ // Replace postman path params :param with berry {{param}}
48
+ url = url.replace(/\/:([a-zA-Z0-9_]+)/g, "/{{$1}}");
49
+ // 2. ID Generation (Abbreviation)
50
+ const apiId = this.abbreviate(name);
51
+ // 3. Construct Berry Block
52
+ lines.push(`Api ${method} #${apiId} ${name}`);
53
+ lines.push(`Url ${url}`);
54
+ // 4. Headers
55
+ const headers = this.extractHeaders(req.header);
56
+ if (headers.length > 0) {
57
+ lines.push("Header");
58
+ for (const h of headers) {
59
+ lines.push(`- ${h}`);
60
+ }
61
+ }
62
+ // 5. Body
63
+ const bodyContent = this.extractBody(req.body);
64
+ if (bodyContent) {
65
+ const bodyType = this.detectBodyType(req.header, req.body);
66
+ // Clean body to single line for preview-style import or preserve formatting?
67
+ // Let's preserve formatting if it's multiple lines
68
+ if (bodyContent.includes("\n")) {
69
+ lines.push(`Body ${bodyType} \`${bodyContent.trim()}\``);
70
+ }
71
+ else {
72
+ lines.push(`Body ${bodyType} \`${bodyContent}\``);
73
+ }
74
+ }
75
+ lines.push(""); // Spacer
76
+ }
77
+ }
78
+ /**
79
+ * Build URL string from Postman URL object
80
+ */
81
+ static buildUrl(url) {
82
+ if (typeof url === "string")
83
+ return url;
84
+ if (!url)
85
+ return "";
86
+ if (url.raw)
87
+ return url.raw;
88
+ if (url.host) {
89
+ const protocol = url.protocol || "https";
90
+ const host = url.host.join(".");
91
+ const path = url.path ? "/" + url.path.join("/") : "";
92
+ let fullUrl = `${protocol}://${host}${path}`;
93
+ if (url.query && url.query.length > 0) {
94
+ const queryParams = url.query
95
+ .filter((q) => q.key)
96
+ .map((q) => `${q.key}={{${q.key}}}`)
97
+ .join("&");
98
+ fullUrl += `?${queryParams}`;
99
+ }
100
+ return fullUrl;
101
+ }
102
+ return "";
103
+ }
104
+ /**
105
+ * Abbreviate title to camelCase ID
106
+ */
107
+ static abbreviate(title) {
108
+ const stopWords = new Set(["a", "in", "the", "with", "and", "of", "to", "for", "on", "at", "by", "is", "it"]);
109
+ return title
110
+ .toLowerCase()
111
+ .replace(/[^a-z0-9 ]/g, "") // Remove special chars
112
+ .split(" ")
113
+ .filter((word) => word.length > 0 && !stopWords.has(word))
114
+ .map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1)))
115
+ .join("");
116
+ }
117
+ /**
118
+ * Extract headers to Berry format
119
+ */
120
+ static extractHeaders(headers) {
121
+ if (!headers || !Array.isArray(headers))
122
+ return [];
123
+ return headers
124
+ .filter((h) => h.key && h.value !== undefined)
125
+ .map((h) => `${h.key.trim()}: '${h.value.trim()}'`);
126
+ }
127
+ /**
128
+ * Extract body content from Postman request
129
+ */
130
+ static extractBody(body) {
131
+ if (!body || !body.mode)
132
+ return "";
133
+ switch (body.mode) {
134
+ case "raw":
135
+ return body.raw || "";
136
+ case "urlencoded":
137
+ if (body.urlencoded) {
138
+ const obj = {};
139
+ body.urlencoded.forEach((kv) => (obj[kv.key] = kv.value));
140
+ return JSON.stringify(obj, null, 2);
141
+ }
142
+ break;
143
+ case "formdata":
144
+ if (body.formdata) {
145
+ const obj = {};
146
+ body.formdata.forEach((kv) => (obj[kv.key] = kv.value));
147
+ return JSON.stringify(obj, null, 2);
148
+ }
149
+ break;
150
+ case "graphql":
151
+ // Postman stores graphql in a specific way, but we'll treat as raw for now
152
+ return body.raw || "";
153
+ }
154
+ return "";
155
+ }
156
+ /**
157
+ * Detect Berry body type from content-type or Postman options
158
+ */
159
+ static detectBodyType(headers, body) {
160
+ if (body?.options?.raw?.language) {
161
+ const lang = body.options.raw.language.toUpperCase();
162
+ if (["JSON", "XML"].includes(lang))
163
+ return lang;
164
+ }
165
+ const contentType = headers?.find((h) => h.key.toLowerCase() === "content-type")?.value.toLowerCase() || "";
166
+ if (contentType.includes("json"))
167
+ return "JSON";
168
+ if (contentType.includes("xml"))
169
+ return "XML";
170
+ if (contentType.includes("form"))
171
+ return "FORM";
172
+ if (contentType.includes("octet-stream"))
173
+ return "BINARY";
174
+ return "RAW";
175
+ }
176
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Swagger / OpenAPI Utility
3
+ *
4
+ * Utilities for converting Swagger/OpenAPI definitions to Berry code.
5
+ */
6
+ export interface OpenApiSchema {
7
+ type?: string;
8
+ properties?: Record<string, OpenApiSchema>;
9
+ items?: OpenApiSchema;
10
+ $ref?: string;
11
+ example?: any;
12
+ default?: any;
13
+ }
14
+ export interface OpenApiParameter {
15
+ name: string;
16
+ in: "path" | "query" | "header" | "body";
17
+ required?: boolean;
18
+ schema?: OpenApiSchema;
19
+ example?: any;
20
+ default?: any;
21
+ }
22
+ export interface OpenApiOperation {
23
+ operationId?: string;
24
+ summary?: string;
25
+ parameters?: OpenApiParameter[];
26
+ requestBody?: {
27
+ content: Record<string, {
28
+ schema: OpenApiSchema;
29
+ example?: any;
30
+ }>;
31
+ };
32
+ consumes?: string[];
33
+ produces?: string[];
34
+ }
35
+ export interface OpenApiDefinition {
36
+ openapi?: string;
37
+ swagger?: string;
38
+ host?: string;
39
+ basePath?: string;
40
+ schemes?: string[];
41
+ servers?: Array<{
42
+ url: string;
43
+ }>;
44
+ paths: Record<string, Record<string, OpenApiOperation>>;
45
+ components?: {
46
+ schemas: Record<string, OpenApiSchema>;
47
+ };
48
+ definitions?: Record<string, OpenApiSchema>;
49
+ }
50
+ export declare class SwaggerUtil {
51
+ /**
52
+ * Fetch a Swagger/OpenAPI doc from a URL and convert it to Berry code
53
+ */
54
+ static convertFromSwaggerApi(url: string): Promise<string>;
55
+ /**
56
+ * Convert a Swagger/OpenAPI JSON string to Berry code
57
+ */
58
+ static convertSwaggerToBerry(swagger: string | OpenApiDefinition, id: string): string;
59
+ /**
60
+ * Replace {param} with {{param}} and extract query params
61
+ */
62
+ private static handlePathParams;
63
+ /**
64
+ * Build the full URL
65
+ */
66
+ private static buildUrl;
67
+ /**
68
+ * Extract headers from operation details
69
+ */
70
+ private static extractHeaders;
71
+ /**
72
+ * Generate the Body JSON block
73
+ */
74
+ private static generateBodyBlock;
75
+ private static mapContentTypeToBodyType;
76
+ /**
77
+ * Recursively generate dummy data from an OpenAPI schema
78
+ */
79
+ private static generateDummy;
80
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Swagger / OpenAPI Utility
3
+ *
4
+ * Utilities for converting Swagger/OpenAPI definitions to Berry code.
5
+ */
6
+ export class SwaggerUtil {
7
+ /**
8
+ * Fetch a Swagger/OpenAPI doc from a URL and convert it to Berry code
9
+ */
10
+ static async convertFromSwaggerApi(url) {
11
+ try {
12
+ const response = await fetch(url);
13
+ if (!response.ok) {
14
+ return `Failed to fetch Swagger: ${response.status} ${response.statusText}`;
15
+ }
16
+ const swaggerJson = await response.text();
17
+ const id = url.split("/").pop() || "api";
18
+ return this.convertSwaggerToBerry(swaggerJson, id);
19
+ }
20
+ catch (e) {
21
+ return `Error fetching Swagger: ${e instanceof Error ? e.message : String(e)}`;
22
+ }
23
+ }
24
+ /**
25
+ * Convert a Swagger/OpenAPI JSON string to Berry code
26
+ */
27
+ static convertSwaggerToBerry(swagger, id) {
28
+ let obj;
29
+ try {
30
+ obj = typeof swagger === "string" ? JSON.parse(swagger) : swagger;
31
+ }
32
+ catch (e) {
33
+ return `Invalid Swagger/OpenAPI JSON`;
34
+ }
35
+ if (!obj.paths)
36
+ return "No paths found in Swagger definition";
37
+ const lines = [];
38
+ const serverUrl = obj.servers?.[0]?.url || "";
39
+ const schemas = obj.components?.schemas || obj.definitions || {};
40
+ let apiCount = 1;
41
+ for (const [path, methods] of Object.entries(obj.paths)) {
42
+ if (!methods)
43
+ continue;
44
+ for (const [method, details] of Object.entries(methods)) {
45
+ if (typeof details !== "object")
46
+ continue;
47
+ const opId = details.operationId || `${id}${apiCount}`;
48
+ const { modPath, queryParams } = this.handlePathParams(path, details.parameters || []);
49
+ const finalUrl = this.buildUrl(modPath, serverUrl, obj, queryParams);
50
+ const headers = this.extractHeaders(details);
51
+ const bodyBlock = this.generateBodyBlock(details, schemas);
52
+ // ── Construct API Block ──
53
+ lines.push(`Api ${method.toUpperCase()} #${opId} ${details.summary || ""}`);
54
+ lines.push(`Url ${finalUrl}`);
55
+ if (headers.length > 0) {
56
+ lines.push("Header");
57
+ for (const h of headers)
58
+ lines.push(`- ${h}`);
59
+ }
60
+ if (bodyBlock)
61
+ lines.push(bodyBlock);
62
+ lines.push(""); // Spacer
63
+ apiCount++;
64
+ }
65
+ }
66
+ return lines.join("\n").trim() + "\n";
67
+ }
68
+ /**
69
+ * Replace {param} with {{param}} and extract query params
70
+ */
71
+ static handlePathParams(path, params) {
72
+ let modPath = path;
73
+ const queryParams = [];
74
+ for (const param of params) {
75
+ if (param.in === "path") {
76
+ modPath = modPath.replace(new RegExp(`{${param.name}}`, "g"), `{{${param.name}}}`);
77
+ }
78
+ else if (param.in === "query") {
79
+ queryParams.push(`${param.name}={{${param.name}}}`);
80
+ }
81
+ }
82
+ return { modPath, queryParams };
83
+ }
84
+ /**
85
+ * Build the full URL
86
+ */
87
+ static buildUrl(modPath, serverUrl, obj, queryParams) {
88
+ let url = modPath;
89
+ if (serverUrl) {
90
+ url = serverUrl.endsWith("/") && modPath.startsWith("/") ? serverUrl + modPath.slice(1) : serverUrl + modPath;
91
+ }
92
+ else if (obj.host) {
93
+ const scheme = obj.schemes?.[0] || "https";
94
+ const base = obj.basePath || "";
95
+ url = `${scheme}://${obj.host}${base}${modPath}`;
96
+ }
97
+ if (queryParams.length > 0) {
98
+ url += (url.includes("?") ? "&" : "?") + queryParams.join("&");
99
+ }
100
+ return url;
101
+ }
102
+ /**
103
+ * Extract headers from operation details
104
+ */
105
+ static extractHeaders(details) {
106
+ const headers = [];
107
+ // Content-Type
108
+ if (details.consumes?.[0]) {
109
+ headers.push(`Content-Type: '${details.consumes[0]}'`);
110
+ }
111
+ else if (details.requestBody?.content) {
112
+ const types = Object.keys(details.requestBody.content);
113
+ if (types[0])
114
+ headers.push(`Content-Type: '${types[0]}'`);
115
+ }
116
+ // Accept
117
+ if (details.produces?.[0]) {
118
+ headers.push(`Accept: '${details.produces[0]}'`);
119
+ }
120
+ // Custom headers from parameters
121
+ if (details.parameters) {
122
+ for (const p of details.parameters) {
123
+ if (p.in === "header") {
124
+ headers.push(`${p.name}: '${p.example || p.default || ""}'`);
125
+ }
126
+ }
127
+ }
128
+ return headers;
129
+ }
130
+ /**
131
+ * Generate the Body JSON block
132
+ */
133
+ static generateBodyBlock(details, schemas) {
134
+ // OpenAPI 3
135
+ if (details.requestBody?.content) {
136
+ const content = details.requestBody.content;
137
+ for (const [ctype, cval] of Object.entries(content)) {
138
+ const dummy = cval.example || this.generateDummy(cval.schema, schemas);
139
+ const bodyType = this.mapContentTypeToBodyType(ctype);
140
+ return `Body ${bodyType} \`${JSON.stringify(dummy, null, 2)}\``;
141
+ }
142
+ }
143
+ // Swagger 2
144
+ if (details.parameters) {
145
+ const bodyParam = details.parameters.find((p) => p.in === "body");
146
+ if (bodyParam?.schema) {
147
+ const dummy = this.generateDummy(bodyParam.schema, schemas);
148
+ return `Body JSON \`${JSON.stringify(dummy, null, 2)}\``;
149
+ }
150
+ }
151
+ return "";
152
+ }
153
+ static mapContentTypeToBodyType(ctype) {
154
+ const ct = ctype.toLowerCase();
155
+ if (ct.includes("json"))
156
+ return "JSON";
157
+ if (ct.includes("xml"))
158
+ return "XML";
159
+ if (ct.includes("form"))
160
+ return "FORM";
161
+ if (ct.includes("octet-stream"))
162
+ return "BINARY";
163
+ return "RAW";
164
+ }
165
+ /**
166
+ * Recursively generate dummy data from an OpenAPI schema
167
+ */
168
+ static generateDummy(schema, schemas, depth = 0) {
169
+ if (!schema || depth > 5)
170
+ return {};
171
+ if (schema.example !== undefined)
172
+ return schema.example;
173
+ if (schema.default !== undefined)
174
+ return schema.default;
175
+ if (schema.$ref) {
176
+ const refName = schema.$ref.replace(/^#\/(definitions|components\/schemas)\//, "");
177
+ const resolved = schemas[refName];
178
+ return resolved ? this.generateDummy(resolved, schemas, depth + 1) : {};
179
+ }
180
+ switch (schema.type) {
181
+ case "string":
182
+ return "string";
183
+ case "integer":
184
+ case "number":
185
+ return 0;
186
+ case "boolean":
187
+ return false;
188
+ case "array":
189
+ return schema.items ? [this.generateDummy(schema.items, schemas, depth + 1)] : [];
190
+ case "object":
191
+ default:
192
+ if (schema.properties) {
193
+ const obj = {};
194
+ for (const [key, prop] of Object.entries(schema.properties)) {
195
+ obj[key] = this.generateDummy(prop, schemas, depth + 1);
196
+ }
197
+ return obj;
198
+ }
199
+ return {};
200
+ }
201
+ }
202
+ }
@@ -0,0 +1,5 @@
1
+ export declare class StoreUtility {
2
+ systemDocumentFolder: string;
3
+ static storeData(data: Record<string, any>, fileName: string): Promise<void>;
4
+ static retriveData(fileName: string): Promise<void>;
5
+ }
@@ -0,0 +1,22 @@
1
+ import * as fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ export class StoreUtility {
5
+ systemDocumentFolder = path.join(os.homedir(), "Documents");
6
+ static async storeData(data, fileName) {
7
+ const fileContent = Object.entries(data)
8
+ .map(([key, value]) => `${key}:${value}`)
9
+ .join("\n");
10
+ fs.writeFileSync(fileName, fileContent);
11
+ }
12
+ static async retriveData(fileName) {
13
+ const fileData = fs.readFileSync(fileName, "utf8");
14
+ const keyValueMap = {};
15
+ fileData.split("\n").forEach((line) => {
16
+ const [key, value] = line.split(":");
17
+ if (key && value) {
18
+ keyValueMap[key.trim()] = value.trim();
19
+ }
20
+ });
21
+ }
22
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@flexiberry/berrycore",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "main": "./dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "berrycore": "./dist/index.js"
9
+ },
10
+ "keywords": [],
11
+ "author": "rintu raj c",
12
+ "license": "MIT",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "dependencies": {},
17
+ "devDependencies": {
18
+ "typescript": "^5.9.2"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc && node scripts/fix-esm.mjs",
22
+ "watch": "tsc --watch",
23
+ "clean": "rm -rf dist"
24
+ }
25
+ }
package/readme.md ADDED
@@ -0,0 +1,107 @@
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/Flexiberry/flexiberry/main/assets/favicon/android-icon-192x192.png" height="120" width="120" alt="FlexiBerry Logo" />
3
+ <h1>🚀 @flexiberry/berrycore</h1>
4
+ <p><strong>The official Lexer, Parser, AST, and Runtime Engine for the Berry DSL</strong></p>
5
+
6
+ [![npm version](https://img.shields.io/npm/v/@flexiberry/berrycore.svg?style=flat-square)](https://www.npmjs.com/package/@flexiberry/berrycore)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
8
+
9
+ <br />
10
+ </div>
11
+
12
+ ## ✨ Overview
13
+
14
+ **BerryCore** is a lightweight, zero-dependency compiler architecture built entirely in TypeScript. It provides the foundational tools required to interpret the **Berry DSL** (Domain Specific Language), a custom scripting language explicitly designed to orchestrate API flows, manage testing environments, and execute complex task runners.
15
+
16
+ This package exposes the complete language engineering pipeline:
17
+ 1. **LexerEngine** - Scans characters and generates grammar-aware tokens.
18
+ 2. **AstEngine** - Parses tokens into a strongly-typed Abstract Syntax Tree (AST).
19
+ 3. **Interpreter** - The runtime execution engine that evaluates the AST, manages isolated environment scopes, and fires runtime events.
20
+ 4. **Formatter** - Converts the AST back into cleanly formatted `.berry` script.
21
+
22
+ ---
23
+
24
+ ## 📦 Installation
25
+
26
+ Install `@flexiberry/berrycore` as a core dependency for your own test runners, CI pipelines, or editor integrations:
27
+
28
+ ```bash
29
+ npm install @flexiberry/berrycore
30
+ ```
31
+
32
+ ---
33
+
34
+ ## 🛠️ Programmatic Usage
35
+
36
+ The core engine is heavily decoupled, allowing you to use just the parts you need—whether you are building a language server, an IDE, or a CLI.
37
+
38
+
39
+ ## 📖 Language Syntax Reference
40
+
41
+ The Berry DSL was designed to be easily readable, removing complex syntax clutter in favor of a clean, indentation-aware syntax.
42
+
43
+ ### 1. Environments (`Env`)
44
+ Defines the deployment environments available for the script.
45
+ ```berry
46
+ Env UAT, SIT, PROD
47
+ ```
48
+
49
+ ### 2. Variables (`Var`)
50
+ Isolated memory scopes for storing state. Variables are initialized directly in the runtime environment.
51
+ ```berry
52
+ Var UserData
53
+ - id: 101
54
+ - name: "John Doe"
55
+ ```
56
+
57
+ ### 3. API Declarations (`Api`)
58
+ Defines HTTP endpoints with strict schema definitions.
59
+ ```berry
60
+ Api POST #CreateUser "Creates a new user"
61
+ Url 'https://api.example.com/users'
62
+ Body JSON
63
+ `{
64
+ "name": "{{UserData.name}}"
65
+ }`
66
+ Header
67
+ - Authorization: 'Bearer {{token}}'
68
+ ```
69
+
70
+ ### 4. Tasks and Steps (`Task` / `Step`)
71
+ The execution blocks where the interpreter orchestrates logic.
72
+ ```berry
73
+ Task "User Onboarding"
74
+ Step "Register New User"
75
+ Call #CreateUser
76
+ Capture
77
+ - token: "response.data.token"
78
+ Check
79
+ - status == 201
80
+ ```
81
+
82
+ ### 5. Control Flow (`Jump`, `Break`)
83
+ The interpreter supports localized control flow within Tasks.
84
+ ```berry
85
+ Break if status == 400
86
+ Jump To @2 If status == 401
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 🧠 Architectural Principles
92
+
93
+ Following standard language engineering practices, `berrycore` adheres strictly to:
94
+ - **Separation of Concerns:** The parser never executes code. The interpreter never scans tokens.
95
+ - **Type Safety:** All AST nodes are backed by strict TypeScript interfaces (`BaseNode`, `TaskBlockNode`, etc.).
96
+ - **Immutability:** The AST generated by `AstEngine` is treated as a pure, immutable data layer.
97
+ - **Event-Driven Execution:** The `Interpreter` engine acts as an event emitter, allowing external UI layers (like CLI spinners or web dashboards) to hook directly into the runtime state.
98
+
99
+ ---
100
+
101
+ ## 🤝 Contributing
102
+
103
+ This package is part of the FlexiBerry monorepo. We welcome contributions to grammar definitions, execution optimizations, and AST tooling. Please see the root `CONTRIBUTING.md` for guidelines.
104
+
105
+ ## 📄 License
106
+
107
+ MIT © FlexiBerry.dev