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