summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Lee <linus@thesephist.com>2020-09-24 05:30:15 -0400
committerLinus Lee <linus@thesephist.com>2020-09-24 05:30:15 -0400
commitc79c0e4fec62e535732fff69fc216a0f31f95566 (patch)
treec12068f83e7571162f875be1289d9deb65067e40
parent81b5a5b0f1852dd9b7367ba577fd94da64e9be76 (diff)
downloadtabloid-fake-closure-c79c0e4fec62e535732fff69fc216a0f31f95566.tar.gz
tabloid-fake-closure-c79c0e4fec62e535732fff69fc216a0f31f95566.zip
Working tree-walk executor
-rw-r--r--main.js161
1 files changed, 150 insertions, 11 deletions
diff --git a/main.js b/main.js
index 1233cc9..67ef012 100644
--- a/main.js
+++ b/main.js
@@ -8,7 +8,7 @@ WE SAID
SHOCKING DEVELOPMENT 1
END OF STORY
LIES! WE SAID
- SHOCKING DEVELOPMENT n MULTIPLY factorial OF n
+ SHOCKING DEVELOPMENT n MULTIPLY factorial OF n SUBTRACT 1
END OF STORY
END OF STORY
@@ -19,6 +19,12 @@ YOU WON'T WANT TO MISS result
PLEASE LIKE AND SUBSCRIBE
`
+const Runtime = {
+ print(s) {
+ console.log(s.toString());
+ }
+}
+
/* tokenizer */
/**
@@ -369,7 +375,7 @@ class Parser {
}
return nodes;
}
- expectIdent() {
+ expectIdentString() {
const ident = this.tokens.next();
if (typeof ident === 'string' && !ident.startsWith('"')) {
return ident;
@@ -404,10 +410,10 @@ class Parser {
if (this.tokens.peek(T.With)) {
this.tokens.next(); // with
// with args
- const args = [this.expectIdent()];
+ const args = [this.expectIdentString()];
while (this.tokens.peek() === T.Comma) {
this.tokens.next(); // comma
- args.push(this.expectIdent());
+ args.push(this.expectIdentString());
}
return {
type: N.FnDecl,
@@ -430,7 +436,10 @@ class Parser {
exprs.push(this.expr());
}
this.tokens.expect(T.EndOfStory);
- return exprs;
+ return {
+ type: N.ExprGroup,
+ exprs: exprs,
+ };
}
throw new Error(`Parsing error: expected ident, literal, or block, got ${
@@ -457,7 +466,7 @@ class Parser {
}
} else if (next === T.ExpertsClaim) {
// assignment
- const name = this.expectIdent();
+ const name = this.expectIdentString();
this.tokens.expect(T.ToBe);
const val = this.expr();
return {
@@ -494,6 +503,7 @@ class Parser {
const right = this.atom();
return {
type: N.BinaryOp,
+ op,
left,
right,
}
@@ -515,14 +525,143 @@ class Parser {
/* executor (tree walk) */
+/**
+ * Abused (slightly) to easily return values upstack
+ */
+class ReturnError {
+ constructor(value) {
+ this.value = value;
+ }
+ unwrap() {
+ return this.value;
+ }
+}
+
class Environment {
- constructor(nodes) {
+ constructor() {
this.scopes = [{}]; // begin with global scope
}
+ run(nodes) {
+ let rv;
+ for (const node of nodes) {
+ rv = this.eval(node);
+ }
+ return rv;
+ }
+ eval(node) {
+ const scope = this.scopes[this.scopes.length - 1];
+
+ switch (node.type) {
+ case N.NumberLiteral: {
+ return node.val;
+ }
+ case N.StringLiteral: {
+ return node.val;
+ }
+ case N.FnDecl: {
+ scope[node.name] = node;
+ return node;
+ }
+ case N.FnCall: {
+ const fn = this.eval(node.fn);
+ const args = node.args.map(arg => this.eval(arg));
+
+ const calleeScope = {};
+ fn.args.forEach((argName, i) => {
+ calleeScope[argName] = args[i];
+ });
+
+ this.scopes.push(calleeScope);
+ let rv;
+ try {
+ this.eval(fn.body);
+ } catch (maybeReturnErr) {
+ if (maybeReturnErr instanceof ReturnError) {
+ rv = maybeReturnErr.unwrap();
+ } else {
+ // re-throw
+ throw maybeReturnErr;
+ }
+ }
+ this.scopes.pop();
+
+ return rv;
+ }
+ case N.Ident: {
+ let i = this.scopes.length - 1;
+ while (i >= 0) {
+ if (node.val in this.scopes[i]) {
+ return this.scopes[i][node.val];
+ }
+ i --;
+ }
+ console.log(this.scopes[this.scopes.length - 1]);
+ throw new Error(`Runtime error: Undefined variable "${node.val}"`);
+ }
+ case N.Assignment: {
+ scope[node.name] = this.eval(node.val);
+ return scope[node.name];
+ }
+ case N.BinaryOp: {
+ const left = this.eval(node.left);
+ const right = this.eval(node.right);
+ switch (node.op) {
+ // TODO: other ops
+ case T.IsActually:
+ return left === right;
+ case T.Add:
+ return left + right;
+ case T.Subtract:
+ return left - right;
+ case T.Multiply:
+ return left * right;
+ default:
+ throw new Error(`Runtime error: Unknown binary op ${node.op.toString()}`);
+ }
+ }
+ case N.IfExpr: {
+ if (this.eval(node.cond)) {
+ return this.eval(node.ifBody);
+ }
+ if (node.elseBody != null) {
+ return this.eval(node.elseBody);
+ }
+ }
+ case N.ExprGroup: {
+ let rv = false; // TODO: make null value? make this illegal?
+ for (const expr of node.exprs) {
+ rv = this.eval(expr);
+ }
+ return rv;
+ }
+ case N.ReturnExpr: {
+ const rv = this.eval(node.val);
+ throw new ReturnError(rv);
+ }
+ case N.ProgEndExpr: {
+ // do nothing
+ break;
+ }
+ case N.PrintExpr: {
+ const val = this.eval(node.val);
+ Runtime.print(val);
+ return val;
+ }
+ default:
+ console.log(JSON.stringify(node, null, 2));
+ throw new Error(`Runtime error: Unknown AST Node of type ${
+ node.type.toString()
+ }:\n${JSON.stringify(node, null, 2)}`);
+ }
+ }
}
// main
-const tokens = tokenize(prog);
-console.log(tokens);
-const nodes = new Parser(tokens).parse();
-console.log(nodes);
+try {
+ const tokens = tokenize(prog);
+ const nodes = new Parser(tokens).parse();
+ const env = new Environment();
+ env.run(nodes);
+} catch (e) {
+ console.error(e);
+}