Created Parser class
parent
72b2edcf07
commit
8aa86a34ce
@ -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<Token>) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue