summaryrefslogtreecommitdiff
path: root/src/lang/core/eval.ts
blob: 61eb63ae7698516c9754a964d918d5b1e14865ab (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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import {
  BUILTIN_Add,
  BUILTIN_Arr,
  BUILTIN_Mul,
  BUILTIN_ToString,
} from "../builtin";
import { ToString } from "../util";
import { ASTNode, NodeType } from "./common";
import { Lex } from "./lexer";
import { Parse } from "./parser";

export type GetEvaluatedChildren<
  Node extends ASTNode,
  Frame extends StackFrame
> = Node["children"] extends infer Children extends readonly ASTNode[]
  ? {
      [Idx in keyof Children]: Children[Idx] extends ASTNode
        ? Evaluate<Children[Idx], Frame>
        : never;
    }
  : never;

export type SENTINEL_NO_BUILTIN = "__NO_BUILTIN__";
export type MapBuiltins<
  Node extends ASTNode,
  Frame extends StackFrame
> = GetEvaluatedChildren<Node, Frame> extends infer Args extends readonly any[]
  ? Node["name"] extends "tostring"
    ? BUILTIN_ToString<Args>
    : Node["name"] extends "arr"
    ? BUILTIN_Arr<Args>
    : Node["name"] extends "add"
    ? BUILTIN_Add<Args>
    : Node["name"] extends "mul"
    ? BUILTIN_Mul<Args>
    : SENTINEL_NO_BUILTIN
  : never;

export type EvalError<T extends string> = `Eval error: ${T}`;

export type StackFrame<
  Bindings extends Record<ASTNode["name"], any> = Record<ASTNode["name"], any>,
  Parent extends StackFrame | null = any
> = {
  bindings: Bindings;
  parent: Parent;
};

export type EmptyStackFrame = StackFrame<{}, null>;

export type WithPushedBindings<
  OldFrame extends StackFrame,
  Bindings extends StackFrame["bindings"]
> = StackFrame<Bindings, OldFrame>;

export type FindInStack<
  Frame extends StackFrame,
  NameToFind
> = NameToFind extends keyof Frame["bindings"]
  ? Frame["bindings"][NameToFind]
  : Frame["parent"] extends null
  ? EvalError<`Can't find name "${ToString<NameToFind>}" on the stack`>
  : FindInStack<Frame["parent"], NameToFind>;

export type MapOnStack<
  Node extends ASTNode,
  Frame extends StackFrame
> = FindInStack<Frame, Node["name"]>;

export type FnPrim<
  Args extends readonly ASTNode[] = readonly ASTNode[],
  Fn extends ASTNode = any
> = { args: Args; fn: Fn };

// Can support multiple args, just need to make the last arg be the fn
export type HandleFn<Node extends ASTNode> = Node["children"] extends [
  infer Arg extends ASTNode,
  infer Fn extends ASTNode
]
  ? FnPrim<[Arg], Fn>
  : never;

type MapZip<T extends readonly ASTNode[], U extends readonly PropertyKey[]> = {
  [Idx in Exclude<
    keyof T,
    keyof any[]
  > as T[Idx] extends infer Node extends ASTNode
    ? Node["name"]
    : never]: Idx extends keyof U ? U[Idx] : never;
};

export type CallFn<
  Fn extends FnPrim,
  Values extends readonly any[],
  Frame extends StackFrame
> = Evaluate<Fn["fn"], StackFrame<MapZip<Fn["args"], Values>, Frame>>;

export type Evaluate<
  Node extends ASTNode,
  Frame extends StackFrame = EmptyStackFrame
> = Node["type"] extends NodeType.INT
  ? Node["value"]
  : Node["type"] extends NodeType.EXT
  ? // special builtin
    Node["name"] extends "call"
    ? GetEvaluatedChildren<Node, Frame> extends [
        infer Fn extends FnPrim,
        ...infer Values extends readonly any[]
      ]
      ? CallFn<Fn, Values, Frame>
      : EvalError<`Invalid params for function call: ${ToString<
          GetEvaluatedChildren<Node, Frame>
        >}`>
    : // special builtin
    Node["name"] extends "fn"
    ? HandleFn<Node>
    : MapBuiltins<Node, Frame> extends infer BI
    ? BI extends SENTINEL_NO_BUILTIN
      ? MapOnStack<Node, Frame>
      : BI
    : never
  : EvalError<`Unhandled node type ${Node["type"]}`>;

// const input = `map(arr(1,2,3), fn(n, add(n, 1)))` as const;
const input = `call(fn(a, add(a, 5)), 12)` as const;
const lex_result = null as unknown as Lex<typeof input>;
const parse_result = null as unknown as Parse<typeof lex_result>;
const eval_result = null as unknown as Evaluate<typeof parse_result>;