Skip to content

Instantly share code, notes, and snippets.

@sampersand
Last active March 9, 2022 19:18
Show Gist options
  • Save sampersand/0db4d9a79523a56ac5e6e53f2c2fb11b to your computer and use it in GitHub Desktop.
Save sampersand/0db4d9a79523a56ac5e6e53f2c2fb11b to your computer and use it in GitHub Desktop.
class Literal {
constructor(value) { this.value = value; }
run(env) { return this; }
toInteger() { return parseInt(this.value); }
toString() { return "" + this.value; }
toBoolean() { return !!this.value; }
}
class Variable {
constructor(name) { this.name = name; }
run(env) { return env[this.name]; }
}
class IfStatement {
constructor(cond, iftrue, iffalse=new Statements([])) {
this.cond = cond;
this.iftrue = iftrue;
this.iffalse = iffalse;
}
run(env) {
if (this.cond.run(env).toBoolean())
this.iftrue.run(env);
else
this.iffalse.run(env);
}
}
class WhileStatement {
constructor(cond, body) {
this.cond = cond;
this.body = body;
}
run(env) {
while (this.cond.run(env).toBoolean())
this.body.run(env);
}
}
class Statements {
constructor(statements) {
this.statements = statements;
}
run(env) {
for (let i = 0; i < this.statements.length; i++)
this.statements[i].run(env);
}
}
class UnaryOperator {
constructor(op, rhs) {
this.op = op;
this.rhs = rhs;
}
run(env) {
let rhs = this.rhs.run(env);
switch (this.op) {
case "!": return new Literal(!rhs.toBoolean());
case "-": return new Literal(-rhs.toInteger());
default: throw Error(`unknown unary operator ${this.op}`);
}
}
}
class BinaryOperator {
constructor(op, lhs, rhs) {
this.op = op;
this.lhs = lhs;
this.rhs = rhs;
}
run(env) {
let lhs = this.lhs.run(env);
let rhs = this.rhs.run(env);
// These are only for integers. for strings, use builtin functions.
switch (this.op) {
case "+": return new Literal(lhs.toInteger() + rhs.toInteger());
case "-": return new Literal(lhs.toInteger() - rhs.toInteger());
case "*": return new Literal(lhs.toInteger() * rhs.toInteger());
case "/": return new Literal(lhs.toInteger() / rhs.toInteger());
case "%": return new Literal(lhs.toInteger() % rhs.toInteger());
case "==": return new Literal(lhs.toInteger() == rhs.toInteger());
case "!=": return new Literal(lhs.toInteger() != rhs.toInteger());
case "<": return new Literal(lhs.toInteger() < rhs.toInteger());
case "<=": return new Literal(lhs.toInteger() <= rhs.toInteger());
case ">": return new Literal(lhs.toInteger() > rhs.toInteger());
case ">=": return new Literal(lhs.toInteger() >= rhs.toInteger());
// note these aren't short-circuiting logical operators
case "|": return new Literal(lhs.toBoolean() || rhs.toBoolean());
case "&": return new Literal(lhs.toBoolean() && rhs.toBoolean());
default: throw Error(`unknown binary operator ${this.op}`);
}
}
}
class Assignment {
constructor(variable, value) {
this.variable = variable;
this.value = value;
}
run(env) {
env[this.variable] = this.value.run(env);
}
}
class FunctionCall {
constructor(func, args) {
this.func = func;
this.args = args;
}
run(env) {
let args = [];
for (var i = 0; i < this.args.length; i++)
args[i] = this.args[i].run(env);
switch (this.func) {
case "print":
console.log(args[0].toString());
return args[0]; // print returns its argument
case "concat":
return new Literal(args[0].toString() + args[1].toString());
case "substr":
return new Literal(args[0].toString().substr(args[1].toInteger(), args[2].toInteger()));
case "length":
return new Literal(args[0].toString().length);
default:
throw new Error(`unknown function '${this.func}'.`)
}
}
}
/*
n = 1;
while (n != 100) {
output = "";
if (n % 3 == 0) {
output = "Fizz";
}
if (n % 5 == 0) {
output = concat(output, "Buzz");
}
if (!length(output)) {
output = concat(n, "");
}
print(output);
n = n + 1;
}
*/
let fizzbuzz = new Statements([
// n = 1
new Assignment("n", new Literal(1)),
// while
new WhileStatement(
// (n != 100)
new BinaryOperator("!=", new Variable("n"), new Literal(100)),
// {
new Statements([
// output = "";
new Assignment("output", new Literal("")),
// if
new IfStatement(
// (n % 3 == 0)
new BinaryOperator("==",
new BinaryOperator("%", new Variable("n"), new Literal(3)),
new Literal(0)
),
// { output = "fizz"; }
new Statements([new Assignment("output", new Literal("Fizz"))]),
),
// if
new IfStatement(
// (n % 5 == 0)
new BinaryOperator("==",
new BinaryOperator("%", new Variable("n"), new Literal(5)),
new Literal(0)
),
// { output = concat(output, "buzz"); }
new Statements([
new Assignment("output",
new FunctionCall("concat", [new Variable("output"), new Literal("Buzz")])
),
]),
),
// if
new IfStatement(
// (!length(output))
new UnaryOperator("!", new FunctionCall("length", [new Variable("output")])),
// { output = concat(n, ""); }
new Statements([
new Assignment("output",
new FunctionCall("concat", [new Variable("n"), new Literal("")])
),
]),
),
// print(output);
new FunctionCall("print", [new Variable("output")]),
// n = n + 1;
new Assignment("n", new BinaryOperator("+", new Variable("n"), new Literal("1"))),
])
// }
)
]);
fizzbuzz.run({});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment