kimchilang 1.0.1
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/.github/workflows/ci.yml +66 -0
- package/README.md +1547 -0
- package/create-kimchi-app/README.md +44 -0
- package/create-kimchi-app/index.js +214 -0
- package/create-kimchi-app/package.json +22 -0
- package/editors/README.md +121 -0
- package/editors/sublime/KimchiLang.sublime-syntax +138 -0
- package/editors/vscode/README.md +90 -0
- package/editors/vscode/kimchilang-1.1.0.vsix +0 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +55 -0
- package/editors/vscode/src/extension.js +354 -0
- package/editors/vscode/syntaxes/kimchi.tmLanguage.json +215 -0
- package/examples/api/client.km +36 -0
- package/examples/async_pipe.km +58 -0
- package/examples/basic.kimchi +109 -0
- package/examples/cli_framework/README.md +92 -0
- package/examples/cli_framework/calculator.km +61 -0
- package/examples/cli_framework/deploy.km +126 -0
- package/examples/cli_framework/greeter.km +26 -0
- package/examples/config.static +27 -0
- package/examples/config.static.js +10 -0
- package/examples/env_test.km +37 -0
- package/examples/fibonacci.kimchi +17 -0
- package/examples/greeter.km +15 -0
- package/examples/hello.js +1 -0
- package/examples/hello.kimchi +3 -0
- package/examples/js_interop.km +42 -0
- package/examples/logger_example.km +34 -0
- package/examples/memo_fibonacci.km +17 -0
- package/examples/myapp/lib/http.js +14 -0
- package/examples/myapp/lib/http.km +16 -0
- package/examples/myapp/main.km +16 -0
- package/examples/myapp/main_with_mock.km +42 -0
- package/examples/myapp/services/api.js +18 -0
- package/examples/myapp/services/api.km +18 -0
- package/examples/new_features.kimchi +52 -0
- package/examples/project_example.static +20 -0
- package/examples/readme_examples.km +240 -0
- package/examples/reduce_pattern_match.km +85 -0
- package/examples/regex_match.km +46 -0
- package/examples/sample.js +45 -0
- package/examples/sample.km +39 -0
- package/examples/secrets.static +35 -0
- package/examples/secrets.static.js +30 -0
- package/examples/shell-example.mjs +144 -0
- package/examples/shell_example.km +19 -0
- package/examples/stdlib_test.km +22 -0
- package/examples/test_example.km +69 -0
- package/examples/testing/README.md +88 -0
- package/examples/testing/http_client.km +18 -0
- package/examples/testing/math.km +48 -0
- package/examples/testing/math.test.km +93 -0
- package/examples/testing/user_service.km +29 -0
- package/examples/testing/user_service.test.km +72 -0
- package/examples/use-config.mjs +141 -0
- package/examples/use_config.km +13 -0
- package/install.sh +59 -0
- package/package.json +29 -0
- package/pantry/acorn/index.km +1 -0
- package/pantry/is_number/index.km +1 -0
- package/pantry/is_odd/index.km +2 -0
- package/project.static +6 -0
- package/src/cli.js +1245 -0
- package/src/generator.js +1241 -0
- package/src/index.js +141 -0
- package/src/js2km.js +568 -0
- package/src/lexer.js +822 -0
- package/src/linter.js +810 -0
- package/src/package-manager.js +307 -0
- package/src/parser.js +1876 -0
- package/src/static-parser.js +500 -0
- package/src/typechecker.js +950 -0
- package/stdlib/array.km +0 -0
- package/stdlib/bitwise.km +38 -0
- package/stdlib/console.km +49 -0
- package/stdlib/date.km +97 -0
- package/stdlib/function.km +44 -0
- package/stdlib/http.km +197 -0
- package/stdlib/http.md +333 -0
- package/stdlib/index.km +26 -0
- package/stdlib/json.km +17 -0
- package/stdlib/logger.js +114 -0
- package/stdlib/logger.km +104 -0
- package/stdlib/math.km +120 -0
- package/stdlib/object.km +41 -0
- package/stdlib/promise.km +33 -0
- package/stdlib/string.km +93 -0
- package/stdlib/testing.md +265 -0
- package/test/test.js +599 -0
package/src/generator.js
ADDED
|
@@ -0,0 +1,1241 @@
|
|
|
1
|
+
// KimchiLang Code Generator - Converts AST to JavaScript
|
|
2
|
+
|
|
3
|
+
import { NodeType } from './parser.js';
|
|
4
|
+
|
|
5
|
+
// Helper to check if a node or its children contain shell blocks
|
|
6
|
+
function containsShellBlock(node) {
|
|
7
|
+
if (!node) return false;
|
|
8
|
+
if (node.type === NodeType.ShellBlock) return true;
|
|
9
|
+
|
|
10
|
+
// Check all properties that could contain child nodes
|
|
11
|
+
for (const key of Object.keys(node)) {
|
|
12
|
+
const value = node[key];
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
for (const item of value) {
|
|
15
|
+
if (item && typeof item === 'object' && containsShellBlock(item)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} else if (value && typeof value === 'object' && value.type) {
|
|
20
|
+
if (containsShellBlock(value)) return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class CodeGenerator {
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.indent = 0;
|
|
29
|
+
this.indentStr = options.indentStr || ' ';
|
|
30
|
+
this.output = '';
|
|
31
|
+
this.options = options;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
generate(ast) {
|
|
35
|
+
this.output = '';
|
|
36
|
+
this.visitProgram(ast);
|
|
37
|
+
return this.output;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
emit(code) {
|
|
41
|
+
this.output += code;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
emitLine(code = '') {
|
|
45
|
+
this.output += this.getIndent() + code + '\n';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getIndent() {
|
|
49
|
+
return this.indentStr.repeat(this.indent);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pushIndent() {
|
|
53
|
+
this.indent++;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
popIndent() {
|
|
57
|
+
this.indent--;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
emitRuntimeExtensions() {
|
|
61
|
+
// Extend Array prototype with stdlib methods
|
|
62
|
+
this.emitLine('// KimchiLang stdlib extensions');
|
|
63
|
+
this.emitLine('if (!Array.prototype._kmExtended) {');
|
|
64
|
+
this.pushIndent();
|
|
65
|
+
this.emitLine('Array.prototype._kmExtended = true;');
|
|
66
|
+
this.emitLine('Array.prototype.first = function() { return this[0]; };');
|
|
67
|
+
this.emitLine('Array.prototype.last = function() { return this[this.length - 1]; };');
|
|
68
|
+
this.emitLine('Array.prototype.isEmpty = function() { return this.length === 0; };');
|
|
69
|
+
this.emitLine('Array.prototype.sum = function() { return this.reduce((a, b) => a + b, 0); };');
|
|
70
|
+
this.emitLine('Array.prototype.product = function() { return this.reduce((a, b) => a * b, 1); };');
|
|
71
|
+
this.emitLine('Array.prototype.average = function() { return this.reduce((a, b) => a + b, 0) / this.length; };');
|
|
72
|
+
this.emitLine('Array.prototype.max = function() { return Math.max(...this); };');
|
|
73
|
+
this.emitLine('Array.prototype.min = function() { return Math.min(...this); };');
|
|
74
|
+
this.emitLine('Array.prototype.take = function(n) { return this.slice(0, n); };');
|
|
75
|
+
this.emitLine('Array.prototype.drop = function(n) { return this.slice(n); };');
|
|
76
|
+
this.emitLine('Array.prototype.flatten = function() { return this.flat(Infinity); };');
|
|
77
|
+
this.emitLine('Array.prototype.unique = function() { return [...new Set(this)]; };');
|
|
78
|
+
this.popIndent();
|
|
79
|
+
this.emitLine('}');
|
|
80
|
+
|
|
81
|
+
// Extend String prototype with stdlib methods
|
|
82
|
+
this.emitLine('if (!String.prototype._kmExtended) {');
|
|
83
|
+
this.pushIndent();
|
|
84
|
+
this.emitLine('String.prototype._kmExtended = true;');
|
|
85
|
+
this.emitLine('String.prototype.isEmpty = function() { return this.length === 0; };');
|
|
86
|
+
this.emitLine('String.prototype.isBlank = function() { return this.trim().length === 0; };');
|
|
87
|
+
this.emitLine('String.prototype.toChars = function() { return this.split(""); };');
|
|
88
|
+
this.emitLine('String.prototype.toLines = function() { return this.split("\\n"); };');
|
|
89
|
+
this.emitLine('String.prototype.capitalize = function() { return this.length === 0 ? this : this[0].toUpperCase() + this.slice(1); };');
|
|
90
|
+
this.popIndent();
|
|
91
|
+
this.emitLine('}');
|
|
92
|
+
|
|
93
|
+
// Add Object utility functions (can't extend Object.prototype safely)
|
|
94
|
+
this.emitLine('const _obj = {');
|
|
95
|
+
this.pushIndent();
|
|
96
|
+
this.emitLine('keys: (o) => Object.keys(o),');
|
|
97
|
+
this.emitLine('values: (o) => Object.values(o),');
|
|
98
|
+
this.emitLine('entries: (o) => Object.entries(o),');
|
|
99
|
+
this.emitLine('fromEntries: (arr) => Object.fromEntries(arr),');
|
|
100
|
+
this.emitLine('has: (o, k) => Object.hasOwn(o, k),');
|
|
101
|
+
this.emitLine('freeze: (o) => Object.freeze(o),');
|
|
102
|
+
this.emitLine('isEmpty: (o) => Object.keys(o).length === 0,');
|
|
103
|
+
this.emitLine('size: (o) => Object.keys(o).length,');
|
|
104
|
+
this.popIndent();
|
|
105
|
+
this.emitLine('};');
|
|
106
|
+
this.emitLine();
|
|
107
|
+
|
|
108
|
+
// Add error helper function for typed errors
|
|
109
|
+
this.emitLine('function error(message, _id = "Error") {');
|
|
110
|
+
this.pushIndent();
|
|
111
|
+
this.emitLine('const e = new Error(message);');
|
|
112
|
+
this.emitLine('e._id = _id;');
|
|
113
|
+
this.emitLine('return e;');
|
|
114
|
+
this.popIndent();
|
|
115
|
+
this.emitLine('}');
|
|
116
|
+
this.emitLine('error.create = (_id) => {');
|
|
117
|
+
this.pushIndent();
|
|
118
|
+
this.emitLine('const fn = (message) => error(message, _id);');
|
|
119
|
+
this.emitLine('fn._id = _id;');
|
|
120
|
+
this.emitLine('return fn;');
|
|
121
|
+
this.popIndent();
|
|
122
|
+
this.emitLine('};');
|
|
123
|
+
this.emitLine();
|
|
124
|
+
|
|
125
|
+
// Secret wrapper class - masks value when converted to string
|
|
126
|
+
this.emitLine('class _Secret {');
|
|
127
|
+
this.pushIndent();
|
|
128
|
+
this.emitLine('constructor(value) { this._value = value; }');
|
|
129
|
+
this.emitLine('toString() { return "********"; }');
|
|
130
|
+
this.emitLine('valueOf() { return this._value; }');
|
|
131
|
+
this.emitLine('get value() { return this._value; }');
|
|
132
|
+
this.emitLine('[Symbol.toPrimitive](hint) { return hint === "string" ? "********" : this._value; }');
|
|
133
|
+
this.popIndent();
|
|
134
|
+
this.emitLine('}');
|
|
135
|
+
this.emitLine('function _secret(value) { return new _Secret(value); }');
|
|
136
|
+
this.emitLine();
|
|
137
|
+
|
|
138
|
+
// Deep freeze helper for dec declarations
|
|
139
|
+
this.emitLine('function _deepFreeze(obj) {');
|
|
140
|
+
this.pushIndent();
|
|
141
|
+
this.emitLine('if (obj === null || typeof obj !== "object") return obj;');
|
|
142
|
+
this.emitLine('Object.keys(obj).forEach(key => _deepFreeze(obj[key]));');
|
|
143
|
+
this.emitLine('return Object.freeze(obj);');
|
|
144
|
+
this.popIndent();
|
|
145
|
+
this.emitLine('}');
|
|
146
|
+
this.emitLine();
|
|
147
|
+
|
|
148
|
+
// Async-aware pipe helper - awaits each step in the chain
|
|
149
|
+
this.emitLine('async function _pipe(value, ...fns) {');
|
|
150
|
+
this.pushIndent();
|
|
151
|
+
this.emitLine('let result = value;');
|
|
152
|
+
this.emitLine('for (const fn of fns) {');
|
|
153
|
+
this.pushIndent();
|
|
154
|
+
this.emitLine('result = await fn(result);');
|
|
155
|
+
this.popIndent();
|
|
156
|
+
this.emitLine('}');
|
|
157
|
+
this.emitLine('return result;');
|
|
158
|
+
this.popIndent();
|
|
159
|
+
this.emitLine('}');
|
|
160
|
+
this.emitLine();
|
|
161
|
+
|
|
162
|
+
// Async-aware flow helper - creates an async composed function
|
|
163
|
+
this.emitLine('function _flow(...fns) {');
|
|
164
|
+
this.pushIndent();
|
|
165
|
+
this.emitLine('return async (...args) => {');
|
|
166
|
+
this.pushIndent();
|
|
167
|
+
this.emitLine('let result = await fns[0](...args);');
|
|
168
|
+
this.emitLine('for (let i = 1; i < fns.length; i++) {');
|
|
169
|
+
this.pushIndent();
|
|
170
|
+
this.emitLine('result = await fns[i](result);');
|
|
171
|
+
this.popIndent();
|
|
172
|
+
this.emitLine('}');
|
|
173
|
+
this.emitLine('return result;');
|
|
174
|
+
this.popIndent();
|
|
175
|
+
this.emitLine('};');
|
|
176
|
+
this.popIndent();
|
|
177
|
+
this.emitLine('}');
|
|
178
|
+
this.emitLine();
|
|
179
|
+
|
|
180
|
+
// Shell execution helper (async)
|
|
181
|
+
this.emitLine('async function _shell(command, inputs = {}) {');
|
|
182
|
+
this.pushIndent();
|
|
183
|
+
this.emitLine('const { exec } = await import("child_process");');
|
|
184
|
+
this.emitLine('const { promisify } = await import("util");');
|
|
185
|
+
this.emitLine('const execAsync = promisify(exec);');
|
|
186
|
+
this.emitLine('// Interpolate inputs into command');
|
|
187
|
+
this.emitLine('let cmd = command;');
|
|
188
|
+
this.emitLine('for (const [key, value] of Object.entries(inputs)) {');
|
|
189
|
+
this.pushIndent();
|
|
190
|
+
this.emitLine('cmd = cmd.replace(new RegExp("\\\\$" + key + "\\\\b", "g"), String(value));');
|
|
191
|
+
this.popIndent();
|
|
192
|
+
this.emitLine('}');
|
|
193
|
+
this.emitLine('try {');
|
|
194
|
+
this.pushIndent();
|
|
195
|
+
this.emitLine('const { stdout, stderr } = await execAsync(cmd);');
|
|
196
|
+
this.emitLine('return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 };');
|
|
197
|
+
this.popIndent();
|
|
198
|
+
this.emitLine('} catch (error) {');
|
|
199
|
+
this.pushIndent();
|
|
200
|
+
this.emitLine('return { stdout: error.stdout?.trim() || "", stderr: error.stderr?.trim() || error.message, exitCode: error.code || 1 };');
|
|
201
|
+
this.popIndent();
|
|
202
|
+
this.emitLine('}');
|
|
203
|
+
this.popIndent();
|
|
204
|
+
this.emitLine('}');
|
|
205
|
+
this.emitLine();
|
|
206
|
+
|
|
207
|
+
// Testing framework runtime
|
|
208
|
+
this.emitLine('// Testing framework');
|
|
209
|
+
this.emitLine('const _tests = [];');
|
|
210
|
+
this.emitLine('let _currentDescribe = null;');
|
|
211
|
+
this.emitLine('function _describe(name, fn) {');
|
|
212
|
+
this.pushIndent();
|
|
213
|
+
this.emitLine('const prev = _currentDescribe;');
|
|
214
|
+
this.emitLine('_currentDescribe = { name, tests: [], parent: prev };');
|
|
215
|
+
this.emitLine('fn();');
|
|
216
|
+
this.emitLine('if (prev) { prev.tests.push(_currentDescribe); }');
|
|
217
|
+
this.emitLine('else { _tests.push(_currentDescribe); }');
|
|
218
|
+
this.emitLine('_currentDescribe = prev;');
|
|
219
|
+
this.popIndent();
|
|
220
|
+
this.emitLine('}');
|
|
221
|
+
this.emitLine('function _test(name, fn) {');
|
|
222
|
+
this.pushIndent();
|
|
223
|
+
this.emitLine('const test = { name, fn, describe: _currentDescribe };');
|
|
224
|
+
this.emitLine('if (_currentDescribe) { _currentDescribe.tests.push(test); }');
|
|
225
|
+
this.emitLine('else { _tests.push(test); }');
|
|
226
|
+
this.popIndent();
|
|
227
|
+
this.emitLine('}');
|
|
228
|
+
this.emitLine('function _expect(actual) {');
|
|
229
|
+
this.pushIndent();
|
|
230
|
+
this.emitLine('return {');
|
|
231
|
+
this.pushIndent();
|
|
232
|
+
this.emitLine('toBe(expected) { if (actual !== expected) throw new Error(`Expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`); },');
|
|
233
|
+
this.emitLine('toEqual(expected) { if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error(`Expected ${JSON.stringify(expected)} to equal ${JSON.stringify(actual)}`); },');
|
|
234
|
+
this.emitLine('toContain(item) { if (!actual.includes(item)) throw new Error(`Expected ${JSON.stringify(actual)} to contain ${JSON.stringify(item)}`); },');
|
|
235
|
+
this.emitLine('toBeNull() { if (actual !== null) throw new Error(`Expected null but got ${JSON.stringify(actual)}`); },');
|
|
236
|
+
this.emitLine('toBeTruthy() { if (!actual) throw new Error(`Expected truthy but got ${JSON.stringify(actual)}`); },');
|
|
237
|
+
this.emitLine('toBeFalsy() { if (actual) throw new Error(`Expected falsy but got ${JSON.stringify(actual)}`); },');
|
|
238
|
+
this.emitLine('toBeGreaterThan(n) { if (actual <= n) throw new Error(`Expected ${actual} > ${n}`); },');
|
|
239
|
+
this.emitLine('toBeLessThan(n) { if (actual >= n) throw new Error(`Expected ${actual} < ${n}`); },');
|
|
240
|
+
this.emitLine('toHaveLength(n) { if (actual.length !== n) throw new Error(`Expected length ${n} but got ${actual.length}`); },');
|
|
241
|
+
this.emitLine('toMatch(pattern) { if (!pattern.test(actual)) throw new Error(`Expected ${JSON.stringify(actual)} to match ${pattern}`); },');
|
|
242
|
+
this.emitLine('toThrow(msg) { try { actual(); throw new Error("Expected to throw"); } catch(e) { if (msg && !e.message.includes(msg)) throw new Error(`Expected error containing "${msg}" but got "${e.message}"`); } },');
|
|
243
|
+
this.popIndent();
|
|
244
|
+
this.emitLine('};');
|
|
245
|
+
this.popIndent();
|
|
246
|
+
this.emitLine('}');
|
|
247
|
+
this.emitLine('function _assert(condition, message) { if (!condition) throw new Error(message); }');
|
|
248
|
+
this.emitLine('async function _runTests() {');
|
|
249
|
+
this.pushIndent();
|
|
250
|
+
this.emitLine('let passed = 0, failed = 0;');
|
|
251
|
+
this.emitLine('async function runItem(item, indent = "") {');
|
|
252
|
+
this.pushIndent();
|
|
253
|
+
this.emitLine('if (item.fn) {');
|
|
254
|
+
this.pushIndent();
|
|
255
|
+
this.emitLine('try { await item.fn(); console.log(indent + "✓ " + item.name); passed++; }');
|
|
256
|
+
this.emitLine('catch (e) { console.log(indent + "✗ " + item.name); console.log(indent + " " + e.message); failed++; }');
|
|
257
|
+
this.popIndent();
|
|
258
|
+
this.emitLine('} else {');
|
|
259
|
+
this.pushIndent();
|
|
260
|
+
this.emitLine('console.log(indent + item.name);');
|
|
261
|
+
this.emitLine('for (const t of item.tests) await runItem(t, indent + " ");');
|
|
262
|
+
this.popIndent();
|
|
263
|
+
this.emitLine('}');
|
|
264
|
+
this.popIndent();
|
|
265
|
+
this.emitLine('}');
|
|
266
|
+
this.emitLine('for (const item of _tests) await runItem(item);');
|
|
267
|
+
this.emitLine('console.log(`\\n${passed + failed} tests, ${passed} passed, ${failed} failed`);');
|
|
268
|
+
this.emitLine('return { passed, failed };');
|
|
269
|
+
this.popIndent();
|
|
270
|
+
this.emitLine('}');
|
|
271
|
+
this.emitLine();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
visit(node) {
|
|
275
|
+
if (!node) return '';
|
|
276
|
+
|
|
277
|
+
const methodName = `visit${node.type}`;
|
|
278
|
+
if (this[methodName]) {
|
|
279
|
+
return this[methodName](node);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
throw new Error(`Unknown node type: ${node.type}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
visitProgram(node) {
|
|
286
|
+
// Separate deps, args, env, dec declarations, and other statements
|
|
287
|
+
const depStatements = node.body.filter(stmt => stmt.type === NodeType.DepStatement);
|
|
288
|
+
const argDeclarations = node.body.filter(stmt => stmt.type === NodeType.ArgDeclaration);
|
|
289
|
+
const envDeclarations = node.body.filter(stmt => stmt.type === NodeType.EnvDeclaration);
|
|
290
|
+
const decDeclarations = node.body.filter(stmt => stmt.type === NodeType.DecDeclaration);
|
|
291
|
+
const otherStatements = node.body.filter(stmt =>
|
|
292
|
+
stmt.type !== NodeType.DepStatement && stmt.type !== NodeType.ArgDeclaration && stmt.type !== NodeType.EnvDeclaration
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// Build list of dep paths for distinguishing deps from args in _opts
|
|
296
|
+
const depPaths = depStatements.map(dep => dep.path);
|
|
297
|
+
const argNames = argDeclarations.map(arg => arg.name);
|
|
298
|
+
|
|
299
|
+
// First, emit the raw imports for all dependencies
|
|
300
|
+
// Static files (.static) are imported directly, regular modules use factory pattern
|
|
301
|
+
// External modules (@ prefix) are resolved from .km_modules directory
|
|
302
|
+
for (const dep of depStatements) {
|
|
303
|
+
const moduleVar = `_dep_${dep.alias}`;
|
|
304
|
+
if (dep.isStatic) {
|
|
305
|
+
// Static files export all declarations directly, no factory function
|
|
306
|
+
// Use absolute path if basePath is provided (for run command)
|
|
307
|
+
const relativePath = './' + dep.pathParts.join('/') + '.static.js';
|
|
308
|
+
// For absolute paths, go up from basePath to project root, then use full path
|
|
309
|
+
const filePath = this.options.basePath
|
|
310
|
+
? `file://${this.options.basePath}/../${dep.pathParts.join('/')}.static.js`
|
|
311
|
+
: relativePath;
|
|
312
|
+
this.emitLine(`import * as ${moduleVar} from '${filePath}';`);
|
|
313
|
+
} else if (dep.isExternal) {
|
|
314
|
+
// External module from .km_modules: @foo.bar -> .km_modules/foo/bar.km
|
|
315
|
+
const filePath = './.km_modules/' + dep.pathParts.join('/') + '.km';
|
|
316
|
+
this.emitLine(`import ${moduleVar} from '${filePath}';`);
|
|
317
|
+
} else {
|
|
318
|
+
const filePath = './' + dep.pathParts.join('/') + '.km';
|
|
319
|
+
this.emitLine(`import ${moduleVar} from '${filePath}';`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (depStatements.length > 0) {
|
|
323
|
+
this.emitLine();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Emit stdlib prototype extensions
|
|
327
|
+
this.emitRuntimeExtensions();
|
|
328
|
+
|
|
329
|
+
// Export default factory function
|
|
330
|
+
this.emitLine('export default function(_opts = {}) {');
|
|
331
|
+
this.pushIndent();
|
|
332
|
+
|
|
333
|
+
// Validate required args
|
|
334
|
+
for (const arg of argDeclarations) {
|
|
335
|
+
if (arg.required) {
|
|
336
|
+
this.emitLine(`if (_opts["${arg.name}"] === undefined) throw new Error("Required argument '${arg.name}' not provided");`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (argDeclarations.some(a => a.required)) {
|
|
340
|
+
this.emitLine();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Extract args from _opts with defaults
|
|
344
|
+
for (const arg of argDeclarations) {
|
|
345
|
+
const secretWrap = arg.secret ? '_secret(' : '';
|
|
346
|
+
const secretClose = arg.secret ? ')' : '';
|
|
347
|
+
if (arg.defaultValue) {
|
|
348
|
+
const defaultCode = this.visitExpression(arg.defaultValue);
|
|
349
|
+
this.emitLine(`const ${arg.name} = ${secretWrap}_opts["${arg.name}"] !== undefined ? _opts["${arg.name}"] : ${defaultCode}${secretClose};`);
|
|
350
|
+
} else {
|
|
351
|
+
this.emitLine(`const ${arg.name} = ${secretWrap}_opts["${arg.name}"]${secretClose};`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (argDeclarations.length > 0) {
|
|
355
|
+
this.emitLine();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Extract env vars from process.env
|
|
359
|
+
for (const env of envDeclarations) {
|
|
360
|
+
if (env.required) {
|
|
361
|
+
this.emitLine(`if (process.env["${env.name}"] === undefined) throw new Error("Required environment variable '${env.name}' not set");`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (envDeclarations.some(e => e.required)) {
|
|
365
|
+
this.emitLine();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
for (const env of envDeclarations) {
|
|
369
|
+
const secretWrap = env.secret ? '_secret(' : '';
|
|
370
|
+
const secretClose = env.secret ? ')' : '';
|
|
371
|
+
if (env.defaultValue) {
|
|
372
|
+
const defaultCode = this.visitExpression(env.defaultValue);
|
|
373
|
+
this.emitLine(`const ${env.name} = ${secretWrap}process.env["${env.name}"] !== undefined ? process.env["${env.name}"] : ${defaultCode}${secretClose};`);
|
|
374
|
+
} else {
|
|
375
|
+
this.emitLine(`const ${env.name} = ${secretWrap}process.env["${env.name}"]${secretClose};`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (envDeclarations.length > 0) {
|
|
379
|
+
this.emitLine();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Resolve each dependency, checking _opts first (for injection)
|
|
383
|
+
for (const dep of depStatements) {
|
|
384
|
+
const moduleVar = `_dep_${dep.alias}`;
|
|
385
|
+
if (dep.isStatic) {
|
|
386
|
+
// Static files are imported directly, no factory function, no overrides
|
|
387
|
+
this.emitLine(`const ${dep.alias} = ${moduleVar};`);
|
|
388
|
+
} else if (dep.overrides) {
|
|
389
|
+
const overridesCode = this.visitExpression(dep.overrides);
|
|
390
|
+
this.emitLine(`const ${dep.alias} = _opts["${dep.path}"] || ${moduleVar}(${overridesCode});`);
|
|
391
|
+
} else {
|
|
392
|
+
this.emitLine(`const ${dep.alias} = _opts["${dep.path}"] || ${moduleVar}();`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (depStatements.length > 0) {
|
|
396
|
+
this.emitLine();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Emit the rest of the module body (top-level statements)
|
|
400
|
+
for (const stmt of otherStatements) {
|
|
401
|
+
this.visitStatement(stmt, true);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Return an object with all exported values (collect exports)
|
|
405
|
+
const exports = this.collectExports(otherStatements);
|
|
406
|
+
if (exports.length > 0) {
|
|
407
|
+
this.emitLine();
|
|
408
|
+
this.emitLine(`return { ${exports.join(', ')} };`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
this.popIndent();
|
|
412
|
+
this.emitLine('}');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
collectExports(statements) {
|
|
416
|
+
const exports = [];
|
|
417
|
+
for (const stmt of statements) {
|
|
418
|
+
// Only export items marked with 'expose'
|
|
419
|
+
if (!stmt.exposed) continue;
|
|
420
|
+
|
|
421
|
+
if (stmt.type === NodeType.FunctionDeclaration) {
|
|
422
|
+
exports.push(stmt.name);
|
|
423
|
+
} else if (stmt.type === NodeType.DecDeclaration) {
|
|
424
|
+
exports.push(stmt.name);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return exports;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
visitStatement(node, isTopLevel = false) {
|
|
431
|
+
switch (node.type) {
|
|
432
|
+
case NodeType.DecDeclaration:
|
|
433
|
+
this.visitDecDeclaration(node);
|
|
434
|
+
break;
|
|
435
|
+
case NodeType.FunctionDeclaration:
|
|
436
|
+
this.visitFunctionDeclaration(node);
|
|
437
|
+
break;
|
|
438
|
+
case NodeType.IfStatement:
|
|
439
|
+
this.visitIfStatement(node);
|
|
440
|
+
break;
|
|
441
|
+
case NodeType.WhileStatement:
|
|
442
|
+
this.visitWhileStatement(node);
|
|
443
|
+
break;
|
|
444
|
+
case NodeType.ForInStatement:
|
|
445
|
+
this.visitForInStatement(node);
|
|
446
|
+
break;
|
|
447
|
+
case NodeType.ReturnStatement:
|
|
448
|
+
this.visitReturnStatement(node);
|
|
449
|
+
break;
|
|
450
|
+
case NodeType.BreakStatement:
|
|
451
|
+
this.emitLine('break;');
|
|
452
|
+
break;
|
|
453
|
+
case NodeType.ContinueStatement:
|
|
454
|
+
this.emitLine('continue;');
|
|
455
|
+
break;
|
|
456
|
+
case NodeType.TryStatement:
|
|
457
|
+
this.visitTryStatement(node);
|
|
458
|
+
break;
|
|
459
|
+
case NodeType.ThrowStatement:
|
|
460
|
+
this.visitThrowStatement(node);
|
|
461
|
+
break;
|
|
462
|
+
case NodeType.PatternMatch:
|
|
463
|
+
this.visitPatternMatch(node, isTopLevel);
|
|
464
|
+
break;
|
|
465
|
+
case NodeType.PrintStatement:
|
|
466
|
+
this.visitPrintStatement(node);
|
|
467
|
+
break;
|
|
468
|
+
case NodeType.DepStatement:
|
|
469
|
+
this.visitDepStatement(node);
|
|
470
|
+
break;
|
|
471
|
+
case NodeType.EnumDeclaration:
|
|
472
|
+
this.visitEnumDeclaration(node);
|
|
473
|
+
break;
|
|
474
|
+
case NodeType.ArgDeclaration:
|
|
475
|
+
// Args are handled in visitProgram, not individually
|
|
476
|
+
break;
|
|
477
|
+
case NodeType.EnvDeclaration:
|
|
478
|
+
// Env vars are handled in visitProgram, not individually
|
|
479
|
+
break;
|
|
480
|
+
case NodeType.JSBlock:
|
|
481
|
+
this.visitJSBlock(node);
|
|
482
|
+
break;
|
|
483
|
+
case NodeType.ShellBlock:
|
|
484
|
+
this.visitShellBlock(node);
|
|
485
|
+
break;
|
|
486
|
+
case NodeType.TestBlock:
|
|
487
|
+
this.visitTestBlock(node);
|
|
488
|
+
break;
|
|
489
|
+
case NodeType.DescribeBlock:
|
|
490
|
+
this.visitDescribeBlock(node);
|
|
491
|
+
break;
|
|
492
|
+
case NodeType.ExpectStatement:
|
|
493
|
+
this.visitExpectStatement(node);
|
|
494
|
+
break;
|
|
495
|
+
case NodeType.AssertStatement:
|
|
496
|
+
this.visitAssertStatement(node);
|
|
497
|
+
break;
|
|
498
|
+
case NodeType.ExpressionStatement:
|
|
499
|
+
this.emitLine(this.visitExpression(node.expression) + ';');
|
|
500
|
+
break;
|
|
501
|
+
case NodeType.BlockStatement:
|
|
502
|
+
this.visitBlockStatement(node);
|
|
503
|
+
break;
|
|
504
|
+
default:
|
|
505
|
+
throw new Error(`Unknown statement type: ${node.type}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
visitDecDeclaration(node) {
|
|
510
|
+
// dec creates deeply immutable variables using Object.freeze recursively
|
|
511
|
+
let init = this.visitExpression(node.init);
|
|
512
|
+
|
|
513
|
+
// Wrap with _secret() if marked as secret
|
|
514
|
+
if (node.secret) {
|
|
515
|
+
init = `_secret(${init})`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (node.destructuring) {
|
|
519
|
+
// Handle destructuring patterns
|
|
520
|
+
if (node.pattern.type === NodeType.ObjectPattern) {
|
|
521
|
+
// Object destructuring: const { a, b } = _deepFreeze(obj);
|
|
522
|
+
const props = node.pattern.properties.map(p => {
|
|
523
|
+
if (p.key === p.value) {
|
|
524
|
+
return p.key;
|
|
525
|
+
}
|
|
526
|
+
return `${p.key}: ${p.value}`;
|
|
527
|
+
}).join(', ');
|
|
528
|
+
this.emitLine(`const { ${props} } = _deepFreeze(${init});`);
|
|
529
|
+
} else if (node.pattern.type === NodeType.ArrayPattern) {
|
|
530
|
+
// Array destructuring: const [x, y] = _deepFreeze(arr);
|
|
531
|
+
const elems = node.pattern.elements.map(e => {
|
|
532
|
+
if (e === null) return '';
|
|
533
|
+
return e.name;
|
|
534
|
+
}).join(', ');
|
|
535
|
+
this.emitLine(`const [${elems}] = _deepFreeze(${init});`);
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
this.emitLine(`const ${node.name} = _deepFreeze(${init});`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
visitFunctionDeclaration(node) {
|
|
543
|
+
// Auto-make functions async if they contain shell blocks
|
|
544
|
+
const hasShellBlock = containsShellBlock(node.body);
|
|
545
|
+
const async = (node.async || hasShellBlock) ? 'async ' : '';
|
|
546
|
+
const params = this.generateParams(node.params);
|
|
547
|
+
|
|
548
|
+
if (node.memoized) {
|
|
549
|
+
// Generate memoized function
|
|
550
|
+
this.emitLine(`const ${node.name} = (() => {`);
|
|
551
|
+
this.pushIndent();
|
|
552
|
+
this.emitLine('const _cache = new Map();');
|
|
553
|
+
this.emitLine(`return ${async}function(${params}) {`);
|
|
554
|
+
this.pushIndent();
|
|
555
|
+
this.emitLine('const _key = JSON.stringify([...arguments]);');
|
|
556
|
+
this.emitLine('if (_cache.has(_key)) return _cache.get(_key);');
|
|
557
|
+
// Generate function body, capturing the result
|
|
558
|
+
// For async functions, the inner IIFE must also be async and awaited
|
|
559
|
+
if (node.async) {
|
|
560
|
+
this.emitLine('const _result = await (async () => {');
|
|
561
|
+
} else {
|
|
562
|
+
this.emitLine('const _result = (() => {');
|
|
563
|
+
}
|
|
564
|
+
this.pushIndent();
|
|
565
|
+
for (const stmt of node.body.body) {
|
|
566
|
+
this.visitStatement(stmt);
|
|
567
|
+
}
|
|
568
|
+
this.popIndent();
|
|
569
|
+
this.emitLine('})();');
|
|
570
|
+
this.emitLine('_cache.set(_key, _result);');
|
|
571
|
+
this.emitLine('return _result;');
|
|
572
|
+
this.popIndent();
|
|
573
|
+
this.emitLine('};');
|
|
574
|
+
this.popIndent();
|
|
575
|
+
this.emitLine('})();');
|
|
576
|
+
} else {
|
|
577
|
+
this.emitLine(`${async}function ${node.name}(${params}) {`);
|
|
578
|
+
this.pushIndent();
|
|
579
|
+
for (const stmt of node.body.body) {
|
|
580
|
+
this.visitStatement(stmt);
|
|
581
|
+
}
|
|
582
|
+
this.popIndent();
|
|
583
|
+
this.emitLine('}');
|
|
584
|
+
}
|
|
585
|
+
this.emitLine();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
visitEnumDeclaration(node) {
|
|
589
|
+
// Generate enum as a frozen object with auto-incrementing values
|
|
590
|
+
this.emitLine(`const ${node.name} = Object.freeze({`);
|
|
591
|
+
this.pushIndent();
|
|
592
|
+
|
|
593
|
+
let autoValue = 0;
|
|
594
|
+
for (let i = 0; i < node.members.length; i++) {
|
|
595
|
+
const member = node.members[i];
|
|
596
|
+
let value;
|
|
597
|
+
|
|
598
|
+
if (member.value !== null) {
|
|
599
|
+
value = this.visitExpression(member.value);
|
|
600
|
+
// If it's a number literal, update autoValue for next member
|
|
601
|
+
if (member.value.type === NodeType.Literal && typeof member.value.value === 'number') {
|
|
602
|
+
autoValue = member.value.value + 1;
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
value = autoValue;
|
|
606
|
+
autoValue++;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const comma = i < node.members.length - 1 ? ',' : '';
|
|
610
|
+
this.emitLine(`${member.name}: ${value}${comma}`);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
this.popIndent();
|
|
614
|
+
this.emitLine('});');
|
|
615
|
+
this.emitLine();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
visitJSBlock(node) {
|
|
619
|
+
// JS interop block - wraps raw JavaScript in an IIFE with optional inputs
|
|
620
|
+
// Syntax: js { code } -> (() => { code })();
|
|
621
|
+
// js(a, b) { code } -> ((a, b) => { code })(a, b);
|
|
622
|
+
|
|
623
|
+
if (node.inputs.length === 0) {
|
|
624
|
+
// No inputs - simple IIFE
|
|
625
|
+
this.emitLine('(() => {');
|
|
626
|
+
this.pushIndent();
|
|
627
|
+
// Emit the raw JS code, preserving formatting
|
|
628
|
+
const lines = node.code.split('\n');
|
|
629
|
+
for (const line of lines) {
|
|
630
|
+
if (line.trim()) {
|
|
631
|
+
this.emitLine(line.trim());
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
this.popIndent();
|
|
635
|
+
this.emitLine('})();');
|
|
636
|
+
} else {
|
|
637
|
+
// With inputs - IIFE that receives kimchi variables
|
|
638
|
+
const params = node.inputs.join(', ');
|
|
639
|
+
this.emitLine(`((${params}) => {`);
|
|
640
|
+
this.pushIndent();
|
|
641
|
+
const lines = node.code.split('\n');
|
|
642
|
+
for (const line of lines) {
|
|
643
|
+
if (line.trim()) {
|
|
644
|
+
this.emitLine(line.trim());
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
this.popIndent();
|
|
648
|
+
this.emitLine(`})(${params});`);
|
|
649
|
+
}
|
|
650
|
+
this.emitLine();
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
visitJSBlockExpression(node) {
|
|
654
|
+
// JS block as expression - returns an IIFE that can be assigned
|
|
655
|
+
// Syntax: dec result = js(a, b) { return a + b; }
|
|
656
|
+
// Generates: ((a, b) => { return a + b; })(a, b)
|
|
657
|
+
|
|
658
|
+
const lines = node.code.split('\n').filter(l => l.trim()).map(l => l.trim()).join(' ');
|
|
659
|
+
|
|
660
|
+
if (node.inputs.length === 0) {
|
|
661
|
+
return `(() => { ${lines} })()`;
|
|
662
|
+
} else {
|
|
663
|
+
const params = node.inputs.join(', ');
|
|
664
|
+
return `((${params}) => { ${lines} })(${params})`;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
visitShellBlock(node) {
|
|
669
|
+
// Shell interop block - executes shell command asynchronously
|
|
670
|
+
// Syntax: shell { command } -> await _shell("command");
|
|
671
|
+
// shell(a, b) { command } -> await _shell("command", { a, b });
|
|
672
|
+
|
|
673
|
+
const command = JSON.stringify(node.command);
|
|
674
|
+
|
|
675
|
+
if (node.inputs.length === 0) {
|
|
676
|
+
this.emitLine(`await _shell(${command});`);
|
|
677
|
+
} else {
|
|
678
|
+
const inputsObj = `{ ${node.inputs.join(', ')} }`;
|
|
679
|
+
this.emitLine(`await _shell(${command}, ${inputsObj});`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
visitShellBlockExpression(node) {
|
|
684
|
+
// Shell block as expression - returns the shell result
|
|
685
|
+
// Syntax: dec result = shell { ls -la }
|
|
686
|
+
// Generates: await _shell("ls -la")
|
|
687
|
+
|
|
688
|
+
const command = JSON.stringify(node.command);
|
|
689
|
+
|
|
690
|
+
if (node.inputs.length === 0) {
|
|
691
|
+
return `await _shell(${command})`;
|
|
692
|
+
} else {
|
|
693
|
+
const inputsObj = `{ ${node.inputs.join(', ')} }`;
|
|
694
|
+
return `await _shell(${command}, ${inputsObj})`;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
generateParams(params) {
|
|
699
|
+
return params.map(p => {
|
|
700
|
+
if (p.type === 'RestElement') {
|
|
701
|
+
return `...${p.argument}`;
|
|
702
|
+
}
|
|
703
|
+
if (p.defaultValue) {
|
|
704
|
+
return `${p.name} = ${this.visitExpression(p.defaultValue)}`;
|
|
705
|
+
}
|
|
706
|
+
return p.name;
|
|
707
|
+
}).join(', ');
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
visitBlockStatement(node) {
|
|
711
|
+
this.emitLine('{');
|
|
712
|
+
this.pushIndent();
|
|
713
|
+
for (const stmt of node.body) {
|
|
714
|
+
this.visitStatement(stmt);
|
|
715
|
+
}
|
|
716
|
+
this.popIndent();
|
|
717
|
+
this.emitLine('}');
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
visitIfStatement(node, isElseIf = false) {
|
|
721
|
+
const prefix = isElseIf ? '' : this.getIndent();
|
|
722
|
+
this.emit(prefix + `if (${this.visitExpression(node.test)}) {\n`);
|
|
723
|
+
this.pushIndent();
|
|
724
|
+
for (const stmt of node.consequent.body) {
|
|
725
|
+
this.visitStatement(stmt);
|
|
726
|
+
}
|
|
727
|
+
this.popIndent();
|
|
728
|
+
|
|
729
|
+
if (node.alternate) {
|
|
730
|
+
if (node.alternate.type === NodeType.IfStatement) {
|
|
731
|
+
this.emit(this.getIndent() + '} else ');
|
|
732
|
+
this.visitIfStatement(node.alternate, true);
|
|
733
|
+
} else {
|
|
734
|
+
this.emitLine('} else {');
|
|
735
|
+
this.pushIndent();
|
|
736
|
+
for (const stmt of node.alternate.body) {
|
|
737
|
+
this.visitStatement(stmt);
|
|
738
|
+
}
|
|
739
|
+
this.popIndent();
|
|
740
|
+
this.emitLine('}');
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
this.emitLine('}');
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
visitWhileStatement(node) {
|
|
748
|
+
this.emitLine(`while (${this.visitExpression(node.test)}) {`);
|
|
749
|
+
this.pushIndent();
|
|
750
|
+
for (const stmt of node.body.body) {
|
|
751
|
+
this.visitStatement(stmt);
|
|
752
|
+
}
|
|
753
|
+
this.popIndent();
|
|
754
|
+
this.emitLine('}');
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
visitForInStatement(node) {
|
|
758
|
+
const iterable = this.visitExpression(node.iterable);
|
|
759
|
+
this.emitLine(`for (const ${node.variable} of ${iterable}) {`);
|
|
760
|
+
this.pushIndent();
|
|
761
|
+
for (const stmt of node.body.body) {
|
|
762
|
+
this.visitStatement(stmt);
|
|
763
|
+
}
|
|
764
|
+
this.popIndent();
|
|
765
|
+
this.emitLine('}');
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
visitReturnStatement(node) {
|
|
769
|
+
if (node.argument) {
|
|
770
|
+
this.emitLine(`return ${this.visitExpression(node.argument)};`);
|
|
771
|
+
} else {
|
|
772
|
+
this.emitLine('return;');
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
visitTryStatement(node) {
|
|
777
|
+
this.emitLine('try {');
|
|
778
|
+
this.pushIndent();
|
|
779
|
+
for (const stmt of node.block.body) {
|
|
780
|
+
this.visitStatement(stmt);
|
|
781
|
+
}
|
|
782
|
+
this.popIndent();
|
|
783
|
+
|
|
784
|
+
if (node.handler) {
|
|
785
|
+
const param = node.handler.param ? `(${node.handler.param})` : '';
|
|
786
|
+
this.emitLine(`} catch ${param} {`);
|
|
787
|
+
this.pushIndent();
|
|
788
|
+
for (const stmt of node.handler.body.body) {
|
|
789
|
+
this.visitStatement(stmt);
|
|
790
|
+
}
|
|
791
|
+
this.popIndent();
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (node.finalizer) {
|
|
795
|
+
this.emitLine('} finally {');
|
|
796
|
+
this.pushIndent();
|
|
797
|
+
for (const stmt of node.finalizer.body) {
|
|
798
|
+
this.visitStatement(stmt);
|
|
799
|
+
}
|
|
800
|
+
this.popIndent();
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
this.emitLine('}');
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
visitThrowStatement(node) {
|
|
807
|
+
this.emitLine(`throw ${this.visitExpression(node.argument)};`);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
visitPatternMatch(node, isTopLevel = false) {
|
|
811
|
+
// Standalone pattern matching: |condition| => code
|
|
812
|
+
// Regex pattern matching: subject ~ /regex/ => code
|
|
813
|
+
// At top level: use if/else if chain (no return)
|
|
814
|
+
// Inside function: each case returns from the function when matched
|
|
815
|
+
|
|
816
|
+
// For regex pattern matching with subject, evaluate subject once
|
|
817
|
+
let subjectVar = null;
|
|
818
|
+
if (node.subject && node.isRegex) {
|
|
819
|
+
subjectVar = '_subject';
|
|
820
|
+
const subjectExpr = this.visitExpression(node.subject);
|
|
821
|
+
this.emitLine(`const ${subjectVar} = ${subjectExpr};`);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
for (let i = 0; i < node.cases.length; i++) {
|
|
825
|
+
const matchCase = node.cases[i];
|
|
826
|
+
let condition;
|
|
827
|
+
|
|
828
|
+
if (matchCase.isRegex || matchCase.test.type === NodeType.RegexLiteral) {
|
|
829
|
+
// Regex pattern: test against subject
|
|
830
|
+
// Store match result in $match for access in the body
|
|
831
|
+
const regex = this.visitExpression(matchCase.test);
|
|
832
|
+
const target = subjectVar || '_subject';
|
|
833
|
+
condition = `($match = ${regex}.exec(${target}))`;
|
|
834
|
+
} else {
|
|
835
|
+
condition = this.visitExpression(matchCase.test);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Use else if for subsequent cases
|
|
839
|
+
const prefix = i === 0 ? 'if' : '} else if';
|
|
840
|
+
if (i > 0) {
|
|
841
|
+
this.popIndent();
|
|
842
|
+
}
|
|
843
|
+
this.emitLine(`${prefix} (${condition}) {`);
|
|
844
|
+
this.pushIndent();
|
|
845
|
+
|
|
846
|
+
if (matchCase.consequent.type === NodeType.BlockStatement) {
|
|
847
|
+
for (const stmt of matchCase.consequent.body) {
|
|
848
|
+
this.visitStatement(stmt);
|
|
849
|
+
}
|
|
850
|
+
} else {
|
|
851
|
+
this.visitStatement(matchCase.consequent);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Only add return inside functions, not at top level
|
|
855
|
+
if (!isTopLevel) {
|
|
856
|
+
this.emitLine('return;');
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Close the final if block
|
|
861
|
+
if (node.cases.length > 0) {
|
|
862
|
+
this.popIndent();
|
|
863
|
+
this.emitLine('}');
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
visitPrintStatement(node) {
|
|
868
|
+
this.emitLine(`console.log(${this.visitExpression(node.argument)});`);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
visitDepStatement(node) {
|
|
872
|
+
// Convert dotted path to file path: project.salesforce.client -> ./project/salesforce/client.km
|
|
873
|
+
const filePath = './' + node.pathParts.join('/') + '.km';
|
|
874
|
+
|
|
875
|
+
// Import the module (which exports a factory function)
|
|
876
|
+
const moduleVar = `_dep_${node.alias}`;
|
|
877
|
+
this.emitLine(`import ${moduleVar} from '${filePath}';`);
|
|
878
|
+
|
|
879
|
+
// Call the factory function with optional overrides to get the actual module
|
|
880
|
+
if (node.overrides) {
|
|
881
|
+
const overridesCode = this.visitExpression(node.overrides);
|
|
882
|
+
this.emitLine(`const ${node.alias} = ${moduleVar}(${overridesCode});`);
|
|
883
|
+
} else {
|
|
884
|
+
this.emitLine(`const ${node.alias} = ${moduleVar}();`);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
visitExpression(node) {
|
|
889
|
+
switch (node.type) {
|
|
890
|
+
case NodeType.Literal:
|
|
891
|
+
return this.visitLiteral(node);
|
|
892
|
+
case NodeType.Identifier:
|
|
893
|
+
return node.name;
|
|
894
|
+
case NodeType.BinaryExpression:
|
|
895
|
+
return this.visitBinaryExpression(node);
|
|
896
|
+
case NodeType.UnaryExpression:
|
|
897
|
+
return this.visitUnaryExpression(node);
|
|
898
|
+
case NodeType.AssignmentExpression:
|
|
899
|
+
return this.visitAssignmentExpression(node);
|
|
900
|
+
case NodeType.CallExpression:
|
|
901
|
+
return this.visitCallExpression(node);
|
|
902
|
+
case NodeType.MemberExpression:
|
|
903
|
+
return this.visitMemberExpression(node);
|
|
904
|
+
case NodeType.ArrayExpression:
|
|
905
|
+
return this.visitArrayExpression(node);
|
|
906
|
+
case NodeType.ObjectExpression:
|
|
907
|
+
return this.visitObjectExpression(node);
|
|
908
|
+
case NodeType.ArrowFunctionExpression:
|
|
909
|
+
return this.visitArrowFunctionExpression(node);
|
|
910
|
+
case NodeType.ConditionalExpression:
|
|
911
|
+
return this.visitConditionalExpression(node);
|
|
912
|
+
case NodeType.AwaitExpression:
|
|
913
|
+
return `await ${this.visitExpression(node.argument)}`;
|
|
914
|
+
case NodeType.SpreadElement:
|
|
915
|
+
return `...${this.visitExpression(node.argument)}`;
|
|
916
|
+
case NodeType.RangeExpression:
|
|
917
|
+
return this.visitRangeExpression(node);
|
|
918
|
+
case NodeType.FlowExpression:
|
|
919
|
+
return this.visitFlowExpression(node);
|
|
920
|
+
case NodeType.PipeExpression:
|
|
921
|
+
return this.visitPipeExpression(node);
|
|
922
|
+
case NodeType.TemplateLiteral:
|
|
923
|
+
return this.visitTemplateLiteral(node);
|
|
924
|
+
case NodeType.JSBlock:
|
|
925
|
+
return this.visitJSBlockExpression(node);
|
|
926
|
+
case NodeType.ShellBlock:
|
|
927
|
+
return this.visitShellBlockExpression(node);
|
|
928
|
+
case NodeType.RegexLiteral:
|
|
929
|
+
return this.visitRegexLiteral(node);
|
|
930
|
+
case NodeType.MatchExpression:
|
|
931
|
+
return this.visitMatchExpression(node);
|
|
932
|
+
default:
|
|
933
|
+
throw new Error(`Unknown expression type: ${node.type}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
visitLiteral(node) {
|
|
938
|
+
// Numbers - use raw value to preserve format (hex, binary, etc)
|
|
939
|
+
if (node.isNumber) {
|
|
940
|
+
return node.raw;
|
|
941
|
+
}
|
|
942
|
+
// Strings
|
|
943
|
+
if (node.isString || typeof node.value === 'string') {
|
|
944
|
+
// Check if it's a template string
|
|
945
|
+
if (node.raw && node.raw.startsWith('`')) {
|
|
946
|
+
return node.raw;
|
|
947
|
+
}
|
|
948
|
+
return JSON.stringify(node.value);
|
|
949
|
+
}
|
|
950
|
+
if (node.value === null) {
|
|
951
|
+
return 'null';
|
|
952
|
+
}
|
|
953
|
+
if (typeof node.value === 'boolean') {
|
|
954
|
+
return node.value ? 'true' : 'false';
|
|
955
|
+
}
|
|
956
|
+
return String(node.raw || node.value);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
visitRegexLiteral(node) {
|
|
960
|
+
return `/${node.pattern}/${node.flags}`;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
visitMatchExpression(node) {
|
|
964
|
+
// Match expression: subject ~ /regex/ or subject ~ /regex/ => { body }
|
|
965
|
+
const subject = this.visitExpression(node.subject);
|
|
966
|
+
const regex = this.visitExpression(node.pattern);
|
|
967
|
+
|
|
968
|
+
if (node.body) {
|
|
969
|
+
// With body: execute body with $match available, return result
|
|
970
|
+
// Wrap in IIFE to create scope for $match
|
|
971
|
+
if (node.body.type === NodeType.BlockStatement) {
|
|
972
|
+
// Block body - generate IIFE with statements
|
|
973
|
+
let bodyCode = '';
|
|
974
|
+
for (const stmt of node.body.body) {
|
|
975
|
+
if (stmt.type === NodeType.ReturnStatement) {
|
|
976
|
+
bodyCode += `return ${this.visitExpression(stmt.argument)};`;
|
|
977
|
+
} else {
|
|
978
|
+
// For other statements, we'd need to handle them
|
|
979
|
+
// For now, assume return statements
|
|
980
|
+
bodyCode += this.visitExpression(stmt.expression) + ';';
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return `(($match) => { ${bodyCode} })(${regex}.exec(${subject}))`;
|
|
984
|
+
} else {
|
|
985
|
+
// Expression body
|
|
986
|
+
const bodyExpr = this.visitExpression(node.body);
|
|
987
|
+
return `(($match) => ${bodyExpr})(${regex}.exec(${subject}))`;
|
|
988
|
+
}
|
|
989
|
+
} else {
|
|
990
|
+
// Without body: return first match (match[0]) or null
|
|
991
|
+
return `(${regex}.exec(${subject}) || [])[0]`;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
visitBinaryExpression(node) {
|
|
996
|
+
const left = this.visitExpression(node.left);
|
|
997
|
+
const right = this.visitExpression(node.right);
|
|
998
|
+
|
|
999
|
+
// Handle 'is' operator - compares ._id properties
|
|
1000
|
+
if (node.operator === 'is') {
|
|
1001
|
+
return `(${left}?._id === ${right}?._id)`;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Handle 'is not' operator - negated ._id comparison
|
|
1005
|
+
if (node.operator === 'is not') {
|
|
1006
|
+
return `(${left}?._id !== ${right}?._id)`;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
return `(${left} ${node.operator} ${right})`;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
visitUnaryExpression(node) {
|
|
1013
|
+
const argument = this.visitExpression(node.argument);
|
|
1014
|
+
if (node.operator === '!' || node.operator === '~' || node.operator === '-') {
|
|
1015
|
+
return `${node.operator}${argument}`;
|
|
1016
|
+
}
|
|
1017
|
+
return `${node.operator} ${argument}`;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
visitAssignmentExpression(node) {
|
|
1021
|
+
const left = this.visitExpression(node.left);
|
|
1022
|
+
const right = this.visitExpression(node.right);
|
|
1023
|
+
return `${left} ${node.operator} ${right}`;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
visitCallExpression(node) {
|
|
1027
|
+
const callee = this.visitExpression(node.callee);
|
|
1028
|
+
const args = node.arguments.map(a => this.visitExpression(a)).join(', ');
|
|
1029
|
+
return `${callee}(${args})`;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
visitMemberExpression(node) {
|
|
1033
|
+
const object = this.visitExpression(node.object);
|
|
1034
|
+
if (node.computed) {
|
|
1035
|
+
const property = this.visitExpression(node.property);
|
|
1036
|
+
// Use optional chaining for safe access
|
|
1037
|
+
return `${object}?.[${property}]`;
|
|
1038
|
+
}
|
|
1039
|
+
// Use optional chaining for safe access
|
|
1040
|
+
return `${object}?.${node.property}`;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
visitArrayExpression(node) {
|
|
1044
|
+
const elements = node.elements.map(e => this.visitExpression(e)).join(', ');
|
|
1045
|
+
return `[${elements}]`;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
visitObjectExpression(node) {
|
|
1049
|
+
if (node.properties.length === 0) {
|
|
1050
|
+
return '{}';
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const props = node.properties.map(p => {
|
|
1054
|
+
// Handle spread element
|
|
1055
|
+
if (p.type === NodeType.SpreadElement) {
|
|
1056
|
+
return `...${this.visitExpression(p.argument)}`;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.key) ? p.key : JSON.stringify(p.key);
|
|
1060
|
+
const value = this.visitExpression(p.value);
|
|
1061
|
+
|
|
1062
|
+
if (p.shorthand && p.value.type === NodeType.Identifier && p.value.name === p.key) {
|
|
1063
|
+
return key;
|
|
1064
|
+
}
|
|
1065
|
+
return `${key}: ${value}`;
|
|
1066
|
+
}).join(', ');
|
|
1067
|
+
|
|
1068
|
+
return `{ ${props} }`;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
visitArrowFunctionExpression(node) {
|
|
1072
|
+
const params = this.generateParams(node.params);
|
|
1073
|
+
const paramsStr = node.params.length === 1 && !node.params[0].defaultValue
|
|
1074
|
+
? node.params[0].name
|
|
1075
|
+
: `(${params})`;
|
|
1076
|
+
|
|
1077
|
+
// Auto-make arrow functions async if they contain shell blocks
|
|
1078
|
+
const hasShellBlock = containsShellBlock(node.body);
|
|
1079
|
+
const asyncPrefix = hasShellBlock ? 'async ' : '';
|
|
1080
|
+
|
|
1081
|
+
if (node.body.type === NodeType.BlockStatement) {
|
|
1082
|
+
const bodyLines = [];
|
|
1083
|
+
const savedOutput = this.output;
|
|
1084
|
+
this.output = '';
|
|
1085
|
+
this.pushIndent();
|
|
1086
|
+
for (const stmt of node.body.body) {
|
|
1087
|
+
this.visitStatement(stmt);
|
|
1088
|
+
}
|
|
1089
|
+
this.popIndent();
|
|
1090
|
+
const bodyContent = this.output;
|
|
1091
|
+
this.output = savedOutput;
|
|
1092
|
+
return `${asyncPrefix}${paramsStr} => {\n${bodyContent}${this.getIndent()}}`;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
const body = this.visitExpression(node.body);
|
|
1096
|
+
return `${asyncPrefix}${paramsStr} => ${body}`;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
visitConditionalExpression(node) {
|
|
1100
|
+
const test = this.visitExpression(node.test);
|
|
1101
|
+
const consequent = this.visitExpression(node.consequent);
|
|
1102
|
+
const alternate = this.visitExpression(node.alternate);
|
|
1103
|
+
return `(${test} ? ${consequent} : ${alternate})`;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
visitRangeExpression(node) {
|
|
1107
|
+
const start = this.visitExpression(node.start);
|
|
1108
|
+
const end = this.visitExpression(node.end);
|
|
1109
|
+
return `Array.from({ length: ${end} - ${start} }, (_, i) => ${start} + i)`;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
visitFlowExpression(node) {
|
|
1113
|
+
// Flow: composedFn >> fn1 fn2 fn3
|
|
1114
|
+
// Generates: const composedFn = _flow(fn1, fn2, fn3);
|
|
1115
|
+
// This creates an async composed function that awaits each step
|
|
1116
|
+
const { name, functions } = node;
|
|
1117
|
+
|
|
1118
|
+
if (functions.length === 0) {
|
|
1119
|
+
return `const ${name} = (x) => x`;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Use the _flow helper which handles async functions
|
|
1123
|
+
return `const ${name} = _flow(${functions.join(', ')})`;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
visitPipeExpression(node) {
|
|
1127
|
+
// Pipe: value ~> fn1 ~> fn2
|
|
1128
|
+
// Collect all pipe steps and use _pipe helper for async support
|
|
1129
|
+
const steps = [];
|
|
1130
|
+
let current = node;
|
|
1131
|
+
|
|
1132
|
+
// Walk the pipe chain to collect all steps
|
|
1133
|
+
while (current.type === NodeType.PipeExpression) {
|
|
1134
|
+
steps.unshift(this.visitExpression(current.right));
|
|
1135
|
+
current = current.left;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// current is now the initial value
|
|
1139
|
+
const initial = this.visitExpression(current);
|
|
1140
|
+
|
|
1141
|
+
// Use _pipe helper which awaits each step
|
|
1142
|
+
return `_pipe(${initial}, ${steps.join(', ')})`;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
visitTemplateLiteral(node) {
|
|
1146
|
+
// Convert KimchiLang string interpolation to JavaScript template literal
|
|
1147
|
+
// node.parts contains string segments, node.expressions contains parsed AST nodes
|
|
1148
|
+
let result = '`';
|
|
1149
|
+
|
|
1150
|
+
for (let i = 0; i < node.parts.length; i++) {
|
|
1151
|
+
// Escape backticks in the string parts
|
|
1152
|
+
result += node.parts[i].replace(/`/g, '\\`');
|
|
1153
|
+
|
|
1154
|
+
// Add expression if there is one after this part
|
|
1155
|
+
if (i < node.expressions.length) {
|
|
1156
|
+
result += '${' + this.visitExpression(node.expressions[i]) + '}';
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
result += '`';
|
|
1161
|
+
return result;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Testing framework code generation
|
|
1165
|
+
visitTestBlock(node) {
|
|
1166
|
+
this.emitLine(`_test(${JSON.stringify(node.name)}, async () => {`);
|
|
1167
|
+
this.pushIndent();
|
|
1168
|
+
for (const stmt of node.body.body) {
|
|
1169
|
+
this.visitStatement(stmt);
|
|
1170
|
+
}
|
|
1171
|
+
this.popIndent();
|
|
1172
|
+
this.emitLine('});');
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
visitDescribeBlock(node) {
|
|
1176
|
+
this.emitLine(`_describe(${JSON.stringify(node.name)}, () => {`);
|
|
1177
|
+
this.pushIndent();
|
|
1178
|
+
for (const stmt of node.body.body) {
|
|
1179
|
+
this.visitStatement(stmt);
|
|
1180
|
+
}
|
|
1181
|
+
this.popIndent();
|
|
1182
|
+
this.emitLine('});');
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
visitExpectStatement(node) {
|
|
1186
|
+
const actual = this.visitExpression(node.actual);
|
|
1187
|
+
const matcher = node.matcher;
|
|
1188
|
+
const expected = node.expected ? this.visitExpression(node.expected) : '';
|
|
1189
|
+
|
|
1190
|
+
// Generate appropriate assertion based on matcher
|
|
1191
|
+
switch (matcher) {
|
|
1192
|
+
case 'toBe':
|
|
1193
|
+
this.emitLine(`_expect(${actual}).toBe(${expected});`);
|
|
1194
|
+
break;
|
|
1195
|
+
case 'toEqual':
|
|
1196
|
+
this.emitLine(`_expect(${actual}).toEqual(${expected});`);
|
|
1197
|
+
break;
|
|
1198
|
+
case 'toContain':
|
|
1199
|
+
this.emitLine(`_expect(${actual}).toContain(${expected});`);
|
|
1200
|
+
break;
|
|
1201
|
+
case 'toBeNull':
|
|
1202
|
+
this.emitLine(`_expect(${actual}).toBeNull();`);
|
|
1203
|
+
break;
|
|
1204
|
+
case 'toBeTruthy':
|
|
1205
|
+
this.emitLine(`_expect(${actual}).toBeTruthy();`);
|
|
1206
|
+
break;
|
|
1207
|
+
case 'toBeFalsy':
|
|
1208
|
+
this.emitLine(`_expect(${actual}).toBeFalsy();`);
|
|
1209
|
+
break;
|
|
1210
|
+
case 'toBeGreaterThan':
|
|
1211
|
+
this.emitLine(`_expect(${actual}).toBeGreaterThan(${expected});`);
|
|
1212
|
+
break;
|
|
1213
|
+
case 'toBeLessThan':
|
|
1214
|
+
this.emitLine(`_expect(${actual}).toBeLessThan(${expected});`);
|
|
1215
|
+
break;
|
|
1216
|
+
case 'toThrow':
|
|
1217
|
+
this.emitLine(`_expect(${actual}).toThrow(${expected});`);
|
|
1218
|
+
break;
|
|
1219
|
+
case 'toMatch':
|
|
1220
|
+
this.emitLine(`_expect(${actual}).toMatch(${expected});`);
|
|
1221
|
+
break;
|
|
1222
|
+
case 'toHaveLength':
|
|
1223
|
+
this.emitLine(`_expect(${actual}).toHaveLength(${expected});`);
|
|
1224
|
+
break;
|
|
1225
|
+
default:
|
|
1226
|
+
// Generic matcher
|
|
1227
|
+
this.emitLine(`_expect(${actual}).${matcher}(${expected});`);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
visitAssertStatement(node) {
|
|
1232
|
+
const condition = this.visitExpression(node.condition);
|
|
1233
|
+
const message = node.message ? this.visitExpression(node.message) : JSON.stringify('Assertion failed');
|
|
1234
|
+
this.emitLine(`_assert(${condition}, ${message});`);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
export function generate(ast, options = {}) {
|
|
1239
|
+
const generator = new CodeGenerator(options);
|
|
1240
|
+
return generator.generate(ast);
|
|
1241
|
+
}
|