You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
crafting-interpreters/ruby/test/lox/test_interpreter.rb

583 lines
11 KiB

require_relative "../test_helper"
require "lox/interpreter"
require "lox/parser"
require "lox/resolver"
require "lox/scanner"
class TestInterpreter < Lox::Test
def setup
@scanner = Lox::Scanner.new
end
# def test_literal
# assert_evaluated(42.0, "42")
# end
def test_grouping
assert_evaluated "42", "(42)"
end
def test_unary
assert_evaluated "-42", "-42"
assert_evaluated "false", "!42"
assert_evaluated "false", "!true"
assert_evaluated "true", "!false"
assert_evaluated "true", "!nil"
end
def test_binary
assert_evaluated "42", "100 - 58"
assert_evaluated "42", "84 / 2"
assert_evaluated "42", "21 * 2"
# precedence
assert_evaluated "42", "2 * 25 - 8"
assert_evaluated "42", "40 + 2"
assert_evaluated "42", "\"4\" + \"2\""
assert_evaluated "true", "1 > 0"
assert_evaluated "false", "0 > 0"
assert_evaluated "false", "0 > 1"
assert_evaluated "true", "1 >= 0"
assert_evaluated "true", "0 >= 0"
assert_evaluated "false", "0 >= 1"
assert_evaluated "false", "1 < 0"
assert_evaluated "false", "0 < 0"
assert_evaluated "true", "0 < 1"
assert_evaluated "false", "1 <= 0"
assert_evaluated "true", "0 <= 0"
assert_evaluated "true", "0 <= 1"
assert_evaluated "true", "0 != 1"
assert_evaluated "false", "0 != 0"
assert_evaluated "false", "nil != nil"
assert_evaluated "true", "nil != 1"
assert_evaluated "false", "0 == 1"
assert_evaluated "true", "0 == 0"
assert_evaluated "true", "nil == nil"
assert_evaluated "false", "nil == 1"
end
def test_errors
[
"-true",
"12 > true",
"true < 23",
"false * 23",
"false + 23",
].each do |src|
assert_raises Lox::RuntimeError do
assert_evaluated nil, src
end
end
end
def test_stringify
assert_evaluated "nil", "nil"
assert_evaluated "42", "42"
assert_evaluated "42.1", "42.1"
assert_evaluated "foo", "\"foo\""
end
def test_multiple_statements
assert_interpreted <<~EXPECTED.chomp, <<~SRC
one
true
3
EXPECTED
print "one";
print true;
print 2 + 1;
SRC
end
def test_environment
assert_interpreted <<~EXPECTED.chomp, <<~SRC
3
EXPECTED
var a = 1;
var b = 2;
print a + b;
SRC
end
def test_assignment
assert_interpreted <<~EXPECTED.chomp, <<~SRC
2
EXPECTED
var a = 1;
print a = 2;
SRC
end
def test_block
assert_interpreted <<~EXPECTED.chomp, <<~SRC
inner a
outer b
global c
outer a
outer b
global c
global a
global b
global c
EXPECTED
var a = "global a";
var b = "global b";
var c = "global c";
{
var a = "outer a";
var b = "outer b";
{
var a = "inner a";
print a;
print b;
print c;
}
print a;
print b;
print c;
}
print a;
print b;
print c;
SRC
end
def test_uninitialized_vars_are_nil
assert_interpreted <<~EXPECTED.chomp, <<~SRC
nil
EXPECTED
var a;
print a;
SRC
end
def test_if
assert_interpreted <<~EXPECTED.chomp, <<~SRC
true
false
EXPECTED
if (true)
print "true";
else
print "false";
if (false)
print "true";
else
print "false";
SRC
end
def test_logical
assert_interpreted <<~EXPECTED.chomp, <<~SRC
hi
yes
EXPECTED
print "hi" or 2; // "hi".
print nil or "yes"; // "yes".
SRC
end
def test_while
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
2
EXPECTED
var a = 0;
while (a < 3) {
print a;
a = a + 1;
}
SRC
end
def test_for
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
1
2
3
5
8
13
21
34
EXPECTED
var a = 0;
var temp;
for (var b = 1; a < 50; b = temp + b) {
print a;
temp = a;
a = b;
}
SRC
end
def test_function
assert_interpreted <<~EXPECTED.chomp, <<~SRC
Hi, Dear Reader!
EXPECTED
fun sayHi(first, last) {
print "Hi, " + first + " " + last + "!";
}
sayHi("Dear", "Reader");
SRC
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
2
EXPECTED
{
var i = 0; while (i < 3) { { print i; } i = i + 1; }
}
SRC
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
2
EXPECTED
for (var i = 0; i < 3; i = i + 1) print i;
SRC
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
1
2
3
5
8
EXPECTED
fun fib(n) {
if (n <= 1) return n;
return fib(n - 2) + fib(n - 1);
}
for (var i = 0; i < 7; i = i + 1) {
print fib(i);
}
SRC
end
def test_local_functions_and_closures
assert_interpreted <<~EXPECTED.chomp, <<~SRC
1
2
EXPECTED
fun makeCounter() {
var i = 0;
fun count() {
i = i + 1;
print i;
}
return count;
}
var counter = makeCounter();
counter(); // "1".
counter(); // "2".
SRC
end
def test_resolver
assert_interpreted <<~EXPECTED.chomp, <<~SRC
1
EXPECTED
var foo = 1;
{
print foo;
var foo = 2;
}
SRC
end
def test_class
assert_interpreted "", <<~SRC
class Breakfast {
cook() {
print "Eggs a-fryin'!";
}
serve(who) {
print "Enjoy your breakfast, " + who + ".";
}
}
SRC
assert_interpreted "DevonshireCream", <<~SRC
class DevonshireCream {
serveOn() {
return "Scones";
}
}
print DevonshireCream; // Prints "DevonshireCream".
SRC
assert_interpreted "Bagel instance", <<~SRC
class Bagel {}
var bagel = Bagel();
print bagel; // Prints "Bagel instance".
SRC
end
def test_set
assert_interpreted "bar", <<~SRC
class Bagel {}
var bagel = Bagel();
bagel.foo = "bar";
print bagel.foo;
SRC
end
def test_class_methods
assert_interpreted "Crunch crunch crunch!", <<~SRC
class Bacon {
eat() {
print "Crunch crunch crunch!";
}
}
Bacon().eat(); // Prints "Crunch crunch crunch!".
SRC
assert_interpreted "The German chocolate cake is delicious!", <<~SRC
class Cake {
taste() {
var adjective = "delicious";
print "The " + this.flavor + " cake is " + adjective + "!";
}
}
var cake = Cake();
cake.flavor = "German chocolate";
cake.taste(); // Prints "The German chocolate cake is delicious!".
SRC
assert_interpreted "Thing instance", <<~SRC
class Thing {
getCallback() {
fun localFunction() {
print this;
}
return localFunction;
}
}
var callback = Thing().getCallback();
callback();
SRC
assert_interpreted "Jane", <<~SRC
class Person {
sayName() {
print this.name;
}
}
var jane = Person();
jane.name = "Jane";
var bill = Person();
bill.name = "Bill";
bill.sayName = jane.sayName;
bill.sayName(); // "Jane"
SRC
end
def test_invalid_this
assert_raises Lox::ResolverError, "Can't use 'this' outside of a class." do
interpret("print this;")
end
assert_raises Lox::ResolverError, "Can't use 'this' outside of a class." do
interpret(<<~SRC)
fun notAMethod() {
print this;
}
SRC
end
end
def test_init
assert_interpreted <<~EXPECTED.chomp, <<~SRC
Foo instance
Foo instance
Foo instance
EXPECTED
class Foo {
init() {
print this;
}
}
var foo = Foo();
print foo.init();
SRC
assert_raises Lox::ResolverError do
interpret(<<~SRC)
class Foo {
init() {
return "something else";
}
}
SRC
end
assert_interpreted "", <<~SRC
class Foo {
init() {
return;
}
}
SRC
end
def test_inheritance
assert_interpreted "", <<~SRC
class Doughnut {}
class BostonCream < Doughnut {}
SRC
assert_raises Lox::ResolverError do
interpret("class Oops < Oops {}")
end
assert_raises Lox::RuntimeError do
interpret(<<~SRC)
var NotAClass = "I am totally not a class";
class Subclass < NotAClass {} // ?!
SRC
end
end
def test_inheriting_methods
assert_interpreted "Fry until golden brown.", <<~SRC
class Doughnut {
cook() {
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut {}
BostonCream().cook();
SRC
end
def test_calling_superclass_methods
assert_interpreted <<~OUT.chomp, <<~SRC
Fry until golden brown.
Pipe full of custard and coat with chocolate.
OUT
class Doughnut {
cook() {
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut {
cook() {
super.cook();
print "Pipe full of custard and coat with chocolate.";
}
}
BostonCream().cook();
SRC
assert_interpreted "A method", <<~SRC
class A {
method() {
print "A method";
}
}
class B < A {
method() {
print "B method";
}
test() {
super.method();
}
}
class C < B {}
C().test();
SRC
end
def test_invalid_super
assert_raises Lox::ResolverError do
interpret(<<~SRC)
class Eclair {
cook() {
super.cook();
print "Pipe full of crème pâtissière.";
}
}
SRC
end
assert_raises Lox::ResolverError do
interpret("super.notEvenInAClass();")
end
end
private
def assert_interpreted(expected, src)
output = interpret(src)
assert_equal expected, output
end
def assert_evaluated(expected, src)
assert_interpreted(expected, "print #{src};")
end
def with_stdout
original = $stdout
$stdout = StringIO.new
yield
output = $stdout.string.chomp
$stdout = original
output
end
def interpret(src)
with_stdout {
stmts = Lox::Parser.new(@scanner.scan(src)).parse!
interpreter = Lox::Interpreter.new
resolver = Lox::Resolver.new(interpreter);
resolver.resolve(*stmts);
interpreter.interpret(stmts)
}
end
end