From 8aa86a34cefa5658f38e371a3f812e3a38a3e827 Mon Sep 17 00:00:00 2001 From: oabrivard Date: Mon, 1 Jul 2024 14:50:01 +0200 Subject: [PATCH] Created Parser class --- src/main/fr/celticinfo/lox/Lox.kt | 18 ++- src/main/fr/celticinfo/lox/Parser.kt | 151 +++++++++++++++++++++++ src/test/fr/celticinfo/lox/ParserTest.kt | 32 +++++ 3 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 src/main/fr/celticinfo/lox/Parser.kt create mode 100644 src/test/fr/celticinfo/lox/ParserTest.kt diff --git a/src/main/fr/celticinfo/lox/Lox.kt b/src/main/fr/celticinfo/lox/Lox.kt index 62b00be..63d3719 100644 --- a/src/main/fr/celticinfo/lox/Lox.kt +++ b/src/main/fr/celticinfo/lox/Lox.kt @@ -38,9 +38,13 @@ class Lox { private fun run(source: String) { val scanner = Scanner(source) val tokens = scanner.scanTokens() - for (token in tokens) { - println(token) - } + val parser = Parser(tokens) + val expression = parser.parse() + + // Stop if there was a syntax error. + if (hadError) return + + println(AstPrinter().print(expression!!)) } companion object { @@ -48,6 +52,14 @@ class Lox { report(line, "", s) } + fun error(token: Token, message: String) { + if (token.type == TokenType.EOF) { + report(token.line, " at end", message) + } else { + report(token.line, " at '${token.lexeme}'", message) + } + } + private fun report(line: Int, where: String, message: String) { System.err.println("[line $line] Error$where: $message") } diff --git a/src/main/fr/celticinfo/lox/Parser.kt b/src/main/fr/celticinfo/lox/Parser.kt new file mode 100644 index 0000000..c8abc4c --- /dev/null +++ b/src/main/fr/celticinfo/lox/Parser.kt @@ -0,0 +1,151 @@ +package fr.celticinfo.lox + +import fr.celticinfo.lox.TokenType.* + +/** + * The Parser class is responsible for parsing the tokens produced by the Scanner into an abstract syntax tree (AST). + * It is a recursive descent parser that uses a series of methods to parse different parts of the grammar. + */ +class Parser(private val tokens: List) { + private var current = 0 + + fun parse(): Expr? { + return try { + expression() + } catch (error: ParseError) { + null + } + } + + private fun expression(): Expr { + return equality() + } + + private fun equality(): Expr { + var expr = comparison() + + while (match(BANG_EQUAL, EQUAL_EQUAL)) { + val operator = previous() + val right = comparison() + expr = Binary(expr, operator, right) + } + + return expr + } + + private fun comparison(): Expr { + var expr = term() + + while (match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)) { + val operator = previous() + val right = term() + expr = Binary(expr, operator, right) + } + + return expr + } + + private fun term(): Expr { + var expr = factor() + + while (match(MINUS, PLUS)) { + val operator = previous() + val right = factor() + expr = Binary(expr, operator, right) + } + + return expr + } + + private fun factor(): Expr { + var expr = unary() + + while (match(SLASH, STAR)) { + val operator = previous() + val right = unary() + expr = Binary(expr, operator, right) + } + + return expr + } + + private fun unary(): Expr { + if (match(BANG, MINUS)) { + val operator = previous() + val right = unary() + return Unary(operator, right) + } + + return primary() + } + + private fun match(vararg types: TokenType): Boolean { + for (type in types) { + if (check(type)) { + advance() + return true + } + } + return false + } + + private fun primary(): Expr { + when { + match(FALSE) -> return Literal(false) + match(TRUE) -> return Literal(true) + match(NIL) -> return Literal(null) + match(NUMBER, STRING) -> return Literal(previous().literal) + match(LEFT_PAREN) -> { + val expr = expression() + consume(RIGHT_PAREN, "Expect ')' after expression.") + return Grouping(expr) + } + else -> throw error(peek(), "Expect expression.") + } + } + + private fun check(type: TokenType): Boolean { + if (isAtEnd()) return false + return peek().type == type + } + + private fun advance(): Token { + if (!isAtEnd()) current++ + return previous() + } + + private fun isAtEnd(): Boolean { + return peek().type == EOF + } + + private fun peek(): Token { + return tokens[current] + } + + private fun previous(): Token { + return tokens[current - 1] + } + + private fun consume(type: TokenType, message: String): Token { + if (check(type)) return advance() + throw error(peek(), message) + } + + private fun error(token: Token, message: String): ParseError { + Lox.error(token, message) + return ParseError() + } + + private fun synchronize() { + advance() + while (!isAtEnd()) { + if (previous().type == SEMICOLON) return + when (peek().type) { + CLASS, FUN, VAR, FOR, IF, WHILE, PRINT, RETURN -> return + else -> advance() + } + } + } + + class ParseError: RuntimeException() +} \ No newline at end of file diff --git a/src/test/fr/celticinfo/lox/ParserTest.kt b/src/test/fr/celticinfo/lox/ParserTest.kt new file mode 100644 index 0000000..e663801 --- /dev/null +++ b/src/test/fr/celticinfo/lox/ParserTest.kt @@ -0,0 +1,32 @@ +package fr.celticinfo.lox + +import fr.celticinfo.loxext.RpnPrinter +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.* + +class ParserTest { + @Test + fun `validate parser`() { + val code = """ + 1 + 2 * 3 - 4 / 5 + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val expr = parser.parse() + assertNotNull(expr) + assertEquals("1.0 2.0 3.0 * + 4.0 5.0 / -", RpnPrinter().print(expr!!)) + } + + @Test + fun `invalid code generates parsing error`() { + val code = """ + 1 + (2 + """.trimIndent() + val scanner = Scanner(code) + val tokens = scanner.scanTokens() + val parser = Parser(tokens) + val expr = parser.parse() + assertNull(expr) + } +} \ No newline at end of file