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

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)
}
}}