diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/index.ts | 3 | ||||
| -rw-r--r-- | src/js-lang/builtin/builtin.ts | 39 | ||||
| -rw-r--r-- | src/js-lang/builtin/index.ts | 2 | ||||
| -rw-r--r-- | src/js-lang/builtin/sbuiltin.ts | 46 | ||||
| -rw-r--r-- | src/js-lang/core/eval.ts | 83 | ||||
| -rw-r--r-- | src/js-lang/core/index.ts | 3 | ||||
| -rw-r--r-- | src/js-lang/core/lexer.ts | 46 | ||||
| -rw-r--r-- | src/js-lang/core/parser.ts | 132 | ||||
| -rw-r--r-- | src/js-lang/index.ts (renamed from src/lang/index.ts) | 0 | ||||
| -rw-r--r-- | src/lang/core/index.ts | 0 | ||||
| -rw-r--r-- | src/ts-lang/builtin/builtin.ts (renamed from src/lang/builtin/builtin.ts) | 0 | ||||
| -rw-r--r-- | src/ts-lang/builtin/index.ts (renamed from src/lang/builtin/index.ts) | 0 | ||||
| -rw-r--r-- | src/ts-lang/builtin/sbuiltin.ts (renamed from src/lang/builtin/sbuiltin.ts) | 0 | ||||
| -rw-r--r-- | src/ts-lang/core/common.ts (renamed from src/lang/core/common.ts) | 6 | ||||
| -rw-r--r-- | src/ts-lang/core/eval.ts (renamed from src/lang/core/eval.ts) | 37 | ||||
| -rw-r--r-- | src/ts-lang/core/index.ts | 4 | ||||
| -rw-r--r-- | src/ts-lang/core/lexer.ts (renamed from src/lang/core/lexer.ts) | 11 | ||||
| -rw-r--r-- | src/ts-lang/core/parser.ts (renamed from src/lang/core/parser.ts) | 10 | ||||
| -rw-r--r-- | src/ts-lang/index.ts | 1 | ||||
| -rw-r--r-- | src/ts-lang/util/index.ts (renamed from src/lang/util/index.ts) | 0 | ||||
| -rw-r--r-- | src/ts-lang/util/number.ts (renamed from src/lang/util/number.ts) | 0 | ||||
| -rw-r--r-- | src/ts-lang/util/string.ts (renamed from src/lang/util/string.ts) | 0 | ||||
| -rw-r--r-- | src/ts-lang/util/utils.ts (renamed from src/lang/util/utils.ts) | 0 |
23 files changed, 375 insertions, 48 deletions
diff --git a/src/index.ts b/src/index.ts index 8cd5167..ab79901 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ -export * from "./lib"; +export * from "./ts-lang"; +export * from "./js-lang"; diff --git a/src/js-lang/builtin/builtin.ts b/src/js-lang/builtin/builtin.ts new file mode 100644 index 0000000..dde91b6 --- /dev/null +++ b/src/js-lang/builtin/builtin.ts @@ -0,0 +1,39 @@ +type BUILTIN = (args: any[]) => any; + +export const V_BUILTIN_Arr: BUILTIN = (args) => args; + +// FIXME actually implement this properly +export const V_BUILTIN_ToString: BUILTIN = (args) => + args.length === 1 ? JSON.stringify(args[0]) : JSON.stringify(args); + +export const V_BUILTIN_Add: BUILTIN = (args) => { + if (args.every((arg) => ["string", "number"].includes(typeof arg))) { + return args.reduce( + (acc, cur) => acc + cur, + typeof args[0] === "string" ? "" : 0 + ); + } + + throw new Error(`Cannot add operands ${JSON.stringify(args, undefined, 2)}`); +}; + +export const V_BUILTIN_Mul: BUILTIN = (args) => { + if (args.every((arg) => typeof arg === "number") && args.length === 2) { + return args.reduce((acc, cur) => acc * cur, 1); + } + + throw new Error( + `Can only multiply [number, number], but got ${JSON.stringify( + args, + undefined, + 2 + )}` + ); +}; + +export const nameToBUILTIN: Record<string, BUILTIN> = { + arr: V_BUILTIN_Arr, + tostring: V_BUILTIN_ToString, + add: V_BUILTIN_Add, + mul: V_BUILTIN_Mul, +}; diff --git a/src/js-lang/builtin/index.ts b/src/js-lang/builtin/index.ts new file mode 100644 index 0000000..00e77f7 --- /dev/null +++ b/src/js-lang/builtin/index.ts @@ -0,0 +1,2 @@ +export * from "./builtin"; +export * from "./sbuiltin"; diff --git a/src/js-lang/builtin/sbuiltin.ts b/src/js-lang/builtin/sbuiltin.ts new file mode 100644 index 0000000..44c969d --- /dev/null +++ b/src/js-lang/builtin/sbuiltin.ts @@ -0,0 +1,46 @@ +import { callFn, getEvaluatedChildren } from "../core/eval"; +import { ASTNode, FnPrim, StackFrame } from "../../ts-lang"; + +type SBUILTIN = (node: ASTNode, frame: StackFrame) => any; + +export const V_SBUITLIN_Call: SBUILTIN = (node, frame) => { + const children = getEvaluatedChildren(node, frame); + const fn = children[0] as FnPrim | undefined; + + if (!fn?.fn) { + throw new Error( + `Invalid params for function call: ${JSON.stringify( + children, + undefined, + 2 + )}` + ); + } + + return callFn(fn, children.slice(1), frame); +}; + +export const V_SBUILTIN_Map: SBUILTIN = (node, frame) => { + const children = getEvaluatedChildren(node, frame); + const fn = children[1] as FnPrim | undefined; + + if (!fn?.fn) { + throw new Error( + `Invalid params for map: ${JSON.stringify(children, undefined, 2)}` + ); + } + + const values = children[0]; + + if (!Array.isArray(values)) { + // add to ts + throw new Error(`Can't map non-array value: ${values}`); + } + + return values.map((v, i) => callFn(fn, [v, i], frame)); +}; + +export const nameToSBUILTIN: Record<string, SBUILTIN> = { + call: V_SBUITLIN_Call, + map: V_SBUILTIN_Map, +}; diff --git a/src/js-lang/core/eval.ts b/src/js-lang/core/eval.ts new file mode 100644 index 0000000..60a2059 --- /dev/null +++ b/src/js-lang/core/eval.ts @@ -0,0 +1,83 @@ +import { + ASTNode, + StackFrame, + Evaluate, + EmptyStackFrame, + NodeType, + FnPrim, + SENTINEL_NO_BUILTIN, +} from "../../ts-lang"; +import { nameToBUILTIN, nameToSBUILTIN, V_SBUILTIN_Map } from "../builtin"; + +const V_SENTINEL_NO_BUILTIN: SENTINEL_NO_BUILTIN = "__NO_BUILTIN__"; + +const mapBuiltins = (node: ASTNode, frame: StackFrame): any => { + if (node.name in nameToSBUILTIN) { + return nameToSBUILTIN[node.name](node, frame); + } + if (node.name in nameToBUILTIN) { + return nameToBUILTIN[node.name](getEvaluatedChildren(node, frame)); + } + + return V_SENTINEL_NO_BUILTIN; +}; + +const findInStack = (frame: StackFrame, nameToFind: string) => { + if (nameToFind in frame.bindings) { + return frame.bindings[nameToFind]; + } + + if (!frame.parent) { + throw new Error(`Can't find name ${nameToFind} on the stack`); + } + + return findInStack(frame.parent, nameToFind); +}; + +const handleFn = (node: ASTNode): FnPrim => { + const fn = node.children[node.children.length - 1]; + + return { + args: node.children.slice(0, node.children.length - 1), + fn, + }; +}; + +const mapZip = (args: ASTNode[], values: any[]) => + Object.fromEntries(args.map(({ name }, i) => [name, values[i]])); + +export const callFn = (fn: FnPrim, values: any[], frame: StackFrame) => + _evaluate(fn.fn, { + bindings: mapZip(fn.args as ASTNode[], values), + parent: frame, + }); + +export const _evaluate = (node: ASTNode, frame: StackFrame) => { + if (node.type === NodeType.INT) { + return node.value; + } + + if (node.type === NodeType.EXT) { + if (node.name === "fn") { + return handleFn(node); + } + + const builtinResult = mapBuiltins(node, frame); + if (builtinResult !== V_SENTINEL_NO_BUILTIN) { + return builtinResult; + } + + return findInStack(frame, node.name); + } + + throw new Error(`Unhandled node type ${node.type}`); +}; + +export const getEvaluatedChildren = (node: ASTNode, frame: StackFrame) => + node.children.map((child) => _evaluate(child, frame)); + +export const emptyStackFrame: EmptyStackFrame = { bindings: {}, parent: null }; + +export const evaluate = <const Node extends ASTNode>( + node: Node +): Evaluate<Node> => _evaluate(node, emptyStackFrame) as Evaluate<Node>; diff --git a/src/js-lang/core/index.ts b/src/js-lang/core/index.ts new file mode 100644 index 0000000..22ac2d2 --- /dev/null +++ b/src/js-lang/core/index.ts @@ -0,0 +1,3 @@ +export * from "./eval"; +export * from "./parser"; +export * from "./lexer"; diff --git a/src/js-lang/core/lexer.ts b/src/js-lang/core/lexer.ts new file mode 100644 index 0000000..95e0e19 --- /dev/null +++ b/src/js-lang/core/lexer.ts @@ -0,0 +1,46 @@ +import { TokenType, Lex, Token } from "../../ts-lang"; + +const WHITESPACE_TOKENS = [ + TokenType.SPACE, + TokenType.COMMA, + TokenType.SEMICOLON, +] as string[]; + +export const lex = <const Raw extends string>(raw: Raw): Lex<Raw> => { + let _raw: string = raw; + let nameCollection = ""; + const tokens: Token[] = []; + + while (_raw) { + const head = _raw[0]; + _raw = _raw.slice(1); + + const processNameCollection = () => { + if (nameCollection) { + tokens.push({ type: TokenType.NAME, name: nameCollection }); + nameCollection = ""; + } + }; + + if (WHITESPACE_TOKENS.includes(head)) { + processNameCollection(); + continue; + } + + if (head === TokenType.OPEN_PAREN) { + processNameCollection(); + tokens.push({ type: TokenType.OPEN_PAREN, name: "" }); + continue; + } + + if (head === TokenType.CLOSE_PAREN) { + processNameCollection(); + tokens.push({ type: TokenType.CLOSE_PAREN, name: "" }); + continue; + } + + nameCollection += head; + } + + return tokens as Lex<Raw>; +}; diff --git a/src/js-lang/core/parser.ts b/src/js-lang/core/parser.ts new file mode 100644 index 0000000..f193d6a --- /dev/null +++ b/src/js-lang/core/parser.ts @@ -0,0 +1,132 @@ +import { Token, Parse, TokenType, ASTNode, NodeType } from "../../ts-lang"; +import { lex } from "./lexer"; + +const resolveNodeFromToken = (token: Token): ASTNode => { + // FIXME not correct + if (!isNaN(Number(token.name))) { + return { + type: NodeType.INT, + name: "", + value: Number(token.name), + children: [], + }; + } + if (token.name[0] === '"' && token.name[token.name.length - 1] === '"') { + return { + type: NodeType.INT, + name: "", + value: token.name.slice(1, token.name.length - 1), + children: [], + }; + } + return { + type: NodeType.EXT, + name: token.name, + value: null, + children: [], + }; +}; + +const _parse = ({ + remainingTokens, + lastToken, + stack, +}: { + remainingTokens: Token[]; + lastToken: Token | null; + stack: ASTNode[]; +}): ASTNode | null => { + const head = remainingTokens.shift(); + if (!head) { + if (lastToken) { + (stack[stack.length - 1].children as ASTNode[]).push( + resolveNodeFromToken(lastToken) + ); + + return _parse({ + lastToken: null, + remainingTokens: [], + stack, + }); + } + + return stack[0] ?? null; + } + + if (lastToken) { + if (head.type === TokenType.NAME) { + (stack[stack.length - 1].children as ASTNode[]).push( + resolveNodeFromToken(lastToken) + ); + return _parse({ + lastToken: head, + remainingTokens, + stack, + }); + } + + if (head.type === TokenType.CLOSE_PAREN) { + const top = stack.pop()!; + (top.children as ASTNode[]).push(resolveNodeFromToken(lastToken)); + (stack[stack.length - 1].children as ASTNode[]).push(top); + + return _parse({ + lastToken: null, + remainingTokens, + stack, + }); + } + + if (head.type === TokenType.OPEN_PAREN) { + return _parse({ + lastToken: null, + remainingTokens, + stack: [...stack, resolveNodeFromToken(lastToken)], + }); + } + + throw new Error( + `${JSON.stringify({ + lastToken, + remainingTokens, + stack, + })} Was not expecting ${head.type}` + ); + } + + if (head.type === TokenType.NAME) { + return _parse({ + lastToken: head, + remainingTokens, + stack, + }); + } + + if (head.type === TokenType.CLOSE_PAREN) { + const top = stack.pop()!; + (stack[stack.length - 1].children as ASTNode[]).push(top); + + return _parse({ + lastToken: null, + remainingTokens, + stack, + }); + } + + throw new Error( + `${JSON.stringify({ + lastToken, + remainingTokens, + stack, + })} Expected nextToken to be a name or close paren at ${head.type}` + ); +}; + +export const parse = <const Raw extends readonly Token[]>( + raw: Raw +): Parse<Raw> => + _parse({ + remainingTokens: raw as unknown as Token[], + lastToken: null, + stack: [{ type: NodeType.EXT, name: "arr", value: null, children: [] }], + }) as Parse<Raw>; diff --git a/src/lang/index.ts b/src/js-lang/index.ts index 8d119de..8d119de 100644 --- a/src/lang/index.ts +++ b/src/js-lang/index.ts diff --git a/src/lang/core/index.ts b/src/lang/core/index.ts deleted file mode 100644 index e69de29..0000000 --- a/src/lang/core/index.ts +++ /dev/null diff --git a/src/lang/builtin/builtin.ts b/src/ts-lang/builtin/builtin.ts index a289867..a289867 100644 --- a/src/lang/builtin/builtin.ts +++ b/src/ts-lang/builtin/builtin.ts diff --git a/src/lang/builtin/index.ts b/src/ts-lang/builtin/index.ts index de2bee3..de2bee3 100644 --- a/src/lang/builtin/index.ts +++ b/src/ts-lang/builtin/index.ts diff --git a/src/lang/builtin/sbuiltin.ts b/src/ts-lang/builtin/sbuiltin.ts index 01f197e..01f197e 100644 --- a/src/lang/builtin/sbuiltin.ts +++ b/src/ts-lang/builtin/sbuiltin.ts diff --git a/src/lang/core/common.ts b/src/ts-lang/core/common.ts index 9e3840b..fa5cb7c 100644 --- a/src/lang/core/common.ts +++ b/src/ts-lang/core/common.ts @@ -7,12 +7,6 @@ export enum TokenType { NAME = "NAME", } -export enum TokenSubType { - NA = "NA", - LITERAL = "LITERAL", - REFERENCE = "REFERENCE", -} - export type Token< Type extends TokenType = TokenType, Name extends string = string diff --git a/src/lang/core/eval.ts b/src/ts-lang/core/eval.ts index 9181c81..5f22299 100644 --- a/src/lang/core/eval.ts +++ b/src/ts-lang/core/eval.ts @@ -8,8 +8,6 @@ import { } from "../builtin"; import { ToString } from "../util"; import { ASTNode, EmptyStackFrame, NodeType, StackFrame } from "./common"; -import { Lex } from "./lexer"; -import { Parse } from "./parser"; export type SENTINEL_NO_BUILTIN = "__NO_BUILTIN__"; export type MapBuiltins< @@ -42,11 +40,6 @@ export type FindInStack< ? 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 = ASTNode @@ -57,29 +50,31 @@ export type HandleFn<Node extends ASTNode> = Node["children"] extends [ infer Fn extends ASTNode ] ? FnPrim<Args, Fn> - : never; + : // error? + never; +// any[] was propertykey export type MapZip< - T extends readonly ASTNode[], - U extends readonly PropertyKey[] + Args extends readonly ASTNode[], + Values extends readonly any[] > = { [Idx in Exclude< - keyof T, + keyof Args, keyof any[] - > as T[Idx] extends infer Node extends ASTNode + > as Args[Idx] extends infer Node extends ASTNode ? Node["name"] - : never]: Idx extends keyof U ? U[Idx] : never; + : 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>>; +> = _Evaluate<Fn["fn"], StackFrame<MapZip<Fn["args"], Values>, Frame>>; -export type Evaluate< +export type _Evaluate< Node extends ASTNode, - Frame extends StackFrame = EmptyStackFrame + Frame extends StackFrame > = Node["type"] extends NodeType.INT ? Node["value"] : Node["type"] extends NodeType.EXT @@ -88,7 +83,7 @@ export type Evaluate< ? HandleFn<Node> : MapBuiltins<Node, Frame> extends infer BI ? BI extends SENTINEL_NO_BUILTIN - ? MapOnStack<Node, Frame> + ? FindInStack<Frame, Node["name"]> : BI : never : EvalError<`Unhandled node type ${Node["type"]}`>; @@ -99,13 +94,9 @@ export type GetEvaluatedChildren< > = Node["children"] extends infer Children extends readonly ASTNode[] ? { [Idx in keyof Children]: Children[Idx] extends ASTNode - ? Evaluate<Children[Idx], Frame> + ? _Evaluate<Children[Idx], Frame> : never; } : never; -const input = - `map(arr("hello", "world"), fn(s, i, add(tostring(i), ":", s)))` 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>; +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/lang/core/lexer.ts b/src/ts-lang/core/lexer.ts index 567964f..bcd5785 100644 --- a/src/lang/core/lexer.ts +++ b/src/ts-lang/core/lexer.ts @@ -1,11 +1,4 @@ -import { LexerCtx, Token, TokenSubType, TokenType } from "./common"; - -export type BreakingToken = - | TokenType.OPEN_PAREN - | TokenType.CLOSE_PAREN - | TokenType.COMMA - | TokenType.SEMICOLON - | TokenType.SPACE; +import { LexerCtx, Token, TokenType } from "./common"; export type IsWhitespace<T extends string> = T extends `${TokenType.SPACE}` ? true @@ -88,4 +81,4 @@ export type InnerLex< : never : never; -export type Lex<Raw extends string> = InnerLex<Raw>; +export type Lex<Raw extends string> = InnerLex<`${Raw};`>; diff --git a/src/lang/core/parser.ts b/src/ts-lang/core/parser.ts index 2b481fd..db6f3aa 100644 --- a/src/lang/core/parser.ts +++ b/src/ts-lang/core/parser.ts @@ -1,12 +1,4 @@ -import { - ASTNode, - NodeType, - ParserCtx, - Token, - TokenSubType, - TokenType, -} from "./common"; -import { Lex } from "./lexer"; +import { ASTNode, NodeType, ParserCtx, Token, TokenType } from "./common"; export type Error<T extends string> = ASTNode< NodeType.PARSER_ERROR, diff --git a/src/ts-lang/index.ts b/src/ts-lang/index.ts new file mode 100644 index 0000000..8d119de --- /dev/null +++ b/src/ts-lang/index.ts @@ -0,0 +1 @@ +export * from "./core"; diff --git a/src/lang/util/index.ts b/src/ts-lang/util/index.ts index 00a3e54..00a3e54 100644 --- a/src/lang/util/index.ts +++ b/src/ts-lang/util/index.ts diff --git a/src/lang/util/number.ts b/src/ts-lang/util/number.ts index 6e4e360..6e4e360 100644 --- a/src/lang/util/number.ts +++ b/src/ts-lang/util/number.ts diff --git a/src/lang/util/string.ts b/src/ts-lang/util/string.ts index 5772f40..5772f40 100644 --- a/src/lang/util/string.ts +++ b/src/ts-lang/util/string.ts diff --git a/src/lang/util/utils.ts b/src/ts-lang/util/utils.ts index ac36ca1..ac36ca1 100644 --- a/src/lang/util/utils.ts +++ b/src/ts-lang/util/utils.ts |
