@venn-lang/core 0.1.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/LICENSE +21 -0
- package/README.md +303 -0
- package/dist/index.d.ts +3654 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13137 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
- package/src/ast/call-args.ts +15 -0
- package/src/ast/dotted-path.ts +22 -0
- package/src/ast/index.ts +7 -0
- package/src/ast/is-statement.ts +40 -0
- package/src/ast/split-call.ts +28 -0
- package/src/ast/walk.ts +6 -0
- package/src/codes/build-problem.ts +24 -0
- package/src/codes/catalog.ts +35 -0
- package/src/codes/code.types.ts +7 -0
- package/src/codes/index.ts +3 -0
- package/src/compile/compile.ts +169 -0
- package/src/compile/compile.types.ts +40 -0
- package/src/compile/index.ts +3 -0
- package/src/compile/lex-scope.ts +78 -0
- package/src/compile/nodes/binary.ts +56 -0
- package/src/compile/nodes/call.ts +50 -0
- package/src/compile/nodes/collection.ts +83 -0
- package/src/compile/nodes/const-lit.ts +30 -0
- package/src/compile/nodes/fast-binary.ts +71 -0
- package/src/compile/nodes/fn.ts +87 -0
- package/src/compile/nodes/index.ts +8 -0
- package/src/compile/nodes/literal.ts +86 -0
- package/src/compile/nodes/map-fields.ts +79 -0
- package/src/compile/nodes/member.ts +32 -0
- package/src/events/envelope.types.ts +17 -0
- package/src/events/event-data.types.ts +25 -0
- package/src/events/ids.types.ts +10 -0
- package/src/events/index.ts +5 -0
- package/src/events/run-plan.types.ts +18 -0
- package/src/events/status.types.ts +2 -0
- package/src/expand/accepted-kinds.ts +23 -0
- package/src/expand/deco/deco-decorator.ts +77 -0
- package/src/expand/deco/deco-env.ts +24 -0
- package/src/expand/deco/deco.types.ts +43 -0
- package/src/expand/deco/document-decos.ts +64 -0
- package/src/expand/deco/index.ts +7 -0
- package/src/expand/deco/read-signature.ts +57 -0
- package/src/expand/deco/run-body.ts +119 -0
- package/src/expand/decorate-callable.ts +49 -0
- package/src/expand/decorations.ts +41 -0
- package/src/expand/expand.ts +108 -0
- package/src/expand/expand.types.ts +78 -0
- package/src/expand/handles/around-verbs.ts +35 -0
- package/src/expand/handles/binding-verbs.ts +27 -0
- package/src/expand/handles/common-verbs.ts +40 -0
- package/src/expand/handles/fn-verbs.ts +58 -0
- package/src/expand/handles/handle.types.ts +33 -0
- package/src/expand/handles/index.ts +4 -0
- package/src/expand/handles/kind-of.ts +36 -0
- package/src/expand/handles/make-handle.ts +100 -0
- package/src/expand/handles/missing-verb.ts +27 -0
- package/src/expand/handles/runnable-verbs.ts +13 -0
- package/src/expand/handles/type-verbs.ts +56 -0
- package/src/expand/index.ts +40 -0
- package/src/expand/make-context.ts +69 -0
- package/src/expand/node-meta.ts +30 -0
- package/src/expand/node-span.ts +17 -0
- package/src/expand/swap-node.ts +28 -0
- package/src/expand/wrong-kind.ts +102 -0
- package/src/expr/cell.types.ts +25 -0
- package/src/expr/closure.ts +53 -0
- package/src/expr/closure.types.ts +31 -0
- package/src/expr/eval-env.types.ts +9 -0
- package/src/expr/evaluate.ts +18 -0
- package/src/expr/frame.ts +60 -0
- package/src/expr/index.ts +14 -0
- package/src/expr/invoke.ts +140 -0
- package/src/expr/member-value.ts +91 -0
- package/src/expr/methods/index.ts +65 -0
- package/src/expr/methods/list-grouping.ts +107 -0
- package/src/expr/methods/list-methods.ts +57 -0
- package/src/expr/methods/list-selection.ts +142 -0
- package/src/expr/methods/map-extras.ts +87 -0
- package/src/expr/methods/map-methods.ts +17 -0
- package/src/expr/methods/number-methods.ts +29 -0
- package/src/expr/methods/string-extras.ts +66 -0
- package/src/expr/methods/string-methods.ts +32 -0
- package/src/expr/methods/unit-methods.ts +54 -0
- package/src/expr/namespace.ts +20 -0
- package/src/expr/native.types.ts +40 -0
- package/src/expr/operators.ts +91 -0
- package/src/expr/pending.ts +47 -0
- package/src/expr/prelude.ts +75 -0
- package/src/expr/task.ts +71 -0
- package/src/format/format-text.ts +22 -0
- package/src/format/format.types.ts +18 -0
- package/src/format/from-settings.ts +40 -0
- package/src/format/index.ts +5 -0
- package/src/format/organize-header.ts +66 -0
- package/src/format/reindent.ts +35 -0
- package/src/generated/ast.ts +2383 -0
- package/src/generated/grammar.ts +5236 -0
- package/src/generated/module.ts +25 -0
- package/src/grammar/venn.langium +291 -0
- package/src/graph/graph.types.ts +20 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/to-graph.ts +87 -0
- package/src/index.ts +93 -0
- package/src/interpolation/compile-template.ts +40 -0
- package/src/interpolation/index.ts +4 -0
- package/src/interpolation/interpolation.types.ts +18 -0
- package/src/interpolation/scan-interpolations.ts +47 -0
- package/src/interpolation/template.types.ts +19 -0
- package/src/lang/default-services.ts +14 -0
- package/src/lang/index.ts +3 -0
- package/src/lang/venn-lexer.ts +34 -0
- package/src/lang/venn-module.ts +51 -0
- package/src/module/index.ts +5 -0
- package/src/module/specifier.ts +21 -0
- package/src/parse/error-to-problem.ts +43 -0
- package/src/parse/index.ts +3 -0
- package/src/parse/parse-expression.ts +27 -0
- package/src/parse/parse-output.types.ts +8 -0
- package/src/parse/parse.ts +26 -0
- package/src/problem/build-diff.ts +116 -0
- package/src/problem/diff.types.ts +20 -0
- package/src/problem/format-value.ts +40 -0
- package/src/problem/index.ts +8 -0
- package/src/problem/problem-error.ts +15 -0
- package/src/problem/problem.types.ts +21 -0
- package/src/problem/related.types.ts +7 -0
- package/src/problem/severity.types.ts +2 -0
- package/src/problem/span.types.ts +8 -0
- package/src/typecheck/action-signature.ts +53 -0
- package/src/typecheck/builtin-types.ts +63 -0
- package/src/typecheck/builtins.ts +267 -0
- package/src/typecheck/catalog.types.ts +16 -0
- package/src/typecheck/check-deco.ts +174 -0
- package/src/typecheck/check-stmts.ts +151 -0
- package/src/typecheck/check-types.ts +232 -0
- package/src/typecheck/context.ts +31 -0
- package/src/typecheck/imported-types.ts +127 -0
- package/src/typecheck/index.ts +26 -0
- package/src/typecheck/infer.ts +416 -0
- package/src/typecheck/kind-types.ts +79 -0
- package/src/typecheck/member-docs.ts +211 -0
- package/src/typecheck/named-types.ts +57 -0
- package/src/typecheck/prelude-types.ts +152 -0
- package/src/typecheck/reshaped-fns.ts +58 -0
- package/src/typecheck/scheme.ts +75 -0
- package/src/typecheck/seed-params.ts +72 -0
- package/src/typecheck/seed-values.ts +64 -0
- package/src/typecheck/show.ts +64 -0
- package/src/typecheck/solidify.ts +60 -0
- package/src/typecheck/spec-to-type.ts +88 -0
- package/src/typecheck/type-env.ts +46 -0
- package/src/typecheck/type-ref.ts +62 -0
- package/src/typecheck/type.types.ts +168 -0
- package/src/typecheck/unify.ts +192 -0
- package/src/typecheck/unit-types.ts +49 -0
- package/src/units/combine.ts +97 -0
- package/src/units/index.ts +6 -0
- package/src/units/parse-instant.ts +12 -0
- package/src/units/parse-number.ts +48 -0
- package/src/units/unit-guards.ts +16 -0
- package/src/units/unit.types.ts +38 -0
- package/src/value/equals.ts +4 -0
- package/src/value/index.ts +4 -0
- package/src/value/is-numeric.ts +6 -0
- package/src/value/truthiness.ts +9 -0
- package/src/value/value.types.ts +12 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { EvalEnv } from "./eval-env.types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A binding's storage, addressed once and read many times.
|
|
5
|
+
*
|
|
6
|
+
* The language has no assignment, so a name never changes what it holds, but it
|
|
7
|
+
* can be *filled in* after the function reading it was built. A recursive `fn`
|
|
8
|
+
* is the plain case: `fib` becomes a value before the name `fib` is bound, so
|
|
9
|
+
* anything resolved eagerly would capture nothing. The cell is handed out empty
|
|
10
|
+
* and filled when the binding happens, which is what lets a name be resolved at
|
|
11
|
+
* compile time and still read the right value.
|
|
12
|
+
*/
|
|
13
|
+
export interface Cell {
|
|
14
|
+
value: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** An environment that can hand out a cell per name: the top of the chain. */
|
|
18
|
+
export interface CellEnv extends EvalEnv {
|
|
19
|
+
cell(name: string): Cell;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Whether this environment addresses its bindings by cell. */
|
|
23
|
+
export function hasCells(env: EvalEnv): env is CellEnv {
|
|
24
|
+
return typeof (env as CellEnv).cell === "function";
|
|
25
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CompiledBody } from "../compile/compile.types.js";
|
|
2
|
+
import { hasCells } from "./cell.types.js";
|
|
3
|
+
import { CLOSURE, type Closure } from "./closure.types.js";
|
|
4
|
+
import type { EvalEnv } from "./eval-env.types.js";
|
|
5
|
+
|
|
6
|
+
/** Whether this value is a `fn`. The brand distinguishes it from a lookalike map. */
|
|
7
|
+
export function isClosure(value: unknown): value is Closure {
|
|
8
|
+
return typeof value === "object" && value !== null && CLOSURE in value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build a function value from parts the compiler already prepared.
|
|
13
|
+
*
|
|
14
|
+
* The free names are addressed here, once, against the environment the function
|
|
15
|
+
* is being defined in, so the body reads them by index rather than walking the
|
|
16
|
+
* chain on every call. Only where that environment hands out cells: a function
|
|
17
|
+
* nested in another reads its parent's frame, which has slots and no cells, and
|
|
18
|
+
* there the body falls back to asking by name.
|
|
19
|
+
*/
|
|
20
|
+
export function makeClosure(params: readonly string[], body: CompiledBody, env: EvalEnv): Closure {
|
|
21
|
+
if (body.free.length === 0 || !hasCells(env)) return { [CLOSURE]: true, params, body, env };
|
|
22
|
+
const up = body.free.map((name) => env.cell(name));
|
|
23
|
+
return { [CLOSURE]: true, params, body, env, up };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A class, not an object literal holding a closure: one shared prototype method
|
|
28
|
+
* means the `lookup` call site inside the evaluator sees a single callee.
|
|
29
|
+
*
|
|
30
|
+
* `Object.hasOwn`, not `in`: `in` walks the prototype chain, so a Venn binding
|
|
31
|
+
* named `constructor` or `toString` would resolve to JavaScript's own.
|
|
32
|
+
*/
|
|
33
|
+
class ChildEnv implements EvalEnv {
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly parent: EvalEnv,
|
|
36
|
+
private readonly bindings: Record<string, unknown>,
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
lookup(name: string): unknown {
|
|
40
|
+
if (Object.hasOwn(this.bindings, name)) return this.bindings[name];
|
|
41
|
+
return this.parent.lookup(name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* An environment nested in `parent`, holding the given bindings.
|
|
47
|
+
*
|
|
48
|
+
* Only own properties of `bindings` count as bound, so a name such as
|
|
49
|
+
* `toString` resolves through to the parent rather than to JavaScript's own.
|
|
50
|
+
*/
|
|
51
|
+
export function childEnv(parent: EvalEnv, bindings: Record<string, unknown>): EvalEnv {
|
|
52
|
+
return new ChildEnv(parent, bindings);
|
|
53
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CompiledBody } from "../compile/compile.types.js";
|
|
2
|
+
import type { Cell } from "./cell.types.js";
|
|
3
|
+
import type { EvalEnv } from "./eval-env.types.js";
|
|
4
|
+
|
|
5
|
+
/** The brand that tells a function value apart from a map that happens to look like one. */
|
|
6
|
+
export const CLOSURE: unique symbol = Symbol("venn.closure");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A function value: its parameter names, its compiled body, and the environment
|
|
10
|
+
* it was defined in.
|
|
11
|
+
*
|
|
12
|
+
* Because `fn` is pure (§08), a closure runs entirely in the evaluator, which
|
|
13
|
+
* is what makes functions first-class and callable from any expression,
|
|
14
|
+
* interpolation included.
|
|
15
|
+
*
|
|
16
|
+
* The body arrives already compiled. A `fn (…) => …` written inside `map` is
|
|
17
|
+
* evaluated once per call to `map`, so compiling there would put the compiler
|
|
18
|
+
* back in the hot loop.
|
|
19
|
+
*/
|
|
20
|
+
export interface Closure {
|
|
21
|
+
readonly [CLOSURE]: true;
|
|
22
|
+
readonly params: readonly string[];
|
|
23
|
+
readonly body: CompiledBody;
|
|
24
|
+
readonly env: EvalEnv;
|
|
25
|
+
/**
|
|
26
|
+
* A cell per free name of the body, resolved when this value was made.
|
|
27
|
+
* Absent when the defining environment does not address bindings by cell: a
|
|
28
|
+
* function nested inside another, whose free names live in its frame.
|
|
29
|
+
*/
|
|
30
|
+
readonly up?: readonly Cell[];
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scope the evaluator reads from: `let`/`const`, captures, parameters.
|
|
3
|
+
*
|
|
4
|
+
* Actions and matchers are deliberately absent. They belong to the runtime
|
|
5
|
+
* registry, which keeps the evaluator pure and free of protocol execution.
|
|
6
|
+
*/
|
|
7
|
+
export interface EvalEnv {
|
|
8
|
+
lookup(name: string): unknown;
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { compileExpr } from "../compile/index.js";
|
|
2
|
+
import type { Expr } from "../generated/ast.js";
|
|
3
|
+
import type { EvalEnv } from "./eval-env.types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Evaluate a kernel expression against an injected environment.
|
|
7
|
+
*
|
|
8
|
+
* Compilation happens once per node and is remembered, so calling this in a
|
|
9
|
+
* loop pays for the tree only on the first pass.
|
|
10
|
+
*
|
|
11
|
+
* @returns The value, or a promise for it when the expression reached something
|
|
12
|
+
* that has not arrived yet.
|
|
13
|
+
* @throws ProblemError When the expression fails: a unit mismatch, a value that
|
|
14
|
+
* is not callable, or a placeholder that does not parse.
|
|
15
|
+
*/
|
|
16
|
+
export function evaluate(expr: Expr, env: EvalEnv): unknown {
|
|
17
|
+
return compileExpr(expr)(env);
|
|
18
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Closure } from "./closure.types.js";
|
|
2
|
+
import type { EvalEnv } from "./eval-env.types.js";
|
|
3
|
+
|
|
4
|
+
/** Past this many names a function keeps the rest in an array. */
|
|
5
|
+
export const INLINE_SLOTS = 3;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A function call's own storage: one slot per name it binds.
|
|
9
|
+
*
|
|
10
|
+
* The compiler knows every name a function introduces, so a reference to one
|
|
11
|
+
* becomes a field read instead of a search. A name it does *not* introduce (a
|
|
12
|
+
* top-level binding, a namespace, the prelude) is a cell the closure resolved
|
|
13
|
+
* when it was built, which is an index into `up`.
|
|
14
|
+
*
|
|
15
|
+
* The first three slots are fields rather than an array because almost every
|
|
16
|
+
* function binds three names or fewer, and a frame plus an array is two
|
|
17
|
+
* allocations per call. Beyond three, `rest` holds the remainder.
|
|
18
|
+
*
|
|
19
|
+
* `lookup` stays because a name can still arrive as text: an expression inside
|
|
20
|
+
* `"${…}"` is compiled apart from the body that holds it, so it asks by name.
|
|
21
|
+
* Scanning a handful of parameters beats walking a chain of maps.
|
|
22
|
+
*/
|
|
23
|
+
export class Frame implements EvalEnv {
|
|
24
|
+
s0: unknown;
|
|
25
|
+
s1: unknown;
|
|
26
|
+
s2: unknown;
|
|
27
|
+
rest: unknown[] | undefined;
|
|
28
|
+
/** The cells this function's free names resolved to, in compile order. */
|
|
29
|
+
readonly up: readonly { value: unknown }[] | undefined;
|
|
30
|
+
|
|
31
|
+
constructor(readonly closure: Closure) {
|
|
32
|
+
const body = closure.body;
|
|
33
|
+
this.s0 = undefined;
|
|
34
|
+
this.s1 = undefined;
|
|
35
|
+
this.s2 = undefined;
|
|
36
|
+
this.rest = body.extra === 0 ? undefined : new Array<unknown>(body.extra);
|
|
37
|
+
this.up = closure.up;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
lookup(name: string): unknown {
|
|
41
|
+
const at = this.closure.body.names.indexOf(name);
|
|
42
|
+
return at === -1 ? this.closure.env.lookup(name) : readSlot(this, at);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The slot at `at`, wherever it is held. */
|
|
47
|
+
export function readSlot(frame: Frame, at: number): unknown {
|
|
48
|
+
if (at === 0) return frame.s0;
|
|
49
|
+
if (at === 1) return frame.s1;
|
|
50
|
+
if (at === 2) return frame.s2;
|
|
51
|
+
return (frame.rest as unknown[])[at - INLINE_SLOTS];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Write the slot at `at`. Used for the parameters and the body's locals. */
|
|
55
|
+
export function writeSlot(frame: Frame, at: number, value: unknown): void {
|
|
56
|
+
if (at === 0) frame.s0 = value;
|
|
57
|
+
else if (at === 1) frame.s1 = value;
|
|
58
|
+
else if (at === 2) frame.s2 = value;
|
|
59
|
+
else (frame.rest as unknown[])[at - INLINE_SLOTS] = value;
|
|
60
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type { Cell, CellEnv } from "./cell.types.js";
|
|
2
|
+
export { hasCells } from "./cell.types.js";
|
|
3
|
+
export { childEnv, isClosure, makeClosure } from "./closure.js";
|
|
4
|
+
export type { Closure } from "./closure.types.js";
|
|
5
|
+
export type { EvalEnv } from "./eval-env.types.js";
|
|
6
|
+
export { evaluate } from "./evaluate.js";
|
|
7
|
+
export { callClosure, invoke, invoke1, isCallable } from "./invoke.js";
|
|
8
|
+
export { memberValue } from "./member-value.js";
|
|
9
|
+
export { isNamespaceValue, namespaceValue } from "./namespace.js";
|
|
10
|
+
export { isNativeFn, type NativeFn, nativeFn } from "./native.types.js";
|
|
11
|
+
export { applyBinary, negate, PLAIN } from "./operators.js";
|
|
12
|
+
export { isWaiting, whenAllReady, whenBothReady, whenReady } from "./pending.js";
|
|
13
|
+
export { display, PRELUDE_VALUES, typeName } from "./prelude.js";
|
|
14
|
+
export { isTask, startTask, type Task } from "./task.js";
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { buildProblem, CODES } from "../codes/index.js";
|
|
2
|
+
import { ProblemError } from "../problem/index.js";
|
|
3
|
+
import { isClosure } from "./closure.js";
|
|
4
|
+
import type { Closure } from "./closure.types.js";
|
|
5
|
+
import { Frame, writeSlot } from "./frame.js";
|
|
6
|
+
import { type Invoke, isNativeFn } from "./native.types.js";
|
|
7
|
+
|
|
8
|
+
const NO_SPAN = { uri: "", offset: 0, length: 0, line: 1, column: 1 };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Call any Venn callable, a `fn` closure or a built-in method, with values.
|
|
12
|
+
*
|
|
13
|
+
* A callee declaring fewer parameters than it is handed ignores the rest.
|
|
14
|
+
*
|
|
15
|
+
* @throws ProblemError VN3013 when the value is not callable.
|
|
16
|
+
*/
|
|
17
|
+
export function invoke(callee: unknown, values: readonly unknown[]): unknown {
|
|
18
|
+
if (isClosure(callee)) return callClosure(callee, values);
|
|
19
|
+
if (isNativeFn(callee)) return callee.call(values);
|
|
20
|
+
throw notCallable(callee);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Whether {@link invoke} can call this, asked before committing to a call. */
|
|
24
|
+
export function isCallable(value: unknown): boolean {
|
|
25
|
+
return isClosure(value) || isNativeFn(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The invoker handed to the built-in methods, fixed arities included. */
|
|
29
|
+
export const INVOKE: Invoke = Object.assign(invoke, {
|
|
30
|
+
one: invoke1,
|
|
31
|
+
two: invoke2,
|
|
32
|
+
three: invoke3,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Call with a single value, without building an argument list for it.
|
|
37
|
+
*
|
|
38
|
+
* One argument is what most calls carry, and the array holding it would live
|
|
39
|
+
* exactly as long as the call takes to read it out again.
|
|
40
|
+
*
|
|
41
|
+
* @throws ProblemError VN3013 when the value is not callable.
|
|
42
|
+
*/
|
|
43
|
+
export function invoke1(callee: unknown, arg: unknown): unknown {
|
|
44
|
+
if (isClosure(callee)) return callClosure1(callee, arg);
|
|
45
|
+
if (isNativeFn(callee)) return callee.call([arg]);
|
|
46
|
+
throw notCallable(callee);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The same for two and three values, which is what the list methods hand over.
|
|
51
|
+
*
|
|
52
|
+
* `xs.map(fn (x) => …)` calls its function once per element, so an argument
|
|
53
|
+
* list would be one array per element built only to be taken apart again.
|
|
54
|
+
*/
|
|
55
|
+
export function invoke2(callee: unknown, a: unknown, b: unknown): unknown {
|
|
56
|
+
if (isClosure(callee)) return callClosure2(callee, a, b);
|
|
57
|
+
if (isNativeFn(callee)) return callee.call([a, b]);
|
|
58
|
+
throw notCallable(callee);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function invoke3(callee: unknown, a: unknown, b: unknown, c: unknown): unknown {
|
|
62
|
+
if (isClosure(callee)) return callClosure3(callee, a, b, c);
|
|
63
|
+
if (isNativeFn(callee)) return callee.call([a, b, c]);
|
|
64
|
+
throw notCallable(callee);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Run a closure's compiled body against the given argument values.
|
|
69
|
+
*
|
|
70
|
+
* One allocation for the call: the frame itself. The names, the body and every
|
|
71
|
+
* expression in it were settled when the function was compiled.
|
|
72
|
+
*
|
|
73
|
+
* @returns The body's result, or a promise for it when the body reached
|
|
74
|
+
* something that has not arrived yet.
|
|
75
|
+
*/
|
|
76
|
+
export function callClosure(closure: Closure, values: readonly unknown[]): unknown {
|
|
77
|
+
if (closure.body.bare) return closure.body.result(values[0] as never);
|
|
78
|
+
const frame = new Frame(closure);
|
|
79
|
+
const arity = closure.params.length;
|
|
80
|
+
for (let at = 0; at < arity; at += 1) writeSlot(frame, at, values[at]);
|
|
81
|
+
return runBody(closure, frame);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The one-argument call.
|
|
86
|
+
*
|
|
87
|
+
* A body the compiler found needs no environment gets the value itself as one:
|
|
88
|
+
* its single name reads as `env`, so the call is the body running on the
|
|
89
|
+
* argument, with nothing allocated. That is most of what `map`, `filter` and
|
|
90
|
+
* `sortBy` call.
|
|
91
|
+
*/
|
|
92
|
+
function callClosure1(closure: Closure, value: unknown): unknown {
|
|
93
|
+
const body = closure.body;
|
|
94
|
+
if (body.bare) return body.result(value as never);
|
|
95
|
+
const frame = new Frame(closure);
|
|
96
|
+
if (closure.params.length > 0) frame.s0 = value;
|
|
97
|
+
return runBody(closure, frame);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// A function declaring fewer parameters than it is handed ignores the rest:
|
|
101
|
+
// `xs.map(fn (x) => x)` never asks for the index it is offered.
|
|
102
|
+
function callClosure2(closure: Closure, a: unknown, b: unknown): unknown {
|
|
103
|
+
if (closure.body.bare) return closure.body.result(a as never);
|
|
104
|
+
const frame = new Frame(closure);
|
|
105
|
+
const arity = closure.params.length;
|
|
106
|
+
if (arity > 0) frame.s0 = a;
|
|
107
|
+
if (arity > 1) frame.s1 = b;
|
|
108
|
+
return runBody(closure, frame);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function callClosure3(closure: Closure, a: unknown, b: unknown, c: unknown): unknown {
|
|
112
|
+
if (closure.body.bare) return closure.body.result(a as never);
|
|
113
|
+
const frame = new Frame(closure);
|
|
114
|
+
const arity = closure.params.length;
|
|
115
|
+
if (arity > 0) frame.s0 = a;
|
|
116
|
+
if (arity > 1) frame.s1 = b;
|
|
117
|
+
if (arity > 2) frame.s2 = c;
|
|
118
|
+
return runBody(closure, frame);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Fill the body's locals in order, then answer with its result. */
|
|
122
|
+
function runBody(closure: Closure, frame: Frame): unknown {
|
|
123
|
+
const body = closure.body;
|
|
124
|
+
const locals = body.locals;
|
|
125
|
+
for (let at = 0; at < locals.length; at += 1) {
|
|
126
|
+
const local = locals[at] as (typeof locals)[number];
|
|
127
|
+
writeSlot(frame, local.slot, local.value(frame));
|
|
128
|
+
}
|
|
129
|
+
return body.result(frame);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function notCallable(value: unknown): ProblemError {
|
|
133
|
+
return new ProblemError(
|
|
134
|
+
buildProblem({
|
|
135
|
+
spec: CODES.VN3013_NOT_CALLABLE,
|
|
136
|
+
span: NO_SPAN,
|
|
137
|
+
title: `This value is not a function, so it cannot be called: ${typeof value}.`,
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { isUnitValue } from "../units/index.js";
|
|
2
|
+
import { INVOKE } from "./invoke.js";
|
|
3
|
+
import { builtinMember, NO_METHOD } from "./methods/index.js";
|
|
4
|
+
import { nativeFn } from "./native.types.js";
|
|
5
|
+
import { isWaiting } from "./pending.js";
|
|
6
|
+
import { isTask } from "./task.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* One member of a value: `xs.len`, `m.user`, `target.wrap`.
|
|
10
|
+
*
|
|
11
|
+
* A map's own data wins; a list or a string only ever has built-in members.
|
|
12
|
+
* Lives here rather than in the compiler because the compiler is not the only
|
|
13
|
+
* reader: a decorator body reaches a handle's verbs by the same rule, and two
|
|
14
|
+
* spellings of "what does `.x` mean" would drift apart.
|
|
15
|
+
*
|
|
16
|
+
* @returns The member's value, undefined when there is none, or a promise when
|
|
17
|
+
* the receiver has not arrived yet.
|
|
18
|
+
*/
|
|
19
|
+
export function memberValue(receiver: unknown, member: string): unknown {
|
|
20
|
+
if (isWaiting(receiver)) return receiver.then((ready) => memberValue(ready, member));
|
|
21
|
+
// A unit is not a map. Reading it as one would expose how it is stored, with
|
|
22
|
+
// `300ms.kind` answering "duration", and shadow its own conversions.
|
|
23
|
+
if (isData(receiver) && Object.hasOwn(receiver as object, member)) {
|
|
24
|
+
const data = own(receiver, member);
|
|
25
|
+
if (data !== undefined) return data;
|
|
26
|
+
}
|
|
27
|
+
const built = builtinMember(receiver, member, INVOKE);
|
|
28
|
+
if (built !== NO_METHOD) return built;
|
|
29
|
+
return isOwned(receiver) ? undefined : own(receiver, member);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether the language, not the host, decides what this value answers to.
|
|
34
|
+
*
|
|
35
|
+
* A list and a string are the language's own, with a published member set and a
|
|
36
|
+
* checker that knows it, so they must never fall through to the host: nothing
|
|
37
|
+
* the runtime happens to store them as is offered by the editor or checked, and
|
|
38
|
+
* it would stop working the day either value is held differently. A plugin's
|
|
39
|
+
* handle is the opposite case, being a host object whose published verbs are
|
|
40
|
+
* exactly what it answers to.
|
|
41
|
+
*/
|
|
42
|
+
const OWNED = new Set(["string", "number", "boolean", "bigint"]);
|
|
43
|
+
|
|
44
|
+
function isOwned(receiver: unknown): boolean {
|
|
45
|
+
return (
|
|
46
|
+
receiver == null ||
|
|
47
|
+
OWNED.has(typeof receiver) ||
|
|
48
|
+
Array.isArray(receiver) ||
|
|
49
|
+
isUnitValue(receiver) ||
|
|
50
|
+
isTask(receiver) ||
|
|
51
|
+
isPlainMap(receiver)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A map the language made, as against an object a plugin handed over.
|
|
57
|
+
*
|
|
58
|
+
* Both are objects, so the line is drawn where it can be: a map literal
|
|
59
|
+
* inherits from nothing but `Object`, while a handle is built by the host and
|
|
60
|
+
* carries its verbs on its own prototype. Only what a map itself holds is its
|
|
61
|
+
* data; what every object in the runtime inherits is not.
|
|
62
|
+
*/
|
|
63
|
+
function isPlainMap(receiver: unknown): boolean {
|
|
64
|
+
if (typeof receiver !== "object" || receiver === null) return false;
|
|
65
|
+
const proto = Object.getPrototypeOf(receiver);
|
|
66
|
+
return proto === Object.prototype || proto === null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A property of a host value, callable if it is a method.
|
|
71
|
+
*
|
|
72
|
+
* This is the boundary a plugin's handle crosses to become a value in the
|
|
73
|
+
* language. The wrapping is required: a bare host function is not a Venn
|
|
74
|
+
* callable, and the receiver has to be bound or `api.close()` loses its `this`.
|
|
75
|
+
*/
|
|
76
|
+
function own(receiver: unknown, member: string): unknown {
|
|
77
|
+
const value = (receiver as Record<string, unknown>)[member];
|
|
78
|
+
if (typeof value !== "function") return value;
|
|
79
|
+
const method = value as (...args: unknown[]) => unknown;
|
|
80
|
+
return nativeFn((values) => method.apply(receiver, [...values]));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isData(receiver: unknown): boolean {
|
|
84
|
+
return (
|
|
85
|
+
receiver !== null &&
|
|
86
|
+
typeof receiver === "object" &&
|
|
87
|
+
!Array.isArray(receiver) &&
|
|
88
|
+
!isUnitValue(receiver) &&
|
|
89
|
+
!isTask(receiver)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Invoke } from "../native.types.js";
|
|
2
|
+
import { isTask, TASK_METHODS } from "../task.js";
|
|
3
|
+
import { LIST_GROUPING } from "./list-grouping.js";
|
|
4
|
+
import { LIST_METHODS } from "./list-methods.js";
|
|
5
|
+
import { LIST_SELECTION } from "./list-selection.js";
|
|
6
|
+
import { MAP_EXTRAS } from "./map-extras.js";
|
|
7
|
+
import { MAP_METHODS } from "./map-methods.js";
|
|
8
|
+
import { NUMBER_METHODS } from "./number-methods.js";
|
|
9
|
+
import { STRING_EXTRAS } from "./string-extras.js";
|
|
10
|
+
import { STRING_METHODS } from "./string-methods.js";
|
|
11
|
+
import { DURATION_METHODS, NUMBER_TO_UNIT, PERCENT_METHODS, SIZE_METHODS } from "./unit-methods.js";
|
|
12
|
+
|
|
13
|
+
/** Distinguishes "no such method" from a method that legitimately returns undefined. */
|
|
14
|
+
export const NO_METHOD = Symbol("venn.no-method");
|
|
15
|
+
|
|
16
|
+
const LIST_TABLE = { ...LIST_METHODS, ...LIST_SELECTION, ...LIST_GROUPING };
|
|
17
|
+
const STRING_TABLE = { ...STRING_METHODS, ...STRING_EXTRAS };
|
|
18
|
+
const MAP_TABLE = { ...MAP_METHODS, ...MAP_EXTRAS };
|
|
19
|
+
const NUMBER_TABLE = { ...NUMBER_METHODS, ...NUMBER_TO_UNIT };
|
|
20
|
+
|
|
21
|
+
/** Every member name a value of this kind answers to. Read by the editor. */
|
|
22
|
+
export const MEMBER_NAMES: Readonly<Record<string, readonly string[]>> = {
|
|
23
|
+
list: Object.keys(LIST_TABLE),
|
|
24
|
+
string: Object.keys(STRING_TABLE),
|
|
25
|
+
map: Object.keys(MAP_TABLE),
|
|
26
|
+
number: Object.keys(NUMBER_TABLE),
|
|
27
|
+
task: Object.keys(TASK_METHODS),
|
|
28
|
+
duration: Object.keys(DURATION_METHODS),
|
|
29
|
+
size: Object.keys(SIZE_METHODS),
|
|
30
|
+
percent: Object.keys(PERCENT_METHODS),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The built-in member of a native value: a property like `length` or a callable
|
|
35
|
+
* like `map`. `NO_METHOD` when the type has no such member, so the caller can
|
|
36
|
+
* fall back to a map's own data.
|
|
37
|
+
*/
|
|
38
|
+
export function builtinMember(receiver: unknown, member: string, invoke: Invoke): unknown {
|
|
39
|
+
const table = tableFor(receiver);
|
|
40
|
+
// These tables are ordinary objects, so a plain lookup would also reach what
|
|
41
|
+
// every object inherits: `x.toString` would find `Object.prototype.toString`
|
|
42
|
+
// and answer for a member the language never had. Only what a table declares
|
|
43
|
+
// is a member.
|
|
44
|
+
if (!table || !Object.hasOwn(table, member)) return NO_METHOD;
|
|
45
|
+
const method = table[member];
|
|
46
|
+
if (!method) return NO_METHOD;
|
|
47
|
+
return (method as (r: unknown, i: Invoke) => unknown)(receiver, invoke);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const UNIT_TABLES: Record<string, Record<string, unknown>> = {
|
|
51
|
+
duration: DURATION_METHODS,
|
|
52
|
+
size: SIZE_METHODS,
|
|
53
|
+
percent: PERCENT_METHODS,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function tableFor(receiver: unknown): Record<string, unknown> | undefined {
|
|
57
|
+
if (typeof receiver === "string") return STRING_TABLE;
|
|
58
|
+
if (typeof receiver === "number") return NUMBER_TABLE;
|
|
59
|
+
if (Array.isArray(receiver)) return LIST_TABLE;
|
|
60
|
+
if (receiver === null || typeof receiver !== "object") return undefined;
|
|
61
|
+
if (isTask(receiver)) return TASK_METHODS;
|
|
62
|
+
// A unit value carries its kind, and its methods are the way back to a number.
|
|
63
|
+
const kind = (receiver as { kind?: unknown }).kind;
|
|
64
|
+
return (typeof kind === "string" ? UNIT_TABLES[kind] : undefined) ?? MAP_TABLE;
|
|
65
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type Invoke, type Method, nativeFn } from "../native.types.js";
|
|
2
|
+
|
|
3
|
+
type Dict = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A grouping key: what `groupBy`, `countBy` and `keyBy` file an item under.
|
|
7
|
+
*
|
|
8
|
+
* A number or a boolean converts directly; only a shape is serialised. Passing
|
|
9
|
+
* scalars through `JSON.stringify` would be both slower and wrong, since it
|
|
10
|
+
* renders `Infinity` and `NaN` as `null` and would file them alongside items
|
|
11
|
+
* whose key genuinely is null.
|
|
12
|
+
*/
|
|
13
|
+
function key(value: unknown): string {
|
|
14
|
+
const kind = typeof value;
|
|
15
|
+
if (kind === "string") return value as string;
|
|
16
|
+
if (kind === "number" || kind === "boolean") return String(value);
|
|
17
|
+
// `JSON.stringify` answers `undefined` (not a string) for undefined and for a
|
|
18
|
+
// function, so anything it cannot render is named rather than cast.
|
|
19
|
+
return JSON.stringify(value) ?? String(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The operations that are a chore to hand-roll everywhere else: grouping,
|
|
24
|
+
* partitioning, keying, counting. Each returns a new value and never mutates.
|
|
25
|
+
*/
|
|
26
|
+
export const LIST_GROUPING: Record<string, Method> = {
|
|
27
|
+
groupBy: (list: readonly unknown[], invoke: Invoke) =>
|
|
28
|
+
nativeFn((args) => group(list, (item, i) => key(invoke.two(args[0], item, i)))),
|
|
29
|
+
countBy: (list: readonly unknown[], invoke: Invoke) =>
|
|
30
|
+
nativeFn((args) => counts(list, (item, i) => key(invoke.two(args[0], item, i)))),
|
|
31
|
+
keyBy: (list: readonly unknown[], invoke: Invoke) =>
|
|
32
|
+
nativeFn((args) => keyed(list, (item, i) => key(invoke.two(args[0], item, i)))),
|
|
33
|
+
partition: (list: readonly unknown[], invoke: Invoke) =>
|
|
34
|
+
nativeFn((args) => split(list, (item, i) => Boolean(invoke.two(args[0], item, i)))),
|
|
35
|
+
chunk: (list: readonly unknown[]) => nativeFn((args) => chunk(list, Number(args[0] ?? 1))),
|
|
36
|
+
windows: (list: readonly unknown[]) => nativeFn((args) => windows(list, Number(args[0] ?? 2))),
|
|
37
|
+
pairwise: (list: readonly unknown[]) => windows(list, 2),
|
|
38
|
+
zip: (list: readonly unknown[]) => nativeFn((args) => zip(list, asList(args[0]))),
|
|
39
|
+
unzip: (list: readonly unknown[]) => unzip(list),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function group(list: readonly unknown[], keyOf: (item: unknown, i: number) => string): Dict {
|
|
43
|
+
const out: Dict = {};
|
|
44
|
+
list.forEach((item, index) => {
|
|
45
|
+
const name = keyOf(item, index);
|
|
46
|
+
out[name] = [...((out[name] as unknown[]) ?? []), item];
|
|
47
|
+
});
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function counts(list: readonly unknown[], keyOf: (item: unknown, i: number) => string): Dict {
|
|
52
|
+
const out: Dict = {};
|
|
53
|
+
list.forEach((item, index) => {
|
|
54
|
+
const name = keyOf(item, index);
|
|
55
|
+
out[name] = Number(out[name] ?? 0) + 1;
|
|
56
|
+
});
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Like `groupBy`, but the last item under a key wins: an index, not buckets. */
|
|
61
|
+
function keyed(list: readonly unknown[], keyOf: (item: unknown, i: number) => string): Dict {
|
|
62
|
+
const out: Dict = {};
|
|
63
|
+
list.forEach((item, index) => {
|
|
64
|
+
out[keyOf(item, index)] = item;
|
|
65
|
+
});
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function split(list: readonly unknown[], keep: (item: unknown, i: number) => boolean): unknown[][] {
|
|
70
|
+
const yes: unknown[] = [];
|
|
71
|
+
const no: unknown[] = [];
|
|
72
|
+
list.forEach((item, index) => {
|
|
73
|
+
(keep(item, index) ? yes : no).push(item);
|
|
74
|
+
});
|
|
75
|
+
return [yes, no];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function chunk(list: readonly unknown[], size: number): unknown[][] {
|
|
79
|
+
const step = Math.max(1, Math.trunc(size));
|
|
80
|
+
const out: unknown[][] = [];
|
|
81
|
+
for (let index = 0; index < list.length; index += step) out.push(list.slice(index, index + step));
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Every consecutive run of `size` items: `[1,2,3].windows(2)` gives `[[1,2],[2,3]]`. */
|
|
86
|
+
function windows(list: readonly unknown[], size: number): unknown[][] {
|
|
87
|
+
const width = Math.max(1, Math.trunc(size));
|
|
88
|
+
const out: unknown[][] = [];
|
|
89
|
+
for (let index = 0; index + width <= list.length; index += 1) {
|
|
90
|
+
out.push(list.slice(index, index + width));
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function zip(list: readonly unknown[], other: readonly unknown[]): unknown[][] {
|
|
96
|
+
return list.slice(0, Math.min(list.length, other.length)).map((item, i) => [item, other[i]]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function unzip(list: readonly unknown[]): unknown[][] {
|
|
100
|
+
const pairs = list.map((item) => (Array.isArray(item) ? item : [item]));
|
|
101
|
+
const width = pairs.reduce((most, pair) => Math.max(most, pair.length), 0);
|
|
102
|
+
return Array.from({ length: width }, (_column, index) => pairs.map((pair) => pair[index]));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function asList(value: unknown): unknown[] {
|
|
106
|
+
return Array.isArray(value) ? value : [];
|
|
107
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { truthy } from "../../value/index.js";
|
|
2
|
+
import { type Invoke, type Method, nativeFn } from "../native.types.js";
|
|
3
|
+
|
|
4
|
+
/** How `sort` orders when given no comparator: numbers by value, else by text. */
|
|
5
|
+
function compare(a: unknown, b: unknown): number {
|
|
6
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
7
|
+
return String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const at = (list: readonly unknown[], index: number): unknown => list[index] ?? null;
|
|
11
|
+
|
|
12
|
+
/** Methods on a list value. Every one is pure and returns a new value. */
|
|
13
|
+
export const LIST_METHODS: Record<string, Method> = {
|
|
14
|
+
len: (list: readonly unknown[]) => list.length,
|
|
15
|
+
first: (list: readonly unknown[]) => at(list, 0),
|
|
16
|
+
last: (list: readonly unknown[]) => at(list, list.length - 1),
|
|
17
|
+
map: (list: readonly unknown[], invoke: Invoke) =>
|
|
18
|
+
nativeFn((args) => list.map((item, i) => invoke.two(args[0], item, i))),
|
|
19
|
+
filter: (list: readonly unknown[], invoke: Invoke) =>
|
|
20
|
+
nativeFn((args) => list.filter((item, i) => truthy(invoke.two(args[0], item, i)))),
|
|
21
|
+
reduce: (list: readonly unknown[], invoke: Invoke) =>
|
|
22
|
+
nativeFn((args) => list.reduce((acc, item, i) => invoke.three(args[0], acc, item, i), args[1])),
|
|
23
|
+
forEach: (list: readonly unknown[], invoke: Invoke) =>
|
|
24
|
+
nativeFn((args) => {
|
|
25
|
+
list.forEach((item, i) => {
|
|
26
|
+
invoke.two(args[0], item, i);
|
|
27
|
+
});
|
|
28
|
+
return null;
|
|
29
|
+
}),
|
|
30
|
+
find: (list: readonly unknown[], invoke: Invoke) =>
|
|
31
|
+
nativeFn((args) => list.find((item, i) => truthy(invoke.two(args[0], item, i))) ?? null),
|
|
32
|
+
some: (list: readonly unknown[], invoke: Invoke) =>
|
|
33
|
+
nativeFn((args) => list.some((item, i) => truthy(invoke.two(args[0], item, i)))),
|
|
34
|
+
every: (list: readonly unknown[], invoke: Invoke) =>
|
|
35
|
+
nativeFn((args) => list.every((item, i) => truthy(invoke.two(args[0], item, i)))),
|
|
36
|
+
contains: (list: readonly unknown[]) => nativeFn((args) => list.includes(args[0])),
|
|
37
|
+
indexOf: (list: readonly unknown[]) => nativeFn((args) => list.indexOf(args[0])),
|
|
38
|
+
reverse: (list: readonly unknown[]) => [...list].reverse(),
|
|
39
|
+
flatten: (list: readonly unknown[]) => list.flat(),
|
|
40
|
+
join: (list: readonly unknown[]) =>
|
|
41
|
+
nativeFn((args) => list.map(String).join(args[0] === undefined ? "," : String(args[0]))),
|
|
42
|
+
sort: (list: readonly unknown[], invoke: Invoke) =>
|
|
43
|
+
nativeFn((args) =>
|
|
44
|
+
args[0]
|
|
45
|
+
? [...list].sort((a, b) => Number(invoke.two(args[0], a, b)))
|
|
46
|
+
: [...list].sort(compare),
|
|
47
|
+
),
|
|
48
|
+
slice: (list: readonly unknown[]) =>
|
|
49
|
+
nativeFn((args) => list.slice(Number(args[0] ?? 0), end(args[1]))),
|
|
50
|
+
concat: (list: readonly unknown[]) =>
|
|
51
|
+
nativeFn((args) => [...list, ...(Array.isArray(args[0]) ? args[0] : [args[0]])]),
|
|
52
|
+
push: (list: readonly unknown[]) => nativeFn((args) => [...list, ...args]),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function end(value: unknown): number | undefined {
|
|
56
|
+
return value === undefined ? undefined : Number(value);
|
|
57
|
+
}
|