diff options
Diffstat (limited to 'src/js-lang/core')
| -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 |
4 files changed, 264 insertions, 0 deletions
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>; |
