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
@@ -0,0 +1,500 @@
1
+ // KimchiLang Static File Parser
2
+ // Parses .static files containing arrays, objects, and enums
3
+
4
+ export class StaticLexer {
5
+ constructor(source) {
6
+ this.source = source;
7
+ this.pos = 0;
8
+ this.line = 1;
9
+ this.column = 1;
10
+ this.tokens = [];
11
+ }
12
+
13
+ error(message) {
14
+ throw new Error(`Static Lexer Error at ${this.line}:${this.column}: ${message}`);
15
+ }
16
+
17
+ peek(offset = 0) {
18
+ const pos = this.pos + offset;
19
+ if (pos >= this.source.length) return '\0';
20
+ return this.source[pos];
21
+ }
22
+
23
+ advance() {
24
+ const char = this.peek();
25
+ this.pos++;
26
+ if (char === '\n') {
27
+ this.line++;
28
+ this.column = 1;
29
+ } else {
30
+ this.column++;
31
+ }
32
+ return char;
33
+ }
34
+
35
+ match(expected) {
36
+ if (this.peek() === expected) {
37
+ this.advance();
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+
43
+ skipWhitespace() {
44
+ while (this.peek() === ' ' || this.peek() === '\t' || this.peek() === '\r') {
45
+ this.advance();
46
+ }
47
+ }
48
+
49
+ skipLineComment() {
50
+ while (this.peek() !== '\n' && this.peek() !== '\0') {
51
+ this.advance();
52
+ }
53
+ }
54
+
55
+ readString(quote) {
56
+ let value = '';
57
+ while (this.peek() !== quote && this.peek() !== '\0') {
58
+ if (this.peek() === '\\') {
59
+ this.advance();
60
+ const escaped = this.advance();
61
+ switch (escaped) {
62
+ case 'n': value += '\n'; break;
63
+ case 't': value += '\t'; break;
64
+ case 'r': value += '\r'; break;
65
+ case '\\': value += '\\'; break;
66
+ case '"': value += '"'; break;
67
+ case "'": value += "'"; break;
68
+ default: value += escaped;
69
+ }
70
+ } else {
71
+ value += this.advance();
72
+ }
73
+ }
74
+ this.advance(); // closing quote
75
+ return { type: 'STRING', value };
76
+ }
77
+
78
+ readNumber() {
79
+ let value = '';
80
+ const isNegative = this.peek() === '-';
81
+ if (isNegative) value += this.advance();
82
+
83
+ while (/[0-9]/.test(this.peek())) {
84
+ value += this.advance();
85
+ }
86
+
87
+ if (this.peek() === '.' && /[0-9]/.test(this.peek(1))) {
88
+ value += this.advance();
89
+ while (/[0-9]/.test(this.peek())) {
90
+ value += this.advance();
91
+ }
92
+ }
93
+
94
+ return { type: 'NUMBER', value: parseFloat(value) };
95
+ }
96
+
97
+ readIdentifier() {
98
+ let value = '';
99
+ while (/[a-zA-Z0-9_$.]/.test(this.peek())) {
100
+ value += this.advance();
101
+ }
102
+
103
+ // Check for boolean/null keywords
104
+ if (value === 'true') return { type: 'BOOLEAN', value: true };
105
+ if (value === 'false') return { type: 'BOOLEAN', value: false };
106
+ if (value === 'null') return { type: 'NULL', value: null };
107
+ if (value === 'secret') return { type: 'SECRET' };
108
+
109
+ return { type: 'IDENTIFIER', value };
110
+ }
111
+
112
+ tokenize() {
113
+ while (this.pos < this.source.length) {
114
+ this.skipWhitespace();
115
+
116
+ if (this.pos >= this.source.length) break;
117
+
118
+ const char = this.peek();
119
+
120
+ // Comments
121
+ if (char === '/' && this.peek(1) === '/') {
122
+ this.advance();
123
+ this.advance();
124
+ this.skipLineComment();
125
+ continue;
126
+ }
127
+
128
+ // Newlines
129
+ if (char === '\n') {
130
+ this.advance();
131
+ this.tokens.push({ type: 'NEWLINE' });
132
+ continue;
133
+ }
134
+
135
+ // Strings
136
+ if (char === '"' || char === "'") {
137
+ this.advance();
138
+ this.tokens.push(this.readString(char));
139
+ continue;
140
+ }
141
+
142
+ // Numbers (including negative)
143
+ if (/[0-9]/.test(char) || (char === '-' && /[0-9]/.test(this.peek(1)))) {
144
+ this.tokens.push(this.readNumber());
145
+ continue;
146
+ }
147
+
148
+ // Identifiers (including dotted paths like foo.bar.Baz)
149
+ if (/[a-zA-Z_$]/.test(char)) {
150
+ this.tokens.push(this.readIdentifier());
151
+ continue;
152
+ }
153
+
154
+ // Single character tokens
155
+ this.advance();
156
+ switch (char) {
157
+ case '[': this.tokens.push({ type: 'LBRACKET' }); break;
158
+ case ']': this.tokens.push({ type: 'RBRACKET' }); break;
159
+ case '{': this.tokens.push({ type: 'LBRACE' }); break;
160
+ case '}': this.tokens.push({ type: 'RBRACE' }); break;
161
+ case '`': this.tokens.push({ type: 'BACKTICK' }); break;
162
+ case '=': this.tokens.push({ type: 'EQUALS' }); break;
163
+ case ',': this.tokens.push({ type: 'COMMA' }); break;
164
+ default:
165
+ this.error(`Unexpected character: ${char}`);
166
+ }
167
+ }
168
+
169
+ this.tokens.push({ type: 'EOF' });
170
+ return this.tokens;
171
+ }
172
+ }
173
+
174
+ export class StaticParser {
175
+ constructor(tokens, modulePath = '') {
176
+ this.tokens = tokens;
177
+ this.pos = 0;
178
+ this.modulePath = modulePath;
179
+ this.declarations = {};
180
+ }
181
+
182
+ error(message) {
183
+ const token = this.peek();
184
+ throw new Error(`Static Parse Error: ${message} (got ${token.type})`);
185
+ }
186
+
187
+ peek() {
188
+ return this.tokens[this.pos] || { type: 'EOF' };
189
+ }
190
+
191
+ advance() {
192
+ return this.tokens[this.pos++];
193
+ }
194
+
195
+ check(type) {
196
+ return this.peek().type === type;
197
+ }
198
+
199
+ match(type) {
200
+ if (this.check(type)) {
201
+ this.advance();
202
+ return true;
203
+ }
204
+ return false;
205
+ }
206
+
207
+ expect(type, message) {
208
+ if (!this.check(type)) {
209
+ this.error(message || `Expected ${type}`);
210
+ }
211
+ return this.advance();
212
+ }
213
+
214
+ skipNewlines() {
215
+ while (this.match('NEWLINE')) {}
216
+ }
217
+
218
+ parse() {
219
+ this.skipNewlines();
220
+
221
+ while (!this.check('EOF')) {
222
+ this.parseDeclaration();
223
+ this.skipNewlines();
224
+ }
225
+
226
+ return this.declarations;
227
+ }
228
+
229
+ parseDeclaration() {
230
+ // Check for secret modifier
231
+ let isSecret = false;
232
+ if (this.match('SECRET')) {
233
+ isSecret = true;
234
+ this.skipNewlines();
235
+ }
236
+
237
+ // Expect an identifier (the name)
238
+ const nameToken = this.expect('IDENTIFIER', 'Expected declaration name');
239
+ const name = nameToken.value;
240
+
241
+ this.skipNewlines();
242
+
243
+ // Determine type by next token
244
+ if (this.check('LBRACKET')) {
245
+ // Array declaration: Name [value1, value2, ...]
246
+ this.declarations[name] = this.parseArray();
247
+ } else if (this.check('LBRACE')) {
248
+ // Object declaration: Name { key = value, ... }
249
+ this.declarations[name] = this.parseObject();
250
+ } else if (this.check('BACKTICK')) {
251
+ // Enum declaration: Name `ENUM1 = foo, ENUM2 = bar`
252
+ this.declarations[name] = this.parseEnum();
253
+ } else if (this.check('STRING')) {
254
+ // String primitive: Name "value"
255
+ const token = this.advance();
256
+ this.declarations[name] = { type: 'literal', value: token.value, secret: isSecret };
257
+ return;
258
+ } else if (this.check('NUMBER')) {
259
+ // Number primitive: Name 123
260
+ const token = this.advance();
261
+ this.declarations[name] = { type: 'literal', value: token.value, secret: isSecret };
262
+ return;
263
+ } else if (this.check('BOOLEAN')) {
264
+ // Boolean primitive: Name true/false
265
+ const token = this.advance();
266
+ this.declarations[name] = { type: 'literal', value: token.value, secret: isSecret };
267
+ return;
268
+ } else {
269
+ this.error(`Expected [, {, \`, string, or number after declaration name "${name}"`);
270
+ }
271
+
272
+ // Mark the declaration as secret if needed
273
+ if (isSecret && this.declarations[name]) {
274
+ this.declarations[name].secret = true;
275
+ }
276
+ }
277
+
278
+ parseArray() {
279
+ this.expect('LBRACKET', 'Expected [');
280
+ const values = [];
281
+
282
+ this.skipNewlines();
283
+
284
+ while (!this.check('RBRACKET') && !this.check('EOF')) {
285
+ values.push(this.parseValue());
286
+
287
+ // Comma or newline separates values
288
+ if (this.match('COMMA')) {
289
+ this.skipNewlines();
290
+ } else if (this.check('NEWLINE')) {
291
+ this.skipNewlines();
292
+ }
293
+ }
294
+
295
+ this.expect('RBRACKET', 'Expected ]');
296
+
297
+ return { type: 'array', values };
298
+ }
299
+
300
+ parseObject() {
301
+ this.expect('LBRACE', 'Expected {');
302
+ const properties = {};
303
+
304
+ this.skipNewlines();
305
+
306
+ while (!this.check('RBRACE') && !this.check('EOF')) {
307
+ // Check for secret modifier on property
308
+ let isSecret = false;
309
+ if (this.match('SECRET')) {
310
+ isSecret = true;
311
+ this.skipNewlines();
312
+ }
313
+
314
+ const keyToken = this.expect('IDENTIFIER', 'Expected property name');
315
+ const key = keyToken.value;
316
+
317
+ this.expect('EQUALS', 'Expected = after property name');
318
+
319
+ const value = this.parseValue();
320
+ if (isSecret) {
321
+ value.secret = true;
322
+ }
323
+ properties[key] = value;
324
+
325
+ // Comma or newline separates properties
326
+ if (this.match('COMMA')) {
327
+ this.skipNewlines();
328
+ } else if (this.check('NEWLINE')) {
329
+ this.skipNewlines();
330
+ }
331
+ }
332
+
333
+ this.expect('RBRACE', 'Expected }');
334
+
335
+ return { type: 'object', properties };
336
+ }
337
+
338
+ parseEnum() {
339
+ this.expect('BACKTICK', 'Expected `');
340
+ const members = {};
341
+
342
+ while (!this.check('BACKTICK') && !this.check('EOF')) {
343
+ const nameToken = this.expect('IDENTIFIER', 'Expected enum member name');
344
+ const name = nameToken.value;
345
+
346
+ this.expect('EQUALS', 'Expected = after enum member name');
347
+
348
+ const value = this.parseValue();
349
+ members[name] = value;
350
+
351
+ // Comma or newline separates members
352
+ if (this.match('COMMA')) {
353
+ this.skipNewlines();
354
+ } else if (this.check('NEWLINE')) {
355
+ this.skipNewlines();
356
+ }
357
+ }
358
+
359
+ this.expect('BACKTICK', 'Expected closing `');
360
+
361
+ return { type: 'enum', members };
362
+ }
363
+
364
+ parseValue() {
365
+ this.skipNewlines();
366
+
367
+ const token = this.peek();
368
+
369
+ switch (token.type) {
370
+ case 'STRING':
371
+ this.advance();
372
+ return { type: 'literal', value: token.value };
373
+
374
+ case 'NUMBER':
375
+ this.advance();
376
+ return { type: 'literal', value: token.value };
377
+
378
+ case 'BOOLEAN':
379
+ this.advance();
380
+ return { type: 'literal', value: token.value };
381
+
382
+ case 'NULL':
383
+ this.advance();
384
+ return { type: 'literal', value: null };
385
+
386
+ case 'IDENTIFIER':
387
+ this.advance();
388
+ // Could be a reference to another static file's data (e.g., foo.bar.Baz)
389
+ if (token.value.includes('.')) {
390
+ return { type: 'reference', path: token.value };
391
+ }
392
+ // Or a local reference
393
+ return { type: 'reference', path: token.value };
394
+
395
+ case 'LBRACKET':
396
+ return this.parseArray();
397
+
398
+ case 'LBRACE':
399
+ return this.parseObject();
400
+
401
+ default:
402
+ this.error(`Unexpected token in value: ${token.type}`);
403
+ }
404
+ }
405
+ }
406
+
407
+ // Generate JavaScript code from parsed static file
408
+ export function generateStaticCode(declarations, modulePath) {
409
+ let code = '// Generated from .static file\n\n';
410
+
411
+ // Check if any declarations use secrets
412
+ const hasSecrets = checkForSecrets(declarations);
413
+
414
+ if (hasSecrets) {
415
+ // Add _secret helper for secret values
416
+ code += `// Secret wrapper class\n`;
417
+ code += `class _Secret {\n`;
418
+ code += ` constructor(value) { this._value = value; }\n`;
419
+ code += ` toString() { return "********"; }\n`;
420
+ code += ` valueOf() { return this._value; }\n`;
421
+ code += ` get value() { return this._value; }\n`;
422
+ code += ` [Symbol.toPrimitive](hint) { return hint === "string" ? "********" : this._value; }\n`;
423
+ code += `}\n`;
424
+ code += `function _secret(value) { return new _Secret(value); }\n\n`;
425
+ }
426
+
427
+ for (const [name, decl] of Object.entries(declarations)) {
428
+ code += `export const ${name} = ${generateValue(decl)};\n\n`;
429
+ }
430
+
431
+ return code;
432
+ }
433
+
434
+ // Check if any node in the declarations tree has secret: true
435
+ function checkForSecrets(obj) {
436
+ if (!obj || typeof obj !== 'object') return false;
437
+ if (obj.secret === true) return true;
438
+
439
+ for (const value of Object.values(obj)) {
440
+ if (checkForSecrets(value)) return true;
441
+ }
442
+ return false;
443
+ }
444
+
445
+ function generateValue(node) {
446
+ let value;
447
+
448
+ switch (node.type) {
449
+ case 'literal':
450
+ if (typeof node.value === 'string') {
451
+ value = JSON.stringify(node.value);
452
+ } else {
453
+ value = String(node.value);
454
+ }
455
+ break;
456
+
457
+ case 'reference':
458
+ // References are resolved at runtime via the dependency system
459
+ value = node.path;
460
+ break;
461
+
462
+ case 'array':
463
+ const arrayItems = node.values.map(v => generateValue(v)).join(', ');
464
+ value = `[${arrayItems}]`;
465
+ break;
466
+
467
+ case 'object':
468
+ const objProps = Object.entries(node.properties)
469
+ .map(([k, v]) => `${k}: ${generateValue(v)}`)
470
+ .join(', ');
471
+ value = `{ ${objProps} }`;
472
+ break;
473
+
474
+ case 'enum':
475
+ // Enums are frozen objects
476
+ const enumProps = Object.entries(node.members)
477
+ .map(([k, v]) => `${k}: ${generateValue(v)}`)
478
+ .join(', ');
479
+ value = `Object.freeze({ ${enumProps} })`;
480
+ break;
481
+
482
+ default:
483
+ throw new Error(`Unknown node type: ${node.type}`);
484
+ }
485
+
486
+ // Wrap in _secret() if marked as secret
487
+ if (node.secret) {
488
+ return `_secret(${value})`;
489
+ }
490
+
491
+ return value;
492
+ }
493
+
494
+ // Parse a static file and return declarations
495
+ export function parseStaticFile(source, modulePath = '') {
496
+ const lexer = new StaticLexer(source);
497
+ const tokens = lexer.tokenize();
498
+ const parser = new StaticParser(tokens, modulePath);
499
+ return parser.parse();
500
+ }