diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2025-11-09 21:08:12 -0800 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2025-11-09 21:08:30 -0800 |
| commit | 413eaa284e164143c5416cdce5a1de0f9f992409 (patch) | |
| tree | d999e8cbaddefcce9df3265c594083177427b6cb | |
| parent | 93992029bd349185d15de02e0f633e95c62695a9 (diff) | |
map + reduce
| -rw-r--r-- | examples/README.md | 5 | ||||
| -rw-r--r-- | examples/mapReduce.ts | 21 | ||||
| -rw-r--r-- | package.json | 4 | ||||
| -rw-r--r-- | src/lang/js-lang/builtin/sbuiltin.ts | 25 | ||||
| -rw-r--r-- | src/lang/ts-lang/builtin/sbuiltin.ts | 28 | ||||
| -rw-r--r-- | src/lang/ts-lang/core/eval.ts | 3 | ||||
| -rw-r--r-- | tests/type-consistency/spec/index.ts | 4 | ||||
| -rw-r--r-- | tests/type-consistency/spec/mapReduce.ts | 34 | ||||
| -rw-r--r-- | tests/type-consistency/spec/recursion.ts | 25 |
9 files changed, 145 insertions, 4 deletions
diff --git a/examples/README.md b/examples/README.md index 33c957d..1d0e0c1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,7 @@ ## A guided tour of KaiScript through examples -Open this in your IDE of choice--make sure your LSP is running so you can inspect types. +Open this in your IDE of choice--make sure your LSP is running so you can inspect types. Feel free to run `tsx` on each file to verify the runtime outputs. +If you're reading this without an LSP, I've added type annotations in comments above each result. I promise these match what you'd see if you hovered over the value! 1. [Breaking the second wall](./mapper.ts) @@ -11,3 +12,5 @@ Open this in your IDE of choice--make sure your LSP is running so you can inspec 4. [Turing completeness](./branching.ts) 5. [Infinite computation](./recursion.ts) + +6. [Infinite transformation](./mapReduce.ts) diff --git a/examples/mapReduce.ts b/examples/mapReduce.ts new file mode 100644 index 0000000..3904b2a --- /dev/null +++ b/examples/mapReduce.ts @@ -0,0 +1,21 @@ +/* +You can do anything with map + reduce! +*/ + +import { createFn } from "../src"; + +const concatNumbers = createFn<[number[]]>()( + `fn(a, reduce(map(a, fn(n, tostring(n))), fn(acc, cur, add(acc,cur)), ""))` +); + +// const result: "1235" +const concatted = concatNumbers([1, 2, 35]); +console.log(concatted); + +const arrayLength = createFn<[any[]]>()( + `fn(a, reduce(a, fn(acc, add(acc, 1)), 0))` +); + +// const length: 2 +const length = arrayLength(["hello", "world"]); +console.log(length); diff --git a/package.json b/package.json index f4d09b8..e5d98d5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@aberrantflux/kai-script", "description": "A type safe framework for TypeScript", - "version": "0.2.4", + "version": "0.3.0", "repository": { "type": "git", "url": "https://git.aberrantflux.xyz/kai-script.git/" @@ -9,7 +9,7 @@ "scripts": { "build": "tsc --declaration", "generate-tests": "rm -f tests/type-consistency/generated/*.test.ts && tsx tests/type-consistency/generateAll.ts", - "test": "pnpm run generate-tests && vitest run --reporter verbose && tsc --noEmit --project tests/type-consistency/tsconfig.json", + "test": "pnpm run generate-tests && vitest run --reporter verbose && tsc --noEmit --project tests/type-consistency/tsconfig.json && echo 'All tests passed!'", "safe-publish": "rm -rf dist && pnpm build && pnpm test && npm publish" }, "exports": { diff --git a/src/lang/js-lang/builtin/sbuiltin.ts b/src/lang/js-lang/builtin/sbuiltin.ts index 013854c..7d9b851 100644 --- a/src/lang/js-lang/builtin/sbuiltin.ts +++ b/src/lang/js-lang/builtin/sbuiltin.ts @@ -40,6 +40,30 @@ export const V_SBUILTIN_Map: SBUILTIN = (node, frame) => { return values.map((v, i) => callFn(fn, [v, i], frame)); }; +export const V_SBUILTIN_Reduce: SBUILTIN = (node, frame) => { + const children = getEvaluatedChildren(node, frame); + const fn = children[1] as FnPrim | undefined; + const acc = children[2]; + + if (!fn?.fn) { + throw new Error( + `Invalid params for reduce: ${JSON.stringify(children, undefined, 2)}` + ); + } + + const values = children[0]; + + if (!Array.isArray(values)) { + // add to ts + throw new Error(`Can't reduce non-array value: ${values}`); + } + + return values.reduce( + (acc, cur, idx) => callFn(fn, [acc, cur, idx], frame), + acc + ); +}; + export const V_SBUILTIN_IfElse: SBUILTIN = (node, frame) => { const children = node.children; @@ -59,5 +83,6 @@ export const V_SBUILTIN_IfElse: SBUILTIN = (node, frame) => { export const nameToSBUILTIN: Record<string, SBUILTIN> = { call: V_SBUILTIN_Call, map: V_SBUILTIN_Map, + reduce: V_SBUILTIN_Reduce, "?": V_SBUILTIN_IfElse, }; diff --git a/src/lang/ts-lang/builtin/sbuiltin.ts b/src/lang/ts-lang/builtin/sbuiltin.ts index c92fd6a..38b4256 100644 --- a/src/lang/ts-lang/builtin/sbuiltin.ts +++ b/src/lang/ts-lang/builtin/sbuiltin.ts @@ -42,6 +42,34 @@ export type SBUILTIN_Map< GetEvaluatedChildren<Node, Frame, Callstack> >}`>; +type Reduce< + Arr extends readonly any[], + Fn extends FnPrim, + Acc, + IdxLen extends readonly any[] = readonly [] +> = Arr extends [infer Head, ...infer Tail] + ? Reduce< + Tail, + Fn, + CallFn<Fn, [Acc, Head, IdxLen["length"]]>, + [...IdxLen, any] + > + : Acc; + +export type SBUILTIN_Reduce< + Node extends ASTNode, + Frame extends StackFrame, + Callstack extends readonly string[] +> = GetEvaluatedChildren<Node, Frame, Callstack> extends [ + infer Arr extends readonly any[], + infer Fn extends FnPrim, + infer Acc +] + ? Reduce<Arr, Fn, Acc> + : EvalError<`Invalid params for reduce: ${ToString< + GetEvaluatedChildren<Node, Frame, Callstack> + >}`>; + export type SBUILTIN_IfElse< Node extends ASTNode, Frame extends StackFrame, diff --git a/src/lang/ts-lang/core/eval.ts b/src/lang/ts-lang/core/eval.ts index ebc58e7..bef0ef8 100644 --- a/src/lang/ts-lang/core/eval.ts +++ b/src/lang/ts-lang/core/eval.ts @@ -8,6 +8,7 @@ import { SBUILTIN_Call, SBUILTIN_IfElse, SBUILTIN_Map, + SBUILTIN_Reduce, } from "../builtin"; import { ToString } from "../util"; import { @@ -34,6 +35,8 @@ export type MapBuiltins< ? SBUILTIN_Call<Node, Frame, Callstack> : Node["name"] extends "map" ? SBUILTIN_Map<Node, Frame, Callstack> + : Node["name"] extends "reduce" + ? SBUILTIN_Reduce<Node, Frame, Callstack> : Node["name"] extends "?" ? SBUILTIN_IfElse<Node, Frame, Callstack> : Node["name"] extends "tostring" diff --git a/tests/type-consistency/spec/index.ts b/tests/type-consistency/spec/index.ts index b2da682..780c20c 100644 --- a/tests/type-consistency/spec/index.ts +++ b/tests/type-consistency/spec/index.ts @@ -2,5 +2,7 @@ import array from "./array"; import functions from "./function"; import types from "./types"; import tostring from "./tostring"; +import mapreduce from "./mapReduce"; +import recursion from "./recursion"; -export default [array, functions, types, tostring]; +export default [array, functions, types, tostring, mapreduce, recursion]; diff --git a/tests/type-consistency/spec/mapReduce.ts b/tests/type-consistency/spec/mapReduce.ts new file mode 100644 index 0000000..b5077ff --- /dev/null +++ b/tests/type-consistency/spec/mapReduce.ts @@ -0,0 +1,34 @@ +import path from "path"; +import { createTestHarness } from "../harness"; + +export default createTestHarness({ + harnessName: "Map reduce", + generatedPath: path.join(__dirname, "..", "generated"), +}) + .createFunctionTest({ + name: "Map: numbers to string", + program: "fn(a, map(a, fn(n, tostring(n))))", + cases: [ + { input: [1, 2, 3], output: ["1", "2", "3"] }, + { input: [50], output: ["50"] }, + { input: [], output: [] }, + ], + }) + .createFunctionTest({ + name: "Reduce: array length", + program: "fn(a, reduce(a, fn(acc, add(acc, 1)), 0))", + cases: [ + { input: [1, 2, 3], output: 3 }, + { input: ["hello", ["hello", "world"]], output: 2 }, + { input: [], output: 0 }, + ], + }) + .createFunctionTest({ + name: "Reduce: sum of numbers times index", + program: "fn(a, reduce(a, fn(acc, cur, idx, add(acc, mul(cur, idx))), 0))", + cases: [ + { input: [1, 2, 3], output: 8 }, + { input: [], output: 0 }, + { input: [50, 10, 0], output: 10 }, + ], + }); diff --git a/tests/type-consistency/spec/recursion.ts b/tests/type-consistency/spec/recursion.ts new file mode 100644 index 0000000..c5de83f --- /dev/null +++ b/tests/type-consistency/spec/recursion.ts @@ -0,0 +1,25 @@ +import path from "path"; +import { createTestHarness } from "../harness"; + +export default createTestHarness({ + harnessName: "Recursive functions", + generatedPath: path.join(__dirname, "..", "generated"), +}) + .createFunctionTest({ + name: "n!", + program: "bind(fac,fn(n,?(eq(n, 1),n,mul(n,call(fac,sub(n,1))))))", + cases: [ + { input: 3, output: 6 }, + { input: 1, output: 1 }, + { input: 5, output: 120 }, + ], + }) + .createFunctionTest({ + name: "Sum of natural numbers on [0, n]", + program: "bind(sumnn,fn(n,?(eq(n, 1),n,add(n,call(sumnn,sub(n,1))))))", + cases: [ + { input: 5, output: 15 }, + { input: 1, output: 1 }, + { input: 2, output: 3 }, + ], + }); |
