Working tree-walk executor
This commit is contained in:
parent
81b5a5b0f1
commit
c79c0e4fec
161
main.js
161
main.js
@ -8,7 +8,7 @@ WE SAID
|
|||||||
SHOCKING DEVELOPMENT 1
|
SHOCKING DEVELOPMENT 1
|
||||||
END OF STORY
|
END OF STORY
|
||||||
LIES! WE SAID
|
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
|
||||||
END OF STORY
|
END OF STORY
|
||||||
|
|
||||||
@ -19,6 +19,12 @@ YOU WON'T WANT TO MISS result
|
|||||||
PLEASE LIKE AND SUBSCRIBE
|
PLEASE LIKE AND SUBSCRIBE
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const Runtime = {
|
||||||
|
print(s) {
|
||||||
|
console.log(s.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* tokenizer */
|
/* tokenizer */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -369,7 +375,7 @@ class Parser {
|
|||||||
}
|
}
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
expectIdent() {
|
expectIdentString() {
|
||||||
const ident = this.tokens.next();
|
const ident = this.tokens.next();
|
||||||
if (typeof ident === 'string' && !ident.startsWith('"')) {
|
if (typeof ident === 'string' && !ident.startsWith('"')) {
|
||||||
return ident;
|
return ident;
|
||||||
@ -404,10 +410,10 @@ class Parser {
|
|||||||
if (this.tokens.peek(T.With)) {
|
if (this.tokens.peek(T.With)) {
|
||||||
this.tokens.next(); // with
|
this.tokens.next(); // with
|
||||||
// with args
|
// with args
|
||||||
const args = [this.expectIdent()];
|
const args = [this.expectIdentString()];
|
||||||
while (this.tokens.peek() === T.Comma) {
|
while (this.tokens.peek() === T.Comma) {
|
||||||
this.tokens.next(); // comma
|
this.tokens.next(); // comma
|
||||||
args.push(this.expectIdent());
|
args.push(this.expectIdentString());
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: N.FnDecl,
|
type: N.FnDecl,
|
||||||
@ -430,7 +436,10 @@ class Parser {
|
|||||||
exprs.push(this.expr());
|
exprs.push(this.expr());
|
||||||
}
|
}
|
||||||
this.tokens.expect(T.EndOfStory);
|
this.tokens.expect(T.EndOfStory);
|
||||||
return exprs;
|
return {
|
||||||
|
type: N.ExprGroup,
|
||||||
|
exprs: exprs,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Parsing error: expected ident, literal, or block, got ${
|
throw new Error(`Parsing error: expected ident, literal, or block, got ${
|
||||||
@ -457,7 +466,7 @@ class Parser {
|
|||||||
}
|
}
|
||||||
} else if (next === T.ExpertsClaim) {
|
} else if (next === T.ExpertsClaim) {
|
||||||
// assignment
|
// assignment
|
||||||
const name = this.expectIdent();
|
const name = this.expectIdentString();
|
||||||
this.tokens.expect(T.ToBe);
|
this.tokens.expect(T.ToBe);
|
||||||
const val = this.expr();
|
const val = this.expr();
|
||||||
return {
|
return {
|
||||||
@ -494,6 +503,7 @@ class Parser {
|
|||||||
const right = this.atom();
|
const right = this.atom();
|
||||||
return {
|
return {
|
||||||
type: N.BinaryOp,
|
type: N.BinaryOp,
|
||||||
|
op,
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
}
|
}
|
||||||
@ -515,14 +525,143 @@ class Parser {
|
|||||||
|
|
||||||
/* executor (tree walk) */
|
/* executor (tree walk) */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abused (slightly) to easily return values upstack
|
||||||
|
*/
|
||||||
|
class ReturnError {
|
||||||
|
constructor(value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
unwrap() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Environment {
|
class Environment {
|
||||||
constructor(nodes) {
|
constructor() {
|
||||||
this.scopes = [{}]; // begin with global scope
|
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
|
// main
|
||||||
const tokens = tokenize(prog);
|
try {
|
||||||
console.log(tokens);
|
const tokens = tokenize(prog);
|
||||||
const nodes = new Parser(tokens).parse();
|
const nodes = new Parser(tokens).parse();
|
||||||
console.log(nodes);
|
const env = new Environment();
|
||||||
|
env.run(nodes);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user