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/test/test.js
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
// KimchiLang Test Suite
|
|
2
|
+
|
|
3
|
+
import { compile, tokenize, parse, generate, KimchiCompiler } from '../src/index.js';
|
|
4
|
+
import { TypeChecker } from '../src/typechecker.js';
|
|
5
|
+
|
|
6
|
+
let passed = 0;
|
|
7
|
+
let failed = 0;
|
|
8
|
+
|
|
9
|
+
function test(name, fn) {
|
|
10
|
+
try {
|
|
11
|
+
fn();
|
|
12
|
+
console.log(`✓ ${name}`);
|
|
13
|
+
passed++;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.log(`✗ ${name}`);
|
|
16
|
+
console.log(` Error: ${error.message}`);
|
|
17
|
+
failed++;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function assertEqual(actual, expected, message = '') {
|
|
22
|
+
if (actual !== expected) {
|
|
23
|
+
throw new Error(`${message}\n Expected: ${expected}\n Actual: ${actual}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assertContains(str, substring, message = '') {
|
|
28
|
+
if (!str.includes(substring)) {
|
|
29
|
+
throw new Error(`${message}\n Expected to contain: ${substring}\n Actual: ${str}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('KimchiLang Test Suite\n');
|
|
34
|
+
console.log('='.repeat(50));
|
|
35
|
+
|
|
36
|
+
// Lexer Tests
|
|
37
|
+
console.log('\n--- Lexer Tests ---\n');
|
|
38
|
+
|
|
39
|
+
test('Tokenize numbers', () => {
|
|
40
|
+
const tokens = tokenize('42 3.14 0xFF 0b1010');
|
|
41
|
+
assertEqual(tokens[0].value, '42');
|
|
42
|
+
assertEqual(tokens[1].value, '3.14');
|
|
43
|
+
assertEqual(tokens[2].value, '0xFF');
|
|
44
|
+
assertEqual(tokens[3].value, '0b1010');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('Tokenize strings', () => {
|
|
48
|
+
const tokens = tokenize('"hello" \'world\'');
|
|
49
|
+
assertEqual(tokens[0].value, 'hello');
|
|
50
|
+
assertEqual(tokens[1].value, 'world');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('Tokenize identifiers and keywords', () => {
|
|
54
|
+
const tokens = tokenize('dec x fn if else');
|
|
55
|
+
assertEqual(tokens[0].type, 'DEC');
|
|
56
|
+
assertEqual(tokens[1].type, 'IDENTIFIER');
|
|
57
|
+
assertEqual(tokens[2].type, 'FN');
|
|
58
|
+
assertEqual(tokens[3].type, 'IF');
|
|
59
|
+
assertEqual(tokens[4].type, 'ELSE');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('Tokenize operators', () => {
|
|
63
|
+
// Test arithmetic operators with operands (/ after identifier is division)
|
|
64
|
+
const tokens1 = tokenize('a + b - c * d / e');
|
|
65
|
+
assertEqual(tokens1[0].type, 'IDENTIFIER');
|
|
66
|
+
assertEqual(tokens1[1].type, 'PLUS');
|
|
67
|
+
assertEqual(tokens1[2].type, 'IDENTIFIER');
|
|
68
|
+
assertEqual(tokens1[3].type, 'MINUS');
|
|
69
|
+
assertEqual(tokens1[4].type, 'IDENTIFIER');
|
|
70
|
+
assertEqual(tokens1[5].type, 'STAR');
|
|
71
|
+
assertEqual(tokens1[6].type, 'IDENTIFIER');
|
|
72
|
+
assertEqual(tokens1[7].type, 'SLASH');
|
|
73
|
+
assertEqual(tokens1[8].type, 'IDENTIFIER');
|
|
74
|
+
|
|
75
|
+
// Test comparison and logical operators
|
|
76
|
+
const tokens2 = tokenize('a == b != c <= d >= e && f || g >> h');
|
|
77
|
+
assertEqual(tokens2[1].type, 'EQ');
|
|
78
|
+
assertEqual(tokens2[3].type, 'NEQ');
|
|
79
|
+
assertEqual(tokens2[5].type, 'LTE');
|
|
80
|
+
assertEqual(tokens2[7].type, 'GTE');
|
|
81
|
+
assertEqual(tokens2[9].type, 'AND');
|
|
82
|
+
assertEqual(tokens2[11].type, 'OR');
|
|
83
|
+
assertEqual(tokens2[13].type, 'FLOW');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Parser Tests
|
|
87
|
+
console.log('\n--- Parser Tests ---\n');
|
|
88
|
+
|
|
89
|
+
test('Parse dec declaration', () => {
|
|
90
|
+
const tokens = tokenize('dec x = 42');
|
|
91
|
+
const ast = parse(tokens);
|
|
92
|
+
assertEqual(ast.body[0].type, 'DecDeclaration');
|
|
93
|
+
assertEqual(ast.body[0].name, 'x');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('Parse function declaration', () => {
|
|
97
|
+
const tokens = tokenize('fn add(a, b) { return a + b }');
|
|
98
|
+
const ast = parse(tokens);
|
|
99
|
+
assertEqual(ast.body[0].type, 'FunctionDeclaration');
|
|
100
|
+
assertEqual(ast.body[0].name, 'add');
|
|
101
|
+
assertEqual(ast.body[0].params.length, 2);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('Parse if statement', () => {
|
|
105
|
+
const tokens = tokenize('if x > 0 { print x }');
|
|
106
|
+
const ast = parse(tokens);
|
|
107
|
+
assertEqual(ast.body[0].type, 'IfStatement');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('Parse while loop', () => {
|
|
111
|
+
const tokens = tokenize('while x < 10 { x = x + 1 }');
|
|
112
|
+
const ast = parse(tokens);
|
|
113
|
+
assertEqual(ast.body[0].type, 'WhileStatement');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('Parse for loop', () => {
|
|
117
|
+
const tokens = tokenize('for item in items { print item }');
|
|
118
|
+
const ast = parse(tokens);
|
|
119
|
+
assertEqual(ast.body[0].type, 'ForInStatement');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('Parse array literal', () => {
|
|
123
|
+
const tokens = tokenize('dec arr = [1, 2, 3]');
|
|
124
|
+
const ast = parse(tokens);
|
|
125
|
+
assertEqual(ast.body[0].init.type, 'ArrayExpression');
|
|
126
|
+
assertEqual(ast.body[0].init.elements.length, 3);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('Parse object literal', () => {
|
|
130
|
+
const tokens = tokenize('dec obj = { a: 1, b: 2 }');
|
|
131
|
+
const ast = parse(tokens);
|
|
132
|
+
assertEqual(ast.body[0].init.type, 'ObjectExpression');
|
|
133
|
+
assertEqual(ast.body[0].init.properties.length, 2);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Code Generator Tests
|
|
137
|
+
console.log('\n--- Code Generator Tests ---\n');
|
|
138
|
+
|
|
139
|
+
test('Generate dec declaration', () => {
|
|
140
|
+
const js = compile('dec x = 42');
|
|
141
|
+
assertContains(js, '_deepFreeze(42)');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('Generate function declaration', () => {
|
|
145
|
+
const js = compile('fn greet(name) { return "Hello " + name }');
|
|
146
|
+
assertContains(js, 'function greet(name)');
|
|
147
|
+
assertContains(js, 'return');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('Generate print statement', () => {
|
|
151
|
+
const js = compile('print "hello"');
|
|
152
|
+
assertContains(js, 'console.log("hello")');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('Generate if statement', () => {
|
|
156
|
+
const js = compile('if x > 0 { print x }', { skipTypeCheck: true });
|
|
157
|
+
assertContains(js, 'if (');
|
|
158
|
+
assertContains(js, 'console.log');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('Generate while loop', () => {
|
|
162
|
+
const js = compile('while x < 10 { x = x + 1 }', { skipTypeCheck: true });
|
|
163
|
+
assertContains(js, 'while (');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('Generate for loop', () => {
|
|
167
|
+
const js = compile('for item in items { print item }', { skipTypeCheck: true });
|
|
168
|
+
assertContains(js, 'for (const item of items)');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('Generate arrow function', () => {
|
|
172
|
+
const js = compile('dec double = x => x * 2');
|
|
173
|
+
assertContains(js, 'x => (x * 2)');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('Generate flow expression', () => {
|
|
177
|
+
const js = compile('transform >> addOne double', { skipTypeCheck: true });
|
|
178
|
+
assertContains(js, 'const transform = _flow(addOne, double)');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('Generate pipe expression with _pipe helper', () => {
|
|
182
|
+
const js = compile('dec result = 5 ~> double ~> addOne', { skipTypeCheck: true });
|
|
183
|
+
assertContains(js, '_pipe(5, double, addOne)');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('Generate range expression', () => {
|
|
187
|
+
const js = compile('dec nums = 0..5');
|
|
188
|
+
assertContains(js, 'Array.from');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('Generate ternary expression', () => {
|
|
192
|
+
const js = compile('dec x = a > b ? a : b', { skipTypeCheck: true });
|
|
193
|
+
assertContains(js, '?');
|
|
194
|
+
assertContains(js, ':');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('Generate try/catch', () => {
|
|
198
|
+
const js = compile('try { risky() } catch(e) { print e }', { skipTypeCheck: true });
|
|
199
|
+
assertContains(js, 'try {');
|
|
200
|
+
assertContains(js, 'catch');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('Generate spread operator', () => {
|
|
204
|
+
const js = compile('dec arr = [...other, 1, 2]', { skipTypeCheck: true });
|
|
205
|
+
assertContains(js, '...other');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('Equality uses strict equality', () => {
|
|
209
|
+
const js = compile('dec x = a == b', { skipTypeCheck: true });
|
|
210
|
+
assertContains(js, '===');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('Inequality uses strict inequality', () => {
|
|
214
|
+
const js = compile('dec x = a != b', { skipTypeCheck: true });
|
|
215
|
+
assertContains(js, '!==');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Integration Tests
|
|
219
|
+
console.log('\n--- Integration Tests ---\n');
|
|
220
|
+
|
|
221
|
+
test('Compile fibonacci function', () => {
|
|
222
|
+
const source = `
|
|
223
|
+
fn fib(n) {
|
|
224
|
+
if n <= 1 {
|
|
225
|
+
return n
|
|
226
|
+
}
|
|
227
|
+
return fib(n - 1) + fib(n - 2)
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
const js = compile(source);
|
|
231
|
+
assertContains(js, 'function fib(n)');
|
|
232
|
+
assertContains(js, 'if (');
|
|
233
|
+
assertContains(js, 'return');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('Compile complex expression', () => {
|
|
237
|
+
const source = 'dec result = (a + b) * (c - d) / e ** 2';
|
|
238
|
+
const js = compile(source, { skipTypeCheck: true });
|
|
239
|
+
assertContains(js, '**');
|
|
240
|
+
assertContains(js, '/');
|
|
241
|
+
assertContains(js, '*');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Dependency System Tests
|
|
245
|
+
console.log('\n--- Dependency System Tests ---\n');
|
|
246
|
+
|
|
247
|
+
test('Parse dep statement', () => {
|
|
248
|
+
const tokens = tokenize('as client dep project.salesforce.client');
|
|
249
|
+
const ast = parse(tokens);
|
|
250
|
+
assertEqual(ast.body[0].type, 'DepStatement');
|
|
251
|
+
assertEqual(ast.body[0].alias, 'client');
|
|
252
|
+
assertEqual(ast.body[0].path, 'project.salesforce.client');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('Parse dep statement with overrides', () => {
|
|
256
|
+
const tokens = tokenize('as client dep project.salesforce.client({"bar.foo": mockFn})');
|
|
257
|
+
const ast = parse(tokens);
|
|
258
|
+
assertEqual(ast.body[0].type, 'DepStatement');
|
|
259
|
+
assertEqual(ast.body[0].alias, 'client');
|
|
260
|
+
assertEqual(ast.body[0].path, 'project.salesforce.client');
|
|
261
|
+
assertEqual(ast.body[0].overrides.type, 'ObjectExpression');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('Generate dep statement imports module', () => {
|
|
265
|
+
const js = compile('as sfdcClient dep project.salesforce.client');
|
|
266
|
+
assertContains(js, "import _dep_sfdcClient from './project/salesforce/client.km'");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('Generate dep statement with factory call', () => {
|
|
270
|
+
const js = compile('as sfdcClient dep project.salesforce.client');
|
|
271
|
+
assertContains(js, '_opts["project.salesforce.client"] || _dep_sfdcClient()');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('Generate dep statement with overrides', () => {
|
|
275
|
+
const js = compile('as client dep myapp.api.client({"lib.http": mockHttp})');
|
|
276
|
+
assertContains(js, '"lib.http": mockHttp');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('Module wraps as factory function', () => {
|
|
280
|
+
const js = compile('expose fn hello() { print "hi" }');
|
|
281
|
+
assertContains(js, 'export default function(_opts = {})');
|
|
282
|
+
assertContains(js, 'return { hello }');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Arg System Tests
|
|
286
|
+
console.log('\n--- Arg System Tests ---\n');
|
|
287
|
+
|
|
288
|
+
test('Parse optional arg', () => {
|
|
289
|
+
const tokens = tokenize('arg clientId');
|
|
290
|
+
const ast = parse(tokens);
|
|
291
|
+
assertEqual(ast.body[0].type, 'ArgDeclaration');
|
|
292
|
+
assertEqual(ast.body[0].name, 'clientId');
|
|
293
|
+
assertEqual(ast.body[0].required, false);
|
|
294
|
+
assertEqual(ast.body[0].defaultValue, null);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('Parse required arg', () => {
|
|
298
|
+
const tokens = tokenize('!arg apiKey');
|
|
299
|
+
const ast = parse(tokens);
|
|
300
|
+
assertEqual(ast.body[0].type, 'ArgDeclaration');
|
|
301
|
+
assertEqual(ast.body[0].name, 'apiKey');
|
|
302
|
+
assertEqual(ast.body[0].required, true);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('Parse arg with default value', () => {
|
|
306
|
+
const tokens = tokenize('arg timeout = 5000');
|
|
307
|
+
const ast = parse(tokens);
|
|
308
|
+
assertEqual(ast.body[0].type, 'ArgDeclaration');
|
|
309
|
+
assertEqual(ast.body[0].name, 'timeout');
|
|
310
|
+
assertEqual(ast.body[0].required, false);
|
|
311
|
+
assertEqual(ast.body[0].defaultValue.type, 'Literal');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('Generate optional arg extraction', () => {
|
|
315
|
+
const js = compile('arg clientId');
|
|
316
|
+
assertContains(js, 'const clientId = _opts["clientId"]');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('Generate required arg validation', () => {
|
|
320
|
+
const js = compile('!arg apiKey');
|
|
321
|
+
assertContains(js, 'if (_opts["apiKey"] === undefined) throw new Error');
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('Generate arg with default value', () => {
|
|
325
|
+
const js = compile('arg timeout = 5000');
|
|
326
|
+
assertContains(js, '_opts["timeout"] !== undefined ? _opts["timeout"] : 5000');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test('Generate module with deps and args', () => {
|
|
330
|
+
const source = `
|
|
331
|
+
as http dep myapp.lib.http
|
|
332
|
+
arg clientId = "default123"
|
|
333
|
+
!arg apiKey
|
|
334
|
+
fn doRequest() { return http.get("/api") }
|
|
335
|
+
`;
|
|
336
|
+
const js = compile(source);
|
|
337
|
+
assertContains(js, 'import _dep_http');
|
|
338
|
+
assertContains(js, 'if (_opts["apiKey"] === undefined)');
|
|
339
|
+
assertContains(js, 'const clientId = _opts["clientId"] !== undefined');
|
|
340
|
+
assertContains(js, 'const apiKey = _opts["apiKey"]');
|
|
341
|
+
assertContains(js, 'const http = _opts["myapp.lib.http"] || _dep_http()');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('Compile-time error for missing required arg in dep call', () => {
|
|
345
|
+
// First, register a module with required args
|
|
346
|
+
KimchiCompiler.registerModule('test.api', ['apiKey']);
|
|
347
|
+
|
|
348
|
+
// Then try to use that module without providing the required arg
|
|
349
|
+
let error = null;
|
|
350
|
+
try {
|
|
351
|
+
const compiler = new KimchiCompiler();
|
|
352
|
+
compiler.compile('as api dep test.api');
|
|
353
|
+
} catch (e) {
|
|
354
|
+
error = e;
|
|
355
|
+
}
|
|
356
|
+
assertEqual(error !== null, true);
|
|
357
|
+
assertContains(error.message, "Required argument 'apiKey' not provided");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('No error when required arg is provided in dep call', () => {
|
|
361
|
+
// Register a module with required args
|
|
362
|
+
KimchiCompiler.registerModule('test.service', ['token']);
|
|
363
|
+
|
|
364
|
+
// Use that module with the required arg provided
|
|
365
|
+
let error = null;
|
|
366
|
+
try {
|
|
367
|
+
const compiler = new KimchiCompiler();
|
|
368
|
+
compiler.compile('as svc dep test.service({ token: "abc123" })');
|
|
369
|
+
} catch (e) {
|
|
370
|
+
error = e;
|
|
371
|
+
}
|
|
372
|
+
assertEqual(error, null);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Dec System Tests
|
|
376
|
+
console.log('\n--- Dec System Tests ---\n');
|
|
377
|
+
|
|
378
|
+
test('Parse dec declaration', () => {
|
|
379
|
+
const tokens = tokenize('dec config = { foo: "bar" }');
|
|
380
|
+
const ast = parse(tokens);
|
|
381
|
+
assertEqual(ast.body[0].type, 'DecDeclaration');
|
|
382
|
+
assertEqual(ast.body[0].name, 'config');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('Generate dec with deepFreeze', () => {
|
|
386
|
+
const js = compile('dec config = { foo: "bar" }');
|
|
387
|
+
assertContains(js, 'function _deepFreeze(obj)');
|
|
388
|
+
assertContains(js, 'const config = _deepFreeze({ foo: "bar" })');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('Compile-time error on dec reassignment', () => {
|
|
392
|
+
let error = null;
|
|
393
|
+
try {
|
|
394
|
+
compile('dec x = 5\nx = 10');
|
|
395
|
+
} catch (e) {
|
|
396
|
+
error = e;
|
|
397
|
+
}
|
|
398
|
+
assertEqual(error !== null, true);
|
|
399
|
+
assertContains(error.message, 'deeply immutable');
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('Compile-time error on dec nested property reassignment', () => {
|
|
403
|
+
let error = null;
|
|
404
|
+
try {
|
|
405
|
+
compile('dec obj = { foo: { bar: "baz" } }\nobj.foo.bar = "new"');
|
|
406
|
+
} catch (e) {
|
|
407
|
+
error = e;
|
|
408
|
+
}
|
|
409
|
+
assertEqual(error !== null, true);
|
|
410
|
+
assertContains(error.message, 'deeply immutable');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('Dec requires initialization', () => {
|
|
414
|
+
let error = null;
|
|
415
|
+
try {
|
|
416
|
+
compile('dec x');
|
|
417
|
+
} catch (e) {
|
|
418
|
+
error = e;
|
|
419
|
+
}
|
|
420
|
+
assertEqual(error !== null, true);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Expose System Tests
|
|
424
|
+
console.log('\n--- Expose System Tests ---\n');
|
|
425
|
+
|
|
426
|
+
test('Parse expose dec declaration', () => {
|
|
427
|
+
const tokens = tokenize('expose dec foo = 42');
|
|
428
|
+
const ast = parse(tokens);
|
|
429
|
+
assertEqual(ast.body[0].type, 'DecDeclaration');
|
|
430
|
+
assertEqual(ast.body[0].name, 'foo');
|
|
431
|
+
assertEqual(ast.body[0].exposed, true);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test('Parse non-exposed dec declaration', () => {
|
|
435
|
+
const tokens = tokenize('dec bar = 42');
|
|
436
|
+
const ast = parse(tokens);
|
|
437
|
+
assertEqual(ast.body[0].exposed, false);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('Parse expose fn declaration', () => {
|
|
441
|
+
const tokens = tokenize('expose fn greet() { return "hi" }');
|
|
442
|
+
const ast = parse(tokens);
|
|
443
|
+
assertEqual(ast.body[0].type, 'FunctionDeclaration');
|
|
444
|
+
assertEqual(ast.body[0].exposed, true);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test('Only exposed items are exported', () => {
|
|
448
|
+
const source = `
|
|
449
|
+
expose fn publicFn() { return 1 }
|
|
450
|
+
fn privateFn() { return 2 }
|
|
451
|
+
expose dec publicVar = 10
|
|
452
|
+
dec privateVar = 20
|
|
453
|
+
`;
|
|
454
|
+
const js = compile(source);
|
|
455
|
+
assertContains(js, 'return { publicFn, publicVar }');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('Private function not in exports', () => {
|
|
459
|
+
const js = compile('fn privateFn() { return 1 }');
|
|
460
|
+
// Should not have return statement with exports since nothing is exposed
|
|
461
|
+
assertEqual(js.includes('return {'), false);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Dependency Type Checking Tests
|
|
465
|
+
console.log('\n--- Dependency Type Checking Tests ---\n');
|
|
466
|
+
|
|
467
|
+
test('TypeChecker registers module export types', () => {
|
|
468
|
+
TypeChecker.clearRegistry();
|
|
469
|
+
|
|
470
|
+
// Compile a module with exposed declarations
|
|
471
|
+
const source = `
|
|
472
|
+
expose dec apiVersion = "1.0"
|
|
473
|
+
expose fn greet(name) { return "Hello " + name }
|
|
474
|
+
arg timeout = 5000
|
|
475
|
+
`;
|
|
476
|
+
const tokens = tokenize(source);
|
|
477
|
+
const ast = parse(tokens);
|
|
478
|
+
|
|
479
|
+
const checker = new TypeChecker({ modulePath: 'test.module' });
|
|
480
|
+
checker.check(ast);
|
|
481
|
+
|
|
482
|
+
const moduleType = TypeChecker.getModuleType('test.module');
|
|
483
|
+
assertEqual(moduleType !== null, true);
|
|
484
|
+
assertEqual(moduleType.kind, 'object');
|
|
485
|
+
assertEqual('apiVersion' in moduleType.properties, true);
|
|
486
|
+
assertEqual('greet' in moduleType.properties, true);
|
|
487
|
+
assertEqual('timeout' in moduleType.properties, true);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test('Dep alias is defined in scope with module type', () => {
|
|
491
|
+
TypeChecker.clearRegistry();
|
|
492
|
+
|
|
493
|
+
// First register a module type
|
|
494
|
+
TypeChecker.registerModuleType('lib.http', {
|
|
495
|
+
kind: 'object',
|
|
496
|
+
properties: {
|
|
497
|
+
get: { kind: 'function', params: [], returnType: { kind: 'unknown' } },
|
|
498
|
+
post: { kind: 'function', params: [], returnType: { kind: 'unknown' } }
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Now compile code that uses that dependency
|
|
503
|
+
const source = `
|
|
504
|
+
as http dep lib.http
|
|
505
|
+
dec result = http.get("/api")
|
|
506
|
+
`;
|
|
507
|
+
const tokens = tokenize(source);
|
|
508
|
+
const ast = parse(tokens);
|
|
509
|
+
|
|
510
|
+
const checker = new TypeChecker();
|
|
511
|
+
const errors = checker.check(ast);
|
|
512
|
+
|
|
513
|
+
// Should not have errors - http.get exists
|
|
514
|
+
assertEqual(errors.length, 0);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// Regex Pattern Matching Tests
|
|
518
|
+
console.log('\n--- Regex Pattern Matching Tests ---\n');
|
|
519
|
+
|
|
520
|
+
test('Tokenize regex literal', () => {
|
|
521
|
+
const tokens = tokenize('/hello/');
|
|
522
|
+
assertEqual(tokens[0].type, 'REGEX');
|
|
523
|
+
assertEqual(tokens[0].value.pattern, 'hello');
|
|
524
|
+
assertEqual(tokens[0].value.flags, '');
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test('Tokenize regex literal with flags', () => {
|
|
528
|
+
const tokens = tokenize('/hello/gi');
|
|
529
|
+
assertEqual(tokens[0].type, 'REGEX');
|
|
530
|
+
assertEqual(tokens[0].value.pattern, 'hello');
|
|
531
|
+
assertEqual(tokens[0].value.flags, 'gi');
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
test('Tokenize regex with escaped characters', () => {
|
|
535
|
+
const tokens = tokenize('/\\d+\\.\\d+/');
|
|
536
|
+
assertEqual(tokens[0].type, 'REGEX');
|
|
537
|
+
assertEqual(tokens[0].value.pattern, '\\d+\\.\\d+');
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test('Tokenize match operator', () => {
|
|
541
|
+
const tokens = tokenize('input ~ /hello/');
|
|
542
|
+
assertEqual(tokens[0].type, 'IDENTIFIER');
|
|
543
|
+
assertEqual(tokens[1].type, 'MATCH');
|
|
544
|
+
assertEqual(tokens[2].type, 'REGEX');
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test('Parse simple match expression', () => {
|
|
548
|
+
const tokens = tokenize('dec result = "hello world" ~ /hello/');
|
|
549
|
+
const ast = parse(tokens);
|
|
550
|
+
assertEqual(ast.body[0].type, 'DecDeclaration');
|
|
551
|
+
assertEqual(ast.body[0].init.type, 'MatchExpression');
|
|
552
|
+
assertEqual(ast.body[0].init.subject.type, 'Literal');
|
|
553
|
+
assertEqual(ast.body[0].init.pattern.type, 'RegexLiteral');
|
|
554
|
+
assertEqual(ast.body[0].init.pattern.pattern, 'hello');
|
|
555
|
+
assertEqual(ast.body[0].init.body, null);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test('Parse match expression with body', () => {
|
|
559
|
+
const tokens = tokenize('dec result = "hello" ~ /hello/ => { return "bar" }');
|
|
560
|
+
const ast = parse(tokens);
|
|
561
|
+
assertEqual(ast.body[0].type, 'DecDeclaration');
|
|
562
|
+
assertEqual(ast.body[0].init.type, 'MatchExpression');
|
|
563
|
+
assertEqual(ast.body[0].init.body.type, 'BlockStatement');
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test('Generate simple match expression', () => {
|
|
567
|
+
const source = 'dec foo = "test foo" ~ /foo/';
|
|
568
|
+
const tokens = tokenize(source);
|
|
569
|
+
const ast = parse(tokens);
|
|
570
|
+
const code = generate(ast);
|
|
571
|
+
assertEqual(code.includes('/foo/.exec'), true);
|
|
572
|
+
assertEqual(code.includes('|| [])[0]'), true);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test('Generate match expression with body', () => {
|
|
576
|
+
const source = 'dec foo = "test" ~ /test/ => { return "bar" }';
|
|
577
|
+
const tokens = tokenize(source);
|
|
578
|
+
const ast = parse(tokens);
|
|
579
|
+
const code = generate(ast);
|
|
580
|
+
assertEqual(code.includes('$match'), true);
|
|
581
|
+
assertEqual(code.includes('return "bar"'), true);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test('Regex literal in expression', () => {
|
|
585
|
+
const source = 'dec pattern = /\\w+/g';
|
|
586
|
+
const tokens = tokenize(source);
|
|
587
|
+
const ast = parse(tokens);
|
|
588
|
+
assertEqual(ast.body[0].init.type, 'RegexLiteral');
|
|
589
|
+
assertEqual(ast.body[0].init.pattern, '\\w+');
|
|
590
|
+
assertEqual(ast.body[0].init.flags, 'g');
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// Summary
|
|
594
|
+
console.log('\n' + '='.repeat(50));
|
|
595
|
+
console.log(`\nTests: ${passed + failed} total, ${passed} passed, ${failed} failed`);
|
|
596
|
+
|
|
597
|
+
if (failed > 0) {
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|