Last active
March 9, 2022 19:18
-
-
Save sampersand/0db4d9a79523a56ac5e6e53f2c2fb11b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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