You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
911 lines
24 KiB
Kotlin
911 lines
24 KiB
Kotlin
package fr.celticinfo.lox
|
|
|
|
import fr.celticinfo.loxext.RpnPrinter
|
|
import org.junit.jupiter.api.Assertions
|
|
import org.junit.jupiter.api.assertDoesNotThrow
|
|
import java.io.ByteArrayOutputStream
|
|
import java.io.PrintStream
|
|
import kotlin.test.*
|
|
import kotlin.test.Test
|
|
|
|
class InterpreterTest {
|
|
|
|
@BeforeTest
|
|
fun setUp() {
|
|
Lox.resetError()
|
|
}
|
|
|
|
@Test
|
|
fun `validate interpreter`() {
|
|
val code = """
|
|
1 + 2 * 3 - 4 / 5;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(1, statements.size)
|
|
val stmt = statements.first()
|
|
assertTrue(stmt is Expression)
|
|
val expr = stmt.expression
|
|
assertEquals("1.0 2.0 3.0 * + 4.0 5.0 / -", RpnPrinter().print(expr))
|
|
}
|
|
|
|
@Test
|
|
fun `Division by zero should raise error`() {
|
|
val code = """
|
|
1 / 0;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(1, statements.size)
|
|
|
|
assertFailsWith<RuntimeError>(
|
|
block = {
|
|
Interpreter().interpret(statements)
|
|
}
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `Invalid type raise error`() {
|
|
val code = """
|
|
1 + false;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(1, statements.size)
|
|
|
|
assertFailsWith<RuntimeError>(
|
|
block = {
|
|
Interpreter().interpret(statements)
|
|
}
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `Shadowing a variable should not raise error`() {
|
|
val code = """
|
|
var a = 1;
|
|
{
|
|
var a = 2;
|
|
}
|
|
print a;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(3, statements.size)
|
|
|
|
assertDoesNotThrow {
|
|
Interpreter().interpret(statements)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Variable reassignment should not raise error`() {
|
|
val code = """
|
|
var a = 1;
|
|
a = 2;
|
|
print a;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(3, statements.size)
|
|
|
|
assertDoesNotThrow {
|
|
Interpreter().interpret(statements)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Variable reassignment with different type should not raise error`() {
|
|
val code = """
|
|
var a = 1;
|
|
a = false;
|
|
print a;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(3, statements.size)
|
|
|
|
assertDoesNotThrow {
|
|
Interpreter().interpret(statements)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Variable shadowing should not raise error`() {
|
|
val code = """
|
|
var a = 1;
|
|
{
|
|
var a = false;
|
|
print a;
|
|
}
|
|
print a;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(3, statements.size)
|
|
|
|
assertDoesNotThrow {
|
|
Interpreter().interpret(statements)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Variable shadowing with different type should not raise error`() {
|
|
val code = """
|
|
var a = 1;
|
|
{
|
|
var a = false;
|
|
print a;
|
|
}
|
|
print a;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(3, statements.size)
|
|
|
|
assertDoesNotThrow {
|
|
Interpreter().interpret(statements)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Variable shadowing should work with block`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
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;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(7, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("inner a\nouter b\nglobal c\nouter a\nouter b\nglobal c\nglobal a\nglobal b\nglobal c", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `If statement should work`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
if (1 < 2) print "true";
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(1, statements.size)
|
|
|
|
Interpreter().interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("true", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `If statement with else should work`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
if (1 > 2) print "true";
|
|
else print "false";
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(1, statements.size)
|
|
|
|
Interpreter().interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("false", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `If statement with else if should work`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
if (1 > 2) print "true";
|
|
else if (1 < 2) print "false";
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(1, statements.size)
|
|
|
|
Interpreter().interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("false", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `If statement with else if and else should work`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
if (1 > 2) print "true";
|
|
else if (1 < 2) print "false";
|
|
else print "else";
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(1, statements.size)
|
|
|
|
Interpreter().interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("false", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Fibonacci test`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
var a = 0;
|
|
var temp;
|
|
|
|
for (var b = 1; a < 10000; b = temp + b) {
|
|
print a;
|
|
temp = a;
|
|
a = b;
|
|
}
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(3, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("0\n" +
|
|
"1\n" +
|
|
"1\n" +
|
|
"2\n" +
|
|
"3\n" +
|
|
"5\n" +
|
|
"8\n" +
|
|
"13\n" +
|
|
"21\n" +
|
|
"34\n" +
|
|
"55\n" +
|
|
"89\n" +
|
|
"144\n" +
|
|
"233\n" +
|
|
"377\n" +
|
|
"610\n" +
|
|
"987\n" +
|
|
"1597\n" +
|
|
"2584\n" +
|
|
"4181\n" +
|
|
"6765", output)
|
|
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Clock native function should return a valid timestamp`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
print clock();
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
Assertions.assertEquals(1, statements.size)
|
|
|
|
Interpreter().interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
val timestamp = output.toDoubleOrNull()
|
|
Assertions.assertNotNull(timestamp)
|
|
Assertions.assertTrue(timestamp!! >= 0)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Function should work`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
fun sayHi(first, last) {
|
|
print "Hi, " + first + " " + last + "!";
|
|
}
|
|
|
|
sayHi("Dear", "Reader");
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("Hi, Dear Reader!", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Function should work with return statement`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
fun fib(n) {
|
|
if (n <= 1) return n;
|
|
return fib(n - 1) + fib(n - 2);
|
|
}
|
|
|
|
print fib(10);
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("55", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Function should work with return statement in block`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
fun fib(n) {
|
|
if (n <= 1) {
|
|
return n;
|
|
}
|
|
return fib(n - 1) + fib(n - 2);
|
|
}
|
|
|
|
print fib(10);
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("55", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Function should work with return statement in block with shadowing`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
fun fib(n) {
|
|
var a = 1;
|
|
if (n <= 1) {
|
|
return n;
|
|
}
|
|
return fib(n - 1) + fib(n - 2);
|
|
|
|
}
|
|
|
|
print fib(10);
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("55", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
|
|
@Test
|
|
fun `Closures should work`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
fun makeCounter() {
|
|
var i = 0;
|
|
fun count() {
|
|
i = i + 1;
|
|
print i;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
var counter = makeCounter();
|
|
counter();
|
|
counter();
|
|
counter();
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(5, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("1\n2\n3", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Closures should work with shadowing`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
fun makeCounter() {
|
|
var i = 0;
|
|
fun count() {
|
|
var i = 0;
|
|
i = i + 1;
|
|
print i;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
var counter = makeCounter();
|
|
counter();
|
|
counter();
|
|
counter();
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(5, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("1\n1\n1", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `Initiate a variable with the same name as a function should not work`() {
|
|
val code = """
|
|
fun a() {
|
|
return 1;
|
|
|
|
}
|
|
|
|
var a = a();
|
|
|
|
print a;
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(3, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
assert(Lox.hadError())
|
|
}
|
|
|
|
@Test
|
|
fun `Initiate a variable with the same name from an outer scope variable should not work`() {
|
|
val code = """
|
|
var a = 1;
|
|
{
|
|
var a = a;
|
|
print a;
|
|
}
|
|
"""
|
|
assert(!Lox.hadError())
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
assert(Lox.hadError())
|
|
}
|
|
|
|
@Test
|
|
fun `Initiate a variable with the same name from an outer scope function should not work`() {
|
|
val code = """
|
|
fun a() {
|
|
return 1;
|
|
}
|
|
|
|
{
|
|
var a = a;
|
|
print a;
|
|
}
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
assert(Lox.hadError())
|
|
}
|
|
|
|
@Test
|
|
fun `Initiate a variable with the same name from an outer scope function should not work with shadowing`() {
|
|
val code = """
|
|
fun a() {
|
|
return 1;
|
|
}
|
|
|
|
{
|
|
fun a() {
|
|
return 2;
|
|
}
|
|
var a = a;
|
|
print a;
|
|
}
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
assert(Lox.hadError())
|
|
}
|
|
|
|
@Test
|
|
fun `Closures should work with shadowing and outer scope variables with the same name`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
var a = "global";
|
|
{
|
|
fun showA() {
|
|
print a;
|
|
}
|
|
|
|
showA();
|
|
var a = "block";
|
|
showA();
|
|
}
|
|
"""
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("global\nglobal", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `valid code with class declaration`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
class DevonshireCream {
|
|
serveOn() {
|
|
return "Scones";
|
|
}
|
|
}
|
|
|
|
print DevonshireCream;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("DevonshireCream", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `valid code with class declaration and instantiation`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
class DevonshireCream {}
|
|
var cream = DevonshireCream();
|
|
print cream;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(3, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("DevonshireCream instance", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `valid code with properties initialization and access`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
class DevonshireCream {
|
|
}
|
|
|
|
var cream = DevonshireCream();
|
|
cream.color = "White";
|
|
print cream.color;
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(4, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("White", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun `valid code with method`() {
|
|
val standardOut = System.out
|
|
val outputStreamCaptor = ByteArrayOutputStream()
|
|
|
|
System.setOut(PrintStream(outputStreamCaptor))
|
|
|
|
try {
|
|
val code = """
|
|
class Bacon {
|
|
eat() {
|
|
print "Crunch crunch crunch!";
|
|
}
|
|
}
|
|
|
|
Bacon().eat();
|
|
""".trimIndent()
|
|
val scanner = Scanner(code)
|
|
val tokens = scanner.scanTokens()
|
|
val parser = Parser(tokens)
|
|
val statements = parser.parse()
|
|
assertEquals(2, statements.size)
|
|
|
|
val interpreter = Interpreter()
|
|
|
|
val resolver = Resolver(interpreter)
|
|
resolver.resolve(statements)
|
|
|
|
interpreter.interpret(statements)
|
|
val output = outputStreamCaptor.toString().trim()
|
|
assertEquals("Crunch crunch crunch!", output)
|
|
} finally {
|
|
System.setOut(standardOut)
|
|
}
|
|
}}
|