summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/index.ts3
-rw-r--r--src/js-lang/builtin/builtin.ts39
-rw-r--r--src/js-lang/builtin/index.ts2
-rw-r--r--src/js-lang/builtin/sbuiltin.ts46
-rw-r--r--src/js-lang/core/eval.ts83
-rw-r--r--src/js-lang/core/index.ts3
-rw-r--r--src/js-lang/core/lexer.ts46
-rw-r--r--src/js-lang/core/parser.ts132
-rw-r--r--src/js-lang/index.ts (renamed from src/lang/index.ts)0
-rw-r--r--src/lang/core/index.ts0
-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.ts4
-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.ts1
-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