summaryrefslogtreecommitdiff
path: root/src/lang/ks-lang/index.ts
blob: 633d9b92a3dd7f5a92b8b02daa56680254833577 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { callFn, emptyStackFrame, evaluate, lex, parse } from "../js-lang";
import {
  Evaluate,
  FnPrim,
  Lex,
  Parse,
  EvalError,
  CallFn,
  EmptyStackFrame,
  ASTNode,
  StackFrame,
} from "../ts-lang";

export type TransformArgs<Args extends readonly ASTNode[]> = {
  [Idx in keyof Args]: any;
};

export type AssertArgs<
  Args extends readonly ASTNode[],
  ArgsConstraint extends readonly any[]
> = TransformArgs<Args> extends ArgsConstraint ? ArgsConstraint : never;

export const createFn =
  <ArgsConstraint extends any[]>() =>
  <Program extends string>(
    program: Program
  ): Evaluate<Parse<Lex<Program>>> extends infer E
    ? E extends [infer ProgramFn extends FnPrim]
      ? TransformArgs<ProgramFn["args"]> extends ArgsConstraint
        ? <const Args extends ArgsConstraint>(
            ...args: Args
          ) => CallFn<ProgramFn, Args, EmptyStackFrame, []>
        : EvalError<`Program's args do not extend args constraint`>
      : E extends readonly [
          readonly [infer Prim extends FnPrim, infer StackFrame, infer Name]
        ]
      ? TransformArgs<Prim["args"]> extends ArgsConstraint
        ? <const Args extends ArgsConstraint>(
            ...args: Args
          ) => CallFn<E[0], Args, EmptyStackFrame, []>
        : EvalError<`Program's args do not extend args constraint`>
      : EvalError<"Cannot create a function from a program that does not eval to a function">
    : never => {
    const [e] = evaluate(parse(lex(program))) as [
      FnPrim | [FnPrim, StackFrame, ASTNode["name"]]
    ];

    const fn: FnPrim = Array.isArray(e) ? e[0] : e;

    if (!fn.fn) {
      throw new Error(
        "Cannot create a function from a program that does not eval to a function"
      );
    }

    return ((...args: any[]) => callFn(e, args, emptyStackFrame)) as any;
  };