kopscript 0.1.0 → 0.3.0
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/LLM.md +377 -0
- package/README.md +111 -22
- package/assets/logo.png +0 -0
- package/dist/checker.js +194 -20
- package/dist/cli.js +85 -16
- package/dist/codegen.js +9 -1
- package/dist/lexer.js +4 -0
- package/dist/modules.js +6 -3
- package/dist/parser.js +78 -18
- package/dist/tokens.js +3 -0
- package/dist/types.js +31 -16
- package/package.json +4 -2
package/LLM.md
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# KopScript — LLM reference
|
|
2
|
+
|
|
3
|
+
Complete, dense reference for generating correct KopScript (`.ks`) code. This is a spec,
|
|
4
|
+
not a tutorial — see `README.md` for narrative explanation and rationale. Every construct
|
|
5
|
+
below is exhaustive: if something isn't listed here, it doesn't exist in the language.
|
|
6
|
+
Read the "Does not exist" section before assuming any feature from another language
|
|
7
|
+
carries over.
|
|
8
|
+
|
|
9
|
+
Compiles to plain ES modules.
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
ks build <file.ks> # type-check + emit <file>.js next to the source
|
|
15
|
+
ks run <file.ks> # build, then execute with node
|
|
16
|
+
ks watch <file.ks> # build, then rebuild on every change to any file in the graph
|
|
17
|
+
ks check <file.ks> # type-check only, no output files
|
|
18
|
+
ks build|check <file.ks> --json # single JSON object on stdout instead of human text
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`--json` output shape (also what a tool/agent should parse instead of scraping text):
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{ "success": false, "diagnostics": [{ "severity": "error", "message": "...", "line": 2, "col": 14, "file": "/abs/path.ks" }], "written": [] }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`written` is the absolute paths actually written (`build` only, and only on success — a
|
|
28
|
+
failed `build` writes nothing). Exit code is `0` iff `success` is `true`. A missing entry
|
|
29
|
+
file yields `{ success: false, diagnostics: [], written: [], error: "cannot find file '...'" }`.
|
|
30
|
+
|
|
31
|
+
## File shape
|
|
32
|
+
|
|
33
|
+
```ks
|
|
34
|
+
using "./other_file"; // 0+ usings, must be a contiguous prefix at the top
|
|
35
|
+
using "../lib/thing"; // relative only ("./" or "../"), no bare package names
|
|
36
|
+
|
|
37
|
+
// then any number of top-level statements/declarations, in any order
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`using` resolves `<path>.ks` relative to the current file and pulls its `public`
|
|
41
|
+
top-level classes/interfaces/enums/functions/extern-decls into unqualified scope. No
|
|
42
|
+
package-import mechanism — cross-package consumption goes through `extern` instead (see
|
|
43
|
+
below). No circular `using` (compile error).
|
|
44
|
+
|
|
45
|
+
## Types
|
|
46
|
+
|
|
47
|
+
| Type | Notes |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `number` | one numeric type, no int/float split |
|
|
50
|
+
| `string` | |
|
|
51
|
+
| `bool` | `true` / `false` |
|
|
52
|
+
| `void` | function/method return type only |
|
|
53
|
+
| `T[]` | array of `T`; `T[][]` etc. nest freely |
|
|
54
|
+
| `ClassName` / `InterfaceName` / `EnumName` | any user-declared named type |
|
|
55
|
+
| `(T1, T2) => R` | function type — parenthesized param types, arrow, return type |
|
|
56
|
+
| `task` / `task<T>` | see Async below |
|
|
57
|
+
| `state<T>` | see Reactive state below |
|
|
58
|
+
| `T?` | nullable — see Nullable types below |
|
|
59
|
+
|
|
60
|
+
**Does not exist**: generics (no `class Foo<T>`, no `T Resolve<T>()`), `any`/`unknown` as
|
|
61
|
+
a writable annotation, a `let`/`var` keyword (see Declarations below — there isn't one),
|
|
62
|
+
type inference for declarations (every local/`const`/param/field/return type is written
|
|
63
|
+
out explicitly), union types, tuples, structural/duck typing (all typing is nominal).
|
|
64
|
+
|
|
65
|
+
## Declarations
|
|
66
|
+
|
|
67
|
+
```ks
|
|
68
|
+
number x = 5; // type-first local var (mutable)
|
|
69
|
+
const string name = "Joe"; // type-first const
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Top-level declarations (function/class/interface/enum/extern/raw-string) default to
|
|
73
|
+
**exported** (visible via `using`) unless marked `private`. A top-level bare `number x = 5;`
|
|
74
|
+
is a plain statement, not exportable — `using` only pulls in types and functions, never
|
|
75
|
+
values.
|
|
76
|
+
|
|
77
|
+
```ks
|
|
78
|
+
private void Helper() { } // file-local, not visible via `using`
|
|
79
|
+
void Exported() { } // default: visible via `using`
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Functions
|
|
83
|
+
|
|
84
|
+
```ks
|
|
85
|
+
number Add(number a, number b) {
|
|
86
|
+
return a + b;
|
|
87
|
+
}
|
|
88
|
+
async task<number> FetchValue() { return 1; } // see Async
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Classes
|
|
92
|
+
|
|
93
|
+
```ks
|
|
94
|
+
class Dog : Animal, IBark { // 0-1 base class + 0+ interfaces, comma-separated
|
|
95
|
+
private string name; // field: no initializer syntax — set in constructor
|
|
96
|
+
public static number InstanceCount = 0; // static field: initializer REQUIRED (no ctor runs for it)
|
|
97
|
+
|
|
98
|
+
public string Name { get; set; } // auto-property, get-only or get+set
|
|
99
|
+
public string ReadOnly { get; } // settable only within this class's own constructor
|
|
100
|
+
|
|
101
|
+
constructor(string name) : base(/* base ctor args */) {
|
|
102
|
+
this.name = name;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public virtual string Speak() { return "..."; } // overridable
|
|
106
|
+
public override string Speak() { return "Woof"; } // in a subclass
|
|
107
|
+
public static Dog Create(string name) { return new Dog(name); }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Dog d = new Dog("Rex");
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- Visibility on members: `public` / `private` / `protected` (default when omitted: `public`).
|
|
114
|
+
- `virtual`/`override` only on instance methods, not static, not fields/properties.
|
|
115
|
+
- No interface-implementation keyword — implementing is structural-by-declaration: list
|
|
116
|
+
the interface in the base list and provide matching public methods, checked at compile time.
|
|
117
|
+
- No nested classes/functions.
|
|
118
|
+
- No multiple inheritance (at most one class in the base list; the rest must be interfaces).
|
|
119
|
+
|
|
120
|
+
### Interfaces
|
|
121
|
+
|
|
122
|
+
```ks
|
|
123
|
+
interface IShape {
|
|
124
|
+
number Area(); // method signatures only — no fields/properties in v1
|
|
125
|
+
}
|
|
126
|
+
interface INamedShape : IShape { // interfaces can extend other interfaces (not classes)
|
|
127
|
+
string GetName();
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Enums
|
|
132
|
+
|
|
133
|
+
```ks
|
|
134
|
+
enum Color { Red, Green, Blue }
|
|
135
|
+
Color c = Color.Red;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
No custom values, no methods on enums.
|
|
139
|
+
|
|
140
|
+
### `match` expression
|
|
141
|
+
|
|
142
|
+
```ks
|
|
143
|
+
string Classify(string input) {
|
|
144
|
+
return match input {
|
|
145
|
+
"cat", "dog" => "animal", // comma = OR within one arm
|
|
146
|
+
r"^[0-9]+$" => "number", // regex pattern, r"..."
|
|
147
|
+
_ => "unknown" // wildcard REQUIRED — exhaustiveness-checked
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`match` is an expression (has a value), matches on literal/regex patterns only (not
|
|
153
|
+
type patterns, not destructuring), and always requires a trailing `_` arm.
|
|
154
|
+
|
|
155
|
+
## Statements
|
|
156
|
+
|
|
157
|
+
```ks
|
|
158
|
+
if (cond) { } else if (cond2) { } else { }
|
|
159
|
+
while (cond) { }
|
|
160
|
+
for (number i = 0; i < n; i = i + 1) { }
|
|
161
|
+
foreach (number x in items) { } // arrays only
|
|
162
|
+
break; continue; return; return expr;
|
|
163
|
+
try { } catch (string e) { } finally { } // at most one catch clause; param type is your choice
|
|
164
|
+
throw "message"; // throw takes any expression, not just string
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`throw` accepts any expression (no base exception/error type to conform to), and a
|
|
168
|
+
`catch (T e)` param's type `T` is **not checked against what's actually thrown** — it
|
|
169
|
+
compiles to a plain JS `catch (e) { ... }` with zero runtime type discrimination. The
|
|
170
|
+
type annotation only affects what `e` type-checks as *inside* the catch block. There's
|
|
171
|
+
no way to have multiple catch clauses per type (only one `catch` per `try`), and no
|
|
172
|
+
built-in exception hierarchy — `catch (string e)` (matching `throw "message";`) is the
|
|
173
|
+
conventional shape used throughout this codebase, but any type is legal.
|
|
174
|
+
|
|
175
|
+
There is **no ternary/conditional expression** (`if` is a statement only). To get a
|
|
176
|
+
conditional *value* inline, use a `match` on a `bool`-shaped case set, or (in Kopular UI
|
|
177
|
+
code) the `If()` helper — see the Kopular reference.
|
|
178
|
+
|
|
179
|
+
## Expressions
|
|
180
|
+
|
|
181
|
+
Precedence, low → high: `=` (right-assoc) → `||` → `&&` → `==` `!=` → `<` `>` `<=` `>=`
|
|
182
|
+
→ `+` `-` → `*` `/` `%` → unary `-` `!` → postfix (`.member`, `(call)`, `[index]`) → primary.
|
|
183
|
+
|
|
184
|
+
```ks
|
|
185
|
+
this
|
|
186
|
+
new ClassName(args)
|
|
187
|
+
obj.Member
|
|
188
|
+
obj.Method(args)
|
|
189
|
+
arr[index]
|
|
190
|
+
await someTask // only inside an async function/method body
|
|
191
|
+
state(0) // constructs a state<T> — see below
|
|
192
|
+
$"Hello {name}, you are {age} years old" // interpolated string
|
|
193
|
+
r"^[a-z]+$" // regex literal (only meaningful as a match pattern)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Lambdas
|
|
197
|
+
|
|
198
|
+
```ks
|
|
199
|
+
(number x) => x * x // expression body
|
|
200
|
+
(number x, number y) => { return x + y; } // block body
|
|
201
|
+
() => this.DoThing()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Every lambda parameter type is required — no inference, no bare `(x) => ...`.** A
|
|
205
|
+
lambda's own first parameter cannot itself be a function type (a rare v1 parser
|
|
206
|
+
limitation, not a semantic one).
|
|
207
|
+
|
|
208
|
+
### Arrays
|
|
209
|
+
|
|
210
|
+
```ks
|
|
211
|
+
number[] xs = [1, 2, 3];
|
|
212
|
+
xs.Length
|
|
213
|
+
xs.Push(4) // NON-mutating — returns a new array, doesn't modify xs
|
|
214
|
+
xs.Map((number x) => x * 2) // -> array of whatever the callback returns
|
|
215
|
+
xs.Filter((number x) => x > 1)
|
|
216
|
+
xs.ForEach((number x) => { print(x); })
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Strings
|
|
220
|
+
|
|
221
|
+
```ks
|
|
222
|
+
s.Length
|
|
223
|
+
s.Contains(other) / s.StartsWith(other) / s.EndsWith(other)
|
|
224
|
+
s.Replace(from, to)
|
|
225
|
+
s.Split(sep) // -> string[]
|
|
226
|
+
s.Trim() / s.ToUpper() / s.ToLower()
|
|
227
|
+
"a" + "b" // + is also string concatenation
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Async
|
|
231
|
+
|
|
232
|
+
```ks
|
|
233
|
+
async task<number> FetchValue() {
|
|
234
|
+
number x = await SomeOtherAsyncFn();
|
|
235
|
+
return x;
|
|
236
|
+
}
|
|
237
|
+
async task DoThing() { } // bare task = "async void" with a value you can await
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`async` requires (and is required by) a `task`/`task<T>` return type. `await` only
|
|
241
|
+
legal inside an async function/method body. No async lambdas. No way to construct a
|
|
242
|
+
`task` value from outside an async function body.
|
|
243
|
+
|
|
244
|
+
### Reactive state — `state<T>`
|
|
245
|
+
|
|
246
|
+
```ks
|
|
247
|
+
state<number> count = state(0);
|
|
248
|
+
count.Value // read/write the current value
|
|
249
|
+
count.Value = count.Value + 1;
|
|
250
|
+
count.Subscribe((number v) => { ... }); // called on every future .Value assignment
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Not a generic type parameter — `state<T>` is one of exactly two hardcoded parametrized
|
|
254
|
+
forms in the type system (the other is `task<T>`).
|
|
255
|
+
|
|
256
|
+
## Nullable types — `T?`
|
|
257
|
+
|
|
258
|
+
```ks
|
|
259
|
+
string? maybeName = null; // null literal, any type-position postfix `?`
|
|
260
|
+
string?[] arrOfNullable = [null, "x"]; // array OF nullable strings
|
|
261
|
+
string[]? nullableArr = null; // NULLABLE array of strings — order matters
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`?` is a postfix modifier on any type, erased at codegen (JS already has native `null` —
|
|
265
|
+
this is purely a compile-time distinction). Assignability is one-directional: a plain `T`
|
|
266
|
+
is always usable where `T?` is expected (widening), but a `T?` is **never** usable where
|
|
267
|
+
`T` is expected without narrowing first — accessing a member or index on an un-narrowed
|
|
268
|
+
`T?` is a compile error:
|
|
269
|
+
|
|
270
|
+
```ks
|
|
271
|
+
void F(string? s) {
|
|
272
|
+
print(s.Length); // ERROR: possibly-null type 'string?'
|
|
273
|
+
if (s != null) {
|
|
274
|
+
print(s.Length); // OK — s is string here, not string?
|
|
275
|
+
}
|
|
276
|
+
print(s.Length); // ERROR again — narrowing doesn't survive past the if
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Narrowing is recognized in exactly these shapes** (anything else doesn't narrow):
|
|
281
|
+
- `if (x != null) { ... }` — narrows `x` inside the then-branch.
|
|
282
|
+
- `if (x == null) { ... } else { ... }` — narrows `x` inside the else-branch.
|
|
283
|
+
- `cond1 && x.Foo` where `cond1` is `x != null` — narrows `x` for the right operand only.
|
|
284
|
+
- `cond1 || x.Foo` where `cond1` is `x == null` — narrows `x` for the right operand only
|
|
285
|
+
(De Morgan's: reaching the right side of `||` means the left was false).
|
|
286
|
+
- Either operand order works (`x != null` and `null != x` both narrow `x`).
|
|
287
|
+
|
|
288
|
+
**Narrowing does NOT handle** (a common instinct from other languages — don't rely on
|
|
289
|
+
these): an early-return guard clause (`if (x == null) { return; } use(x);` — `x` is
|
|
290
|
+
*not* narrowed after the `if`, since that needs control-flow reachability analysis, not
|
|
291
|
+
just scope); narrowing a member/property path (`if (this.Field != null)` narrows
|
|
292
|
+
nothing — only a bare local/parameter identifier narrows); narrowing surviving
|
|
293
|
+
reassignment (assigning a new value to a narrowed variable inside the narrowed block
|
|
294
|
+
doesn't invalidate or restore narrowing — avoid reassigning a narrowed variable within
|
|
295
|
+
the block). `x` stays narrowed for the rest of a narrowed block, including nested blocks
|
|
296
|
+
inside it, since narrowing is scope-based.
|
|
297
|
+
|
|
298
|
+
Comparing a value to `null` when its type **can't** be null is a compile error, not a
|
|
299
|
+
silently-always-false comparison — this is a real bug-catching check, not just a style
|
|
300
|
+
rule:
|
|
301
|
+
|
|
302
|
+
```ks
|
|
303
|
+
class Dog { }
|
|
304
|
+
Dog d = new Dog();
|
|
305
|
+
bool x = d == null; // ERROR: Type 'Dog' can never be null
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## `extern` — describing existing JS/npm code
|
|
309
|
+
|
|
310
|
+
Three forms, always terminated with `;` after the closing brace for the class form:
|
|
311
|
+
|
|
312
|
+
```ks
|
|
313
|
+
extern number ParseFloat(string s) from "npm-package" as "parseFloat"; // `from` before `as`; omit `from` for an ambient global
|
|
314
|
+
extern SomeType globalThing as "actualJsName"; // extern value, ambient
|
|
315
|
+
extern class Widget {
|
|
316
|
+
constructor();
|
|
317
|
+
string label { get; set; } // property sig — get / get+set
|
|
318
|
+
static number Count { get; }
|
|
319
|
+
void Render();
|
|
320
|
+
virtual void Update(); // lets a real subclass `override` it
|
|
321
|
+
} from "npm-package";
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Clause order is always `from "<path>"` then `as "<jsName>"` (both optional, `from`
|
|
325
|
+
first if both are present). `from "..."` omitted = ambient global (assumed to already
|
|
326
|
+
exist at runtime, e.g. `document`). `as "..."` omitted = the JS-side name matches the
|
|
327
|
+
KopScript-declared name exactly. Extern class members use real JS member names verbatim
|
|
328
|
+
(camelCase, no rename mechanism). No inheritance modeling between two `extern class`
|
|
329
|
+
declarations — each stands alone.
|
|
330
|
+
|
|
331
|
+
## `raw string` — compile-time file embedding
|
|
332
|
+
|
|
333
|
+
```ks
|
|
334
|
+
raw string HeroHtml from "./hero.html"; // reads the file at compile time, type is always string
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Keywords (reserved, lowercase, exact match)
|
|
338
|
+
|
|
339
|
+
`using extern raw from as const class interface enum constructor public private
|
|
340
|
+
protected static virtual override get set return if else while for foreach in break
|
|
341
|
+
continue match this base new void true false null task state async await try catch
|
|
342
|
+
finally throw`
|
|
343
|
+
|
|
344
|
+
Anything else (including PascalCase versions of the above, e.g. `If`, `Match`) is a
|
|
345
|
+
valid identifier.
|
|
346
|
+
|
|
347
|
+
## Naming convention used throughout the ecosystem
|
|
348
|
+
|
|
349
|
+
Types, methods, and properties: **PascalCase** (`Speak()`, `Render()`, `Name`).
|
|
350
|
+
Locals/params: **camelCase**. This is convention, not enforced by the compiler.
|
|
351
|
+
|
|
352
|
+
## Does not exist (don't reach for these)
|
|
353
|
+
|
|
354
|
+
Generics · `any`/`unknown` annotations ·
|
|
355
|
+
a `let`/`var` keyword (locals are just `Type name = value;`) · decorators/annotations ·
|
|
356
|
+
reflection · type inference on declarations · ternary expression · union/tuple types ·
|
|
357
|
+
structural typing · multiple class inheritance ·
|
|
358
|
+
nested classes/functions · method overloading · default/optional parameters · varargs ·
|
|
359
|
+
static properties with accessors (static fields only) · interface properties (interface
|
|
360
|
+
*methods* only) · a real exception hierarchy or multi-type `catch` (one `catch` per
|
|
361
|
+
`try`, its param type is unchecked against what's actually thrown) · a package-import
|
|
362
|
+
mechanism (`using` is same-project only; cross-package = `extern`).
|
|
363
|
+
|
|
364
|
+
**Sharp edge, not just "unsupported":** declaring two methods with the same name in one
|
|
365
|
+
class is **not a compile error** — the second declaration silently replaces the first in
|
|
366
|
+
the checker's method table, so call sites resolve against whichever was declared last
|
|
367
|
+
(and its body is the only one ever emitted). If you want to offer more than one
|
|
368
|
+
signature, use different names, not overloading. (Nested function/class/interface/enum
|
|
369
|
+
declarations, by contrast, *are* a clean compile error: "Nested ... declarations are not
|
|
370
|
+
supported".)
|
|
371
|
+
|
|
372
|
+
## Diagnostics
|
|
373
|
+
|
|
374
|
+
Every compiler error/warning is `{ severity, message, line, col }` (1-based). CLI output
|
|
375
|
+
format: `` file:line:col - severity: message `` plus a source line and a `^` pointer.
|
|
376
|
+
`DiagnosticBag.hasErrors` gates whether codegen runs at all — a program with any error
|
|
377
|
+
produces no output.
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<img src="https://cdn.jsdelivr.net/npm/kopscript@latest/assets/logo.png" width="96" height="96" alt="KopScript logo">
|
|
2
|
+
|
|
1
3
|
# KopScript
|
|
2
4
|
|
|
3
5
|
KopScript is a small, strongly-typed, object-oriented programming language that transpiles to
|
|
@@ -9,6 +11,10 @@ auto-properties, enums) rather than TypeScript ones.
|
|
|
9
11
|
Because KopScript compiles to plain JavaScript and runs on Node, it runs identically on Windows,
|
|
10
12
|
macOS, and Linux — there's no native toolchain to maintain.
|
|
11
13
|
|
|
14
|
+
Generating KopScript with an AI coding assistant? Point it at **[`LLM.md`](./LLM.md)** —
|
|
15
|
+
a dense, complete, example-driven language spec designed to be loaded straight into an
|
|
16
|
+
LLM's context, as opposed to this README's narrative explanation.
|
|
17
|
+
|
|
12
18
|
## Highlights
|
|
13
19
|
|
|
14
20
|
- **Object-oriented, C#-flavored**: type-first declarations (`string Name;`, not `name: string`),
|
|
@@ -16,6 +22,10 @@ macOS, and Linux — there's no native toolchain to maintain.
|
|
|
16
22
|
explicit `virtual`/`override` dispatch (methods are sealed unless marked `virtual`), and a full
|
|
17
23
|
`public`/`protected`/`private` access model enforced at compile time.
|
|
18
24
|
- **Strongly typed**: every declaration is explicitly typed and checked at compile time.
|
|
25
|
+
- **Nullable types with compiler-enforced null-checking**: `T?` (`string?`, `Dog?`,
|
|
26
|
+
`number[]?`) — a `T?` can't be used where a `T` is expected without an `if (x != null)`
|
|
27
|
+
check first (checked statically, not just at runtime), and comparing a value that can
|
|
28
|
+
never be null to `null` is itself a compile error.
|
|
19
29
|
- **Built-in string pattern matching**: a `match` expression over strings supporting literal,
|
|
20
30
|
wildcard, and regex patterns — no `if`/`else` chains required.
|
|
21
31
|
- **Real multi-file programs**: `using "./shapes";` compiles a whole dependency graph
|
|
@@ -38,9 +48,9 @@ macOS, and Linux — there's no native toolchain to maintain.
|
|
|
38
48
|
`.Subscribe((T) => void)`) for holding state and reacting to it changing — no
|
|
39
49
|
Observables, no operators, no manual unsubscribe bookkeeping.
|
|
40
50
|
- **A companion framework, [Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular)**:
|
|
41
|
-
components, constructor-injected services (no DI container), and
|
|
42
|
-
config DSL) — built entirely on the features above, in a separate
|
|
43
|
-
real package. [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo)
|
|
51
|
+
components, constructor-injected services via a composition root (no DI container), and
|
|
52
|
+
real-URL routing (no config DSL) — built entirely on the features above, in a separate
|
|
53
|
+
repo consumed as a real package. [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo)
|
|
44
54
|
is a real app built on both.
|
|
45
55
|
|
|
46
56
|
## Getting started
|
|
@@ -299,6 +309,28 @@ describable is old-style Node **callback-based** async that doesn't return a Pro
|
|
|
299
309
|
that specific convention, even though the callback parameter itself is describable as an
|
|
300
310
|
ordinary function type.
|
|
301
311
|
|
|
312
|
+
### Compile-time file embedding (`raw`)
|
|
313
|
+
|
|
314
|
+
`raw string <Name> from "<path>";` reads the file at `<path>` (resolved relative to the
|
|
315
|
+
`.ks` file that declares it) at **compile time** and embeds its contents as a plain string
|
|
316
|
+
constant — no `fetch`, no runtime file access, nothing left over at all once compiled.
|
|
317
|
+
Top-level only, always `string`, exported by default like any other top-level declaration
|
|
318
|
+
(`private` keeps it file-scoped):
|
|
319
|
+
|
|
320
|
+
```ks
|
|
321
|
+
raw string CounterHtml from "./counter.html";
|
|
322
|
+
|
|
323
|
+
void Main() {
|
|
324
|
+
print(CounterHtml); // the file's exact contents, as a string
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Unlike `extern`, there's no real JS export being described here — the compiler fabricates
|
|
329
|
+
the value itself — so there's no `as "jsName"` clause. This exists so a component's static
|
|
330
|
+
markup can live in a real `.html` file instead of a giant `.innerHTML = "..."` string
|
|
331
|
+
literal, the same way Angular's `templateUrl` works — resolved once at build time, not
|
|
332
|
+
fetched at runtime, so `Component.Render()` stays synchronous either way.
|
|
333
|
+
|
|
302
334
|
### async/await, task<T>, and try/catch
|
|
303
335
|
|
|
304
336
|
`task` (a promise of nothing) and `task<T>` (a promise of a `T`) are the one hardcoded
|
|
@@ -489,8 +521,40 @@ foreach (number item in items) {
|
|
|
489
521
|
### Types (v1 scope)
|
|
490
522
|
|
|
491
523
|
`number`, `string`, `bool`, `void`, `T[]` (arrays), class types, interface types, enum
|
|
492
|
-
types,
|
|
493
|
-
|
|
524
|
+
types, function types (`(T, ...) => R`), and nullable types (`T?`, with `null` and
|
|
525
|
+
compiler-enforced null-checking — see "Nullable types" below). No generics yet.
|
|
526
|
+
|
|
527
|
+
### Nullable types — `T?`
|
|
528
|
+
|
|
529
|
+
```ks
|
|
530
|
+
string? maybeName = null;
|
|
531
|
+
void Greet(string? name) {
|
|
532
|
+
if (name != null) {
|
|
533
|
+
print("Hello, " + name); // name is `string` here, not `string?`
|
|
534
|
+
} else {
|
|
535
|
+
print("Hello, stranger");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
`?` is a postfix modifier on any type (`Dog?`, or an array either way — `string?[]` is an
|
|
541
|
+
array of nullable strings, `string[]?` is a nullable array of strings; order matters).
|
|
542
|
+
It's purely a compile-time distinction — erased at codegen, since JS already has native
|
|
543
|
+
`null`. The compiler enforces it in both directions:
|
|
544
|
+
|
|
545
|
+
- A `T?` can never be used where a `T` is expected — accessing a member or index on one
|
|
546
|
+
is a compile error — **unless** it's been narrowed by an `if (x != null)` /
|
|
547
|
+
`if (x == null) {...} else {...}` check (either operand order, and inside a `&&`/`||`
|
|
548
|
+
short-circuit) on that exact local/parameter. Narrowing doesn't survive past the
|
|
549
|
+
checked block, doesn't follow a `this.Field` path (locals/params only), and doesn't
|
|
550
|
+
understand an early-return guard clause (`if (x == null) { return; }`) — each of those
|
|
551
|
+
would need real control-flow analysis, deliberately not taken on in v1.
|
|
552
|
+
- Comparing a value to `null` when its type *can't* be null (i.e. isn't itself a `T?`) is
|
|
553
|
+
a compile error, not a silently-always-false comparison — a real static check, not
|
|
554
|
+
just a style rule.
|
|
555
|
+
|
|
556
|
+
See `LLM.md`'s "Nullable types" section for the exhaustive rules if you're generating
|
|
557
|
+
code against this.
|
|
494
558
|
|
|
495
559
|
### Built-ins
|
|
496
560
|
|
|
@@ -510,7 +574,7 @@ src/
|
|
|
510
574
|
codegen.ts AST -> JavaScript source string (readable ES2020 output)
|
|
511
575
|
modules.ts multi-file orchestration: resolves the `using` graph, checks
|
|
512
576
|
modules in dependency order, drives codegen across all of them
|
|
513
|
-
cli.ts `ks build
|
|
577
|
+
cli.ts `ks build|run|watch|check <file>` entry point
|
|
514
578
|
examples/ sample .ks programs (examples/modules/ is a multi-file one)
|
|
515
579
|
test/ vitest unit and end-to-end tests
|
|
516
580
|
```
|
|
@@ -537,11 +601,32 @@ runtime representation at all and are dropped from the emitted JS entirely.
|
|
|
537
601
|
ks build <file.ks> # type-check and emit <file>.js next to the source
|
|
538
602
|
ks run <file.ks> # build, then execute the emitted JS with node
|
|
539
603
|
ks watch <file.ks> # build, then rebuild on every change to any file in the graph
|
|
604
|
+
ks check <file.ks> # type-check only — no output files written
|
|
540
605
|
```
|
|
541
606
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
607
|
+
`build` and `check` both accept `--json`, which replaces all human-readable output with a
|
|
608
|
+
single JSON object on stdout — meant for CI or a tool/agent parsing the result
|
|
609
|
+
programmatically instead of scraping formatted text:
|
|
610
|
+
|
|
611
|
+
```json
|
|
612
|
+
{
|
|
613
|
+
"success": false,
|
|
614
|
+
"diagnostics": [
|
|
615
|
+
{ "severity": "error", "message": "Argument 2 has type 'string', expected 'number'", "line": 2, "col": 14, "file": "/abs/path/to/file.ks" }
|
|
616
|
+
],
|
|
617
|
+
"written": []
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
`written` lists the absolute paths of every `.js` file actually written (always empty for
|
|
622
|
+
`check`, and for `build` on failure — nothing is written unless the whole graph is
|
|
623
|
+
error-free). A missing entry file reports `{ "success": false, "diagnostics": [],
|
|
624
|
+
"written": [], "error": "cannot find file '...'" }` instead of throwing. Exit code is 0
|
|
625
|
+
exactly when `success` is `true`, both with and without `--json`.
|
|
626
|
+
|
|
627
|
+
During development, use `npm run ks -- <build|run|watch|check> <file.ks>` (backed by
|
|
628
|
+
`tsx`), or run `npm run build` to compile the TypeScript compiler itself to `dist/` and
|
|
629
|
+
use `node dist/cli.js` directly.
|
|
545
630
|
|
|
546
631
|
`watch` rebuilds on a save to *any* `.ks` file it reached while compiling — the entry and
|
|
547
632
|
everything it (transitively, non-transitively per-file) `using`s — not just the entry
|
|
@@ -564,25 +649,29 @@ into your extensions folder).
|
|
|
564
649
|
|
|
565
650
|
## Status
|
|
566
651
|
|
|
567
|
-
This is a v1 / hobby-project scope.
|
|
568
|
-
auto-properties, interface properties (methods only), and
|
|
569
|
-
|
|
570
|
-
everything else here —
|
|
571
|
-
constraint checking) rather than being additive
|
|
572
|
-
for a dedicated future pass rather than bolted on.
|
|
652
|
+
This is a v1 / hobby-project scope. Nullable types (`T?`) shipped — see above. Not yet
|
|
653
|
+
supported: generics, static auto-properties, interface properties (methods only), and
|
|
654
|
+
nested functions/classes. Generics in particular is a substantially bigger undertaking
|
|
655
|
+
than everything else here — it touches the type system's core (type parameters,
|
|
656
|
+
variance, constraint checking) rather than being an additive feature, so it's
|
|
657
|
+
deliberately left for a dedicated future pass rather than bolted on. Nullable types
|
|
658
|
+
turned out to fit that "additive feature" shape after all: no variance or constraint
|
|
659
|
+
solving involved, so it landed as a normal-sized change — a lesson for scoping generics
|
|
660
|
+
itself, not evidence generics will be similarly sized.
|
|
573
661
|
|
|
574
662
|
`async`/`await`, `task<T>`, and `try`/`catch`/`finally`/`throw` are now in place (see the
|
|
575
663
|
language tour above) — the ceiling that's left is what's *inside* those: no async lambdas,
|
|
576
664
|
and no way to build a `task` value by hand outside an `async` function body.
|
|
577
665
|
|
|
578
|
-
###
|
|
666
|
+
### A frontend framework: Kopular
|
|
579
667
|
|
|
580
668
|
The language shape — modules, closures, DOM interop, `state<T>`, and `extern`/`virtual`
|
|
581
669
|
subclassing across a package boundary — exists to support real UI components, not just
|
|
582
670
|
scripts. That framework itself, [Kopular](https://dev.azure.com/koppinator/Koppindependence/_git/Kopular),
|
|
583
|
-
lives in its own repo
|
|
584
|
-
(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
671
|
+
lives in its own repo, published to npm as `kopular` and consumed like any other package
|
|
672
|
+
(no local checkout or `file:` dependency needed). A real app built on both lives in
|
|
673
|
+
[KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo) — several
|
|
674
|
+
routed pages, `state<T>`-driven reactivity, constructor-injected services via a
|
|
675
|
+
composition root (no DI container), structural-directive equivalents for `*ngIf`/`*ngFor`/
|
|
676
|
+
`*ngSwitch`, and real-URL (History API) routing — verified against a real DOM via jsdom
|
|
677
|
+
and in an actual browser.
|
package/assets/logo.png
ADDED
|
Binary file
|