kopscript 0.15.0 → 0.16.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 +17 -2
- package/README.md +34 -10
- package/dist/checker.js +87 -8
- package/dist/codegen.js +28 -4
- package/package.json +1 -1
package/LLM.md
CHANGED
|
@@ -238,12 +238,27 @@ limitation, not a semantic one).
|
|
|
238
238
|
```ks
|
|
239
239
|
number[] xs = [1, 2, 3];
|
|
240
240
|
xs.Length
|
|
241
|
-
xs.Push(4)
|
|
242
|
-
xs.Map((number x) => x * 2)
|
|
241
|
+
xs.Push(4) // NON-mutating — returns a new array, doesn't modify xs
|
|
242
|
+
xs.Map((number x) => x * 2) // -> array of whatever the callback returns
|
|
243
243
|
xs.Filter((number x) => x > 1)
|
|
244
244
|
xs.ForEach((number x) => { print(x); })
|
|
245
|
+
xs.Find((number x) => x > 1) // -> T?, null if nothing matches
|
|
246
|
+
xs.FindIndex((number x) => x > 1) // -> number, -1 if nothing matches
|
|
247
|
+
xs.Includes(2) // -> bool
|
|
248
|
+
xs.IndexOf(2) // -> number, -1 if not present
|
|
249
|
+
xs.Sort((number a, number b) => a - b) // NON-mutating, comparator always required
|
|
250
|
+
xs.Reverse() // NON-mutating
|
|
251
|
+
xs.Slice(1, 2) // both bounds always required, no optional params
|
|
252
|
+
xs.Concat(ys) // ys: same element type
|
|
253
|
+
xs.Join(", ") // -> string; number[]/string[]/bool[] element types only
|
|
254
|
+
xs.Reduce((number acc, number x) => acc + x, 0) // (acc, initial) — accumulator type = initial's type
|
|
245
255
|
```
|
|
246
256
|
|
|
257
|
+
`Push`/`Sort`/`Reverse` are non-mutating (return a new array) — the one deliberate
|
|
258
|
+
departure from real JS, where all three mutate in place; everything else here compiles
|
|
259
|
+
straight to its real `Array.prototype` equivalent. `Reduce`'s argument order matches real
|
|
260
|
+
JS (`reducer, initial`, not the other way around).
|
|
261
|
+
|
|
247
262
|
### Strings
|
|
248
263
|
|
|
249
264
|
```ks
|
package/README.md
CHANGED
|
@@ -676,8 +676,9 @@ KopScript's `string` type exposes PascalCase members that map directly onto
|
|
|
676
676
|
|
|
677
677
|
### Array stdlib
|
|
678
678
|
|
|
679
|
-
Arrays expose `.Length`, plus `Map`/`Filter`/`ForEach`/`Push
|
|
680
|
-
|
|
679
|
+
Arrays expose `.Length`, plus `Map`/`Filter`/`ForEach`/`Push`/`Find`/`FindIndex`/
|
|
680
|
+
`Includes`/`IndexOf`/`Sort`/`Reverse`/`Slice`/`Concat`/`Join`/`Reduce`, using lambdas or
|
|
681
|
+
any other function-valued expression (a named function, a variable holding one, ...):
|
|
681
682
|
|
|
682
683
|
```ks
|
|
683
684
|
number[] xs = [1, 2, 3, 4];
|
|
@@ -685,21 +686,44 @@ string[] labels = xs.Map((number x) => "n" + x); // ["n1", "n2", "n3", "n4"
|
|
|
685
686
|
number[] evens = xs.Filter((number x) => x % 2 == 0); // [2, 4]
|
|
686
687
|
xs.ForEach((number x) => print(x));
|
|
687
688
|
number[] grown = xs.Push(5); // [1, 2, 3, 4, 5]; xs itself is untouched
|
|
688
|
-
```
|
|
689
689
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
690
|
+
number? found = xs.Find((number x) => x > 2); // 3, or null if nothing matches
|
|
691
|
+
number idx = xs.FindIndex((number x) => x > 2); // 2, or -1 if nothing matches
|
|
692
|
+
bool has = xs.Includes(3); // true
|
|
693
|
+
number at = xs.IndexOf(3); // 2, or -1 if not present
|
|
694
|
+
|
|
695
|
+
number[] sorted = xs.Sort((number a, number b) => a - b); // new array; xs untouched
|
|
696
|
+
number[] rev = xs.Reverse(); // new array; xs untouched
|
|
697
|
+
number[] mid = xs.Slice(1, 3); // [2, 3]
|
|
698
|
+
number[] joined = xs.Concat([5, 6]); // [1, 2, 3, 4, 5, 6]
|
|
699
|
+
string csv = xs.Join(", "); // "1, 2, 3, 4"
|
|
700
|
+
|
|
701
|
+
number sum = xs.Reduce((number acc, number x) => acc + x, 0); // 10
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
`Map`/`Filter`/`ForEach`/`Find`/`FindIndex`/`Includes`/`IndexOf`/`Slice`/`Concat`/`Join`/
|
|
705
|
+
`Reduce` compile straight to their real `Array.prototype` equivalents. `Push`/`Sort`/
|
|
706
|
+
`Reverse` are the departures from JS: all three are **non-mutating** (return a new array;
|
|
707
|
+
the original is unchanged) — unlike JS's own `push`/`sort`/`reverse`, which all mutate in
|
|
708
|
+
place — chosen for consistency and because nothing else in KopScript's type system models
|
|
709
|
+
aliasing/mutable-reference semantics, so a silently-mutating method would be a surprising
|
|
710
|
+
outlier. `Sort`/`Reverse` compile to a spread-copy first (`[...xs].sort(...)`), not a
|
|
711
|
+
direct `.sort()`/`.reverse()` call. `Sort`'s comparator is always required (no
|
|
712
|
+
optional-parameter support in KopScript to fall back on, and it sidesteps real JS
|
|
713
|
+
`Array.sort`'s own well-known footgun — lexicographic-by-default on non-strings). `Slice`'s
|
|
714
|
+
both bounds are likewise always required. `Join` is scoped to primitive element types
|
|
715
|
+
(`number[]`/`string[]`/`bool[]`) only — a class/interface element has no way to customize
|
|
716
|
+
`toString()` in KopScript, so allowing it there would just produce `"[object Object]"`.
|
|
696
717
|
|
|
697
718
|
`Map`'s result type is the one genuinely polymorphic piece of the whole language — the
|
|
698
719
|
result element type is whatever the callback actually returns, not a fixed signature.
|
|
699
720
|
A plain function reference (`xs.Map(SomeFunction)`) already carries a fully-known type, so
|
|
700
721
|
that case is exact; an inline expression-bodied lambda (`xs.Map((number x) => ...)`) has
|
|
701
722
|
its return type inferred from the body. A block-bodied lambda passed to `Map` is a known
|
|
702
|
-
v1 gap — its result type can't be inferred that way yet.
|
|
723
|
+
v1 gap — its result type can't be inferred that way yet. `Reduce` is similarly
|
|
724
|
+
polymorphic, but simpler to infer: its accumulator type comes directly from the `initial`
|
|
725
|
+
argument (`xs.Reduce(reducer, initial)`, matching real JS's own argument order), not from
|
|
726
|
+
inspecting the callback body.
|
|
703
727
|
|
|
704
728
|
### Control flow
|
|
705
729
|
|
package/dist/checker.js
CHANGED
|
@@ -1714,8 +1714,9 @@ export class Checker {
|
|
|
1714
1714
|
return info.returnType;
|
|
1715
1715
|
}
|
|
1716
1716
|
if (expr.callee.kind === "MemberExpr") {
|
|
1717
|
-
// Array.Map's result
|
|
1718
|
-
//
|
|
1717
|
+
// Array.Map/Reduce's result types are polymorphic (derived from the
|
|
1718
|
+
// callback and, for Reduce, the initial-value argument), which
|
|
1719
|
+
// doesn't fit checkMember's fixed-signature methodInfo shape —
|
|
1719
1720
|
// handled here instead, before the generic member/call path below.
|
|
1720
1721
|
if (expr.callee.property === "Map") {
|
|
1721
1722
|
const objectType = this.checkExpression(expr.callee.object, scope, ctx);
|
|
@@ -1724,6 +1725,13 @@ export class Checker {
|
|
|
1724
1725
|
return this.checkArrayMap(expr, objectType.element, scope, ctx);
|
|
1725
1726
|
}
|
|
1726
1727
|
}
|
|
1728
|
+
if (expr.callee.property === "Reduce") {
|
|
1729
|
+
const objectType = this.checkExpression(expr.callee.object, scope, ctx);
|
|
1730
|
+
if (objectType.kind === "array") {
|
|
1731
|
+
expr.callee.isBuiltin = true;
|
|
1732
|
+
return this.checkArrayReduce(expr, objectType.element, scope, ctx);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1727
1735
|
const { type: methodType, methodInfo } = this.checkMember(expr.callee, scope, ctx);
|
|
1728
1736
|
if (methodInfo) {
|
|
1729
1737
|
this.checkArgs(expr, methodInfo.params, scope, ctx);
|
|
@@ -1911,6 +1919,48 @@ export class Checker {
|
|
|
1911
1919
|
// nothing else in the type system models. Compiles to `[...arr, x]`.
|
|
1912
1920
|
return { ...method, params: [elementType], returnType: T.arrayOf(elementType) };
|
|
1913
1921
|
}
|
|
1922
|
+
if (name === "Find") {
|
|
1923
|
+
// `null` for "no match" rather than throwing/needing a sentinel —
|
|
1924
|
+
// the natural fit given nullable types already exist.
|
|
1925
|
+
return { ...method, params: [T.functionType([elementType], T.BOOL)], returnType: T.nullableOf(elementType) };
|
|
1926
|
+
}
|
|
1927
|
+
if (name === "FindIndex" || name === "IndexOf") {
|
|
1928
|
+
// -1 for "not found", matching real JS — no nullable number needed,
|
|
1929
|
+
// and keeps codegen a plain passthrough with no wrapping/unwrapping.
|
|
1930
|
+
return { ...method, params: name === "FindIndex" ? [T.functionType([elementType], T.BOOL)] : [elementType], returnType: T.NUMBER };
|
|
1931
|
+
}
|
|
1932
|
+
if (name === "Includes") {
|
|
1933
|
+
return { ...method, params: [elementType], returnType: T.BOOL };
|
|
1934
|
+
}
|
|
1935
|
+
if (name === "Sort") {
|
|
1936
|
+
// No optional-parameter support in KopScript, so the comparator is
|
|
1937
|
+
// always required — deliberately avoids real JS Array.sort's own
|
|
1938
|
+
// well-known footgun (lexicographic-by-default on non-strings) by
|
|
1939
|
+
// never having an implicit comparator to fall back to. Non-mutating,
|
|
1940
|
+
// like Push — see genCall's own comment for the codegen shape.
|
|
1941
|
+
return { ...method, params: [T.functionType([elementType, elementType], T.NUMBER)], returnType: T.arrayOf(elementType) };
|
|
1942
|
+
}
|
|
1943
|
+
if (name === "Reverse") {
|
|
1944
|
+
return { ...method, params: [], returnType: T.arrayOf(elementType) };
|
|
1945
|
+
}
|
|
1946
|
+
if (name === "Slice") {
|
|
1947
|
+
// Both bounds always required, same "no optional params" reasoning
|
|
1948
|
+
// as Sort's always-required comparator.
|
|
1949
|
+
return { ...method, params: [T.NUMBER, T.NUMBER], returnType: T.arrayOf(elementType) };
|
|
1950
|
+
}
|
|
1951
|
+
if (name === "Concat") {
|
|
1952
|
+
return { ...method, params: [T.arrayOf(elementType)], returnType: T.arrayOf(elementType) };
|
|
1953
|
+
}
|
|
1954
|
+
if (name === "Join") {
|
|
1955
|
+
// Real JS Array.join stringifies every element via implicit toString
|
|
1956
|
+
// — fine for primitives (numbers/bools stringify sensibly), a real
|
|
1957
|
+
// footgun for a class/interface element with no toString override
|
|
1958
|
+
// ("[object Object]"), which KopScript has no way to customize.
|
|
1959
|
+
// Scoped to primitive element types only, a deliberate v1 cut.
|
|
1960
|
+
if (elementType.kind !== "string" && elementType.kind !== "number" && elementType.kind !== "bool")
|
|
1961
|
+
return null;
|
|
1962
|
+
return { ...method, params: [T.STRING], returnType: T.STRING };
|
|
1963
|
+
}
|
|
1914
1964
|
return null;
|
|
1915
1965
|
}
|
|
1916
1966
|
// Array.Map: `arr.Map(f)` where f: (T) => U, result: U[]. U is whatever
|
|
@@ -1939,6 +1989,34 @@ export class Checker {
|
|
|
1939
1989
|
}
|
|
1940
1990
|
return T.arrayOf(argType.returnType);
|
|
1941
1991
|
}
|
|
1992
|
+
// Array.Reduce: `arr.Reduce(reducer, initial)` where `reducer: (U, T) =>
|
|
1993
|
+
// U`, matching real JS Array.prototype.reduce's own argument order.
|
|
1994
|
+
// Simpler than Map's own inference — U comes directly from `initial`'s
|
|
1995
|
+
// checked type, no partial-lambda-type inference needed at all.
|
|
1996
|
+
checkArrayReduce(expr, elementType, scope, ctx) {
|
|
1997
|
+
if (expr.args.length !== 2) {
|
|
1998
|
+
this.diagnostics.error("KS4102", `Reduce expects exactly 2 arguments, got ${expr.args.length}`, expr.line, expr.col);
|
|
1999
|
+
expr.args.forEach((a) => this.checkExpression(a, scope, ctx));
|
|
2000
|
+
return T.UNKNOWN;
|
|
2001
|
+
}
|
|
2002
|
+
const [reducerArg, initialArg] = expr.args;
|
|
2003
|
+
const accumulatorType = this.checkExpression(initialArg, scope, ctx);
|
|
2004
|
+
const expectedReducerType = T.functionType([accumulatorType, elementType], accumulatorType);
|
|
2005
|
+
const reducerType = this.checkExpressionExpecting(reducerArg, expectedReducerType, scope, ctx);
|
|
2006
|
+
if (reducerType.kind !== "function") {
|
|
2007
|
+
if (reducerType.kind !== "unknown") {
|
|
2008
|
+
this.diagnostics.error("KS4103", `Reduce expects a function as its first argument, got '${T.typeToString(reducerType)}'`, reducerArg.line, reducerArg.col);
|
|
2009
|
+
}
|
|
2010
|
+
return accumulatorType;
|
|
2011
|
+
}
|
|
2012
|
+
const paramsOk = reducerType.params.length === 2 &&
|
|
2013
|
+
this.isAssignableType(accumulatorType, reducerType.params[0]) &&
|
|
2014
|
+
this.isAssignableType(elementType, reducerType.params[1]);
|
|
2015
|
+
if (!paramsOk || !this.isAssignableType(reducerType.returnType, accumulatorType)) {
|
|
2016
|
+
this.diagnostics.error("KS4104", `Reduce callback must take '(${T.typeToString(accumulatorType)}, ${T.typeToString(elementType)})' and return '${T.typeToString(accumulatorType)}', got '${T.typeToString(reducerType)}'`, reducerArg.line, reducerArg.col);
|
|
2017
|
+
}
|
|
2018
|
+
return accumulatorType;
|
|
2019
|
+
}
|
|
1942
2020
|
checkMember(expr, scope, ctx, isAssignTarget = false) {
|
|
1943
2021
|
const result = this.checkMemberInner(expr, scope, ctx, isAssignTarget);
|
|
1944
2022
|
const text = result.methodInfo
|
|
@@ -2003,12 +2081,13 @@ export class Checker {
|
|
|
2003
2081
|
const method = this.arrayMethod(objectType.element, expr.property);
|
|
2004
2082
|
if (method)
|
|
2005
2083
|
return { type: method.returnType, methodInfo: method };
|
|
2006
|
-
// Map
|
|
2007
|
-
// (derived from the callback
|
|
2008
|
-
//
|
|
2009
|
-
//
|
|
2010
|
-
//
|
|
2011
|
-
//
|
|
2084
|
+
// Map/Reduce aren't handled here at all — their result types are
|
|
2085
|
+
// polymorphic (derived from the callback, and for Reduce the initial
|
|
2086
|
+
// value, passed at the call site), which doesn't fit this
|
|
2087
|
+
// fixed-signature lookup. checkCall special-cases both before ever
|
|
2088
|
+
// reaching checkMember, so `arr.Map(f)`/`arr.Reduce(f, init)` work;
|
|
2089
|
+
// either used as a bare value (not called) falls through to this
|
|
2090
|
+
// error, same as an unknown member — a known v1 restriction.
|
|
2012
2091
|
this.diagnostics.error("KS4080", `Unknown array member '${expr.property}'`, expr.line, expr.col);
|
|
2013
2092
|
return { type: T.UNKNOWN, methodInfo: null };
|
|
2014
2093
|
}
|
package/dist/codegen.js
CHANGED
|
@@ -10,10 +10,15 @@ function countNewlines(text) {
|
|
|
10
10
|
// blind syntactic rename (codegen has no type information, so this fires on
|
|
11
11
|
// any member access with a matching name, string/array/user-class alike;
|
|
12
12
|
// same pre-existing risk as any of the entries below, going back to the
|
|
13
|
-
// original string stdlib). Map/Filter/ForEach
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// original string stdlib). Map/Filter/ForEach/Find/FindIndex/Includes/
|
|
14
|
+
// IndexOf/Slice/Concat/Join/Reduce need nothing beyond the rename — the
|
|
15
|
+
// callback and everything else already codegens generically, and every one
|
|
16
|
+
// of these is already non-mutating in real JS, so a plain passthrough is
|
|
17
|
+
// correct as-is. Push/Sort/Reverse are handled separately in genCall
|
|
18
|
+
// instead: Push because it's non-mutating in KopScript (unlike JS's own
|
|
19
|
+
// Array.push) and needs a different call shape entirely; Sort/Reverse for
|
|
20
|
+
// the same reason — JS's own versions mutate in place, and KopScript's
|
|
21
|
+
// don't, matching Push's own non-mutating convention.
|
|
17
22
|
const MEMBER_METHOD_MAP = {
|
|
18
23
|
Contains: "includes",
|
|
19
24
|
StartsWith: "startsWith",
|
|
@@ -26,6 +31,14 @@ const MEMBER_METHOD_MAP = {
|
|
|
26
31
|
Map: "map",
|
|
27
32
|
Filter: "filter",
|
|
28
33
|
ForEach: "forEach",
|
|
34
|
+
Find: "find",
|
|
35
|
+
FindIndex: "findIndex",
|
|
36
|
+
Includes: "includes",
|
|
37
|
+
IndexOf: "indexOf",
|
|
38
|
+
Slice: "slice",
|
|
39
|
+
Concat: "concat",
|
|
40
|
+
Join: "join",
|
|
41
|
+
Reduce: "reduce",
|
|
29
42
|
};
|
|
30
43
|
const BINARY_OP_MAP = {
|
|
31
44
|
"+": "+",
|
|
@@ -461,6 +474,17 @@ export class CodeGenerator {
|
|
|
461
474
|
if (expr.callee.kind === "MemberExpr" && expr.callee.isBuiltin && expr.callee.property === "Push" && expr.args.length === 1) {
|
|
462
475
|
return `[...${this.genExpr(expr.callee.object)}, ${this.genExpr(expr.args[0])}]`;
|
|
463
476
|
}
|
|
477
|
+
// Sort/Reverse are non-mutating in KopScript too, same reasoning as
|
|
478
|
+
// Push — JS's own Array.prototype.sort/reverse both mutate in place
|
|
479
|
+
// *and* return the same array, so a plain rename would silently make
|
|
480
|
+
// KopScript code alias its own "immutable" array. Copy first (`[...arr]`),
|
|
481
|
+
// then call the real JS method on the copy.
|
|
482
|
+
if (expr.callee.kind === "MemberExpr" && expr.callee.isBuiltin && expr.callee.property === "Sort" && expr.args.length === 1) {
|
|
483
|
+
return `[...${this.genExpr(expr.callee.object)}].sort(${this.genExpr(expr.args[0])})`;
|
|
484
|
+
}
|
|
485
|
+
if (expr.callee.kind === "MemberExpr" && expr.callee.isBuiltin && expr.callee.property === "Reverse" && expr.args.length === 0) {
|
|
486
|
+
return `[...${this.genExpr(expr.callee.object)}].reverse()`;
|
|
487
|
+
}
|
|
464
488
|
return `${this.genExpr(expr.callee)}(${expr.args.map((a) => this.genExpr(a)).join(", ")})`;
|
|
465
489
|
}
|
|
466
490
|
genMember(expr) {
|
package/package.json
CHANGED