diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2025-11-03 23:40:02 -0800 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2025-11-03 23:40:02 -0800 |
| commit | 56040f3ff85e77311f0c864a89afd63fcf1bdb50 (patch) | |
| tree | 2eb0166756e76b0483692e79830329c92e7fdcf3 /src/ts-lang/core | |
| parent | a11e6780fbb8bd4143dfec44e2ce147b795772d8 (diff) | |
add js-lang, refactor some ts-lang stuff
Diffstat (limited to 'src/ts-lang/core')
| -rw-r--r-- | src/ts-lang/core/common.ts | 66 | ||||
| -rw-r--r-- | src/ts-lang/core/eval.ts | 102 | ||||
| -rw-r--r-- | src/ts-lang/core/index.ts | 4 | ||||
| -rw-r--r-- | src/ts-lang/core/lexer.ts | 84 | ||||
| -rw-r--r-- | src/ts-lang/core/parser.ts | 165 |
5 files changed, 421 insertions, 0 deletions
diff --git a/src/ts-lang/core/common.ts b/src/ts-lang/core/common.ts new file mode 100644 index 0000000..fa5cb7c --- /dev/null +++ b/src/ts-lang/core/common.ts @@ -0,0 +1,66 @@ +export enum TokenType { + OPEN_PAREN = "(", + CLOSE_PAREN = ")", + SPACE = " ", + SEMICOLON = ";", + COMMA = ",", + NAME = "NAME", +} + +export type Token< + Type extends TokenType = TokenType, + Name extends string = string +> = { + type: Type; + name: Name; +}; + +export type LexerCtx = { + next: string; + nameCollection: string; + tokens: readonly Token[]; +}; + +export enum NodeType { + INT = "INT", + EXT = "EXT", + PARSER_ERROR = "PARSER_ERROR", +} + +export type ASTNode< + Type extends NodeType = NodeType, + Name extends string = string, + Value extends any = any, + Children extends readonly ASTNode[] = readonly ASTNode< + NodeType, + string, + any, + any + >[] +> = { + type: Type; + name: Name; + value: Value; + children: Children; +}; + +export type ParserCtx = { + remainingTokens: readonly Token[]; + lastToken: Token | null; + stack: readonly ASTNode[]; +}; + +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>; diff --git a/src/ts-lang/core/eval.ts b/src/ts-lang/core/eval.ts new file mode 100644 index 0000000..5f22299 --- /dev/null +++ b/src/ts-lang/core/eval.ts @@ -0,0 +1,102 @@ +import { + BUILTIN_Add, + BUILTIN_Arr, + BUILTIN_Mul, + BUILTIN_ToString, + SBUILTIN_Call, + SBUILTIN_Map, +} from "../builtin"; +import { ToString } from "../util"; +import { ASTNode, EmptyStackFrame, NodeType, StackFrame } from "./common"; + +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 "call" + ? SBUILTIN_Call<Node, Frame> + : Node["name"] extends "map" + ? SBUILTIN_Map<Node, Frame> + : 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 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 FnPrim< + Args extends readonly ASTNode[] = readonly ASTNode[], + Fn extends ASTNode = ASTNode +> = { args: Args; fn: Fn }; + +export type HandleFn<Node extends ASTNode> = Node["children"] extends [ + ...infer Args extends ASTNode[], + infer Fn extends ASTNode +] + ? FnPrim<Args, Fn> + : // error? + never; + +// any[] was propertykey +export type MapZip< + Args extends readonly ASTNode[], + Values extends readonly any[] +> = { + [Idx in Exclude< + keyof Args, + keyof any[] + > as Args[Idx] extends infer Node extends ASTNode + ? Node["name"] + : never]: Idx extends keyof Values ? Values[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 +> = Node["type"] extends NodeType.INT + ? Node["value"] + : Node["type"] extends NodeType.EXT + ? // special builtin + Node["name"] extends "fn" + ? HandleFn<Node> + : MapBuiltins<Node, Frame> extends infer BI + ? BI extends SENTINEL_NO_BUILTIN + ? FindInStack<Frame, Node["name"]> + : BI + : never + : EvalError<`Unhandled node type ${Node["type"]}`>; + +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 Evaluate<Node extends ASTNode> = _Evaluate<Node, EmptyStackFrame>; diff --git a/src/ts-lang/core/index.ts b/src/ts-lang/core/index.ts new file mode 100644 index 0000000..881cacf --- /dev/null +++ b/src/ts-lang/core/index.ts @@ -0,0 +1,4 @@ +export * from "./common"; +export * from "./eval"; +export * from "./parser"; +export * from "./lexer"; diff --git a/src/ts-lang/core/lexer.ts b/src/ts-lang/core/lexer.ts new file mode 100644 index 0000000..bcd5785 --- /dev/null +++ b/src/ts-lang/core/lexer.ts @@ -0,0 +1,84 @@ +import { LexerCtx, Token, TokenType } from "./common"; + +export type IsWhitespace<T extends string> = T extends `${TokenType.SPACE}` + ? true + : T extends `${TokenType.COMMA}` + ? true + : T extends `${TokenType.SEMICOLON}` + ? true + : false; + +export type ProcessNameCollection< + Ctx extends LexerCtx, + Tail extends string, + _Token extends Token | null +> = { + next: Tail; + nameCollection: ""; + tokens: _Token extends null + ? [ + ...Ctx["tokens"], + ...(Ctx["nameCollection"] extends "" + ? [] + : [Token<TokenType.NAME, Ctx["nameCollection"]>]) + ] + : [ + ...Ctx["tokens"], + ...(Ctx["nameCollection"] extends "" + ? [_Token] + : [Token<TokenType.NAME, Ctx["nameCollection"]>, _Token]) + ]; +}; + +export type IsOpen<T> = T extends `${TokenType.OPEN_PAREN}` ? true : false; +export type IsClose<T> = T extends `${TokenType.CLOSE_PAREN}` ? true : false; + +export type ChunkedLex< + Ctx extends LexerCtx, + Depth extends any[] = [] +> = Depth["length"] extends 50 + ? Ctx & { + endChunk: true; + } + : Ctx["next"] extends `${infer Head}${infer Tail}` + ? IsWhitespace<Head> extends true + ? ChunkedLex<ProcessNameCollection<Ctx, Tail, null>, [0, ...Depth]> + : IsOpen<Head> extends true + ? ChunkedLex< + ProcessNameCollection<Ctx, Tail, Token<TokenType.OPEN_PAREN>>, + [0, ...Depth] + > + : IsClose<Head> extends true + ? ChunkedLex< + ProcessNameCollection<Ctx, Tail, Token<TokenType.CLOSE_PAREN>>, + [0, ...Depth] + > + : ChunkedLex< + { + next: Tail; + nameCollection: `${Ctx["nameCollection"]}${Head}`; + tokens: Ctx["tokens"]; + }, + [0, ...Depth] + > + : Ctx; + +export type InnerLex< + Next extends string, + NameCollection extends LexerCtx["nameCollection"] = "", + AccTokens extends Token[] = [] +> = Next extends "" + ? AccTokens + : ChunkedLex<{ + next: Next; + tokens: []; + nameCollection: NameCollection; + }> extends infer U + ? U extends LexerCtx & { endChunk: true } + ? InnerLex<U["next"], U["nameCollection"], [...AccTokens, ...U["tokens"]]> + : U extends LexerCtx + ? [...AccTokens, ...U["tokens"]] + : never + : never; + +export type Lex<Raw extends string> = InnerLex<`${Raw};`>; diff --git a/src/ts-lang/core/parser.ts b/src/ts-lang/core/parser.ts new file mode 100644 index 0000000..db6f3aa --- /dev/null +++ b/src/ts-lang/core/parser.ts @@ -0,0 +1,165 @@ +import { ASTNode, NodeType, ParserCtx, Token, TokenType } from "./common"; + +export type Error<T extends string> = ASTNode< + NodeType.PARSER_ERROR, + "Error", + T, + [] +>; + +export type PushChild<Node extends ASTNode, Child extends ASTNode> = { + type: Node["type"]; + value: Node["value"]; + name: Node["name"]; + children: [...Node["children"], Child]; +}; + +export type PushChildToLastElementOfStack< + Stack extends ParserCtx["stack"], + Child extends ASTNode +> = Stack extends [...infer Head, infer Tail extends ASTNode] + ? [...Head, PushChild<Tail, Child>] + : Stack extends [infer Only extends ASTNode] + ? [PushChild<Only, Child>] + : never; + +export type PushChildToSecondLastElementOfStack< + Stack extends ParserCtx["stack"], + Child extends ASTNode +> = Stack extends [ + ...infer Head, + infer Tail extends ASTNode, + infer Final extends ASTNode +] + ? [...Head, PushChild<Tail, Child>, Final] + : Stack extends [infer Only extends ASTNode, infer Final extends ASTNode] + ? [PushChild<Only, Child>, Final] + : never; + +export type GetLastOnStack<Stack extends ParserCtx["stack"]> = Stack extends [ + ...infer Head, + infer Tail extends ASTNode +] + ? Tail + : Stack extends [infer Only extends ASTNode] + ? Only + : never; + +export type StackWithoutLast<Stack extends ParserCtx["stack"]> = Stack extends [ + ...infer Head extends ASTNode[], + infer Tail +] + ? [...Head] + : Stack extends [infer Only extends ASTNode] + ? [] + : never; + +type NULL_SENTINEL = { + NULL: true; +}; + +export type ParseNumberLiteral<T extends string> = + T extends `${infer Inner extends number}` ? Inner : NULL_SENTINEL; + +export type ParseStringLiteral<T extends string> = + T extends `"${infer Inner extends string}"` ? Inner : NULL_SENTINEL; + +export type ResolveNodeFromToken<_Token extends Token> = ParseNumberLiteral< + _Token["name"] +> extends number + ? ASTNode<NodeType.INT, "", ParseNumberLiteral<_Token["name"]>, []> + : ParseStringLiteral<_Token["name"]> extends string + ? ASTNode<NodeType.INT, "", ParseStringLiteral<_Token["name"]>, []> + : ASTNode<NodeType.EXT, _Token["name"], null, []>; + +export type _Parse<Ctx extends ParserCtx> = Ctx["remainingTokens"] extends [ + infer Head extends Token, + ...infer Tail extends readonly Token[] +] + ? Ctx["lastToken"] extends Token + ? Head["type"] extends TokenType.NAME + ? // we already have a lastToken + // mutate last element of stack to push lastToken as child + // lastToken = nextToken + // goto start + _Parse<{ + lastToken: Head; + remainingTokens: Tail; + stack: PushChildToLastElementOfStack< + Ctx["stack"], + ResolveNodeFromToken<Ctx["lastToken"]> + >; + }> + : //nextToken is openParen or close paren + Head["type"] extends TokenType.CLOSE_PAREN + ? // handle lastToken + // set last element of stack as child of prev element on stack + // pop stack + // [stack[last - 1].children.push(stack.pop) + // goto start + _Parse<{ + lastToken: null; + remainingTokens: Tail; + // first push the last name onto the children of the top + // then push the top onto the children of the next + // then remove the top + stack: StackWithoutLast< + PushChildToSecondLastElementOfStack< + Ctx["stack"], + PushChild< + GetLastOnStack<Ctx["stack"]>, + ResolveNodeFromToken<Ctx["lastToken"]> + > + > + >; + }> + : Head["type"] extends TokenType.OPEN_PAREN + ? // push lastToken onto stack + // goto start + _Parse<{ + lastToken: null; + remainingTokens: Tail; + stack: [...Ctx["stack"], ResolveNodeFromToken<Ctx["lastToken"]>]; + }> + : Ctx & Error<`Was not expecting ${Head["type"]}`> + : // expect nextToken to be a name or close paren + Head["type"] extends TokenType.NAME + ? // lastToken = nextToken + // goto start + _Parse<{ + lastToken: Head; + remainingTokens: Tail; + stack: Ctx["stack"]; + }> + : Head["type"] extends TokenType.CLOSE_PAREN + ? _Parse<{ + lastToken: null; + remainingTokens: Tail; + // push the top onto the children of the next + // then remove the top + stack: StackWithoutLast< + PushChildToSecondLastElementOfStack< + Ctx["stack"], + GetLastOnStack<Ctx["stack"]> + > + >; + }> + : Ctx & + Error<`Expected nextToken to be a name or close paren at ${Head["type"]}`> + : Ctx["lastToken"] extends Token + ? // case where we ended with a name + _Parse<{ + lastToken: null; + remainingTokens: []; + stack: PushChildToLastElementOfStack< + Ctx["stack"], + ResolveNodeFromToken<Ctx["lastToken"]> + >; + }> + : Ctx["stack"][0]; + +export type Parse<Raw extends readonly Token[]> = _Parse<{ + lastToken: null; + remainingTokens: Raw; + stack: [ASTNode<NodeType.EXT, "arr", null, []>]; +}>; |
