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.
Files changed (90) hide show
  1. package/.github/workflows/ci.yml +66 -0
  2. package/README.md +1547 -0
  3. package/create-kimchi-app/README.md +44 -0
  4. package/create-kimchi-app/index.js +214 -0
  5. package/create-kimchi-app/package.json +22 -0
  6. package/editors/README.md +121 -0
  7. package/editors/sublime/KimchiLang.sublime-syntax +138 -0
  8. package/editors/vscode/README.md +90 -0
  9. package/editors/vscode/kimchilang-1.1.0.vsix +0 -0
  10. package/editors/vscode/language-configuration.json +37 -0
  11. package/editors/vscode/package.json +55 -0
  12. package/editors/vscode/src/extension.js +354 -0
  13. package/editors/vscode/syntaxes/kimchi.tmLanguage.json +215 -0
  14. package/examples/api/client.km +36 -0
  15. package/examples/async_pipe.km +58 -0
  16. package/examples/basic.kimchi +109 -0
  17. package/examples/cli_framework/README.md +92 -0
  18. package/examples/cli_framework/calculator.km +61 -0
  19. package/examples/cli_framework/deploy.km +126 -0
  20. package/examples/cli_framework/greeter.km +26 -0
  21. package/examples/config.static +27 -0
  22. package/examples/config.static.js +10 -0
  23. package/examples/env_test.km +37 -0
  24. package/examples/fibonacci.kimchi +17 -0
  25. package/examples/greeter.km +15 -0
  26. package/examples/hello.js +1 -0
  27. package/examples/hello.kimchi +3 -0
  28. package/examples/js_interop.km +42 -0
  29. package/examples/logger_example.km +34 -0
  30. package/examples/memo_fibonacci.km +17 -0
  31. package/examples/myapp/lib/http.js +14 -0
  32. package/examples/myapp/lib/http.km +16 -0
  33. package/examples/myapp/main.km +16 -0
  34. package/examples/myapp/main_with_mock.km +42 -0
  35. package/examples/myapp/services/api.js +18 -0
  36. package/examples/myapp/services/api.km +18 -0
  37. package/examples/new_features.kimchi +52 -0
  38. package/examples/project_example.static +20 -0
  39. package/examples/readme_examples.km +240 -0
  40. package/examples/reduce_pattern_match.km +85 -0
  41. package/examples/regex_match.km +46 -0
  42. package/examples/sample.js +45 -0
  43. package/examples/sample.km +39 -0
  44. package/examples/secrets.static +35 -0
  45. package/examples/secrets.static.js +30 -0
  46. package/examples/shell-example.mjs +144 -0
  47. package/examples/shell_example.km +19 -0
  48. package/examples/stdlib_test.km +22 -0
  49. package/examples/test_example.km +69 -0
  50. package/examples/testing/README.md +88 -0
  51. package/examples/testing/http_client.km +18 -0
  52. package/examples/testing/math.km +48 -0
  53. package/examples/testing/math.test.km +93 -0
  54. package/examples/testing/user_service.km +29 -0
  55. package/examples/testing/user_service.test.km +72 -0
  56. package/examples/use-config.mjs +141 -0
  57. package/examples/use_config.km +13 -0
  58. package/install.sh +59 -0
  59. package/package.json +29 -0
  60. package/pantry/acorn/index.km +1 -0
  61. package/pantry/is_number/index.km +1 -0
  62. package/pantry/is_odd/index.km +2 -0
  63. package/project.static +6 -0
  64. package/src/cli.js +1245 -0
  65. package/src/generator.js +1241 -0
  66. package/src/index.js +141 -0
  67. package/src/js2km.js +568 -0
  68. package/src/lexer.js +822 -0
  69. package/src/linter.js +810 -0
  70. package/src/package-manager.js +307 -0
  71. package/src/parser.js +1876 -0
  72. package/src/static-parser.js +500 -0
  73. package/src/typechecker.js +950 -0
  74. package/stdlib/array.km +0 -0
  75. package/stdlib/bitwise.km +38 -0
  76. package/stdlib/console.km +49 -0
  77. package/stdlib/date.km +97 -0
  78. package/stdlib/function.km +44 -0
  79. package/stdlib/http.km +197 -0
  80. package/stdlib/http.md +333 -0
  81. package/stdlib/index.km +26 -0
  82. package/stdlib/json.km +17 -0
  83. package/stdlib/logger.js +114 -0
  84. package/stdlib/logger.km +104 -0
  85. package/stdlib/math.km +120 -0
  86. package/stdlib/object.km +41 -0
  87. package/stdlib/promise.km +33 -0
  88. package/stdlib/string.km +93 -0
  89. package/stdlib/testing.md +265 -0
  90. package/test/test.js +599 -0
package/src/index.js ADDED
@@ -0,0 +1,141 @@
1
+ // KimchiLang - A modern programming language that transpiles to JavaScript
2
+
3
+ import { Lexer, tokenize } from './lexer.js';
4
+ import { Parser, parse } from './parser.js';
5
+ import { CodeGenerator, generate } from './generator.js';
6
+ import { TypeChecker } from './typechecker.js';
7
+ import { Linter, Severity } from './linter.js';
8
+
9
+ // Module registry for tracking required args across modules
10
+ const moduleRegistry = new Map();
11
+
12
+ export class KimchiCompiler {
13
+ constructor(options = {}) {
14
+ this.options = options;
15
+ }
16
+
17
+ // Register a module's required args for cross-module validation
18
+ static registerModule(modulePath, requiredArgs) {
19
+ moduleRegistry.set(modulePath, requiredArgs);
20
+ }
21
+
22
+ // Get required args for a module
23
+ static getModuleRequiredArgs(modulePath) {
24
+ return moduleRegistry.get(modulePath) || [];
25
+ }
26
+
27
+ compile(source, modulePath = null) {
28
+ // Step 1: Tokenize
29
+ const tokens = tokenize(source);
30
+
31
+ if (this.options.debug) {
32
+ console.log('Tokens:', tokens);
33
+ }
34
+
35
+ // Step 2: Parse
36
+ const ast = parse(tokens);
37
+
38
+ if (this.options.debug) {
39
+ console.log('AST:', JSON.stringify(ast, null, 2));
40
+ }
41
+
42
+ // Step 2.5: Extract and register required args for this module
43
+ const argDeclarations = ast.body.filter(stmt => stmt.type === 'ArgDeclaration');
44
+ const requiredArgs = argDeclarations.filter(a => a.required).map(a => a.name);
45
+ if (modulePath) {
46
+ KimchiCompiler.registerModule(modulePath, requiredArgs);
47
+ }
48
+
49
+ // Step 2.6: Validate dep calls against registered modules and detect static files
50
+ const depStatements = ast.body.filter(stmt => stmt.type === 'DepStatement');
51
+ for (const dep of depStatements) {
52
+ // Check if this dependency is a static file
53
+ // Static files have .static extension and are detected by the staticFileResolver option
54
+ if (this.options.staticFileResolver) {
55
+ dep.isStatic = this.options.staticFileResolver(dep.path);
56
+ }
57
+
58
+ // Skip validation for static files (they don't have factory functions)
59
+ if (dep.isStatic) continue;
60
+
61
+ const depRequiredArgs = KimchiCompiler.getModuleRequiredArgs(dep.path);
62
+ if (depRequiredArgs.length > 0) {
63
+ // Check if all required args are provided in the overrides
64
+ const providedArgs = dep.overrides ? this.extractProvidedArgs(dep.overrides) : [];
65
+ for (const requiredArg of depRequiredArgs) {
66
+ if (!providedArgs.includes(requiredArg)) {
67
+ throw new Error(`Compile Error: Required argument '${requiredArg}' not provided for dependency '${dep.path}'`);
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ // Step 2.7: Type checking
74
+ if (!this.options.skipTypeCheck) {
75
+ const typeChecker = new TypeChecker({ modulePath });
76
+ const typeErrors = typeChecker.check(ast);
77
+ if (typeErrors.length > 0) {
78
+ const errorMessages = typeErrors.map(e => `Type Error: ${e.message}`).join('\n');
79
+ throw new Error(errorMessages);
80
+ }
81
+ }
82
+
83
+ // Step 2.8: Linting
84
+ if (!this.options.skipLint) {
85
+ const linter = new Linter(this.options.lintOptions || {});
86
+ const lintMessages = linter.lint(ast, source);
87
+
88
+ // Collect lint errors (not warnings/info)
89
+ const lintErrors = lintMessages.filter(m => m.severity === Severity.Error);
90
+ if (lintErrors.length > 0) {
91
+ const errorMessages = lintErrors.map(m => `Lint Error [${m.rule}]: ${m.message}`).join('\n');
92
+ throw new Error(errorMessages);
93
+ }
94
+
95
+ // Show warnings only if explicitly requested
96
+ if (this.options.showLintWarnings && lintMessages.length > 0) {
97
+ const warnings = lintMessages.filter(m => m.severity !== Severity.Error);
98
+ if (warnings.length > 0) {
99
+ console.error(linter.formatMessages());
100
+ }
101
+ }
102
+ }
103
+
104
+ // Step 3: Generate JavaScript
105
+ const javascript = generate(ast, this.options);
106
+
107
+ if (this.options.debug) {
108
+ console.log('Generated JavaScript:', javascript);
109
+ }
110
+
111
+ return javascript;
112
+ }
113
+
114
+ extractProvidedArgs(overrides) {
115
+ if (overrides.type !== 'ObjectExpression') return [];
116
+ return overrides.properties.map(p => {
117
+ // Key is stored as a string directly in the parser
118
+ if (typeof p.key === 'string') return p.key;
119
+ if (p.key.type === 'Identifier') return p.key.name;
120
+ if (p.key.type === 'Literal') return p.key.value;
121
+ return null;
122
+ }).filter(Boolean);
123
+ }
124
+
125
+ run(source) {
126
+ const javascript = this.compile(source);
127
+ return eval(javascript);
128
+ }
129
+ }
130
+
131
+ export { tokenize, parse, generate };
132
+
133
+ export function compile(source, options = {}) {
134
+ const compiler = new KimchiCompiler(options);
135
+ return compiler.compile(source);
136
+ }
137
+
138
+ export function run(source, options = {}) {
139
+ const compiler = new KimchiCompiler(options);
140
+ return compiler.run(source);
141
+ }
package/src/js2km.js ADDED
@@ -0,0 +1,568 @@
1
+ // JavaScript to KimchiLang Converter
2
+ // Transforms JavaScript source code into KimchiLang syntax
3
+
4
+ import * as acorn from 'acorn';
5
+
6
+ export class JS2KM {
7
+ constructor() {
8
+ this.indent = 0;
9
+ this.output = [];
10
+ }
11
+
12
+ convert(jsSource) {
13
+ const ast = acorn.parse(jsSource, {
14
+ ecmaVersion: 'latest',
15
+ sourceType: 'module',
16
+ });
17
+
18
+ this.output = [];
19
+ this.indent = 0;
20
+
21
+ for (const node of ast.body) {
22
+ this.visitNode(node);
23
+ }
24
+
25
+ return this.output.join('\n');
26
+ }
27
+
28
+ emit(line) {
29
+ const indentation = ' '.repeat(this.indent);
30
+ this.output.push(indentation + line);
31
+ }
32
+
33
+ emitEmpty() {
34
+ this.output.push('');
35
+ }
36
+
37
+ visitNode(node) {
38
+ switch (node.type) {
39
+ case 'VariableDeclaration':
40
+ this.visitVariableDeclaration(node);
41
+ break;
42
+ case 'FunctionDeclaration':
43
+ this.visitFunctionDeclaration(node);
44
+ break;
45
+ case 'ClassDeclaration':
46
+ this.visitClassDeclaration(node);
47
+ break;
48
+ case 'ExpressionStatement':
49
+ this.visitExpressionStatement(node);
50
+ break;
51
+ case 'IfStatement':
52
+ this.visitIfStatement(node);
53
+ break;
54
+ case 'ForStatement':
55
+ this.visitForStatement(node);
56
+ break;
57
+ case 'ForOfStatement':
58
+ this.visitForOfStatement(node);
59
+ break;
60
+ case 'ForInStatement':
61
+ this.visitForInStatement(node);
62
+ break;
63
+ case 'WhileStatement':
64
+ this.visitWhileStatement(node);
65
+ break;
66
+ case 'ReturnStatement':
67
+ this.visitReturnStatement(node);
68
+ break;
69
+ case 'ThrowStatement':
70
+ this.visitThrowStatement(node);
71
+ break;
72
+ case 'TryStatement':
73
+ this.visitTryStatement(node);
74
+ break;
75
+ case 'BlockStatement':
76
+ this.visitBlockStatement(node);
77
+ break;
78
+ case 'ExportNamedDeclaration':
79
+ this.visitExportNamedDeclaration(node);
80
+ break;
81
+ case 'ExportDefaultDeclaration':
82
+ this.visitExportDefaultDeclaration(node);
83
+ break;
84
+ case 'ImportDeclaration':
85
+ this.visitImportDeclaration(node);
86
+ break;
87
+ default:
88
+ this.emit(`// Unsupported: ${node.type}`);
89
+ }
90
+ }
91
+
92
+ visitVariableDeclaration(node, expose = false) {
93
+ for (const decl of node.declarations) {
94
+ const name = decl.id.name;
95
+ const init = decl.init ? this.visitExpression(decl.init) : 'null';
96
+ const prefix = expose ? 'expose ' : '';
97
+
98
+ // Handle require() calls - convert to dep statement
99
+ if (decl.init && decl.init.type === 'CallExpression' &&
100
+ decl.init.callee.name === 'require' &&
101
+ decl.init.arguments.length > 0) {
102
+ const modulePath = decl.init.arguments[0].value;
103
+ const depPath = modulePath.replace(/[\/\.]/g, '.').replace(/^\.+/, '');
104
+ this.emit(`as ${name} dep ${depPath}`);
105
+ } else {
106
+ this.emit(`${prefix}dec ${name} = ${init}`);
107
+ }
108
+ }
109
+ }
110
+
111
+ visitFunctionDeclaration(node, expose = false) {
112
+ const name = node.id.name;
113
+ const params = node.params.map(p => this.visitPattern(p)).join(', ');
114
+ const prefix = expose ? 'expose ' : '';
115
+
116
+ this.emit(`${prefix}fn ${name}(${params}) {`);
117
+ this.indent++;
118
+
119
+ for (const stmt of node.body.body) {
120
+ this.visitNode(stmt);
121
+ }
122
+
123
+ this.indent--;
124
+ this.emit('}');
125
+ this.emitEmpty();
126
+ }
127
+
128
+ visitClassDeclaration(node, expose = false) {
129
+ const name = node.id.name;
130
+ const prefix = expose ? 'expose ' : '';
131
+
132
+ // Transform class to factory function
133
+ this.emit(`// Converted from class ${name}`);
134
+ this.emit(`${prefix}fn create${name}(${this.getConstructorParams(node)}) {`);
135
+ this.indent++;
136
+
137
+ // Create the object with methods
138
+ this.emit('return {');
139
+ this.indent++;
140
+
141
+ const methods = node.body.body.filter(m => m.type === 'MethodDefinition' && m.kind === 'method');
142
+ for (let i = 0; i < methods.length; i++) {
143
+ const method = methods[i];
144
+ const methodName = method.key.name;
145
+ const params = method.value.params.map(p => this.visitPattern(p)).join(', ');
146
+
147
+ this.emit(`${methodName}: fn(${params}) {`);
148
+ this.indent++;
149
+
150
+ for (const stmt of method.value.body.body) {
151
+ this.visitNode(this.transformThisReferences(stmt));
152
+ }
153
+
154
+ this.indent--;
155
+ const comma = i < methods.length - 1 ? ',' : '';
156
+ this.emit(`}${comma}`);
157
+ }
158
+
159
+ this.indent--;
160
+ this.emit('}');
161
+
162
+ this.indent--;
163
+ this.emit('}');
164
+ this.emitEmpty();
165
+ }
166
+
167
+ getConstructorParams(classNode) {
168
+ const constructor = classNode.body.body.find(
169
+ m => m.type === 'MethodDefinition' && m.kind === 'constructor'
170
+ );
171
+ if (constructor) {
172
+ return constructor.value.params.map(p => this.visitPattern(p)).join(', ');
173
+ }
174
+ return '';
175
+ }
176
+
177
+ transformThisReferences(node) {
178
+ // Deep clone and transform this.x to x (captured from closure)
179
+ // For simplicity, we'll handle this in expression generation
180
+ return node;
181
+ }
182
+
183
+ visitExpressionStatement(node) {
184
+ // Skip "use strict" directive
185
+ if (node.expression.type === 'Literal' && node.expression.value === 'use strict') {
186
+ return;
187
+ }
188
+
189
+ // Check for module.exports = ... -> expose dec
190
+ if (node.expression.type === 'AssignmentExpression' &&
191
+ node.expression.left.type === 'MemberExpression' &&
192
+ node.expression.left.object.name === 'module' &&
193
+ node.expression.left.property.name === 'exports') {
194
+ const value = this.visitExpression(node.expression.right);
195
+ this.emit(`expose dec exports = ${value}`);
196
+ return;
197
+ }
198
+
199
+ const expr = this.visitExpression(node.expression);
200
+
201
+ // Check for console.log -> print
202
+ if (node.expression.type === 'CallExpression' &&
203
+ node.expression.callee.type === 'MemberExpression' &&
204
+ node.expression.callee.object.name === 'console' &&
205
+ node.expression.callee.property.name === 'log') {
206
+ const args = node.expression.arguments.map(a => this.visitExpression(a)).join(' + ');
207
+ this.emit(`print ${args}`);
208
+ } else {
209
+ this.emit(expr);
210
+ }
211
+ }
212
+
213
+ visitIfStatement(node) {
214
+ // Convert to pattern matching syntax
215
+ const test = this.visitExpression(node.test);
216
+ this.emit(`|${test}| => {`);
217
+ this.indent++;
218
+
219
+ if (node.consequent.type === 'BlockStatement') {
220
+ for (const stmt of node.consequent.body) {
221
+ this.visitNode(stmt);
222
+ }
223
+ } else {
224
+ this.visitNode(node.consequent);
225
+ }
226
+
227
+ this.indent--;
228
+ this.emit('}');
229
+
230
+ if (node.alternate) {
231
+ if (node.alternate.type === 'IfStatement') {
232
+ this.visitIfStatement(node.alternate);
233
+ } else {
234
+ // else block - use a catch-all pattern
235
+ this.emit('|true| => {');
236
+ this.indent++;
237
+ if (node.alternate.type === 'BlockStatement') {
238
+ for (const stmt of node.alternate.body) {
239
+ this.visitNode(stmt);
240
+ }
241
+ } else {
242
+ this.visitNode(node.alternate);
243
+ }
244
+ this.indent--;
245
+ this.emit('}');
246
+ }
247
+ }
248
+ }
249
+
250
+ visitForStatement(node) {
251
+ // Traditional for loop - convert to while
252
+ if (node.init) {
253
+ this.visitNode(node.init);
254
+ }
255
+
256
+ const test = node.test ? this.visitExpression(node.test) : 'true';
257
+ this.emit(`while ${test} {`);
258
+ this.indent++;
259
+
260
+ if (node.body.type === 'BlockStatement') {
261
+ for (const stmt of node.body.body) {
262
+ this.visitNode(stmt);
263
+ }
264
+ } else {
265
+ this.visitNode(node.body);
266
+ }
267
+
268
+ if (node.update) {
269
+ this.emit(this.visitExpression(node.update));
270
+ }
271
+
272
+ this.indent--;
273
+ this.emit('}');
274
+ }
275
+
276
+ visitForOfStatement(node) {
277
+ const left = this.visitPattern(node.left.declarations ? node.left.declarations[0].id : node.left);
278
+ const right = this.visitExpression(node.right);
279
+
280
+ this.emit(`for ${left} in ${right} {`);
281
+ this.indent++;
282
+
283
+ if (node.body.type === 'BlockStatement') {
284
+ for (const stmt of node.body.body) {
285
+ this.visitNode(stmt);
286
+ }
287
+ } else {
288
+ this.visitNode(node.body);
289
+ }
290
+
291
+ this.indent--;
292
+ this.emit('}');
293
+ }
294
+
295
+ visitForInStatement(node) {
296
+ // for...in iterates over keys
297
+ const left = this.visitPattern(node.left.declarations ? node.left.declarations[0].id : node.left);
298
+ const right = this.visitExpression(node.right);
299
+
300
+ this.emit(`// Note: for...in iterates over keys`);
301
+ this.emit(`for ${left} in Object.keys(${right}) {`);
302
+ this.indent++;
303
+
304
+ if (node.body.type === 'BlockStatement') {
305
+ for (const stmt of node.body.body) {
306
+ this.visitNode(stmt);
307
+ }
308
+ } else {
309
+ this.visitNode(node.body);
310
+ }
311
+
312
+ this.indent--;
313
+ this.emit('}');
314
+ }
315
+
316
+ visitWhileStatement(node) {
317
+ const test = this.visitExpression(node.test);
318
+ this.emit(`while ${test} {`);
319
+ this.indent++;
320
+
321
+ if (node.body.type === 'BlockStatement') {
322
+ for (const stmt of node.body.body) {
323
+ this.visitNode(stmt);
324
+ }
325
+ } else {
326
+ this.visitNode(node.body);
327
+ }
328
+
329
+ this.indent--;
330
+ this.emit('}');
331
+ }
332
+
333
+ visitReturnStatement(node) {
334
+ if (node.argument) {
335
+ this.emit(`return ${this.visitExpression(node.argument)}`);
336
+ } else {
337
+ this.emit('return');
338
+ }
339
+ }
340
+
341
+ visitThrowStatement(node) {
342
+ this.emit(`throw ${this.visitExpression(node.argument)}`);
343
+ }
344
+
345
+ visitTryStatement(node) {
346
+ this.emit('try {');
347
+ this.indent++;
348
+
349
+ for (const stmt of node.block.body) {
350
+ this.visitNode(stmt);
351
+ }
352
+
353
+ this.indent--;
354
+
355
+ if (node.handler) {
356
+ const param = node.handler.param ? node.handler.param.name : 'e';
357
+ this.emit(`} catch(${param}) {`);
358
+ this.indent++;
359
+
360
+ for (const stmt of node.handler.body.body) {
361
+ this.visitNode(stmt);
362
+ }
363
+
364
+ this.indent--;
365
+ }
366
+
367
+ if (node.finalizer) {
368
+ this.emit('} finally {');
369
+ this.indent++;
370
+
371
+ for (const stmt of node.finalizer.body) {
372
+ this.visitNode(stmt);
373
+ }
374
+
375
+ this.indent--;
376
+ }
377
+
378
+ this.emit('}');
379
+ }
380
+
381
+ visitBlockStatement(node) {
382
+ this.emit('{');
383
+ this.indent++;
384
+
385
+ for (const stmt of node.body) {
386
+ this.visitNode(stmt);
387
+ }
388
+
389
+ this.indent--;
390
+ this.emit('}');
391
+ }
392
+
393
+ visitExportNamedDeclaration(node) {
394
+ if (node.declaration) {
395
+ if (node.declaration.type === 'FunctionDeclaration') {
396
+ this.visitFunctionDeclaration(node.declaration, true);
397
+ } else if (node.declaration.type === 'VariableDeclaration') {
398
+ this.visitVariableDeclaration(node.declaration, true);
399
+ } else if (node.declaration.type === 'ClassDeclaration') {
400
+ this.visitClassDeclaration(node.declaration, true);
401
+ }
402
+ }
403
+ }
404
+
405
+ visitExportDefaultDeclaration(node) {
406
+ this.emit('// Default export');
407
+ if (node.declaration.type === 'FunctionDeclaration') {
408
+ this.visitFunctionDeclaration(node.declaration, true);
409
+ } else if (node.declaration.type === 'ClassDeclaration') {
410
+ this.visitClassDeclaration(node.declaration, true);
411
+ } else {
412
+ this.emit(`expose dec default = ${this.visitExpression(node.declaration)}`);
413
+ }
414
+ }
415
+
416
+ visitImportDeclaration(node) {
417
+ const source = node.source.value;
418
+
419
+ for (const spec of node.specifiers) {
420
+ if (spec.type === 'ImportDefaultSpecifier') {
421
+ this.emit(`// Import: ${spec.local.name} from "${source}"`);
422
+ this.emit(`as ${spec.local.name} dep ${source.replace(/[\/\.]/g, '.')}`);
423
+ } else if (spec.type === 'ImportSpecifier') {
424
+ this.emit(`// Import: { ${spec.imported.name} } from "${source}"`);
425
+ this.emit(`as ${spec.local.name} dep ${source.replace(/[\/\.]/g, '.')}`);
426
+ }
427
+ }
428
+ }
429
+
430
+ visitExpression(node) {
431
+ if (!node) return '';
432
+
433
+ switch (node.type) {
434
+ case 'Identifier':
435
+ return node.name;
436
+
437
+ case 'Literal':
438
+ if (typeof node.value === 'string') {
439
+ return `"${node.value}"`;
440
+ }
441
+ return String(node.value);
442
+
443
+ case 'TemplateLiteral':
444
+ return this.visitTemplateLiteral(node);
445
+
446
+ case 'BinaryExpression':
447
+ case 'LogicalExpression':
448
+ return `${this.visitExpression(node.left)} ${node.operator} ${this.visitExpression(node.right)}`;
449
+
450
+ case 'UnaryExpression':
451
+ return `${node.operator}${this.visitExpression(node.argument)}`;
452
+
453
+ case 'UpdateExpression':
454
+ if (node.prefix) {
455
+ return `${node.operator}${this.visitExpression(node.argument)}`;
456
+ }
457
+ return `${this.visitExpression(node.argument)}${node.operator}`;
458
+
459
+ case 'AssignmentExpression':
460
+ return `${this.visitExpression(node.left)} ${node.operator} ${this.visitExpression(node.right)}`;
461
+
462
+ case 'MemberExpression':
463
+ if (node.object.type === 'ThisExpression') {
464
+ // Transform this.x to just x (closure capture)
465
+ return node.property.name;
466
+ }
467
+ if (node.computed) {
468
+ return `${this.visitExpression(node.object)}[${this.visitExpression(node.property)}]`;
469
+ }
470
+ return `${this.visitExpression(node.object)}.${node.property.name}`;
471
+
472
+ case 'CallExpression':
473
+ const callee = this.visitExpression(node.callee);
474
+ const args = node.arguments.map(a => this.visitExpression(a)).join(', ');
475
+ return `${callee}(${args})`;
476
+
477
+ case 'NewExpression':
478
+ // Transform new X() to createX() or X()
479
+ const className = this.visitExpression(node.callee);
480
+ const newArgs = node.arguments.map(a => this.visitExpression(a)).join(', ');
481
+ return `create${className}(${newArgs})`;
482
+
483
+ case 'ArrayExpression':
484
+ const elements = node.elements.map(e => {
485
+ if (e && e.type === 'SpreadElement') {
486
+ return `...${this.visitExpression(e.argument)}`;
487
+ }
488
+ return e ? this.visitExpression(e) : '';
489
+ }).join(', ');
490
+ return `[${elements}]`;
491
+
492
+ case 'ObjectExpression':
493
+ const props = node.properties.map(p => {
494
+ if (p.type === 'SpreadElement') {
495
+ return `...${this.visitExpression(p.argument)}`;
496
+ }
497
+ const key = p.key.type === 'Identifier' ? p.key.name : this.visitExpression(p.key);
498
+ const value = this.visitExpression(p.value);
499
+ if (p.shorthand) {
500
+ return key;
501
+ }
502
+ return `${key}: ${value}`;
503
+ }).join(', ');
504
+ return `{ ${props} }`;
505
+
506
+ case 'ArrowFunctionExpression':
507
+ case 'FunctionExpression':
508
+ const fnParams = node.params.map(p => this.visitPattern(p)).join(', ');
509
+ if (node.body.type === 'BlockStatement') {
510
+ // Multi-line arrow function
511
+ let body = [];
512
+ for (const stmt of node.body.body) {
513
+ // Simplified - just get the expression
514
+ if (stmt.type === 'ReturnStatement' && stmt.argument) {
515
+ body.push(`return ${this.visitExpression(stmt.argument)}`);
516
+ }
517
+ }
518
+ return `fn(${fnParams}) { ${body.join('; ')} }`;
519
+ }
520
+ return `${fnParams} => ${this.visitExpression(node.body)}`;
521
+
522
+ case 'ConditionalExpression':
523
+ return `${this.visitExpression(node.test)} ? ${this.visitExpression(node.consequent)} : ${this.visitExpression(node.alternate)}`;
524
+
525
+ case 'ThisExpression':
526
+ return '/* this */';
527
+
528
+ case 'SpreadElement':
529
+ return `...${this.visitExpression(node.argument)}`;
530
+
531
+ case 'AwaitExpression':
532
+ return `await ${this.visitExpression(node.argument)}`;
533
+
534
+ default:
535
+ return `/* ${node.type} */`;
536
+ }
537
+ }
538
+
539
+ visitTemplateLiteral(node) {
540
+ let result = '"';
541
+ for (let i = 0; i < node.quasis.length; i++) {
542
+ result += node.quasis[i].value.raw;
543
+ if (i < node.expressions.length) {
544
+ result += '" + ' + this.visitExpression(node.expressions[i]) + ' + "';
545
+ }
546
+ }
547
+ result += '"';
548
+ return result;
549
+ }
550
+
551
+ visitPattern(node) {
552
+ if (node.type === 'Identifier') {
553
+ return node.name;
554
+ }
555
+ if (node.type === 'AssignmentPattern') {
556
+ return `${node.left.name} = ${this.visitExpression(node.right)}`;
557
+ }
558
+ if (node.type === 'RestElement') {
559
+ return `...${node.argument.name}`;
560
+ }
561
+ return '/* pattern */';
562
+ }
563
+ }
564
+
565
+ export function convertJS(jsSource) {
566
+ const converter = new JS2KM();
567
+ return converter.convert(jsSource);
568
+ }