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/README.md
ADDED
|
@@ -0,0 +1,1547 @@
|
|
|
1
|
+
# KimchiLang 🌶️
|
|
2
|
+
|
|
3
|
+
*Some will think it stinks, others will love it—no matter what it's spicy and good for you!*
|
|
4
|
+
|
|
5
|
+
A modern, expressive programming language that transpiles to JavaScript.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Language Guide](#language-guide)
|
|
13
|
+
- [Scope and Design Philosophy](#scope-and-design-philosophy)
|
|
14
|
+
- [Variables](#variables)
|
|
15
|
+
- [Type Inference](#type-inference)
|
|
16
|
+
- [Module Visibility (expose)](#module-visibility-expose)
|
|
17
|
+
- [Secrets](#secrets)
|
|
18
|
+
- [Functions](#functions)
|
|
19
|
+
- [Enums](#enums)
|
|
20
|
+
- [Anonymous Functions (Arrow Functions)](#anonymous-functions-arrow-functions)
|
|
21
|
+
- [Control Flow](#control-flow)
|
|
22
|
+
- [Flow Operator](#flow-operator)
|
|
23
|
+
- [Pattern Matching](#pattern-matching)
|
|
24
|
+
- [Arrays & Objects](#arrays--objects)
|
|
25
|
+
- [Safe Member Access](#safe-member-access)
|
|
26
|
+
- [String Interpolation](#string-interpolation)
|
|
27
|
+
- [Pipe Operator](#pipe-operator)
|
|
28
|
+
- [Memoized Functions](#memoized-functions)
|
|
29
|
+
- [Error Handling](#error-handling)
|
|
30
|
+
- [JavaScript Interop](#javascript-interop)
|
|
31
|
+
- [Shell Interop](#shell-interop)
|
|
32
|
+
- [Static Files](#static-files)
|
|
33
|
+
- [Dependency Injection System](#dependency-injection-system)
|
|
34
|
+
- [Module Arguments](#module-arguments)
|
|
35
|
+
- [CLI Commands](#cli-commands)
|
|
36
|
+
- [Module Execution](#module-execution)
|
|
37
|
+
- [Basic Commands](#basic-commands)
|
|
38
|
+
- [Reverse Transpiler](#reverse-transpiler-javascript-to-kimchilang)
|
|
39
|
+
- [NPM Integration](#npm-integration)
|
|
40
|
+
- [Editor Extensions](#editor-extensions)
|
|
41
|
+
- [Windsurf](#windsurf)
|
|
42
|
+
- [VS Code](#vs-code)
|
|
43
|
+
- [Other Editors](#other-editors)
|
|
44
|
+
- [Standard Library](#standard-library)
|
|
45
|
+
- [Logger](#logger)
|
|
46
|
+
- [Bitwise](#bitwise)
|
|
47
|
+
- [Package Management](#package-management)
|
|
48
|
+
- [Testing](#testing)
|
|
49
|
+
- [Test Syntax](#test-syntax)
|
|
50
|
+
- [Matchers](#matchers)
|
|
51
|
+
- [Testing with Mocks](#testing-with-mocks)
|
|
52
|
+
- [Running Tests](#running-tests)
|
|
53
|
+
- [File Extensions](#file-extensions)
|
|
54
|
+
- [How It Works](#how-it-works)
|
|
55
|
+
- [Examples](#examples)
|
|
56
|
+
- [License](#license)
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Clean Syntax** - Python-inspired readability with JavaScript power
|
|
61
|
+
- **Modern Operators** - Pipe operator (`~>`), flow operator (`>>`), range expressions (`0..10`), spread operator
|
|
62
|
+
- **String Interpolation** - Easy string formatting with `"Hello ${name}!"`
|
|
63
|
+
- **Purely Functional** - No classes, no `this`, no global scope
|
|
64
|
+
- **Deeply Immutable** - All values are immutable with compile-time checking
|
|
65
|
+
- **Pattern Matching** - `match`/`when` expressions for elegant control flow
|
|
66
|
+
- **Arrow Functions** - Concise lambda syntax
|
|
67
|
+
- **Print Statement** - Built-in `print` for quick debugging
|
|
68
|
+
- **Strict Equality** - `==` compiles to `===` for safer comparisons
|
|
69
|
+
- **Safe Member Access** - All property access is null-safe by default
|
|
70
|
+
- **Memoization** - Built-in `memo` keyword for memoized functions
|
|
71
|
+
- **Type Inference** - Compile-time type checking without annotations
|
|
72
|
+
- **JavaScript Interop** - Embed raw JavaScript with `js { }` blocks
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Clone the repository
|
|
78
|
+
git clone https://github.com/danajanezic/kimchilang.git
|
|
79
|
+
cd kimchilang
|
|
80
|
+
|
|
81
|
+
# Run the installer (installs dependencies and links the 'kimchi' command)
|
|
82
|
+
./install.sh
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
After installation, the `kimchi` command is available globally:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
kimchi run examples/hello.kimchi
|
|
89
|
+
kimchi compile app.kimchi
|
|
90
|
+
kimchi convert input.js
|
|
91
|
+
kimchi help
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Manual installation** (if you prefer not to use the installer):
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install
|
|
98
|
+
npm link
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Quick Start
|
|
102
|
+
|
|
103
|
+
### Create a New Project
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx create-kimchi-app my-app
|
|
107
|
+
cd my-app
|
|
108
|
+
kimchi src.main
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This creates a new project with:
|
|
112
|
+
- `src/main.km` - Main entry point
|
|
113
|
+
- `lib/utils.km` - Example utility module
|
|
114
|
+
- `tests/utils.test.km` - Example tests
|
|
115
|
+
- `project.static` - Project configuration
|
|
116
|
+
|
|
117
|
+
### Or Create a Single File
|
|
118
|
+
|
|
119
|
+
Create a file called `hello.kimchi`:
|
|
120
|
+
|
|
121
|
+
```kimchi
|
|
122
|
+
print "Hello, KimchiLang!"
|
|
123
|
+
|
|
124
|
+
fn greet(name) {
|
|
125
|
+
return "Welcome, " + name + "!"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
print greet("Developer")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Run it:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
kimchi run hello.kimchi
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Language Guide
|
|
138
|
+
|
|
139
|
+
### Scope and Design Philosophy
|
|
140
|
+
|
|
141
|
+
KimchiLang is a **purely functional language** with these core principles:
|
|
142
|
+
|
|
143
|
+
- **No global scope** - Everything is always local
|
|
144
|
+
- **No `this` keyword** - No object-oriented programming
|
|
145
|
+
- **No classes** - Use functions and modules for code organization
|
|
146
|
+
- **All values are immutable** - Enforced at compile-time
|
|
147
|
+
|
|
148
|
+
### Variables
|
|
149
|
+
|
|
150
|
+
KimchiLang uses `dec` for all variable declarations. All variables are **deeply immutable** - both the variable and any nested properties cannot be reassigned.
|
|
151
|
+
|
|
152
|
+
```kimchi
|
|
153
|
+
dec name = "Alice"
|
|
154
|
+
dec PI = 3.14159
|
|
155
|
+
dec config = {
|
|
156
|
+
api: {
|
|
157
|
+
url: "https://api.example.com",
|
|
158
|
+
timeout: 5000
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Destructuring:**
|
|
164
|
+
|
|
165
|
+
```kimchi
|
|
166
|
+
// Object destructuring
|
|
167
|
+
dec person = { name: "Alice", age: 30, city: "NYC" }
|
|
168
|
+
dec { name, age } = person
|
|
169
|
+
|
|
170
|
+
// With renaming
|
|
171
|
+
dec { name: userName, city: userCity } = person
|
|
172
|
+
|
|
173
|
+
// Array destructuring
|
|
174
|
+
dec numbers = [1, 2, 3, 4, 5]
|
|
175
|
+
dec [first, second, third] = numbers
|
|
176
|
+
|
|
177
|
+
// Skip elements with holes
|
|
178
|
+
dec [a, , c] = numbers // a=1, c=3
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Compile-time immutability checking:**
|
|
182
|
+
|
|
183
|
+
```kimchi
|
|
184
|
+
dec obj = { foo: { bar: "baz" } }
|
|
185
|
+
|
|
186
|
+
obj = {} // Compile error: Cannot reassign 'obj'
|
|
187
|
+
obj.foo = {} // Compile error: Cannot reassign 'obj.foo'
|
|
188
|
+
obj.foo.bar = "new" // Compile error: Cannot reassign 'obj.foo.bar'
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
At runtime, `dec` values are wrapped with `Object.freeze` recursively for additional protection.
|
|
192
|
+
|
|
193
|
+
### Type Inference
|
|
194
|
+
|
|
195
|
+
KimchiLang performs compile-time type checking without requiring type annotations. Types are inferred from values and usage:
|
|
196
|
+
|
|
197
|
+
```kimchi
|
|
198
|
+
dec person = { name: "Alice", age: 30 }
|
|
199
|
+
print person.email // Compile error: Property 'email' does not exist on type { name: string, age: number }
|
|
200
|
+
|
|
201
|
+
dec x = 42
|
|
202
|
+
x() // Compile error: Type 'number' is not callable
|
|
203
|
+
|
|
204
|
+
enum Color { Red, Green, Blue }
|
|
205
|
+
print Color.Yellow // Compile error: Property 'Yellow' does not exist on enum 'Color'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The type checker catches:
|
|
209
|
+
- **Property access on non-existent properties** for known object shapes
|
|
210
|
+
- **Calling non-functions** (e.g., calling a number or string)
|
|
211
|
+
- **Invalid enum member access**
|
|
212
|
+
- **Destructuring non-existent properties**
|
|
213
|
+
- **Type mismatches in dependency injection** (when overriding module deps)
|
|
214
|
+
|
|
215
|
+
### Module Visibility (expose)
|
|
216
|
+
|
|
217
|
+
By default, all declarations are **private**. Use the `expose` keyword to make them available to other modules:
|
|
218
|
+
|
|
219
|
+
```kimchi
|
|
220
|
+
// Private - only accessible within this module
|
|
221
|
+
dec internalConfig = { secret: "hidden" }
|
|
222
|
+
fn helperFn() { return "internal" }
|
|
223
|
+
|
|
224
|
+
// Public - accessible by modules that depend on this one
|
|
225
|
+
expose dec API_VERSION = "1.0"
|
|
226
|
+
expose fn greet(name) { return "Hello, " + name }
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Attempting to access an unexposed member from a dependency will result in a compile-time error.
|
|
230
|
+
|
|
231
|
+
### Secrets
|
|
232
|
+
|
|
233
|
+
KimchiLang provides built-in protection for sensitive values like API keys, tokens, and passwords using the `secret` modifier.
|
|
234
|
+
|
|
235
|
+
**Declaring secrets:**
|
|
236
|
+
|
|
237
|
+
```kimchi
|
|
238
|
+
// Secret variables
|
|
239
|
+
secret dec apiKey = "sk-1234567890"
|
|
240
|
+
secret dec dbPassword = "super-secret"
|
|
241
|
+
|
|
242
|
+
// Secret environment variables
|
|
243
|
+
secret env DATABASE_URL
|
|
244
|
+
|
|
245
|
+
// Secret module arguments
|
|
246
|
+
secret arg authToken
|
|
247
|
+
secret !arg apiKey // Required secret argument
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**How secrets are protected:**
|
|
251
|
+
|
|
252
|
+
1. **Masked in output** - When converted to a string (e.g., in error messages or logs), secrets display as `********` instead of their actual value:
|
|
253
|
+
|
|
254
|
+
```kimchi
|
|
255
|
+
secret dec apiKey = "sk-1234567890"
|
|
256
|
+
print "Key: ${apiKey}" // Output: "Key: ********"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
2. **Compile-time protection in JS interop** - Secrets cannot be passed to `console.log` or other console methods inside `js { }` blocks:
|
|
260
|
+
|
|
261
|
+
```kimchi
|
|
262
|
+
secret dec apiKey = "sk-1234567890"
|
|
263
|
+
|
|
264
|
+
// This will FAIL at compile time:
|
|
265
|
+
js(apiKey) {
|
|
266
|
+
console.log(apiKey); // Error: Cannot pass secret 'apiKey' to console.log
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// This is allowed (using secret for its intended purpose):
|
|
270
|
+
js(apiKey) {
|
|
271
|
+
return fetch(url, { headers: { Authorization: apiKey } });
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The compiler checks for `console.log`, `console.error`, `console.warn`, `console.info`, `console.debug`, and `console.trace`.
|
|
276
|
+
|
|
277
|
+
3. **Value access** - To get the actual value of a secret (e.g., for API calls), use the `.value` property:
|
|
278
|
+
|
|
279
|
+
```kimchi
|
|
280
|
+
secret dec apiKey = "sk-1234567890"
|
|
281
|
+
|
|
282
|
+
// In JS interop, the value is accessible normally
|
|
283
|
+
dec response = js(apiKey) {
|
|
284
|
+
return fetch("https://api.example.com", {
|
|
285
|
+
headers: { "Authorization": "Bearer " + apiKey }
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Best practices:**
|
|
291
|
+
|
|
292
|
+
- Use `secret` for all sensitive values (API keys, passwords, tokens)
|
|
293
|
+
- Use `secret env` for environment variables containing credentials
|
|
294
|
+
- Use `secret arg` for sensitive module arguments
|
|
295
|
+
- Never log secrets - the compiler will catch attempts in JS blocks
|
|
296
|
+
- The `_Secret` wrapper ensures secrets don't accidentally appear in stack traces or error messages
|
|
297
|
+
|
|
298
|
+
### Functions
|
|
299
|
+
|
|
300
|
+
```kimchi
|
|
301
|
+
expose fn add(a, b) {
|
|
302
|
+
return a + b
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Async functions
|
|
306
|
+
async fn fetchData(url) {
|
|
307
|
+
dec response = await fetch(url)
|
|
308
|
+
dec data = await response.json()
|
|
309
|
+
return data
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Async memoized functions
|
|
313
|
+
async memo cachedFetch(url) {
|
|
314
|
+
dec response = await fetch(url)
|
|
315
|
+
return await response.json()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Default parameters
|
|
319
|
+
fn greet(name = "World") {
|
|
320
|
+
return "Hello, " + name
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
greet() // "Hello, World"
|
|
324
|
+
greet("Alice") // "Hello, Alice"
|
|
325
|
+
|
|
326
|
+
// Rest parameters
|
|
327
|
+
fn sum(...nums) {
|
|
328
|
+
return nums.reduce((acc, n) => acc + n, 0)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
sum(1, 2, 3, 4, 5) // 15
|
|
332
|
+
|
|
333
|
+
// Combined
|
|
334
|
+
fn log(prefix, separator = ": ", ...messages) {
|
|
335
|
+
return prefix + separator + messages.join(", ")
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Enums
|
|
340
|
+
|
|
341
|
+
Enums define a set of named constants with auto-incrementing numeric values:
|
|
342
|
+
|
|
343
|
+
```kimchi
|
|
344
|
+
enum Color {
|
|
345
|
+
Red, // 0
|
|
346
|
+
Green, // 1
|
|
347
|
+
Blue // 2
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
print Color.Red // 0
|
|
351
|
+
print Color.Green // 1
|
|
352
|
+
|
|
353
|
+
// Explicit values
|
|
354
|
+
enum HttpStatus {
|
|
355
|
+
OK = 200,
|
|
356
|
+
NotFound = 404,
|
|
357
|
+
ServerError = 500
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Mixed (auto-increment continues from last explicit value)
|
|
361
|
+
enum Priority {
|
|
362
|
+
Low, // 0
|
|
363
|
+
Medium, // 1
|
|
364
|
+
High = 10, // 10
|
|
365
|
+
Critical // 11
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Enums are frozen objects and can be used with pattern matching:
|
|
370
|
+
|
|
371
|
+
```kimchi
|
|
372
|
+
fn getStatusMessage(status) {
|
|
373
|
+
|status == HttpStatus.OK| => { return "Success" }
|
|
374
|
+
|status == HttpStatus.NotFound| => { return "Not Found" }
|
|
375
|
+
|true| => { return "Unknown" }
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Anonymous Functions (Arrow Functions)
|
|
380
|
+
|
|
381
|
+
KimchiLang supports arrow functions for concise anonymous function expressions:
|
|
382
|
+
|
|
383
|
+
```kimchi
|
|
384
|
+
// Single parameter (no parentheses needed)
|
|
385
|
+
dec double = x => x * 2
|
|
386
|
+
|
|
387
|
+
// Multiple parameters
|
|
388
|
+
dec add = (a, b) => a + b
|
|
389
|
+
|
|
390
|
+
// Block body for multiple statements
|
|
391
|
+
dec process = (x) => {
|
|
392
|
+
dec result = x * 2
|
|
393
|
+
return result + 1
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// As callbacks
|
|
397
|
+
dec numbers = [1, 2, 3, 4, 5]
|
|
398
|
+
dec doubled = numbers.map(x => x * 2)
|
|
399
|
+
dec sum = numbers.reduce((acc, n) => acc + n, 0)
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Pattern matching in arrow functions:**
|
|
403
|
+
|
|
404
|
+
Arrow functions with block bodies support pattern matching:
|
|
405
|
+
|
|
406
|
+
```kimchi
|
|
407
|
+
dec categorize = (item) => {
|
|
408
|
+
|item.type == "fruit"| => { return "produce" }
|
|
409
|
+
|item.type == "meat"| => { return "protein" }
|
|
410
|
+
|true| => { return "other" }
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Inline with reduce
|
|
414
|
+
dec balance = transactions.reduce((acc, tx) => {
|
|
415
|
+
|tx.type == "credit"| => { return acc + tx.amount }
|
|
416
|
+
|tx.type == "debit"| => { return acc - tx.amount }
|
|
417
|
+
|true| => { return acc }
|
|
418
|
+
}, 0)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Control Flow
|
|
422
|
+
|
|
423
|
+
```kimchi
|
|
424
|
+
// If/Elif/Else
|
|
425
|
+
if score >= 90 {
|
|
426
|
+
print "A"
|
|
427
|
+
} elif score >= 80 {
|
|
428
|
+
print "B"
|
|
429
|
+
} else {
|
|
430
|
+
print "C"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// While loop (use with JS interop for mutable state)
|
|
434
|
+
js {
|
|
435
|
+
let count = 0;
|
|
436
|
+
while (count < 3) {
|
|
437
|
+
console.log("Count: " + count);
|
|
438
|
+
count++;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// For loop (for-in)
|
|
443
|
+
for item in items {
|
|
444
|
+
print item
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Range expressions
|
|
448
|
+
for i in 0..5 {
|
|
449
|
+
print i // 0, 1, 2, 3, 4
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Flow Operator
|
|
454
|
+
|
|
455
|
+
Create composed functions with the `>>` flow operator:
|
|
456
|
+
|
|
457
|
+
```kimchi
|
|
458
|
+
fn addOne(x) { return x + 1 }
|
|
459
|
+
fn double(x) { return x * 2 }
|
|
460
|
+
fn square(x) { return x * x }
|
|
461
|
+
|
|
462
|
+
// Create a composed function
|
|
463
|
+
transform >> addOne double square
|
|
464
|
+
|
|
465
|
+
// Call it later
|
|
466
|
+
dec result = await transform(5) // square(double(addOne(5))) = 144
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
The flow syntax `name >> fn1 fn2 fn3` creates a new function `name` that composes `fn1`, `fn2`, and `fn3`. When called, arguments are passed to `fn1`, then the result flows through `fn2`, then `fn3`.
|
|
470
|
+
|
|
471
|
+
**Async Support:**
|
|
472
|
+
|
|
473
|
+
Flow-composed functions are async and handle both sync and async functions:
|
|
474
|
+
|
|
475
|
+
```kimchi
|
|
476
|
+
async fn fetchUser(id) { return { id: id, name: "User" + id } }
|
|
477
|
+
async fn enrichUser(user) { return { ...user, role: "admin" } }
|
|
478
|
+
fn formatUser(user) { return "${user.name} (${user.role})" }
|
|
479
|
+
|
|
480
|
+
// Create an async pipeline
|
|
481
|
+
processUser >> fetchUser enrichUser formatUser
|
|
482
|
+
|
|
483
|
+
async fn main() {
|
|
484
|
+
dec result = await processUser(1) // "User1 (admin)"
|
|
485
|
+
print result
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Difference from pipe operator:**
|
|
490
|
+
- `~>` (pipe): Immediately executes — `5 ~> double ~> addOne` returns a Promise
|
|
491
|
+
- `>>` (flow): Creates a reusable async function — `transform >> double addOne` creates a function you call later
|
|
492
|
+
|
|
493
|
+
### Pattern Matching
|
|
494
|
+
|
|
495
|
+
Standalone conditional pattern matching that returns from the enclosing function:
|
|
496
|
+
|
|
497
|
+
```kimchi
|
|
498
|
+
fn handleStatus(status) {
|
|
499
|
+
|status == 200| => print "OK"
|
|
500
|
+
|status == 404| => print "Not Found"
|
|
501
|
+
|status == 500| => print "Server Error"
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Each case uses `|condition|` delimiters followed by `=>` and the code to execute. When a condition matches, the code runs and the function returns.
|
|
506
|
+
|
|
507
|
+
### Arrays & Objects
|
|
508
|
+
|
|
509
|
+
```kimchi
|
|
510
|
+
dec numbers = [1, 2, 3, 4, 5]
|
|
511
|
+
dec person = {
|
|
512
|
+
name: "Bob",
|
|
513
|
+
age: 30
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Spread operator
|
|
517
|
+
dec more = [...numbers, 6, 7, 8]
|
|
518
|
+
|
|
519
|
+
// Object spread
|
|
520
|
+
dec updated = { ...person, age: 31 }
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Safe Member Access
|
|
524
|
+
|
|
525
|
+
All property access in KimchiLang is **null-safe by default**. The dot operator (`.`) compiles to JavaScript's optional chaining (`?.`), so accessing properties on `undefined` or `null` values returns `undefined` instead of throwing an error.
|
|
526
|
+
|
|
527
|
+
```kimchi
|
|
528
|
+
dec obj = { a: { b: { c: 1 } } }
|
|
529
|
+
|
|
530
|
+
print obj.a.b.c // 1
|
|
531
|
+
print obj.x.y.z // undefined (no error!)
|
|
532
|
+
|
|
533
|
+
// Works with arrays too
|
|
534
|
+
dec items = [{ name: "first" }]
|
|
535
|
+
print items[0].name // "first"
|
|
536
|
+
print items[5].name // undefined (no error!)
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
This eliminates the need for manual null checks or the `?.` operator - every property access is automatically safe.
|
|
540
|
+
|
|
541
|
+
### String Interpolation
|
|
542
|
+
|
|
543
|
+
Use `${expression}` inside strings for easy string interpolation:
|
|
544
|
+
|
|
545
|
+
```kimchi
|
|
546
|
+
dec name = "Alice"
|
|
547
|
+
dec age = 30
|
|
548
|
+
|
|
549
|
+
print "Hello, ${name}!" // "Hello, Alice!"
|
|
550
|
+
print "${name} is ${age} years old" // "Alice is 30 years old"
|
|
551
|
+
|
|
552
|
+
// Expressions work too
|
|
553
|
+
dec items = [1, 2, 3]
|
|
554
|
+
print "Count: ${items.length}" // "Count: 3"
|
|
555
|
+
print "Sum: ${items.sum()}" // "Sum: 6"
|
|
556
|
+
|
|
557
|
+
// Nested expressions
|
|
558
|
+
dec user = { name: "Bob", score: 95 }
|
|
559
|
+
print "${user.name} scored ${user.score}%" // "Bob scored 95%"
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
To include a literal `$` followed by `{`, escape it with a backslash:
|
|
563
|
+
|
|
564
|
+
```kimchi
|
|
565
|
+
print "Price: \${99.99}" // "Price: ${99.99}"
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Pipe Operator
|
|
569
|
+
|
|
570
|
+
Chain function calls with the `~>` pipe operator for readable data transformations:
|
|
571
|
+
|
|
572
|
+
```kimchi
|
|
573
|
+
fn double(x) { return x * 2 }
|
|
574
|
+
fn addOne(x) { return x + 1 }
|
|
575
|
+
fn square(x) { return x * x }
|
|
576
|
+
|
|
577
|
+
// Without pipe operator
|
|
578
|
+
dec result1 = square(addOne(double(5))) // 121
|
|
579
|
+
|
|
580
|
+
// With pipe operator - reads left to right!
|
|
581
|
+
dec result2 = 5 ~> double ~> addOne ~> square // 121
|
|
582
|
+
|
|
583
|
+
// Works great with array methods
|
|
584
|
+
dec numbers = [1, 2, 3, 4, 5]
|
|
585
|
+
dec processed = numbers
|
|
586
|
+
~> (arr => arr.map(x => x * 2))
|
|
587
|
+
~> (arr => arr.filter(x => x > 4))
|
|
588
|
+
~> (arr => arr.sum()) // 24
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
The pipe operator passes the left-hand value as the argument to the right-hand function: `a ~> f` becomes `f(a)`.
|
|
592
|
+
|
|
593
|
+
**Async Support:**
|
|
594
|
+
|
|
595
|
+
The pipe operator seamlessly handles async functions - each step is awaited automatically:
|
|
596
|
+
|
|
597
|
+
```kimchi
|
|
598
|
+
async fn fetchUser(id) { return { id: id, name: "User" + id } }
|
|
599
|
+
async fn enrichUser(user) { return { ...user, email: user.name + "@example.com" } }
|
|
600
|
+
|
|
601
|
+
async fn main() {
|
|
602
|
+
// Pipe through async functions - use await on the result
|
|
603
|
+
dec user = await (1 ~> fetchUser ~> enrichUser)
|
|
604
|
+
print user.email // "User1@example.com"
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Memoized Functions
|
|
609
|
+
|
|
610
|
+
Use the `memo` keyword instead of `fn` to create memoized functions that cache their results:
|
|
611
|
+
|
|
612
|
+
```kimchi
|
|
613
|
+
// Memoized fibonacci - exponentially faster!
|
|
614
|
+
memo fib(n) {
|
|
615
|
+
|n <= 1| => { return n }
|
|
616
|
+
|true| => { return fib(n - 1) + fib(n - 2) }
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
print fib(40) // Instant! (would be very slow without memoization)
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
The cache uses a `Map` keyed by `JSON.stringify` of the arguments, so it works with any serializable arguments.
|
|
623
|
+
|
|
624
|
+
### Error Handling
|
|
625
|
+
|
|
626
|
+
**Basic try/catch/finally:**
|
|
627
|
+
|
|
628
|
+
```kimchi
|
|
629
|
+
try {
|
|
630
|
+
riskyOperation()
|
|
631
|
+
} catch(e) {
|
|
632
|
+
print "Error: " + e.message
|
|
633
|
+
} finally {
|
|
634
|
+
cleanup()
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**Creating typed errors with `error.create()`:**
|
|
639
|
+
|
|
640
|
+
Use `error.create(name)` to define reusable error types:
|
|
641
|
+
|
|
642
|
+
```kimchi
|
|
643
|
+
// Define custom error types
|
|
644
|
+
dec NotFoundError = error.create("NotFoundError")
|
|
645
|
+
dec ValidationError = error.create("ValidationError")
|
|
646
|
+
dec AuthError = error.create("AuthError")
|
|
647
|
+
|
|
648
|
+
// Throw errors using your custom types
|
|
649
|
+
throw NotFoundError("User not found")
|
|
650
|
+
throw ValidationError("Email is invalid")
|
|
651
|
+
throw AuthError("Token expired")
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Each error created has:
|
|
655
|
+
- **`e.message`** - The error message you provide
|
|
656
|
+
- **`e._id`** - The error type identifier for matching with `is`
|
|
657
|
+
- **`e.stack`** - Full stack trace
|
|
658
|
+
|
|
659
|
+
**Catching errors by type with `is`:**
|
|
660
|
+
|
|
661
|
+
Use the `is` keyword to check if an error matches a specific type:
|
|
662
|
+
|
|
663
|
+
```kimchi
|
|
664
|
+
dec NotFoundError = error.create("NotFoundError")
|
|
665
|
+
dec ValidationError = error.create("ValidationError")
|
|
666
|
+
|
|
667
|
+
fn fetchUser(id) {
|
|
668
|
+
if id == 0 {
|
|
669
|
+
throw NotFoundError("User ${id} not found")
|
|
670
|
+
}
|
|
671
|
+
return { id: id, name: "Alice" }
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
fn handleRequest(id) {
|
|
675
|
+
try {
|
|
676
|
+
return fetchUser(id)
|
|
677
|
+
} catch(e) {
|
|
678
|
+
|e is NotFoundError| => {
|
|
679
|
+
print "Not found: ${e.message}"
|
|
680
|
+
return null
|
|
681
|
+
}
|
|
682
|
+
|e is ValidationError| => {
|
|
683
|
+
print "Invalid: ${e.message}"
|
|
684
|
+
return null
|
|
685
|
+
}
|
|
686
|
+
|true| => {
|
|
687
|
+
throw e // Re-throw unknown errors
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**Using `is not` for negated type checking:**
|
|
694
|
+
|
|
695
|
+
Use `is not` to check if an error does NOT match a specific type:
|
|
696
|
+
|
|
697
|
+
```kimchi
|
|
698
|
+
dec NetworkError = error.create("NetworkError")
|
|
699
|
+
|
|
700
|
+
fn handleError(e) {
|
|
701
|
+
|e is not NetworkError| => {
|
|
702
|
+
// Handle all non-network errors
|
|
703
|
+
print "Non-network error: ${e.message}"
|
|
704
|
+
return false
|
|
705
|
+
}
|
|
706
|
+
|true| => {
|
|
707
|
+
// Retry network errors
|
|
708
|
+
print "Network issue, retrying..."
|
|
709
|
+
return true
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
The `is` keyword compares the `._id` property of both sides, so `e is NotFoundError` compiles to `e?._id === NotFoundError?._id`.
|
|
715
|
+
|
|
716
|
+
### JavaScript Interop
|
|
717
|
+
|
|
718
|
+
Embed raw JavaScript code using `js { }` blocks. This provides an escape hatch for advanced JavaScript features or library usage.
|
|
719
|
+
|
|
720
|
+
**Basic JS block (no inputs):**
|
|
721
|
+
|
|
722
|
+
```kimchi
|
|
723
|
+
js {
|
|
724
|
+
console.log("Hello from raw JavaScript!");
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
**JS block with inputs from KimchiLang scope:**
|
|
729
|
+
|
|
730
|
+
Pass KimchiLang variables into the JS block explicitly:
|
|
731
|
+
|
|
732
|
+
```kimchi
|
|
733
|
+
dec name = "Alice"
|
|
734
|
+
dec count = 5
|
|
735
|
+
|
|
736
|
+
js(name, count) {
|
|
737
|
+
const greeting = `Hello, ${name}! Count: ${count}`;
|
|
738
|
+
console.log(greeting);
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
**JS block as expression (returns a value):**
|
|
743
|
+
|
|
744
|
+
```kimchi
|
|
745
|
+
dec numbers = [1, 2, 3, 4, 5]
|
|
746
|
+
|
|
747
|
+
dec sum = js(numbers) {
|
|
748
|
+
return numbers.reduce((a, b) => a + b, 0);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
print "Sum: ${sum}" // Sum: 15
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
**Accessing JavaScript libraries:**
|
|
755
|
+
|
|
756
|
+
```kimchi
|
|
757
|
+
dec timestamp = js {
|
|
758
|
+
return Date.now();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
dec uuid = js {
|
|
762
|
+
return crypto.randomUUID();
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**How it works:**
|
|
767
|
+
|
|
768
|
+
JS blocks are compiled to IIFEs (Immediately Invoked Function Expressions):
|
|
769
|
+
|
|
770
|
+
```kimchi
|
|
771
|
+
// KimchiLang
|
|
772
|
+
dec result = js(x, y) {
|
|
773
|
+
return x + y;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Compiles to JavaScript
|
|
777
|
+
const result = ((x, y) => {
|
|
778
|
+
return x + y;
|
|
779
|
+
})(x, y);
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
This ensures:
|
|
783
|
+
- **Isolated scope** - JS code can't accidentally modify KimchiLang variables
|
|
784
|
+
- **Explicit data flow** - Inputs must be declared, making dependencies clear
|
|
785
|
+
- **Return values** - Use `return` to pass data back to KimchiLang
|
|
786
|
+
|
|
787
|
+
### Shell Interop
|
|
788
|
+
|
|
789
|
+
Execute shell commands using `shell { }` blocks. Shell blocks are inherently async and functions containing them are automatically made async at compile time.
|
|
790
|
+
|
|
791
|
+
**Basic shell command:**
|
|
792
|
+
|
|
793
|
+
```kimchi
|
|
794
|
+
fn listFiles() {
|
|
795
|
+
dec result = shell { ls -la }
|
|
796
|
+
print result.stdout
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
listFiles() // Function is automatically async
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
**Shell block with inputs:**
|
|
803
|
+
|
|
804
|
+
Pass KimchiLang variables into the shell command using `$variable` syntax:
|
|
805
|
+
|
|
806
|
+
```kimchi
|
|
807
|
+
fn findFiles(pattern) {
|
|
808
|
+
dec result = shell(pattern) { find . -name "$pattern" }
|
|
809
|
+
return result.stdout
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
dec files = findFiles("*.km")
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
**Return value:**
|
|
816
|
+
|
|
817
|
+
Shell blocks return an object with:
|
|
818
|
+
- **`stdout`** - Standard output (trimmed)
|
|
819
|
+
- **`stderr`** - Standard error (trimmed)
|
|
820
|
+
- **`exitCode`** - Exit code (0 for success)
|
|
821
|
+
|
|
822
|
+
```kimchi
|
|
823
|
+
fn checkGit() {
|
|
824
|
+
dec result = shell { git status }
|
|
825
|
+
|
|
826
|
+
if result.exitCode == 0 {
|
|
827
|
+
print result.stdout
|
|
828
|
+
} else {
|
|
829
|
+
print "Error: ${result.stderr}"
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
**Multi-line commands:**
|
|
835
|
+
|
|
836
|
+
```kimchi
|
|
837
|
+
fn deploy() {
|
|
838
|
+
dec result = shell {
|
|
839
|
+
npm run build
|
|
840
|
+
npm run test
|
|
841
|
+
npm publish
|
|
842
|
+
}
|
|
843
|
+
return result
|
|
844
|
+
}
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**How it works:**
|
|
848
|
+
|
|
849
|
+
1. Shell blocks are captured as raw text (not tokenized)
|
|
850
|
+
2. Functions containing shell blocks are automatically made `async`
|
|
851
|
+
3. The shell command is executed using Node.js `child_process.exec`
|
|
852
|
+
4. Variables passed as inputs are interpolated into the command string
|
|
853
|
+
|
|
854
|
+
### Static Files
|
|
855
|
+
|
|
856
|
+
Static files (`.static` extension) are data-only files for configuration, constants, and enums. They are imported like modules but contain no executable code.
|
|
857
|
+
|
|
858
|
+
**File extension:** `.static`
|
|
859
|
+
|
|
860
|
+
**Syntax:**
|
|
861
|
+
|
|
862
|
+
```
|
|
863
|
+
// Primitive strings and numbers
|
|
864
|
+
AppName "MyApp"
|
|
865
|
+
Version "1.0.0"
|
|
866
|
+
MaxRetries 3
|
|
867
|
+
Timeout 5000
|
|
868
|
+
|
|
869
|
+
// Arrays: Name [value1, value2, ...]
|
|
870
|
+
Colors ["red", "green", "blue"]
|
|
871
|
+
|
|
872
|
+
// Objects: Name { key = value, key = value }
|
|
873
|
+
AppConfig {
|
|
874
|
+
name = "MyApp"
|
|
875
|
+
version = "1.0.0"
|
|
876
|
+
debug = true
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// Enums: Name `MEMBER1 = value, MEMBER2 = value`
|
|
880
|
+
HttpStatus `OK = 200, NOT_FOUND = 404, ERROR = 500`
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
**Multi-line declarations don't need commas:**
|
|
884
|
+
|
|
885
|
+
```
|
|
886
|
+
Endpoints {
|
|
887
|
+
api = "https://api.example.com"
|
|
888
|
+
auth = "https://auth.example.com"
|
|
889
|
+
cdn = "https://cdn.example.com"
|
|
890
|
+
}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
**Secret values:**
|
|
894
|
+
|
|
895
|
+
Use the `secret` keyword to protect sensitive values. Secrets are masked when converted to strings:
|
|
896
|
+
|
|
897
|
+
```
|
|
898
|
+
// Secret primitive
|
|
899
|
+
secret ApiKey "sk-1234567890abcdef"
|
|
900
|
+
secret InternalPort 8443
|
|
901
|
+
|
|
902
|
+
// Object with secret properties
|
|
903
|
+
DatabaseConfig {
|
|
904
|
+
host = "localhost"
|
|
905
|
+
port = 5432
|
|
906
|
+
secret username = "admin"
|
|
907
|
+
secret password = "super-secret-password"
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
Secret values:
|
|
912
|
+
- Display as `********` when logged or converted to string
|
|
913
|
+
- Actual value accessible via `.value` property
|
|
914
|
+
- Protected from accidental exposure in logs and error messages
|
|
915
|
+
|
|
916
|
+
**Importing static files:**
|
|
917
|
+
|
|
918
|
+
```kimchi
|
|
919
|
+
as config dep myapp.config
|
|
920
|
+
|
|
921
|
+
fn main() {
|
|
922
|
+
print config.AppConfig.name
|
|
923
|
+
print config.Colors
|
|
924
|
+
print config.HttpStatus.OK
|
|
925
|
+
}
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
**Key differences from modules:**
|
|
929
|
+
- Everything is exported by default (no `expose` keyword)
|
|
930
|
+
- No factory function wrapper (cannot be overridden)
|
|
931
|
+
- Only data declarations allowed (no functions or executable code)
|
|
932
|
+
- Compiles to plain JavaScript exports
|
|
933
|
+
|
|
934
|
+
**Cross-file references:**
|
|
935
|
+
|
|
936
|
+
Static files can reference data from other static files using dotted paths:
|
|
937
|
+
|
|
938
|
+
```
|
|
939
|
+
// In shared.static
|
|
940
|
+
BaseUrl "https://api.example.com"
|
|
941
|
+
|
|
942
|
+
// In config.static
|
|
943
|
+
Endpoints {
|
|
944
|
+
api = shared.BaseUrl
|
|
945
|
+
}
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
### Dependency Injection System
|
|
949
|
+
|
|
950
|
+
KimchiLang has a built-in dependency injection system using the `dep` keyword. Every module is automatically wrapped as a factory function that can accept dependency overrides.
|
|
951
|
+
|
|
952
|
+
**Basic dependency declaration:**
|
|
953
|
+
|
|
954
|
+
```kimchi
|
|
955
|
+
// Declare a dependency on myapp/lib/http.km
|
|
956
|
+
as http dep myapp.lib.http
|
|
957
|
+
|
|
958
|
+
fn fetchData() {
|
|
959
|
+
return http.get("https://api.example.com/data")
|
|
960
|
+
}
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
**Dependency injection for testing:**
|
|
964
|
+
|
|
965
|
+
```kimchi
|
|
966
|
+
// Create a mock
|
|
967
|
+
dec mockHttp = {
|
|
968
|
+
get: (url) => { return { data: "mock" } }
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Inject the mock when importing
|
|
972
|
+
as api dep myapp.services.api({"myapp.lib.http": mockHttp})
|
|
973
|
+
|
|
974
|
+
// Now api uses the mock http client
|
|
975
|
+
api.fetchData() // Uses mockHttp.get instead of real http.get
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
**How it works:**
|
|
979
|
+
|
|
980
|
+
1. `as <alias> dep <dotted.path>` - Declares a dependency
|
|
981
|
+
2. Dotted paths map to file paths: `myapp.lib.http` → `./myapp/lib/http.km`
|
|
982
|
+
3. Every module exports a factory function that accepts an `_opts` object
|
|
983
|
+
4. Dependencies check `_opts` first before using the real import
|
|
984
|
+
|
|
985
|
+
### Module Arguments
|
|
986
|
+
|
|
987
|
+
Modules can declare arguments using the `arg` keyword. Arguments and dependency overrides share the same options object.
|
|
988
|
+
|
|
989
|
+
**Argument syntax:**
|
|
990
|
+
|
|
991
|
+
```kimchi
|
|
992
|
+
arg timeout = 5000 // Optional arg with default value
|
|
993
|
+
arg clientId // Optional arg (undefined if not provided)
|
|
994
|
+
!arg apiKey // Required arg (throws error if missing)
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
**Using args when importing:**
|
|
998
|
+
|
|
999
|
+
```kimchi
|
|
1000
|
+
// Provide required args and override optional ones
|
|
1001
|
+
as api dep myapp.services.api({
|
|
1002
|
+
apiKey: "my-secret-key", // Required arg
|
|
1003
|
+
version: "v2" // Override default
|
|
1004
|
+
})
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
**Mixing deps and args:**
|
|
1008
|
+
|
|
1009
|
+
```kimchi
|
|
1010
|
+
// Both dependency overrides and args in the same object
|
|
1011
|
+
as api dep myapp.services.api({
|
|
1012
|
+
"myapp.lib.http": mockHttp, // Dependency override (dotted path)
|
|
1013
|
+
apiKey: "test-key", // Required arg
|
|
1014
|
+
timeout: 10000 // Optional arg override
|
|
1015
|
+
})
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
**Example project structure:**
|
|
1019
|
+
|
|
1020
|
+
```
|
|
1021
|
+
myapp/
|
|
1022
|
+
├── lib/
|
|
1023
|
+
│ └── http.km # Low-level HTTP client
|
|
1024
|
+
├── services/
|
|
1025
|
+
│ └── api.km # API service (depends on http)
|
|
1026
|
+
├── main.km # Main app (depends on api)
|
|
1027
|
+
└── main_with_mock.km # Main app with mocked dependencies
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
## CLI Commands
|
|
1031
|
+
|
|
1032
|
+
### Module Execution
|
|
1033
|
+
|
|
1034
|
+
Run modules using dot-notation paths instead of file paths:
|
|
1035
|
+
|
|
1036
|
+
```bash
|
|
1037
|
+
# Run a module by path (salesforce/client.km -> salesforce.client)
|
|
1038
|
+
kimchi salesforce.client
|
|
1039
|
+
|
|
1040
|
+
# Equivalent to:
|
|
1041
|
+
kimchi run salesforce/client.km
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
**Passing named arguments:**
|
|
1045
|
+
|
|
1046
|
+
Modules can declare arguments with `arg` and `!arg` (required). Pass them from the CLI using `--arg-name value`:
|
|
1047
|
+
|
|
1048
|
+
```kimchi
|
|
1049
|
+
// api/client.km
|
|
1050
|
+
!arg clientId // Required argument
|
|
1051
|
+
arg timeout = 5000 // Optional with default
|
|
1052
|
+
|
|
1053
|
+
expose fn connect() {
|
|
1054
|
+
print "Connecting with ${clientId}, timeout: ${timeout}ms"
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
connect()
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
```bash
|
|
1061
|
+
# Pass required and optional args
|
|
1062
|
+
kimchi api.client --client-id ABC123 --timeout 10000
|
|
1063
|
+
|
|
1064
|
+
# Argument names convert: --client-id -> clientId (camelCase)
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
**Injecting dependencies:**
|
|
1068
|
+
|
|
1069
|
+
Override module dependencies at runtime with `--dep alias=path`:
|
|
1070
|
+
|
|
1071
|
+
```kimchi
|
|
1072
|
+
// services/api.km
|
|
1073
|
+
as http dep lib.http // Normal dependency
|
|
1074
|
+
|
|
1075
|
+
expose fn fetch(url) {
|
|
1076
|
+
return http.get(url)
|
|
1077
|
+
}
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
```bash
|
|
1081
|
+
# Inject a mock HTTP module for testing
|
|
1082
|
+
kimchi services.api --dep http=mocks.http
|
|
1083
|
+
|
|
1084
|
+
# Multiple dependency injections
|
|
1085
|
+
kimchi app.main --dep http=mocks.http --dep db=mocks.db
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
**Module help:**
|
|
1089
|
+
|
|
1090
|
+
View a module's description, arguments, and dependencies:
|
|
1091
|
+
|
|
1092
|
+
```bash
|
|
1093
|
+
kimchi help api.client
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
Output:
|
|
1097
|
+
```
|
|
1098
|
+
Module: api.client
|
|
1099
|
+
File: ./api/client.km
|
|
1100
|
+
|
|
1101
|
+
Description:
|
|
1102
|
+
API client for connecting to external services
|
|
1103
|
+
|
|
1104
|
+
Arguments:
|
|
1105
|
+
--client-id (required)
|
|
1106
|
+
--timeout [default: 5000]
|
|
1107
|
+
|
|
1108
|
+
Dependencies:
|
|
1109
|
+
http <- lib.http
|
|
1110
|
+
|
|
1111
|
+
Usage:
|
|
1112
|
+
kimchi api.client --client-id <value>
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
**Listing modules:**
|
|
1116
|
+
|
|
1117
|
+
```bash
|
|
1118
|
+
# List modules in current directory
|
|
1119
|
+
kimchi ls
|
|
1120
|
+
|
|
1121
|
+
# List with descriptions (calls _describe() on each module)
|
|
1122
|
+
kimchi ls --verbose
|
|
1123
|
+
|
|
1124
|
+
# Recursive tree view
|
|
1125
|
+
kimchi ls ./lib --recursive --verbose
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
### Basic Commands
|
|
1129
|
+
|
|
1130
|
+
```bash
|
|
1131
|
+
# Compile a file to JavaScript
|
|
1132
|
+
kimchi compile app.kimchi
|
|
1133
|
+
|
|
1134
|
+
# Compile with custom output
|
|
1135
|
+
kimchi compile app.kimchi -o dist/app.js
|
|
1136
|
+
|
|
1137
|
+
# Run a file directly
|
|
1138
|
+
kimchi run app.kimchi
|
|
1139
|
+
|
|
1140
|
+
# Start interactive REPL
|
|
1141
|
+
kimchi repl
|
|
1142
|
+
|
|
1143
|
+
# Show help
|
|
1144
|
+
kimchi help
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
### Reverse Transpiler (JavaScript to KimchiLang)
|
|
1148
|
+
|
|
1149
|
+
Convert existing JavaScript code to KimchiLang with the `convert` command:
|
|
1150
|
+
|
|
1151
|
+
```bash
|
|
1152
|
+
# Convert a JavaScript file to KimchiLang
|
|
1153
|
+
kimchi convert app.js
|
|
1154
|
+
|
|
1155
|
+
# Convert with custom output path
|
|
1156
|
+
kimchi convert app.js -o src/app.km
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
The reverse transpiler handles:
|
|
1160
|
+
- **Variables** - `const`/`let`/`var` → `dec`
|
|
1161
|
+
- **Functions** - Function declarations → `fn`
|
|
1162
|
+
- **Classes** - Converted to factory functions (e.g., `class User` → `fn createUser()`)
|
|
1163
|
+
- **Imports** - ES modules and `require()` → `dep` statements
|
|
1164
|
+
- **Exports** - Named/default exports → `expose`
|
|
1165
|
+
- **console.log** → `print`
|
|
1166
|
+
- **Conditionals** - `if/else` → pattern matching syntax
|
|
1167
|
+
|
|
1168
|
+
**Example:**
|
|
1169
|
+
|
|
1170
|
+
```javascript
|
|
1171
|
+
// input.js
|
|
1172
|
+
const API_URL = "https://api.example.com";
|
|
1173
|
+
|
|
1174
|
+
class UserService {
|
|
1175
|
+
constructor(apiKey) {
|
|
1176
|
+
this.apiKey = apiKey;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
getUser(id) {
|
|
1180
|
+
return fetch(`${API_URL}/users/${id}`);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
console.log("Ready");
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
```bash
|
|
1188
|
+
kimchi convert input.js
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
```kimchi
|
|
1192
|
+
// output.km
|
|
1193
|
+
dec API_URL = "https://api.example.com"
|
|
1194
|
+
|
|
1195
|
+
// Converted from class UserService
|
|
1196
|
+
fn createUserService(apiKey) {
|
|
1197
|
+
return {
|
|
1198
|
+
getUser: (id) => {
|
|
1199
|
+
return fetch("${API_URL}/users/${id}")
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
print "Ready"
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
### NPM Integration
|
|
1208
|
+
|
|
1209
|
+
The `npm` subcommand runs npm and automatically converts installed packages to the `pantry/` directory:
|
|
1210
|
+
|
|
1211
|
+
```bash
|
|
1212
|
+
# Install a package and convert it to pantry/
|
|
1213
|
+
kimchi npm install lodash
|
|
1214
|
+
|
|
1215
|
+
# Install multiple packages
|
|
1216
|
+
kimchi npm install axios moment
|
|
1217
|
+
|
|
1218
|
+
# Any npm command works, but only install triggers conversion
|
|
1219
|
+
kimchi npm update
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
After installation, packages are available in `pantry/<package>/index.km`:
|
|
1223
|
+
|
|
1224
|
+
```kimchi
|
|
1225
|
+
// Use the converted package
|
|
1226
|
+
as lodash dep pantry.lodash
|
|
1227
|
+
|
|
1228
|
+
dec result = lodash.map([1, 2, 3], x => x * 2)
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
**How it works:**
|
|
1232
|
+
1. Runs the npm command normally
|
|
1233
|
+
2. Scans `node_modules/` for installed packages
|
|
1234
|
+
3. Finds each package's main entry point
|
|
1235
|
+
4. Converts JavaScript to KimchiLang using the reverse transpiler
|
|
1236
|
+
5. Saves to `pantry/<package>/index.km`
|
|
1237
|
+
|
|
1238
|
+
**Note:** Complex packages with advanced JavaScript features may not convert perfectly. The pantry is best for simple utility libraries.
|
|
1239
|
+
|
|
1240
|
+
## Editor Extensions
|
|
1241
|
+
|
|
1242
|
+
KimchiLang provides syntax highlighting extensions for popular editors. Extensions are located in the `editors/` directory.
|
|
1243
|
+
|
|
1244
|
+
### Windsurf
|
|
1245
|
+
|
|
1246
|
+
The VS Code extension is fully compatible with Windsurf.
|
|
1247
|
+
|
|
1248
|
+
**Option 1: Install from VSIX (Recommended)**
|
|
1249
|
+
|
|
1250
|
+
A pre-built VSIX file is included in the repository:
|
|
1251
|
+
|
|
1252
|
+
```bash
|
|
1253
|
+
# The extension is already packaged at:
|
|
1254
|
+
# editors/vscode/kimchilang-1.0.0.vsix
|
|
1255
|
+
|
|
1256
|
+
# To install in Windsurf:
|
|
1257
|
+
# 1. Open Windsurf
|
|
1258
|
+
# 2. Press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows/Linux)
|
|
1259
|
+
# 3. Type "Extensions: Install from VSIX..."
|
|
1260
|
+
# 4. Navigate to editors/vscode/kimchilang-1.0.0.vsix
|
|
1261
|
+
# 5. Click Install
|
|
1262
|
+
# 6. Reload Windsurf when prompted
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
**Option 2: Copy to extensions folder**
|
|
1266
|
+
|
|
1267
|
+
```bash
|
|
1268
|
+
# Copy the extension directly to Windsurf's extensions directory
|
|
1269
|
+
cp -r editors/vscode ~/.windsurf/extensions/kimchilang
|
|
1270
|
+
|
|
1271
|
+
# Restart Windsurf to activate the extension
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
**Option 3: Build and install fresh VSIX**
|
|
1275
|
+
|
|
1276
|
+
```bash
|
|
1277
|
+
cd editors/vscode
|
|
1278
|
+
npm install -g @vscode/vsce
|
|
1279
|
+
vsce package
|
|
1280
|
+
# Then install the generated .vsix file using Option 1 steps
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
After installation, KimchiLang syntax highlighting will automatically activate for `.km`, `.kimchi`, and `.kc` files.
|
|
1284
|
+
|
|
1285
|
+
### VS Code
|
|
1286
|
+
|
|
1287
|
+
Follow the same steps as Windsurf, but use the VS Code extensions directory:
|
|
1288
|
+
|
|
1289
|
+
```bash
|
|
1290
|
+
# Option 1: Install from VSIX (same as Windsurf)
|
|
1291
|
+
|
|
1292
|
+
# Option 2: Copy to extensions folder
|
|
1293
|
+
cp -r editors/vscode ~/.vscode/extensions/kimchilang
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
### Other Editors
|
|
1297
|
+
|
|
1298
|
+
See `editors/README.md` for installation instructions for:
|
|
1299
|
+
- **Sublime Text** - Syntax definition in `editors/sublime/`
|
|
1300
|
+
- **Vim/Neovim** - Syntax file and configuration
|
|
1301
|
+
- **Emacs** - Major mode configuration
|
|
1302
|
+
|
|
1303
|
+
## Standard Library
|
|
1304
|
+
|
|
1305
|
+
KimchiLang includes a standard library in the `stdlib/` directory.
|
|
1306
|
+
|
|
1307
|
+
### Logger
|
|
1308
|
+
|
|
1309
|
+
Structured JSON logging with log levels.
|
|
1310
|
+
|
|
1311
|
+
**Import:**
|
|
1312
|
+
|
|
1313
|
+
```kimchi
|
|
1314
|
+
as log dep stdlib.logger
|
|
1315
|
+
```
|
|
1316
|
+
|
|
1317
|
+
**Usage:**
|
|
1318
|
+
|
|
1319
|
+
```kimchi
|
|
1320
|
+
log.info("Application started")
|
|
1321
|
+
log.debug("Debug info", { userId: 123 })
|
|
1322
|
+
log.warn("Warning message")
|
|
1323
|
+
log.error("Error occurred", { code: "ERR_001" })
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
**Log levels:** `debug`, `info`, `warn`, `error`
|
|
1327
|
+
|
|
1328
|
+
**Environment variable:** Set `LOG_LEVEL` to control minimum level (default: `info`)
|
|
1329
|
+
|
|
1330
|
+
```bash
|
|
1331
|
+
LOG_LEVEL=debug kimchi myapp.main
|
|
1332
|
+
LOG_LEVEL=warn kimchi myapp.main
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
**Output format:** JSON with metadata
|
|
1336
|
+
|
|
1337
|
+
```json
|
|
1338
|
+
{"timestamp":"2024-01-15T10:30:00.000Z","level":"info","module":"main","function":"processOrder","line":42,"message":"Order completed"}
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
**Child loggers:** Add persistent context
|
|
1342
|
+
|
|
1343
|
+
```kimchi
|
|
1344
|
+
dec userLog = log.child({ userId: 456, session: "abc" })
|
|
1345
|
+
userLog.info("User action") // Includes userId and session in output
|
|
1346
|
+
```
|
|
1347
|
+
|
|
1348
|
+
### Bitwise
|
|
1349
|
+
|
|
1350
|
+
Bitwise operations are provided as functions rather than operators.
|
|
1351
|
+
|
|
1352
|
+
**Import:**
|
|
1353
|
+
|
|
1354
|
+
```kimchi
|
|
1355
|
+
as bit dep stdlib.bitwise
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
**Functions:**
|
|
1359
|
+
|
|
1360
|
+
```kimchi
|
|
1361
|
+
bit.band(a, b) // a & b (bitwise AND)
|
|
1362
|
+
bit.bor(a, b) // a | b (bitwise OR)
|
|
1363
|
+
bit.bxor(a, b) // a ^ b (bitwise XOR)
|
|
1364
|
+
bit.bnot(a) // ~a (bitwise NOT)
|
|
1365
|
+
bit.lshift(a, b) // a << b (left shift)
|
|
1366
|
+
bit.rshift(a, b) // a >> b (right shift, sign-propagating)
|
|
1367
|
+
bit.urshift(a, b) // a >>> b (unsigned right shift)
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
**Examples:**
|
|
1371
|
+
|
|
1372
|
+
```kimchi
|
|
1373
|
+
as bit dep stdlib.bitwise
|
|
1374
|
+
|
|
1375
|
+
dec flags = bit.bor(0x01, 0x04) // 5
|
|
1376
|
+
dec masked = bit.band(flags, 0x01) // 1
|
|
1377
|
+
dec shifted = bit.rshift(16, 2) // 4
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
## Package Management
|
|
1381
|
+
|
|
1382
|
+
KimchiLang has a built-in package manager for fetching external dependencies from GitHub.
|
|
1383
|
+
|
|
1384
|
+
### project.static
|
|
1385
|
+
|
|
1386
|
+
Create a `project.static` file in your project root to declare dependencies:
|
|
1387
|
+
|
|
1388
|
+
```
|
|
1389
|
+
// project.static
|
|
1390
|
+
name "my-app"
|
|
1391
|
+
version "1.0.0"
|
|
1392
|
+
|
|
1393
|
+
depend [
|
|
1394
|
+
"github.com/owner/repo",
|
|
1395
|
+
"github.com/owner/repo@v1.0.0",
|
|
1396
|
+
"github.com/owner/repo/path/to/module"
|
|
1397
|
+
]
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
### Installing Dependencies
|
|
1401
|
+
|
|
1402
|
+
```bash
|
|
1403
|
+
# Install all dependencies from project.static
|
|
1404
|
+
kimchi install
|
|
1405
|
+
|
|
1406
|
+
# Remove installed dependencies
|
|
1407
|
+
kimchi clean
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
Dependencies are cloned to `.km_modules/` and tracked in `.km_modules/.lock.json`.
|
|
1411
|
+
|
|
1412
|
+
### Using Installed Modules
|
|
1413
|
+
|
|
1414
|
+
Import external modules using the `@` prefix:
|
|
1415
|
+
|
|
1416
|
+
```kimchi
|
|
1417
|
+
// Module installed at .km_modules/foo/bar.km
|
|
1418
|
+
as bar dep @foo.bar
|
|
1419
|
+
|
|
1420
|
+
// Use the imported module
|
|
1421
|
+
bar.doSomething()
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
The `@` prefix tells the compiler to look in `.km_modules/` instead of the local project. It also makes it clear to the reader that the module is external.
|
|
1425
|
+
|
|
1426
|
+
### Dependency URL Format
|
|
1427
|
+
|
|
1428
|
+
| Format | Description |
|
|
1429
|
+
|--------|-------------|
|
|
1430
|
+
| `github.com/owner/repo` | Latest from main branch |
|
|
1431
|
+
| `github.com/owner/repo@tag` | Specific tag or branch |
|
|
1432
|
+
| `github.com/owner/repo/path` | Subdirectory of repo |
|
|
1433
|
+
|
|
1434
|
+
## Testing
|
|
1435
|
+
|
|
1436
|
+
KimchiLang includes a built-in testing framework with `test`, `describe`, `expect`, and `assert`.
|
|
1437
|
+
|
|
1438
|
+
### Test Syntax
|
|
1439
|
+
|
|
1440
|
+
**Basic test:**
|
|
1441
|
+
|
|
1442
|
+
```kimchi
|
|
1443
|
+
test "addition works" {
|
|
1444
|
+
expect(add(2, 3)).toBe(5)
|
|
1445
|
+
}
|
|
1446
|
+
```
|
|
1447
|
+
|
|
1448
|
+
**Grouped tests with describe:**
|
|
1449
|
+
|
|
1450
|
+
```kimchi
|
|
1451
|
+
describe "Math functions" {
|
|
1452
|
+
test "add returns correct sum" {
|
|
1453
|
+
expect(add(2, 3)).toBe(5)
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
test "multiply returns correct product" {
|
|
1457
|
+
expect(multiply(3, 4)).toBe(12)
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
```
|
|
1461
|
+
|
|
1462
|
+
**Assert statement:**
|
|
1463
|
+
|
|
1464
|
+
```kimchi
|
|
1465
|
+
assert condition, "Error message if false"
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
### Matchers
|
|
1469
|
+
|
|
1470
|
+
| Matcher | Description |
|
|
1471
|
+
|---------|-------------|
|
|
1472
|
+
| `toBe(value)` | Strict equality (`===`) |
|
|
1473
|
+
| `toEqual(value)` | Deep equality (JSON comparison) |
|
|
1474
|
+
| `toContain(item)` | Array/string contains item |
|
|
1475
|
+
| `toBeNull()` | Value is `null` |
|
|
1476
|
+
| `toBeTruthy()` | Value is truthy |
|
|
1477
|
+
| `toBeFalsy()` | Value is falsy |
|
|
1478
|
+
| `toBeGreaterThan(n)` | Value > n |
|
|
1479
|
+
| `toBeLessThan(n)` | Value < n |
|
|
1480
|
+
| `toHaveLength(n)` | Array/string length equals n |
|
|
1481
|
+
| `toMatch(regex)` | String matches regex |
|
|
1482
|
+
| `toThrow(message)` | Function throws error containing message |
|
|
1483
|
+
|
|
1484
|
+
### Testing with Mocks
|
|
1485
|
+
|
|
1486
|
+
Use dependency injection to mock dependencies in tests:
|
|
1487
|
+
|
|
1488
|
+
```kimchi
|
|
1489
|
+
// Create a mock
|
|
1490
|
+
dec mockHttp = {
|
|
1491
|
+
get: (url) => { status: 200, data: { id: 1, name: "Test" } }
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// Inject mock when importing module
|
|
1495
|
+
as userService dep myapp.user-service({
|
|
1496
|
+
"myapp.http-client": mockHttp
|
|
1497
|
+
})
|
|
1498
|
+
|
|
1499
|
+
// Test with mocked dependency
|
|
1500
|
+
test "getUser returns user data" {
|
|
1501
|
+
dec user = userService.getUser(1)
|
|
1502
|
+
expect(user.name).toBe("Test")
|
|
1503
|
+
}
|
|
1504
|
+
```
|
|
1505
|
+
|
|
1506
|
+
**Run tests:**
|
|
1507
|
+
|
|
1508
|
+
```bash
|
|
1509
|
+
kimchi examples.testing.math.test
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
See `examples/testing/` for complete examples.
|
|
1513
|
+
|
|
1514
|
+
## Running Tests
|
|
1515
|
+
|
|
1516
|
+
```bash
|
|
1517
|
+
node test/test.js
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
## File Extensions
|
|
1521
|
+
|
|
1522
|
+
- `.kimchi` - Standard extension
|
|
1523
|
+
- `.kc` - Short extension
|
|
1524
|
+
|
|
1525
|
+
## How It Works
|
|
1526
|
+
|
|
1527
|
+
KimchiLang uses a three-stage compilation process:
|
|
1528
|
+
|
|
1529
|
+
1. **Lexer** (`src/lexer.js`) - Tokenizes source code into tokens
|
|
1530
|
+
2. **Parser** (`src/parser.js`) - Builds an Abstract Syntax Tree (AST)
|
|
1531
|
+
3. **Generator** (`src/generator.js`) - Converts AST to JavaScript
|
|
1532
|
+
|
|
1533
|
+
## Examples
|
|
1534
|
+
|
|
1535
|
+
See the `examples/` directory for more code samples:
|
|
1536
|
+
|
|
1537
|
+
- `hello.kimchi` - Hello World
|
|
1538
|
+
- `basic.kimchi` - Core language features
|
|
1539
|
+
- `fibonacci.kimchi` - Recursive and iterative Fibonacci
|
|
1540
|
+
- `myapp/` - Dependency injection example with mock testing
|
|
1541
|
+
- `logger_example.km` - Structured JSON logging with log levels
|
|
1542
|
+
- `regex_match.km` - Regex pattern matching expressions
|
|
1543
|
+
- `testing/` - Unit testing examples with mocks
|
|
1544
|
+
|
|
1545
|
+
## License
|
|
1546
|
+
|
|
1547
|
+
MIT
|