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/cli.js
ADDED
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// KimchiLang CLI - Command line interface for the KimchiLang compiler
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
6
|
+
import { resolve, dirname, basename, extname, join, relative } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { execSync, spawn } from 'child_process';
|
|
9
|
+
import { compile } from './index.js';
|
|
10
|
+
import { convertJS } from './js2km.js';
|
|
11
|
+
import { Linter, Severity } from './linter.js';
|
|
12
|
+
import { tokenize, parse } from './index.js';
|
|
13
|
+
import { parseStaticFile, generateStaticCode } from './static-parser.js';
|
|
14
|
+
import { installDependencies, cleanDependencies, parseProjectFile } from './package-manager.js';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const VERSION = '1.0.1';
|
|
20
|
+
|
|
21
|
+
const HELP = `
|
|
22
|
+
KimchiLang v${VERSION}
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
kimchi <module.path> [--arg value] [--dep module=path]
|
|
26
|
+
kimchi <command> [options] <file>
|
|
27
|
+
|
|
28
|
+
Module Execution:
|
|
29
|
+
kimchi <module.path> Run a module (e.g., salesforce.client)
|
|
30
|
+
kimchi <module> --arg value Pass args to module (converts to camelCase)
|
|
31
|
+
kimchi <module> --dep name=path Inject dependency
|
|
32
|
+
kimchi help <module.path> Show module help (_describe, args, deps, _help)
|
|
33
|
+
|
|
34
|
+
Commands:
|
|
35
|
+
ls [path] List modules (--verbose for descriptions, --recursive for tree)
|
|
36
|
+
compile <file> Compile a .kimchi file to JavaScript
|
|
37
|
+
run <file> Compile and run a .kimchi file
|
|
38
|
+
test <file> Run tests in a .kimchi file
|
|
39
|
+
lint <file> Run linter on a .kimchi file
|
|
40
|
+
check <file> Check a file for errors (for editor integration)
|
|
41
|
+
convert <file> Convert a JavaScript file to KimchiLang
|
|
42
|
+
npm <args> Run npm and convert installed packages to pantry/
|
|
43
|
+
build <dir> Compile all .kimchi files in a directory
|
|
44
|
+
install Install dependencies from project.static
|
|
45
|
+
clean Remove installed dependencies
|
|
46
|
+
repl Start an interactive REPL session
|
|
47
|
+
help Show this help message
|
|
48
|
+
version Show version information
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
-o, --output <file> Output file path (for compile command)
|
|
52
|
+
-d, --debug Enable debug output
|
|
53
|
+
--no-lint Skip linting during compilation
|
|
54
|
+
--verbose, -v Show module descriptions (for ls command)
|
|
55
|
+
--recursive, -r Recurse into subdirectories (for ls command)
|
|
56
|
+
--dep <name=path> Inject dependency (can be used multiple times)
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
kimchi ls
|
|
60
|
+
kimchi ls ./lib --verbose --recursive
|
|
61
|
+
kimchi salesforce.client --client-id ABC123
|
|
62
|
+
kimchi help salesforce.client
|
|
63
|
+
kimchi compile app.kimchi -o dist/app.js
|
|
64
|
+
|
|
65
|
+
File Extension: .kimchi, .km, or .kc
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
function parseArgs(args) {
|
|
69
|
+
const result = {
|
|
70
|
+
command: null,
|
|
71
|
+
file: null,
|
|
72
|
+
output: null,
|
|
73
|
+
debug: false,
|
|
74
|
+
watch: false,
|
|
75
|
+
help: false,
|
|
76
|
+
verbose: false,
|
|
77
|
+
recursive: false,
|
|
78
|
+
skipLint: false,
|
|
79
|
+
moduleArgs: {}, // Args to pass to module (--arg-name value)
|
|
80
|
+
deps: {}, // Dependency injections (--dep name=path)
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
let i = 0;
|
|
84
|
+
while (i < args.length) {
|
|
85
|
+
const arg = args[i];
|
|
86
|
+
|
|
87
|
+
if (arg === '-h' || arg === '--help') {
|
|
88
|
+
result.help = true;
|
|
89
|
+
} else if (arg === '-d' || arg === '--debug') {
|
|
90
|
+
result.debug = true;
|
|
91
|
+
} else if (arg === '--no-lint') {
|
|
92
|
+
result.skipLint = true;
|
|
93
|
+
} else if (arg === '-v' || arg === '--verbose') {
|
|
94
|
+
result.verbose = true;
|
|
95
|
+
} else if (arg === '-r' || arg === '--recursive') {
|
|
96
|
+
result.recursive = true;
|
|
97
|
+
} else if (arg === '-w' || arg === '--watch') {
|
|
98
|
+
result.watch = true;
|
|
99
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
100
|
+
result.output = args[++i];
|
|
101
|
+
} else if (arg === '--dep') {
|
|
102
|
+
// --dep name=path
|
|
103
|
+
const depArg = args[++i];
|
|
104
|
+
if (depArg && depArg.includes('=')) {
|
|
105
|
+
const [name, path] = depArg.split('=');
|
|
106
|
+
result.deps[name] = path;
|
|
107
|
+
}
|
|
108
|
+
} else if (arg.startsWith('--')) {
|
|
109
|
+
// Module arg: --arg-name value -> argName: value
|
|
110
|
+
const argName = arg.slice(2);
|
|
111
|
+
const camelName = argName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
112
|
+
const value = args[++i];
|
|
113
|
+
result.moduleArgs[camelName] = value;
|
|
114
|
+
} else if (!result.command) {
|
|
115
|
+
result.command = arg;
|
|
116
|
+
} else if (!result.file) {
|
|
117
|
+
result.file = arg;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Convert module path to file path: salesforce.client -> ./salesforce/client.km
|
|
127
|
+
function modulePathToFilePath(modulePath) {
|
|
128
|
+
const parts = modulePath.split('.');
|
|
129
|
+
const filePath = './' + parts.join('/');
|
|
130
|
+
|
|
131
|
+
// Try different extensions (including .static for static data files)
|
|
132
|
+
const extensions = ['.km', '.kimchi', '.kc', '.static'];
|
|
133
|
+
for (const ext of extensions) {
|
|
134
|
+
const fullPath = resolve(filePath + ext);
|
|
135
|
+
if (existsSync(fullPath)) {
|
|
136
|
+
return fullPath;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Try as directory with index
|
|
141
|
+
for (const ext of extensions) {
|
|
142
|
+
const indexPath = resolve(filePath, 'index' + ext);
|
|
143
|
+
if (existsSync(indexPath)) {
|
|
144
|
+
return indexPath;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Extract module metadata (args, deps) from source
|
|
152
|
+
function extractModuleMetadata(source) {
|
|
153
|
+
const metadata = {
|
|
154
|
+
args: [],
|
|
155
|
+
deps: [],
|
|
156
|
+
hasDescribe: false,
|
|
157
|
+
hasHelp: false,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Find arg declarations
|
|
161
|
+
const argRegex = /(!?)arg\s+(\w+)(?:\s*=\s*(.+))?/g;
|
|
162
|
+
let match;
|
|
163
|
+
while ((match = argRegex.exec(source)) !== null) {
|
|
164
|
+
metadata.args.push({
|
|
165
|
+
name: match[2],
|
|
166
|
+
required: match[1] === '!',
|
|
167
|
+
default: match[3] || null,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Find dep statements
|
|
172
|
+
const depRegex = /as\s+(\w+)\s+dep\s+([\w.]+)/g;
|
|
173
|
+
while ((match = depRegex.exec(source)) !== null) {
|
|
174
|
+
metadata.deps.push({
|
|
175
|
+
alias: match[1],
|
|
176
|
+
path: match[2],
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check for _describe and _help functions
|
|
181
|
+
metadata.hasDescribe = /fn\s+_describe\s*\(/.test(source) || /expose\s+fn\s+_describe\s*\(/.test(source);
|
|
182
|
+
metadata.hasHelp = /fn\s+_help\s*\(/.test(source) || /expose\s+fn\s+_help\s*\(/.test(source);
|
|
183
|
+
|
|
184
|
+
return metadata;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Run a module with args and dep injections
|
|
188
|
+
async function runModule(modulePath, moduleArgs, deps, options) {
|
|
189
|
+
const filePath = modulePathToFilePath(modulePath);
|
|
190
|
+
|
|
191
|
+
if (!filePath) {
|
|
192
|
+
console.error(`Error: Module not found: ${modulePath}`);
|
|
193
|
+
console.error(`Looked for: ./${modulePath.split('.').join('/')}.[km|kimchi|kc]`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
198
|
+
const metadata = extractModuleMetadata(source);
|
|
199
|
+
|
|
200
|
+
// Validate required args
|
|
201
|
+
for (const arg of metadata.args) {
|
|
202
|
+
if (arg.required && !moduleArgs[arg.name]) {
|
|
203
|
+
console.error(`Error: Required argument '${arg.name}' not provided`);
|
|
204
|
+
console.error(`Usage: kimchi ${modulePath} --${arg.name.replace(/([A-Z])/g, '-$1').toLowerCase()} <value>`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
// Compile the module
|
|
211
|
+
let javascript = compile(source, { debug: options.debug });
|
|
212
|
+
|
|
213
|
+
// Build the args object to pass
|
|
214
|
+
const argsObj = { ...moduleArgs };
|
|
215
|
+
|
|
216
|
+
// Add defaults for missing optional args
|
|
217
|
+
for (const arg of metadata.args) {
|
|
218
|
+
if (!argsObj[arg.name] && arg.default) {
|
|
219
|
+
argsObj[arg.name] = arg.default;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (options.debug) {
|
|
224
|
+
console.log('\n--- Module Args ---');
|
|
225
|
+
console.log(argsObj);
|
|
226
|
+
console.log('\n--- Dep Injections ---');
|
|
227
|
+
console.log(deps);
|
|
228
|
+
console.log('\n--- Generated JavaScript ---\n');
|
|
229
|
+
console.log(javascript);
|
|
230
|
+
console.log('\n--- Output ---\n');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Write to temp file and execute as ES module
|
|
234
|
+
const os = await import('os');
|
|
235
|
+
const crypto = await import('crypto');
|
|
236
|
+
const tempDir = os.default.tmpdir();
|
|
237
|
+
const tempFile = join(tempDir, `kimchi_${crypto.default.randomBytes(8).toString('hex')}.mjs`);
|
|
238
|
+
|
|
239
|
+
// Replace "export default function" with just "const _module = function" so we can call it
|
|
240
|
+
const modifiedCode = javascript.replace(
|
|
241
|
+
/^export default function/m,
|
|
242
|
+
'const _module = function'
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Wrap the code to call the factory with args
|
|
246
|
+
const wrappedCode = `
|
|
247
|
+
${modifiedCode}
|
|
248
|
+
|
|
249
|
+
// Call the module factory
|
|
250
|
+
_module(${JSON.stringify(argsObj)});
|
|
251
|
+
`;
|
|
252
|
+
|
|
253
|
+
writeFileSync(tempFile, wrappedCode);
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
// Execute the temp file
|
|
257
|
+
const { execSync: execSyncImport } = await import('child_process');
|
|
258
|
+
execSyncImport(`node "${tempFile}"`, {
|
|
259
|
+
stdio: 'inherit',
|
|
260
|
+
cwd: dirname(filePath)
|
|
261
|
+
});
|
|
262
|
+
} finally {
|
|
263
|
+
// Clean up temp file
|
|
264
|
+
try {
|
|
265
|
+
const fs = await import('fs');
|
|
266
|
+
fs.default.unlinkSync(tempFile);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
// Ignore cleanup errors
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('Error:', error.message);
|
|
274
|
+
if (options.debug) {
|
|
275
|
+
console.error(error.stack);
|
|
276
|
+
}
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Show help for a module
|
|
282
|
+
async function showModuleHelp(modulePath, options) {
|
|
283
|
+
const filePath = modulePathToFilePath(modulePath);
|
|
284
|
+
|
|
285
|
+
if (!filePath) {
|
|
286
|
+
console.error(`Error: Module not found: ${modulePath}`);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
291
|
+
const metadata = extractModuleMetadata(source);
|
|
292
|
+
|
|
293
|
+
console.log(`\nModule: ${modulePath}`);
|
|
294
|
+
console.log(`File: ${filePath}`);
|
|
295
|
+
console.log('');
|
|
296
|
+
|
|
297
|
+
// Try to call _describe if it exists
|
|
298
|
+
if (metadata.hasDescribe) {
|
|
299
|
+
try {
|
|
300
|
+
const javascript = compile(source, { debug: false });
|
|
301
|
+
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
302
|
+
const wrappedCode = `
|
|
303
|
+
${javascript}
|
|
304
|
+
if (typeof _describe === 'function') {
|
|
305
|
+
return _describe();
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
`;
|
|
309
|
+
const fn = new AsyncFunction(wrappedCode);
|
|
310
|
+
const description = await fn();
|
|
311
|
+
if (description) {
|
|
312
|
+
console.log('Description:');
|
|
313
|
+
console.log(` ${description}`);
|
|
314
|
+
console.log('');
|
|
315
|
+
}
|
|
316
|
+
} catch (e) {
|
|
317
|
+
// Ignore errors in _describe
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Show args
|
|
322
|
+
if (metadata.args.length > 0) {
|
|
323
|
+
console.log('Arguments:');
|
|
324
|
+
for (const arg of metadata.args) {
|
|
325
|
+
const flag = '--' + arg.name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
326
|
+
const required = arg.required ? ' (required)' : '';
|
|
327
|
+
const defaultVal = arg.default ? ` [default: ${arg.default}]` : '';
|
|
328
|
+
console.log(` ${flag}${required}${defaultVal}`);
|
|
329
|
+
}
|
|
330
|
+
console.log('');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Show dependencies
|
|
334
|
+
if (metadata.deps.length > 0) {
|
|
335
|
+
console.log('Dependencies:');
|
|
336
|
+
for (const dep of metadata.deps) {
|
|
337
|
+
console.log(` ${dep.alias} <- ${dep.path}`);
|
|
338
|
+
}
|
|
339
|
+
console.log('');
|
|
340
|
+
console.log('Inject dependencies with: --dep <alias>=<path>');
|
|
341
|
+
console.log('');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Try to call _help if it exists
|
|
345
|
+
if (metadata.hasHelp) {
|
|
346
|
+
try {
|
|
347
|
+
const javascript = compile(source, { debug: false });
|
|
348
|
+
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
349
|
+
const wrappedCode = `
|
|
350
|
+
${javascript}
|
|
351
|
+
if (typeof _help === 'function') {
|
|
352
|
+
return _help();
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
`;
|
|
356
|
+
const fn = new AsyncFunction(wrappedCode);
|
|
357
|
+
const helpText = await fn();
|
|
358
|
+
if (helpText) {
|
|
359
|
+
console.log('Additional Help:');
|
|
360
|
+
console.log(helpText);
|
|
361
|
+
}
|
|
362
|
+
} catch (e) {
|
|
363
|
+
// Ignore errors in _help
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Usage example
|
|
368
|
+
console.log('Usage:');
|
|
369
|
+
let usage = ` kimchi ${modulePath}`;
|
|
370
|
+
for (const arg of metadata.args) {
|
|
371
|
+
const flag = '--' + arg.name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
372
|
+
if (arg.required) {
|
|
373
|
+
usage += ` ${flag} <value>`;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
console.log(usage);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Get description from a module by calling _describe if it exists
|
|
380
|
+
async function getModuleDescription(filePath) {
|
|
381
|
+
try {
|
|
382
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
383
|
+
const hasDescribe = /fn\s+_describe\s*\(/.test(source) || /expose\s+fn\s+_describe\s*\(/.test(source);
|
|
384
|
+
|
|
385
|
+
if (!hasDescribe) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const javascript = compile(source, { debug: false });
|
|
390
|
+
|
|
391
|
+
// Replace export default with const so we can call it
|
|
392
|
+
const modifiedCode = javascript.replace(
|
|
393
|
+
/^export default function/m,
|
|
394
|
+
'const _module = function'
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// Remove the required arg check so we can call _describe without args
|
|
398
|
+
// Replace: if (_opts["name"] === undefined) throw new Error(...);
|
|
399
|
+
const noArgCheckCode = modifiedCode.replace(
|
|
400
|
+
/if \(_opts\["\w+"\] === undefined\) throw new Error\([^)]+\);\s*/g,
|
|
401
|
+
''
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// Suppress console.log to avoid side effects when getting description
|
|
405
|
+
const wrappedCode = `
|
|
406
|
+
const _originalLog = console.log;
|
|
407
|
+
console.log = () => {};
|
|
408
|
+
try {
|
|
409
|
+
${noArgCheckCode}
|
|
410
|
+
|
|
411
|
+
const result = _module({});
|
|
412
|
+
console.log = _originalLog;
|
|
413
|
+
if (result && typeof result._describe === 'function') {
|
|
414
|
+
return result._describe();
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
} finally {
|
|
418
|
+
console.log = _originalLog;
|
|
419
|
+
}
|
|
420
|
+
`;
|
|
421
|
+
|
|
422
|
+
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
423
|
+
const fn = new AsyncFunction(wrappedCode);
|
|
424
|
+
return await fn();
|
|
425
|
+
} catch (e) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Convert file path to module path: /base/foo/bar.km -> foo.bar
|
|
431
|
+
function filePathToModulePath(filePath, baseDir) {
|
|
432
|
+
const relativePath = relative(baseDir, filePath);
|
|
433
|
+
const ext = extname(relativePath);
|
|
434
|
+
const withoutExt = relativePath.slice(0, -ext.length);
|
|
435
|
+
return withoutExt.replace(/[\/]/g, '.');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// List modules in a directory
|
|
439
|
+
async function listModules(searchPath, options) {
|
|
440
|
+
const targetDir = searchPath ? resolve(searchPath) : process.cwd();
|
|
441
|
+
|
|
442
|
+
if (!existsSync(targetDir)) {
|
|
443
|
+
console.error(`Error: Directory not found: ${targetDir}`);
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const stat = statSync(targetDir);
|
|
448
|
+
if (!stat.isDirectory()) {
|
|
449
|
+
console.error(`Error: Not a directory: ${targetDir}`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log(`\nModules in ${targetDir}:\n`);
|
|
454
|
+
|
|
455
|
+
if (options.recursive) {
|
|
456
|
+
await listModulesRecursive(targetDir, '', options, targetDir);
|
|
457
|
+
} else {
|
|
458
|
+
await listModulesFlat(targetDir, options, targetDir);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// List modules in a single directory (non-recursive)
|
|
463
|
+
async function listModulesFlat(dir, options, baseDir) {
|
|
464
|
+
const entries = readdirSync(dir);
|
|
465
|
+
const extensions = ['.km', '.kimchi', '.kc'];
|
|
466
|
+
|
|
467
|
+
for (const entry of entries) {
|
|
468
|
+
const fullPath = join(dir, entry);
|
|
469
|
+
const stat = statSync(fullPath);
|
|
470
|
+
|
|
471
|
+
if (stat.isFile()) {
|
|
472
|
+
const ext = extname(entry);
|
|
473
|
+
if (extensions.includes(ext)) {
|
|
474
|
+
const moduleName = basename(entry, ext);
|
|
475
|
+
const modulePath = filePathToModulePath(fullPath, baseDir);
|
|
476
|
+
|
|
477
|
+
if (options.verbose) {
|
|
478
|
+
const description = await getModuleDescription(fullPath);
|
|
479
|
+
if (description) {
|
|
480
|
+
console.log(` ${moduleName}`);
|
|
481
|
+
console.log(` ${modulePath}`);
|
|
482
|
+
console.log(` ${description}`);
|
|
483
|
+
console.log('');
|
|
484
|
+
} else {
|
|
485
|
+
console.log(` ${moduleName}`);
|
|
486
|
+
console.log(` ${modulePath}`);
|
|
487
|
+
console.log('');
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
console.log(` ${moduleName}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// List modules recursively with tree format
|
|
498
|
+
async function listModulesRecursive(dir, prefix, options, baseDir) {
|
|
499
|
+
const entries = readdirSync(dir).sort();
|
|
500
|
+
const extensions = ['.km', '.kimchi', '.kc'];
|
|
501
|
+
|
|
502
|
+
// Separate files and directories
|
|
503
|
+
const files = [];
|
|
504
|
+
const dirs = [];
|
|
505
|
+
|
|
506
|
+
for (const entry of entries) {
|
|
507
|
+
const fullPath = join(dir, entry);
|
|
508
|
+
const stat = statSync(fullPath);
|
|
509
|
+
|
|
510
|
+
if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
|
|
511
|
+
dirs.push(entry);
|
|
512
|
+
} else if (stat.isFile()) {
|
|
513
|
+
const ext = extname(entry);
|
|
514
|
+
if (extensions.includes(ext)) {
|
|
515
|
+
files.push(entry);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const totalItems = files.length + dirs.length;
|
|
521
|
+
let itemIndex = 0;
|
|
522
|
+
|
|
523
|
+
// List files first
|
|
524
|
+
for (const file of files) {
|
|
525
|
+
itemIndex++;
|
|
526
|
+
const isLast = itemIndex === totalItems;
|
|
527
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
528
|
+
const ext = extname(file);
|
|
529
|
+
const moduleName = basename(file, ext);
|
|
530
|
+
const fullPath = join(dir, file);
|
|
531
|
+
const modulePath = filePathToModulePath(fullPath, baseDir);
|
|
532
|
+
|
|
533
|
+
if (options.verbose) {
|
|
534
|
+
const description = await getModuleDescription(fullPath);
|
|
535
|
+
console.log(`${prefix}${connector}${moduleName}`);
|
|
536
|
+
console.log(`${prefix}${isLast ? ' ' : '│ '} ${modulePath}`);
|
|
537
|
+
if (description) {
|
|
538
|
+
console.log(`${prefix}${isLast ? ' ' : '│ '} ${description}`);
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
console.log(`${prefix}${connector}${moduleName}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Then recurse into directories
|
|
546
|
+
for (const subdir of dirs) {
|
|
547
|
+
itemIndex++;
|
|
548
|
+
const isLast = itemIndex === totalItems;
|
|
549
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
550
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
551
|
+
const fullPath = join(dir, subdir);
|
|
552
|
+
|
|
553
|
+
console.log(`${prefix}${connector}${subdir}/`);
|
|
554
|
+
await listModulesRecursive(fullPath, newPrefix, options, baseDir);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function compileFile(filePath, options = {}) {
|
|
559
|
+
if (!existsSync(filePath)) {
|
|
560
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Reject kebab-case file names
|
|
565
|
+
const fileName = basename(filePath);
|
|
566
|
+
if (/-/.test(fileName) && /\.(km|kimchi|kc)$/.test(fileName)) {
|
|
567
|
+
console.error(`Error: Kebab-case file names are not allowed: ${fileName}`);
|
|
568
|
+
console.error(`Please rename to use underscores: ${fileName.replace(/-/g, '_')}`);
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
573
|
+
|
|
574
|
+
// Static file resolver - checks if a module path resolves to a .static file
|
|
575
|
+
const staticFileResolver = (modulePath) => {
|
|
576
|
+
const parts = modulePath.split('.');
|
|
577
|
+
const baseFilePath = './' + parts.join('/');
|
|
578
|
+
const staticPath = resolve(baseFilePath + '.static');
|
|
579
|
+
return existsSync(staticPath);
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
const javascript = compile(source, {
|
|
584
|
+
debug: options.debug,
|
|
585
|
+
skipLint: options.skipLint,
|
|
586
|
+
showLintWarnings: true,
|
|
587
|
+
staticFileResolver,
|
|
588
|
+
basePath: options.basePath,
|
|
589
|
+
});
|
|
590
|
+
return javascript;
|
|
591
|
+
} catch (error) {
|
|
592
|
+
console.error(`Compilation Error in ${filePath}:`);
|
|
593
|
+
console.error(error.message);
|
|
594
|
+
if (options.debug) {
|
|
595
|
+
console.error(error.stack);
|
|
596
|
+
}
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function compileStaticFile(filePath, options = {}) {
|
|
602
|
+
if (!existsSync(filePath)) {
|
|
603
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
608
|
+
|
|
609
|
+
try {
|
|
610
|
+
const declarations = parseStaticFile(source, filePath);
|
|
611
|
+
const javascript = generateStaticCode(declarations, filePath);
|
|
612
|
+
return javascript;
|
|
613
|
+
} catch (error) {
|
|
614
|
+
console.error(`Static File Compilation Error in ${filePath}:`);
|
|
615
|
+
console.error(error.message);
|
|
616
|
+
if (options.debug) {
|
|
617
|
+
console.error(error.stack);
|
|
618
|
+
}
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function checkFile(source, filePath = null) {
|
|
624
|
+
// Check file for errors and return JSON-compatible error array
|
|
625
|
+
const errors = [];
|
|
626
|
+
|
|
627
|
+
try {
|
|
628
|
+
// Step 1: Tokenize
|
|
629
|
+
const tokens = tokenize(source);
|
|
630
|
+
|
|
631
|
+
// Step 2: Parse
|
|
632
|
+
const ast = parse(tokens);
|
|
633
|
+
|
|
634
|
+
// Step 3: Lint
|
|
635
|
+
const linter = new Linter();
|
|
636
|
+
const lintMessages = linter.lint(ast, source);
|
|
637
|
+
|
|
638
|
+
for (const msg of lintMessages) {
|
|
639
|
+
if (msg.severity === Severity.Error) {
|
|
640
|
+
errors.push({
|
|
641
|
+
line: msg.line || 1,
|
|
642
|
+
column: msg.column || 1,
|
|
643
|
+
message: `[${msg.rule}] ${msg.message}`,
|
|
644
|
+
severity: 'error',
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Step 4: Type check (compile without generating code)
|
|
650
|
+
compile(source, { skipLint: true });
|
|
651
|
+
|
|
652
|
+
} catch (error) {
|
|
653
|
+
// Parse error format: "Parse Error at line:column: message"
|
|
654
|
+
const match = error.message.match(/(?:Parse |Lexer |Type |Compile )?Error at (\d+):(\d+):\s*(.+)/i);
|
|
655
|
+
if (match) {
|
|
656
|
+
errors.push({
|
|
657
|
+
line: parseInt(match[1], 10),
|
|
658
|
+
column: parseInt(match[2], 10),
|
|
659
|
+
message: match[3],
|
|
660
|
+
severity: 'error',
|
|
661
|
+
});
|
|
662
|
+
} else {
|
|
663
|
+
// Generic error
|
|
664
|
+
errors.push({
|
|
665
|
+
line: 1,
|
|
666
|
+
column: 1,
|
|
667
|
+
message: error.message,
|
|
668
|
+
severity: 'error',
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return errors;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function lintFile(filePath, options = {}) {
|
|
677
|
+
if (!existsSync(filePath)) {
|
|
678
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
const tokens = tokenize(source);
|
|
686
|
+
const ast = parse(tokens);
|
|
687
|
+
|
|
688
|
+
const linter = new Linter();
|
|
689
|
+
const messages = linter.lint(ast, source);
|
|
690
|
+
|
|
691
|
+
if (messages.length === 0) {
|
|
692
|
+
console.log(`✅ ${basename(filePath)}: No lint issues found`);
|
|
693
|
+
} else {
|
|
694
|
+
console.log(`\n📋 Lint results for ${basename(filePath)}:`);
|
|
695
|
+
console.log(linter.formatMessages());
|
|
696
|
+
|
|
697
|
+
if (linter.hasErrors()) {
|
|
698
|
+
process.exit(1);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
} catch (error) {
|
|
702
|
+
console.error(`Error linting ${filePath}:`);
|
|
703
|
+
console.error(error.message);
|
|
704
|
+
if (options.debug) {
|
|
705
|
+
console.error(error.stack);
|
|
706
|
+
}
|
|
707
|
+
process.exit(1);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async function runTests(filePath, options = {}) {
|
|
712
|
+
if (!existsSync(filePath)) {
|
|
713
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const javascript = compileFile(filePath, options);
|
|
718
|
+
|
|
719
|
+
if (options.debug) {
|
|
720
|
+
console.log('\n--- Generated JavaScript ---\n');
|
|
721
|
+
console.log(javascript);
|
|
722
|
+
console.log('\n--- Running Tests ---\n');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
try {
|
|
726
|
+
const os = await import('os');
|
|
727
|
+
const crypto = await import('crypto');
|
|
728
|
+
const tempDir = os.default.tmpdir();
|
|
729
|
+
const tempFile = join(tempDir, `kimchi_test_${crypto.default.randomBytes(8).toString('hex')}.mjs`);
|
|
730
|
+
|
|
731
|
+
const modifiedCode = javascript.replace(
|
|
732
|
+
/^export default function/m,
|
|
733
|
+
'const _module = function'
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const wrappedCode = `
|
|
737
|
+
${modifiedCode}
|
|
738
|
+
|
|
739
|
+
// Call the module factory to register tests
|
|
740
|
+
_module({});
|
|
741
|
+
|
|
742
|
+
// Run all registered tests
|
|
743
|
+
await _runTests();
|
|
744
|
+
`;
|
|
745
|
+
|
|
746
|
+
writeFileSync(tempFile, wrappedCode);
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
execSync(`node "${tempFile}"`, {
|
|
750
|
+
stdio: 'inherit',
|
|
751
|
+
cwd: dirname(filePath)
|
|
752
|
+
});
|
|
753
|
+
} finally {
|
|
754
|
+
try {
|
|
755
|
+
const fs = await import('fs');
|
|
756
|
+
fs.default.unlinkSync(tempFile);
|
|
757
|
+
} catch (e) {
|
|
758
|
+
// Ignore cleanup errors
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
} catch (error) {
|
|
762
|
+
console.error('Test Error:');
|
|
763
|
+
console.error(error.message);
|
|
764
|
+
if (options.debug) {
|
|
765
|
+
console.error(error.stack);
|
|
766
|
+
}
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async function runFile(filePath, options = {}) {
|
|
772
|
+
// Pass basePath for absolute path resolution of static files
|
|
773
|
+
const javascript = compileFile(filePath, { ...options, basePath: dirname(resolve(filePath)) });
|
|
774
|
+
|
|
775
|
+
if (options.debug) {
|
|
776
|
+
console.log('\n--- Generated JavaScript ---\n');
|
|
777
|
+
console.log(javascript);
|
|
778
|
+
console.log('\n--- Output ---\n');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
try {
|
|
782
|
+
// Write to temp file and execute as ES module
|
|
783
|
+
const os = await import('os');
|
|
784
|
+
const crypto = await import('crypto');
|
|
785
|
+
const tempDir = os.default.tmpdir();
|
|
786
|
+
const tempFile = join(tempDir, `kimchi_${crypto.default.randomBytes(8).toString('hex')}.mjs`);
|
|
787
|
+
|
|
788
|
+
// Replace export default with const so we can call it
|
|
789
|
+
const modifiedCode = javascript.replace(
|
|
790
|
+
/^export default function/m,
|
|
791
|
+
'const _module = function'
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
const wrappedCode = `
|
|
795
|
+
${modifiedCode}
|
|
796
|
+
|
|
797
|
+
// Call the module factory
|
|
798
|
+
_module({});
|
|
799
|
+
`;
|
|
800
|
+
|
|
801
|
+
writeFileSync(tempFile, wrappedCode);
|
|
802
|
+
|
|
803
|
+
try {
|
|
804
|
+
execSync(`node "${tempFile}"`, {
|
|
805
|
+
stdio: 'inherit',
|
|
806
|
+
cwd: dirname(filePath)
|
|
807
|
+
});
|
|
808
|
+
} finally {
|
|
809
|
+
try {
|
|
810
|
+
const fs = await import('fs');
|
|
811
|
+
fs.default.unlinkSync(tempFile);
|
|
812
|
+
} catch (e) {
|
|
813
|
+
// Ignore cleanup errors
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
} catch (error) {
|
|
817
|
+
console.error('Runtime Error:');
|
|
818
|
+
console.error(error.message);
|
|
819
|
+
if (options.debug) {
|
|
820
|
+
console.error(error.stack);
|
|
821
|
+
}
|
|
822
|
+
process.exit(1);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async function startRepl() {
|
|
827
|
+
const readline = await import('readline');
|
|
828
|
+
|
|
829
|
+
const rl = readline.createInterface({
|
|
830
|
+
input: process.stdin,
|
|
831
|
+
output: process.stdout,
|
|
832
|
+
prompt: 'kimchi> ',
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
console.log(`KimchiLang REPL v${VERSION}`);
|
|
836
|
+
console.log('Type "exit" or press Ctrl+C to quit.\n');
|
|
837
|
+
|
|
838
|
+
rl.prompt();
|
|
839
|
+
|
|
840
|
+
let multilineBuffer = '';
|
|
841
|
+
let braceCount = 0;
|
|
842
|
+
|
|
843
|
+
rl.on('line', (line) => {
|
|
844
|
+
if (line.trim() === 'exit') {
|
|
845
|
+
rl.close();
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
multilineBuffer += line + '\n';
|
|
850
|
+
braceCount += (line.match(/{/g) || []).length;
|
|
851
|
+
braceCount -= (line.match(/}/g) || []).length;
|
|
852
|
+
|
|
853
|
+
if (braceCount > 0) {
|
|
854
|
+
process.stdout.write('... ');
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (multilineBuffer.trim()) {
|
|
859
|
+
try {
|
|
860
|
+
const javascript = compile(multilineBuffer.trim());
|
|
861
|
+
const result = eval(javascript);
|
|
862
|
+
if (result !== undefined) {
|
|
863
|
+
console.log(result);
|
|
864
|
+
}
|
|
865
|
+
} catch (error) {
|
|
866
|
+
console.error('Error:', error.message);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
multilineBuffer = '';
|
|
871
|
+
braceCount = 0;
|
|
872
|
+
rl.prompt();
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
rl.on('close', () => {
|
|
876
|
+
console.log('\nGoodbye!');
|
|
877
|
+
process.exit(0);
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function getAllJsFiles(dir, files = []) {
|
|
882
|
+
const entries = readdirSync(dir);
|
|
883
|
+
|
|
884
|
+
for (const entry of entries) {
|
|
885
|
+
const fullPath = join(dir, entry);
|
|
886
|
+
const stat = statSync(fullPath);
|
|
887
|
+
|
|
888
|
+
if (stat.isDirectory()) {
|
|
889
|
+
// Skip certain directories
|
|
890
|
+
if (entry === '.bin' || entry === '.cache') continue;
|
|
891
|
+
getAllJsFiles(fullPath, files);
|
|
892
|
+
} else if (entry.endsWith('.js') && !entry.endsWith('.min.js')) {
|
|
893
|
+
files.push(fullPath);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return files;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function getPackageMainFile(packageDir) {
|
|
901
|
+
const packageJsonPath = join(packageDir, 'package.json');
|
|
902
|
+
if (existsSync(packageJsonPath)) {
|
|
903
|
+
try {
|
|
904
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
905
|
+
// Try main, then module, then index.js
|
|
906
|
+
const mainFile = pkg.main || pkg.module || 'index.js';
|
|
907
|
+
const mainPath = join(packageDir, mainFile);
|
|
908
|
+
if (existsSync(mainPath)) {
|
|
909
|
+
return mainPath;
|
|
910
|
+
}
|
|
911
|
+
// Try adding .js extension
|
|
912
|
+
if (existsSync(mainPath + '.js')) {
|
|
913
|
+
return mainPath + '.js';
|
|
914
|
+
}
|
|
915
|
+
} catch (e) {
|
|
916
|
+
// Ignore JSON parse errors
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Fallback to index.js
|
|
921
|
+
const indexPath = join(packageDir, 'index.js');
|
|
922
|
+
if (existsSync(indexPath)) {
|
|
923
|
+
return indexPath;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
async function runNpmAndConvert(npmArgs, options) {
|
|
930
|
+
const cwd = process.cwd();
|
|
931
|
+
const nodeModulesDir = join(cwd, 'node_modules');
|
|
932
|
+
const pantryDir = join(cwd, 'pantry');
|
|
933
|
+
|
|
934
|
+
// Run npm with the provided arguments
|
|
935
|
+
console.log(`Running: npm ${npmArgs.join(' ')}`);
|
|
936
|
+
|
|
937
|
+
try {
|
|
938
|
+
const npmProcess = spawn('npm', npmArgs, {
|
|
939
|
+
cwd,
|
|
940
|
+
stdio: 'inherit',
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
await new Promise((resolve, reject) => {
|
|
944
|
+
npmProcess.on('close', (code) => {
|
|
945
|
+
if (code === 0) {
|
|
946
|
+
resolve();
|
|
947
|
+
} else {
|
|
948
|
+
reject(new Error(`npm exited with code ${code}`));
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
npmProcess.on('error', reject);
|
|
952
|
+
});
|
|
953
|
+
} catch (error) {
|
|
954
|
+
console.error('npm command failed:', error.message);
|
|
955
|
+
process.exit(1);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// Only convert if this was an install command
|
|
959
|
+
if (!npmArgs.includes('install') && !npmArgs.includes('i') && !npmArgs.includes('add')) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Create pantry directory
|
|
964
|
+
if (!existsSync(pantryDir)) {
|
|
965
|
+
mkdirSync(pantryDir, { recursive: true });
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Find installed packages
|
|
969
|
+
if (!existsSync(nodeModulesDir)) {
|
|
970
|
+
console.log('No node_modules directory found');
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const packages = readdirSync(nodeModulesDir).filter(name => {
|
|
975
|
+
// Skip hidden files and scoped packages for now
|
|
976
|
+
if (name.startsWith('.') || name.startsWith('@')) return false;
|
|
977
|
+
const pkgPath = join(nodeModulesDir, name);
|
|
978
|
+
return statSync(pkgPath).isDirectory();
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
console.log(`\nConverting ${packages.length} packages to pantry/...`);
|
|
982
|
+
|
|
983
|
+
let converted = 0;
|
|
984
|
+
let failed = 0;
|
|
985
|
+
|
|
986
|
+
for (const pkgName of packages) {
|
|
987
|
+
const pkgDir = join(nodeModulesDir, pkgName);
|
|
988
|
+
const mainFile = getPackageMainFile(pkgDir);
|
|
989
|
+
|
|
990
|
+
if (!mainFile) {
|
|
991
|
+
if (options.debug) {
|
|
992
|
+
console.log(` Skipping ${pkgName}: no main file found`);
|
|
993
|
+
}
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
try {
|
|
998
|
+
const jsSource = readFileSync(mainFile, 'utf-8');
|
|
999
|
+
const kimchiCode = convertJS(jsSource);
|
|
1000
|
+
|
|
1001
|
+
// Create package directory in pantry
|
|
1002
|
+
const pantryPkgDir = join(pantryDir, pkgName);
|
|
1003
|
+
if (!existsSync(pantryPkgDir)) {
|
|
1004
|
+
mkdirSync(pantryPkgDir, { recursive: true });
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Write the converted file
|
|
1008
|
+
const outputFile = join(pantryPkgDir, 'index.km');
|
|
1009
|
+
writeFileSync(outputFile, kimchiCode);
|
|
1010
|
+
|
|
1011
|
+
converted++;
|
|
1012
|
+
if (options.debug) {
|
|
1013
|
+
console.log(` ✓ ${pkgName}`);
|
|
1014
|
+
}
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
failed++;
|
|
1017
|
+
if (options.debug) {
|
|
1018
|
+
console.log(` ✗ ${pkgName}: ${error.message}`);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
console.log(`\nPantry: ${converted} packages converted, ${failed} failed`);
|
|
1024
|
+
console.log(`Use: as <alias> dep pantry.<package>`);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
async function main() {
|
|
1028
|
+
const args = process.argv.slice(2);
|
|
1029
|
+
const options = parseArgs(args);
|
|
1030
|
+
|
|
1031
|
+
if (options.help || args.length === 0) {
|
|
1032
|
+
console.log(HELP);
|
|
1033
|
+
process.exit(0);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
switch (options.command) {
|
|
1037
|
+
case 'version':
|
|
1038
|
+
case '-v':
|
|
1039
|
+
case '--version':
|
|
1040
|
+
console.log(`KimchiLang v${VERSION}`);
|
|
1041
|
+
break;
|
|
1042
|
+
|
|
1043
|
+
case 'help':
|
|
1044
|
+
// Check if help is for a specific module
|
|
1045
|
+
if (options.file) {
|
|
1046
|
+
// Try to find the module
|
|
1047
|
+
const helpFilePath = modulePathToFilePath(options.file);
|
|
1048
|
+
if (helpFilePath) {
|
|
1049
|
+
await showModuleHelp(options.file, options);
|
|
1050
|
+
} else {
|
|
1051
|
+
console.log(HELP);
|
|
1052
|
+
}
|
|
1053
|
+
} else {
|
|
1054
|
+
console.log(HELP);
|
|
1055
|
+
}
|
|
1056
|
+
break;
|
|
1057
|
+
|
|
1058
|
+
case 'ls': {
|
|
1059
|
+
// List modules in directory
|
|
1060
|
+
await listModules(options.file, options);
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
case 'compile': {
|
|
1065
|
+
if (!options.file) {
|
|
1066
|
+
console.error('Error: No input file specified');
|
|
1067
|
+
process.exit(1);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const filePath = resolve(options.file);
|
|
1071
|
+
|
|
1072
|
+
// Check if it's a static file
|
|
1073
|
+
if (filePath.endsWith('.static')) {
|
|
1074
|
+
const javascript = compileStaticFile(filePath, options);
|
|
1075
|
+
if (options.output) {
|
|
1076
|
+
writeFileSync(options.output, javascript);
|
|
1077
|
+
console.log(`Compiled: ${options.file} -> ${options.output}`);
|
|
1078
|
+
} else {
|
|
1079
|
+
const outputPath = filePath.replace(/\.static$/, '.static.js');
|
|
1080
|
+
writeFileSync(outputPath, javascript);
|
|
1081
|
+
console.log(`Compiled: ${options.file} -> ${basename(outputPath)}`);
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
const javascript = compileFile(filePath, options);
|
|
1085
|
+
if (options.output) {
|
|
1086
|
+
writeFileSync(options.output, javascript);
|
|
1087
|
+
console.log(`Compiled: ${options.file} -> ${options.output}`);
|
|
1088
|
+
} else {
|
|
1089
|
+
const outputPath = filePath.replace(/\.(kimchi|kc)$/, '.js');
|
|
1090
|
+
writeFileSync(outputPath, javascript);
|
|
1091
|
+
console.log(`Compiled: ${options.file} -> ${basename(outputPath)}`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
case 'run': {
|
|
1098
|
+
if (!options.file) {
|
|
1099
|
+
console.error('Error: No input file specified');
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const filePath = resolve(options.file);
|
|
1104
|
+
runFile(filePath, options);
|
|
1105
|
+
break;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
case 'lint': {
|
|
1109
|
+
if (!options.file) {
|
|
1110
|
+
console.error('Error: No input file specified');
|
|
1111
|
+
process.exit(1);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
const filePath = resolve(options.file);
|
|
1115
|
+
lintFile(filePath, options);
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
case 'check': {
|
|
1120
|
+
// Check command for editor integration - outputs JSON errors
|
|
1121
|
+
let source;
|
|
1122
|
+
let filePath = options.file ? resolve(options.file) : null;
|
|
1123
|
+
|
|
1124
|
+
// Read from stdin if --json flag or no file
|
|
1125
|
+
if (process.stdin.isTTY === false) {
|
|
1126
|
+
// Read from stdin
|
|
1127
|
+
const chunks = [];
|
|
1128
|
+
for await (const chunk of process.stdin) {
|
|
1129
|
+
chunks.push(chunk);
|
|
1130
|
+
}
|
|
1131
|
+
source = Buffer.concat(chunks).toString('utf-8');
|
|
1132
|
+
} else if (filePath && existsSync(filePath)) {
|
|
1133
|
+
source = readFileSync(filePath, 'utf-8');
|
|
1134
|
+
} else {
|
|
1135
|
+
console.log(JSON.stringify({ errors: [] }));
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const errors = checkFile(source, filePath);
|
|
1140
|
+
console.log(JSON.stringify({ errors }));
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
case 'test': {
|
|
1145
|
+
if (!options.file) {
|
|
1146
|
+
console.error('Error: No input file specified');
|
|
1147
|
+
process.exit(1);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
const filePath = resolve(options.file);
|
|
1151
|
+
await runTests(filePath, options);
|
|
1152
|
+
break;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
case 'repl':
|
|
1156
|
+
await startRepl();
|
|
1157
|
+
break;
|
|
1158
|
+
|
|
1159
|
+
case 'convert': {
|
|
1160
|
+
if (!options.file) {
|
|
1161
|
+
console.error('Error: No input file specified');
|
|
1162
|
+
process.exit(1);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const filePath = resolve(options.file);
|
|
1166
|
+
if (!existsSync(filePath)) {
|
|
1167
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
1168
|
+
process.exit(1);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const jsSource = readFileSync(filePath, 'utf-8');
|
|
1172
|
+
|
|
1173
|
+
try {
|
|
1174
|
+
const kimchiCode = convertJS(jsSource);
|
|
1175
|
+
|
|
1176
|
+
if (options.output) {
|
|
1177
|
+
writeFileSync(options.output, kimchiCode);
|
|
1178
|
+
console.log(`Converted: ${options.file} -> ${options.output}`);
|
|
1179
|
+
} else {
|
|
1180
|
+
const outputPath = filePath.replace(/\.js$/, '.km');
|
|
1181
|
+
writeFileSync(outputPath, kimchiCode);
|
|
1182
|
+
console.log(`Converted: ${options.file} -> ${basename(outputPath)}`);
|
|
1183
|
+
}
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
console.error(`Conversion Error in ${filePath}:`);
|
|
1186
|
+
console.error(error.message);
|
|
1187
|
+
if (options.debug) {
|
|
1188
|
+
console.error(error.stack);
|
|
1189
|
+
}
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
case 'npm': {
|
|
1196
|
+
// Pass all remaining args to npm
|
|
1197
|
+
const npmArgs = process.argv.slice(3);
|
|
1198
|
+
await runNpmAndConvert(npmArgs, options);
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
case 'install': {
|
|
1203
|
+
// Install dependencies from project.static
|
|
1204
|
+
console.log('KimchiLang Package Manager\n');
|
|
1205
|
+
installDependencies('.');
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
case 'clean': {
|
|
1210
|
+
// Remove installed dependencies
|
|
1211
|
+
console.log('Cleaning dependencies...\n');
|
|
1212
|
+
cleanDependencies('.');
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
default:
|
|
1217
|
+
// Check if it's a file to run directly
|
|
1218
|
+
if (options.command && (options.command.endsWith('.kimchi') || options.command.endsWith('.kc') || options.command.endsWith('.km'))) {
|
|
1219
|
+
const filePath = resolve(options.command);
|
|
1220
|
+
runFile(filePath, options);
|
|
1221
|
+
}
|
|
1222
|
+
// Check if it's a module path (contains dots but not a file extension)
|
|
1223
|
+
else if (options.command && options.command.includes('.') && !options.command.match(/\.(kimchi|kc|km|js)$/)) {
|
|
1224
|
+
await runModule(options.command, options.moduleArgs, options.deps, options);
|
|
1225
|
+
}
|
|
1226
|
+
else if (options.command) {
|
|
1227
|
+
// Try as a simple module name (single word)
|
|
1228
|
+
const filePath = modulePathToFilePath(options.command);
|
|
1229
|
+
if (filePath) {
|
|
1230
|
+
await runModule(options.command, options.moduleArgs, options.deps, options);
|
|
1231
|
+
} else {
|
|
1232
|
+
console.error(`Unknown command or module: ${options.command}`);
|
|
1233
|
+
console.log(HELP);
|
|
1234
|
+
process.exit(1);
|
|
1235
|
+
}
|
|
1236
|
+
} else {
|
|
1237
|
+
console.log(HELP);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
main().catch(error => {
|
|
1243
|
+
console.error('Fatal Error:', error.message);
|
|
1244
|
+
process.exit(1);
|
|
1245
|
+
});
|