Published
Edited
Jan 22, 2020
Insert cell
Insert cell
main(`print 1 + 12 < 23.4;
print 2 + 3;`)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
run(input)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
showTokens(scanTokens(input))
Insert cell
scanTokens(input)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
copyableCode(
defineAst("Expr", [
"Assign : name, value",
"Binary : left, operator, right",
"Call : callee, paren, args",
"Grouping : expression",
"Literal : value",
"Logical : left, operator, right",
"Unary : operator, right",
"Variable : name"
])
)
Insert cell
Insert cell
copyableCode(
defineAst("Stmt", [
"Block : statements",
"Expression : expression",
"Function : name, params, body",
"For : initializer, condition, increment, body",
"If : condition, thenBranch, elseBranch",
"Return : keyword, value",
"Print : expression",
"Var : name, initializer",
"While : condition, body"
])
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
parse
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
pprint(parse(scanTokens(input)))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
evaluate(parseExpr(scanTokens('1 + 1')))
Insert cell
interpret(parse(scanTokens(input)))
Insert cell
interpret(
parse(
scanTokens(`fun foo(x) {
x + 1;
}
foo(1);`)
)
)
Insert cell
interpret = stmts => new ASTInterpreter().interpret(stmts)
Insert cell
execute = stmt => new ASTInterpreter().execute(stmt)
Insert cell
Insert cell
class LoxCallable {
call(interpreter, args) {
throw Error(`Whoops implementor error, no call implementation on ${this}`);
}
arity() {
throw Error(`Whoops implementor error, no arity implementation on ${this}`);
}
}
Insert cell
class ReturnException extends Error {
constructor(value, ...args) {
super(...args);
this.value = value;
}
}
Insert cell
Insert cell
Insert cell
class ASTInterpreter extends AstVisitor {
constructor() {
super();
this.globals = new Environment();
Object.keys(globals).forEach(name =>
this.globals.define(name, new globals[name]())
);
this.env = new Environment(this.globals);
}
interpret(stmts) {
this.outputs = [];
for (let stmt of stmts) {
this.execute(stmt);
}
return this.outputs;
}
execute(stmt) {
return stmt.accept(this);
}
evaluate(expr) {
return expr.accept(this);
}
executeBlock(stmts, env) {
const prevEnv = this.env;
this.env = env;
try {
for (let stmt of stmts) {
this.execute(stmt);
}
} finally {
this.env = prevEnv;
}
}
visit_FunctionStmt(stmt) {
const fun = new LoxFunction(stmt, this.env);
this.env.define(stmt.name.lexeme, fun);
}
visit_IfStmt(stmt) {
if (this.isTruthy(this.evaluate(stmt.condition))) {
this.execute(stmt.thenBranch);
} else if (stmt.elseBranch) {
this.execute(stmt.elseBranch);
}
}
visit_WhileStmt(stmt) {
while (this.isTruthy(this.evaluate(stmt.condition))) {
this.execute(stmt.body);
}
}
visit_BlockStmt(stmt) {
this.executeBlock(stmt.statements, new Environment(this.env));
}
visit_VarStmt(stmt) {
let value = null;
if (stmt.initializer) {
value = this.evaluate(stmt.initializer);
}

this.env.define(stmt.name.lexeme, value);
}
visit_ReturnStmt(stmt) {
let value = null;
if (stmt.value) {
value = this.evaluate(stmt.value);
}

throw new ReturnException(value);
}
visit_PrintStmt(stmt) {
const value = this.evaluate(stmt.expression);
this.outputs.push(this.stringify(value));
}
visit_CallExpr(expr) {
const callee = this.evaluate(expr.callee);

const args = [];
for (let arg of expr.args) {
args.push(this.evaluate(arg));
}
if (!(callee instanceof LoxCallable)) {
throw new LoxRuntimeError(null, `Not callable ${callee}`);
}

if (args.length !== callee.arity()) {
throw new LoxRuntimeError(
null,
`Function ${callee} called with ${
args.length
}, expected ${callee.arity()}`
);
}
return callee.call(this, args);
}
visit_ExpressionStmt(stmt) {
this.evaluate(stmt.expression);
}
visit_AssignExpr(expr) {
const value = this.evaluate(expr.value);
this.env.assign(expr.name, value);
return value;
}
visit_VariableExpr(expr) {
return this.env.get(expr.name);
}
visit_LogicalExpr(expr) {
const left = expr.left.accept(this);

if (expr.operator.type == TokenType.OR) {
if (this.isTruthy(left)) return left;
} else {
if (!this.isTruthy(left)) return left;
}

return this.evaluate(expr.right);
}
visit_BinaryExpr(expr) {
const [left, right] = [this.evaluate(expr.left), this.evaluate(expr.right)];
switch (expr.operator.type) {
case TokenType.PLUS:
if (typeof left === "string" && typeof right === "string") {
return left + right;
} else if (typeof left === "number" && typeof right === "number") {
return left + right;
} else {
throw new LoxRuntimeError(
expr.operator,
`+ expects two strings or two numbers, not ${JSON.stringify(
left
)} and ${JSON.stringify(right)}`
);
}
case TokenType.MINUS:
return this.assertNumber(left) - this.assertNumber(right);
case TokenType.SLASH:
return this.assertNumber(left) / this.assertNumber(right);
case TokenType.STAR:
return this.assertNumber(left) * this.assertNumber(right);
case TokenType.GREATER:
return this.assertNumber(left) > this.assertNumber(right);
case TokenType.GREATER_EQUAL:
return this.assertNumber(left) >= this.assertNumber(right);
case TokenType.LESS:
return this.assertNumber(left) < this.assertNumber(right);
case TokenType.LESS_EQUAL:
return this.assertNumber(left) <= this.assertNumber(right);
case TokenType.BANG_EQUAL:
return !this.isEqual(left, right);
case TokenType.EQUAL_EQUAL:
return this.isEqual(left, right);
}
}
visit_GroupingExpr(expr) {
return this.evaluate(expr.expression);
}
visit_LiteralExpr(expr) {
return expr.value;
}
visit_UnaryExpr(expr) {
const right = this.evaluate(expr.right);
switch (expr.operator.type) {
case TokenType.MINUS: {
return -right;
break;
}
case TokenType.BANG: {
return !this.isTruthy(right);
}
default: {
throw Error(`Unimplemented unary operator: ${expr.operator.type}`);
}
}
}
evaluate(expr) {
return expr.accept(this);
}
isTruthy(expr) {
return !!expr;
}
isEqual(e1, e2) {
return e1 === e2;
}
assertNumber(num, token) {
if (typeof num !== "number") {
throw new LoxRuntimeError(token, `expected number, not ${num}`);
}
return num;
}
stringify(value) {
return '' + value;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import { copyableCode } from '@ballingt/copy-this-code'
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
showTokens(scanTokens('print 1 + 2 * 3; print 4 * 3;'))
Insert cell
Insert cell
Insert cell
showAST(parse(scanTokens('print 1 + 2 * 3; print 4 * 3;')))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more