Implemented Blocks and Assignments

main
oabrivard 2 years ago
parent 72f48b75bb
commit 16e28c07b5

@ -40,15 +40,19 @@ fun defineAst(baseName: String, types: List<String>) {
}
val exprTypes = listOf(
"Assign : Token name, Expr value",
"Binary : Expr left, Token operator, Expr right",
"Grouping : Expr expression",
"Literal : Any? value",
"Unary : Token operator, Expr right"
"Unary : Token operator, Expr right",
"Variable : Token name"
)
defineAst("Expr", exprTypes)
val stmtTypes = listOf(
"Block : List<Stmt?> statements",
"Expression : Expr expression",
"Print : Expr expression"
"Print : Expr expression",
"Var : Token name, Expr? initializer"
)
defineAst("Stmt", stmtTypes)

@ -5,20 +5,28 @@ class AstPrinter : ExprVisitor<String> {
return expr.accept(this)
}
override fun visitBinary(binary: Binary): String {
return parenthesize(binary.operator.lexeme, binary.left, binary.right)
override fun visitAssign(expr: Assign): String {
return parenthesize("=", expr)
}
override fun visitGrouping(grouping: Grouping): String {
return parenthesize("group", grouping.expression)
override fun visitBinary(expr: Binary): String {
return parenthesize(expr.operator.lexeme, expr.left, expr.right)
}
override fun visitLiteral(literal: Literal): String {
return literal.value?.toString() ?: "nil"
override fun visitGrouping(expr: Grouping): String {
return parenthesize("group", expr.expression)
}
override fun visitUnary(unary: Unary): String {
return parenthesize(unary.operator.lexeme, unary.right)
override fun visitLiteral(expr: Literal): String {
return expr.value?.toString() ?: "nil"
}
override fun visitUnary(expr: Unary): String {
return parenthesize(expr.operator.lexeme, expr.right)
}
override fun visitVariable(expr: Variable): String {
return expr.name.lexeme
}
private fun parenthesize(name: String, vararg exprs: Expr): String {

@ -0,0 +1,47 @@
package fr.celticinfo.lox
/**
* The Environment class is used to store the variables and their values.
*/
class Environment {
private val enclosing: Environment?
private val values = mutableMapOf<String, Any?>()
constructor() {
enclosing = null
}
constructor(enclosing: Environment) {
this.enclosing = enclosing
}
fun define(name: String, value: Any?) {
values[name] = value
}
fun get(name: Token): Any? {
if (values.containsKey(name.lexeme)) {
return values[name.lexeme]
}
if (enclosing != null) {
return enclosing.get(name)
}
throw RuntimeError(name, "Undefined variable '${name.lexeme}'.")
}
fun assign(name: Token, value: Any?) {
if (values.containsKey(name.lexeme)) {
values[name.lexeme] = value
return
}
if (enclosing != null) {
enclosing.assign(name, value)
return
}
throw RuntimeError(name, "Undefined variable '${name.lexeme}'.")
}
}

@ -4,10 +4,12 @@ package fr.celticinfo.lox
* The ExprVisitor interface is used to visit the different types of expressions that can be parsed by the Parser.
*/
interface ExprVisitor<R> {
fun visitAssign(expr: Assign): R
fun visitBinary(expr: Binary): R
fun visitGrouping(expr: Grouping): R
fun visitLiteral(expr: Literal): R
fun visitUnary(expr: Unary): R
fun visitVariable(expr: Variable): R
}
/**
@ -17,6 +19,15 @@ sealed class Expr {
abstract fun <R> accept(visitor: ExprVisitor<R>): R
}
data class Assign(
val name: Token,
val value: Expr
) : Expr() {
override fun <R> accept(visitor: ExprVisitor<R>): R {
return visitor.visitAssign(this)
}
}
data class Binary(
val left: Expr, val operator: Token, val right: Expr
) : Expr() {
@ -48,3 +59,11 @@ data class Unary(
return visitor.visitUnary(this)
}
}
data class Variable(
val name: Token
) : Expr() {
override fun <R> accept(visitor: ExprVisitor<R>): R {
return visitor.visitVariable(this)
}
}

@ -3,8 +3,9 @@ package fr.celticinfo.lox
import fr.celticinfo.lox.TokenType.*
class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
private var environment = Environment()
fun interpret(statements: List<Stmt>) {
fun interpret(statements: List<Stmt?>) {
try {
for (statement in statements) {
execute(statement)
@ -15,12 +16,28 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
}
}
private fun execute(stmt: Stmt) {
stmt.accept(this)
private fun execute(stmt: Stmt?) {
stmt?.accept(this)
}
private fun evaluate(expr: Expr): Any? {
return expr.accept(this)
override fun visitBlock(stmt: Block) {
executeBlock(stmt.statements, Environment(environment))
}
private fun executeBlock(statements: List<Stmt?>, environment: Environment) {
val previous = this.environment
try {
this.environment = environment
for (statement in statements) {
execute(statement)
}
} finally {
this.environment = previous
}
}
private fun evaluate(expr: Expr?): Any? {
return expr?.accept(this)
}
override fun visitExpression(stmt: Expression) {
@ -32,6 +49,17 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
println(stringify(value))
}
override fun visitVar(stmt: Var) {
val value = evaluate(stmt.initializer)
environment.define(stmt.name.lexeme, value)
}
override fun visitAssign(expr: Assign): Any? {
val value = evaluate(expr.value)
environment.assign(expr.name, value)
return value
}
override fun visitBinary(expr: Binary): Any? {
val left = evaluate(expr.left)
val right = evaluate(expr.right)
@ -104,6 +132,10 @@ class Interpreter: ExprVisitor<Any?>, StmtVisitor<Unit>{
}
}
override fun visitVariable(expr: Variable): Any? {
return environment.get(expr.name)
}
private fun isTruthy(obj: Any?): Boolean {
return when (obj) {
null -> false

@ -10,18 +10,44 @@ import fr.celticinfo.lox.TokenType.*
class Parser(private val tokens: List<Token>) {
private var current = 0
fun parse(): List<Stmt> {
val statements: MutableList<Stmt> = ArrayList()
fun parse(): List<Stmt?> {
val statements: MutableList<Stmt?> = ArrayList()
while (!isAtEnd()) {
statements.add(statement())
statements.add(declaration())
}
return statements
}
private fun declaration(): Stmt? {
return try {
when {
match(VAR) -> varDeclaration()
else -> statement()
}
} catch (error: ParseError) {
synchronize()
return null
}
}
private fun varDeclaration(): Stmt {
val name = consume(IDENTIFIER, "Expect variable name.")
var initializer: Expr? = null
if (match(EQUAL)) {
initializer = expression()
}
consume(SEMICOLON, "Expect ';' after variable declaration.")
return Var(name, initializer)
}
private fun statement(): Stmt {
return when {
match(PRINT) -> printStatement()
match(LEFT_BRACE) -> Block(blockStatement())
else -> expressionStatement()
}
}
@ -34,12 +60,41 @@ class Parser(private val tokens: List<Token>) {
private fun expressionStatement(): Stmt {
val value = expression()
consume(SEMICOLON, "Expect ';' after value.")
consume(SEMICOLON, "Expect ';' after expression.")
return Expression(value)
}
private fun blockStatement(): List<Stmt?> {
val statements: MutableList<Stmt?> = ArrayList()
while (!check(RIGHT_BRACE) && !isAtEnd()) {
statements.add(declaration())
}
consume(RIGHT_BRACE, "Expect '}' after block.")
return statements
}
private fun expression(): Expr {
return equality()
return assignment()
}
private fun assignment(): Expr {
val expr = equality()
if (match(EQUAL)) {
val equals = previous()
val value = assignment()
if (expr is Variable) {
val name = expr.name
return Assign(name, value)
}
error(equals, "Invalid assignment target.")
}
return expr
}
private fun equality(): Expr {
@ -116,6 +171,7 @@ class Parser(private val tokens: List<Token>) {
match(TRUE) -> return Literal(true)
match(NIL) -> return Literal(null)
match(NUMBER, STRING) -> return Literal(previous().literal)
match(IDENTIFIER) -> return Variable(previous())
match(LEFT_PAREN) -> {
val expr = expression()
consume(RIGHT_PAREN, "Expect ')' after expression.")

@ -4,8 +4,10 @@ package fr.celticinfo.lox
* The StmtVisitor interface is used to visit the different types of statements that can be parsed by the Parser.
*/
interface StmtVisitor<R> {
fun visitBlock(stmt: Block): R
fun visitExpression(stmt: Expression): R
fun visitPrint(stmt: Print): R
fun visitVar(stmt: Var): R
}
/**
@ -15,6 +17,14 @@ sealed class Stmt {
abstract fun <R> accept(visitor: StmtVisitor<R>): R
}
data class Block(
val statements: List<Stmt?>
) : Stmt() {
override fun <R> accept(visitor: StmtVisitor<R>): R {
return visitor.visitBlock(this)
}
}
data class Expression(
val expression: Expr
) : Stmt() {
@ -30,3 +40,12 @@ data class Print(
return visitor.visitPrint(this)
}
}
data class Var(
val name: Token,
val initializer: Expr?
) : Stmt() {
override fun <R> accept(visitor: StmtVisitor<R>): R {
return visitor.visitVar(this)
}
}

@ -7,20 +7,28 @@ class RpnPrinter : ExprVisitor<String> {
return expr.accept(this)
}
override fun visitBinary(binary: Binary): String {
return stack(binary.operator.lexeme, binary.left, binary.right)
override fun visitAssign(expr: Assign): String {
return stack("=", expr)
}
override fun visitGrouping(grouping: Grouping): String {
return stack("", grouping.expression)
override fun visitBinary(expr: Binary): String {
return stack(expr.operator.lexeme, expr.left, expr.right)
}
override fun visitLiteral(literal: Literal): String {
return literal.value?.toString() ?: "nil"
override fun visitGrouping(expr: Grouping): String {
return stack("", expr.expression)
}
override fun visitUnary(unary: Unary): String {
return stack(unary.operator.lexeme, unary.right)
override fun visitLiteral(expr: Literal): String {
return expr.value?.toString() ?: "nil"
}
override fun visitUnary(expr: Unary): String {
return stack(expr.operator.lexeme, expr.right)
}
override fun visitVariable(expr: Variable): String {
return expr.name.lexeme
}
private fun stack(name: String, vararg exprs: Expr): String {

@ -1,8 +1,11 @@
package fr.celticinfo.lox
import fr.celticinfo.loxext.RpnPrinter
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import kotlin.test.*
import kotlin.test.Test
class InterpreterTest {
@ -57,4 +60,145 @@ class InterpreterTest {
}
)
}
@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)
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)
}
}
}

@ -1,8 +1,9 @@
package fr.celticinfo.lox
import fr.celticinfo.loxext.RpnPrinter
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import kotlin.test.*
import kotlin.test.Test
class ParserTest {
@Test
@ -29,12 +30,10 @@ class ParserTest {
val scanner = Scanner(code)
val tokens = scanner.scanTokens()
val parser = Parser(tokens)
assertFailsWith<Parser.ParseError>(
block = {
parser.parse()
}
)
val statements = parser.parse()
assertEquals(1,statements.size)
val stmt = statements.first()
assertNull(stmt)
}
@Test
@ -45,11 +44,86 @@ class ParserTest {
val scanner = Scanner(code)
val tokens = scanner.scanTokens()
val parser = Parser(tokens)
val statements = parser.parse()
assertEquals(1,statements.size)
val stmt = statements.first()
assertNull(stmt)
}
@Test
fun `valid code with multiple statements`() {
val code = """
var a = 1;
var b = 2;
print a + b;
""".trimIndent()
val scanner = Scanner(code)
val tokens = scanner.scanTokens()
val parser = Parser(tokens)
val statements = parser.parse()
assertEquals(3,statements.size)
assertAll(
{ assertTrue(statements[0] is Var) },
{ assertTrue(statements[1] is Var) },
{ assertTrue(statements[2] is Print) }
)
}
assertFailsWith<Parser.ParseError>(
block = {
parser.parse()
@Test
fun `valid code with block`() {
val code = """
{
var a = 1;
var b = 2;
print a + b;
}
""".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 Block)
val block = stmt.statements
assertEquals(3,block.size)
assertAll(
{ assertTrue(block[0] is Var) },
{ assertTrue(block[1] is Var) },
{ assertTrue(block[2] is Print) }
)
}
@Test
fun `valid code with block and nested block`() {
val code = """
{
var a = 1;
{
var b = 2;
print a + b;
}
}
""".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 Block)
val block = stmt.statements
assertEquals(2,block.size)
assertAll(
{ assertTrue(block[0] is Var) },
{ assertTrue(block[1] is Block) }
)
val nestedBlock = block[1] as Block
val nestedStatements = nestedBlock.statements
assertEquals(2,nestedStatements.size)
assertAll(
{ assertTrue(nestedStatements[0] is Var) },
{ assertTrue(nestedStatements[1] is Print) }
)
}
}
Loading…
Cancel
Save