feat(parser): add recursive descent parser
This commit is contained in:
1 parent
7660141d69
commit
0cf76fb6a2
1 file changed
+410
@@ -0,0 +1,410 @@
|
|||||||
|
package lumen.parser
|
||||||
|
|
||||||
|
import lumen.ParseError
|
||||||
|
import lumen.ast.*
|
||||||
|
import lumen.lexer.Token
|
||||||
|
import lumen.lexer.TokenType
|
||||||
|
|
||||||
|
class Parser(private val tokens: List<Token>) {
|
||||||
|
private var current = 0
|
||||||
|
|
||||||
|
fun parseProgram(): List<Stmt> {
|
||||||
|
val statements = mutableListOf<Stmt>()
|
||||||
|
skipSeparators()
|
||||||
|
while (!check(TokenType.Eof)) {
|
||||||
|
statements.add(statement())
|
||||||
|
skipSeparators()
|
||||||
|
}
|
||||||
|
return statements
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun statement(): Stmt = when (peek().type) {
|
||||||
|
TokenType.Let -> declaration(mutable = false)
|
||||||
|
TokenType.Var -> declaration(mutable = true)
|
||||||
|
TokenType.Import -> importStatement()
|
||||||
|
TokenType.Fn -> if (peekNext().type == TokenType.Identifier) functionStatement() else expressionStatement()
|
||||||
|
TokenType.Return -> returnStatement()
|
||||||
|
TokenType.If -> ifStatement()
|
||||||
|
TokenType.While -> whileStatement()
|
||||||
|
TokenType.For -> forStatement()
|
||||||
|
else -> expressionStatement()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun declaration(mutable: Boolean): Stmt {
|
||||||
|
val keyword = advance()
|
||||||
|
val name = consume(TokenType.Identifier, "expected variable name").lexeme
|
||||||
|
consume(TokenType.Assign, "expected '=' in declaration")
|
||||||
|
val initializer = expression()
|
||||||
|
return LetStmt(name, initializer, mutable, keyword.line, keyword.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importStatement(): Stmt {
|
||||||
|
val keyword = advance()
|
||||||
|
val path = consume(TokenType.StringLiteral, "expected module path string after 'import'")
|
||||||
|
return ImportStmt(path.stringValue, keyword.line, keyword.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun functionStatement(): Stmt {
|
||||||
|
val keyword = advance()
|
||||||
|
val name = consume(TokenType.Identifier, "expected function name").lexeme
|
||||||
|
val params = parameters()
|
||||||
|
val body = block()
|
||||||
|
return FunctionStmt(name, FunctionExpr(name, params, body, keyword.line, keyword.column), keyword.line, keyword.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnStatement(): Stmt {
|
||||||
|
val keyword = advance()
|
||||||
|
val value = if (isStatementEnd()) null else expression()
|
||||||
|
return ReturnStmt(value, keyword.line, keyword.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ifStatement(): Stmt {
|
||||||
|
val keyword = advance()
|
||||||
|
consume(TokenType.LeftParen, "expected '(' after 'if'")
|
||||||
|
val condition = expression()
|
||||||
|
consume(TokenType.RightParen, "expected ')' after condition")
|
||||||
|
val thenBranch = block()
|
||||||
|
var elseBranch: List<Stmt>? = null
|
||||||
|
if (matchElse()) {
|
||||||
|
elseBranch = if (check(TokenType.If)) listOf(ifStatement()) else block()
|
||||||
|
}
|
||||||
|
return IfStmt(condition, thenBranch, elseBranch, keyword.line, keyword.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun whileStatement(): Stmt {
|
||||||
|
val keyword = advance()
|
||||||
|
consume(TokenType.LeftParen, "expected '(' after 'while'")
|
||||||
|
val condition = expression()
|
||||||
|
consume(TokenType.RightParen, "expected ')' after condition")
|
||||||
|
val body = block()
|
||||||
|
return WhileStmt(condition, body, keyword.line, keyword.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun forStatement(): Stmt {
|
||||||
|
val keyword = advance()
|
||||||
|
consume(TokenType.LeftParen, "expected '(' after 'for'")
|
||||||
|
val name = consume(TokenType.Identifier, "expected loop variable name").lexeme
|
||||||
|
consume(TokenType.In, "expected 'in' in for loop")
|
||||||
|
val iterable = expression()
|
||||||
|
consume(TokenType.RightParen, "expected ')' after for header")
|
||||||
|
val body = block()
|
||||||
|
return ForStmt(name, iterable, body, keyword.line, keyword.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expressionStatement(): Stmt {
|
||||||
|
val expr = expression()
|
||||||
|
return ExprStmt(expr, expr.line, expr.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun block(): List<Stmt> {
|
||||||
|
consume(TokenType.LeftBrace, "expected '{'")
|
||||||
|
val statements = mutableListOf<Stmt>()
|
||||||
|
skipSeparators()
|
||||||
|
while (!check(TokenType.RightBrace) && !check(TokenType.Eof)) {
|
||||||
|
statements.add(statement())
|
||||||
|
skipSeparators()
|
||||||
|
}
|
||||||
|
consume(TokenType.RightBrace, "expected '}' to close block")
|
||||||
|
return statements
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parameters(): List<String> {
|
||||||
|
consume(TokenType.LeftParen, "expected '(' after function name")
|
||||||
|
val params = mutableListOf<String>()
|
||||||
|
if (!check(TokenType.RightParen)) {
|
||||||
|
do {
|
||||||
|
params.add(consume(TokenType.Identifier, "expected parameter name").lexeme)
|
||||||
|
} while (match(TokenType.Comma))
|
||||||
|
}
|
||||||
|
consume(TokenType.RightParen, "expected ')' after parameters")
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expression(): Expr = assignment()
|
||||||
|
|
||||||
|
private fun assignment(): Expr {
|
||||||
|
val target = or()
|
||||||
|
if (check(TokenType.Assign)) {
|
||||||
|
val op = advance()
|
||||||
|
val value = assignment()
|
||||||
|
return when (target) {
|
||||||
|
is Variable -> Assign(target.name, value, target.line, target.column)
|
||||||
|
is Index -> IndexAssign(target.target, target.index, value, target.line, target.column)
|
||||||
|
else -> throw ParseError("invalid assignment target", op.line, op.column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun or(): Expr {
|
||||||
|
var left = and()
|
||||||
|
while (check(TokenType.Or)) {
|
||||||
|
val op = advance()
|
||||||
|
val right = and()
|
||||||
|
left = Logical(LogicalOp.Or, left, right, op.line, op.column)
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun and(): Expr {
|
||||||
|
var left = equality()
|
||||||
|
while (check(TokenType.And)) {
|
||||||
|
val op = advance()
|
||||||
|
val right = equality()
|
||||||
|
left = Logical(LogicalOp.And, left, right, op.line, op.column)
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun equality(): Expr {
|
||||||
|
var left = comparison()
|
||||||
|
while (check(TokenType.EqualEqual) || check(TokenType.BangEqual)) {
|
||||||
|
val token = advance()
|
||||||
|
val op = if (token.type == TokenType.EqualEqual) BinaryOp.Equal else BinaryOp.NotEqual
|
||||||
|
val right = comparison()
|
||||||
|
left = Binary(op, left, right, token.line, token.column)
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun comparison(): Expr {
|
||||||
|
var left = term()
|
||||||
|
while (true) {
|
||||||
|
val op = when (peek().type) {
|
||||||
|
TokenType.Less -> BinaryOp.Less
|
||||||
|
TokenType.LessEqual -> BinaryOp.LessEqual
|
||||||
|
TokenType.Greater -> BinaryOp.Greater
|
||||||
|
TokenType.GreaterEqual -> BinaryOp.GreaterEqual
|
||||||
|
else -> break
|
||||||
|
}
|
||||||
|
val token = advance()
|
||||||
|
val right = term()
|
||||||
|
left = Binary(op, left, right, token.line, token.column)
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun term(): Expr {
|
||||||
|
var left = factor()
|
||||||
|
while (true) {
|
||||||
|
val op = when (peek().type) {
|
||||||
|
TokenType.Plus -> BinaryOp.Add
|
||||||
|
TokenType.Minus -> BinaryOp.Sub
|
||||||
|
else -> break
|
||||||
|
}
|
||||||
|
val token = advance()
|
||||||
|
val right = factor()
|
||||||
|
left = Binary(op, left, right, token.line, token.column)
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun factor(): Expr {
|
||||||
|
var left = unary()
|
||||||
|
while (true) {
|
||||||
|
val op = when (peek().type) {
|
||||||
|
TokenType.Star -> BinaryOp.Mul
|
||||||
|
TokenType.Slash -> BinaryOp.Div
|
||||||
|
TokenType.Percent -> BinaryOp.Mod
|
||||||
|
else -> break
|
||||||
|
}
|
||||||
|
val token = advance()
|
||||||
|
val right = unary()
|
||||||
|
left = Binary(op, left, right, token.line, token.column)
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unary(): Expr {
|
||||||
|
if (check(TokenType.Minus)) {
|
||||||
|
val op = advance()
|
||||||
|
return Unary(UnaryOp.Negate, unary(), op.line, op.column)
|
||||||
|
}
|
||||||
|
if (check(TokenType.Not)) {
|
||||||
|
val op = advance()
|
||||||
|
return Unary(UnaryOp.Not, unary(), op.line, op.column)
|
||||||
|
}
|
||||||
|
return callChain()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun callChain(): Expr {
|
||||||
|
var expr = primary()
|
||||||
|
while (true) {
|
||||||
|
when (peek().type) {
|
||||||
|
TokenType.LeftParen -> {
|
||||||
|
advance()
|
||||||
|
expr = Call(expr, argumentList(), expr.line, expr.column)
|
||||||
|
}
|
||||||
|
TokenType.LeftBracket -> {
|
||||||
|
advance()
|
||||||
|
val index = expression()
|
||||||
|
consume(TokenType.RightBracket, "expected ']' after index")
|
||||||
|
expr = Index(expr, index, expr.line, expr.column)
|
||||||
|
}
|
||||||
|
TokenType.Dot -> {
|
||||||
|
advance()
|
||||||
|
val name = consume(TokenType.Identifier, "expected method name after '.'")
|
||||||
|
consume(TokenType.LeftParen, "expected '(' after method name")
|
||||||
|
expr = MethodCall(expr, name.lexeme, argumentList(), name.line, name.column)
|
||||||
|
}
|
||||||
|
else -> break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun argumentList(): List<Expr> {
|
||||||
|
val args = mutableListOf<Expr>()
|
||||||
|
skipNewlines()
|
||||||
|
if (!check(TokenType.RightParen)) {
|
||||||
|
do {
|
||||||
|
skipNewlines()
|
||||||
|
args.add(expression())
|
||||||
|
skipNewlines()
|
||||||
|
} while (match(TokenType.Comma))
|
||||||
|
}
|
||||||
|
skipNewlines()
|
||||||
|
consume(TokenType.RightParen, "expected ')' after arguments")
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun primary(): Expr {
|
||||||
|
val token = peek()
|
||||||
|
return when (token.type) {
|
||||||
|
TokenType.IntLiteral -> {
|
||||||
|
advance()
|
||||||
|
IntLiteral(token.intValue, token.line, token.column)
|
||||||
|
}
|
||||||
|
TokenType.DoubleLiteral -> {
|
||||||
|
advance()
|
||||||
|
DoubleLiteral(token.doubleValue, token.line, token.column)
|
||||||
|
}
|
||||||
|
TokenType.StringLiteral -> {
|
||||||
|
advance()
|
||||||
|
StringLiteral(token.stringValue, token.line, token.column)
|
||||||
|
}
|
||||||
|
TokenType.True -> {
|
||||||
|
advance()
|
||||||
|
BoolLiteral(true, token.line, token.column)
|
||||||
|
}
|
||||||
|
TokenType.False -> {
|
||||||
|
advance()
|
||||||
|
BoolLiteral(false, token.line, token.column)
|
||||||
|
}
|
||||||
|
TokenType.Nil -> {
|
||||||
|
advance()
|
||||||
|
NilLiteral(token.line, token.column)
|
||||||
|
}
|
||||||
|
TokenType.Identifier -> {
|
||||||
|
advance()
|
||||||
|
Variable(token.lexeme, token.line, token.column)
|
||||||
|
}
|
||||||
|
TokenType.LeftParen -> {
|
||||||
|
advance()
|
||||||
|
val inner = expression()
|
||||||
|
consume(TokenType.RightParen, "expected ')' after expression")
|
||||||
|
inner
|
||||||
|
}
|
||||||
|
TokenType.LeftBracket -> listLiteral()
|
||||||
|
TokenType.LeftBrace -> mapLiteral()
|
||||||
|
TokenType.Fn -> functionExpression()
|
||||||
|
else -> throw ParseError("unexpected ${describe(token)}", token.line, token.column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun listLiteral(): Expr {
|
||||||
|
val open = advance()
|
||||||
|
val elements = mutableListOf<Expr>()
|
||||||
|
skipNewlines()
|
||||||
|
if (!check(TokenType.RightBracket)) {
|
||||||
|
do {
|
||||||
|
skipNewlines()
|
||||||
|
if (check(TokenType.RightBracket)) break
|
||||||
|
elements.add(expression())
|
||||||
|
skipNewlines()
|
||||||
|
} while (match(TokenType.Comma))
|
||||||
|
}
|
||||||
|
skipNewlines()
|
||||||
|
consume(TokenType.RightBracket, "expected ']' after list")
|
||||||
|
return ListLiteral(elements, open.line, open.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapLiteral(): Expr {
|
||||||
|
val open = advance()
|
||||||
|
val entries = mutableListOf<Pair<Expr, Expr>>()
|
||||||
|
skipNewlines()
|
||||||
|
if (!check(TokenType.RightBrace)) {
|
||||||
|
do {
|
||||||
|
skipNewlines()
|
||||||
|
if (check(TokenType.RightBrace)) break
|
||||||
|
val key = expression()
|
||||||
|
consume(TokenType.Colon, "expected ':' in map entry")
|
||||||
|
val value = expression()
|
||||||
|
entries.add(key to value)
|
||||||
|
skipNewlines()
|
||||||
|
} while (match(TokenType.Comma))
|
||||||
|
}
|
||||||
|
skipNewlines()
|
||||||
|
consume(TokenType.RightBrace, "expected '}' after map")
|
||||||
|
return MapLiteral(entries, open.line, open.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun functionExpression(): Expr {
|
||||||
|
val keyword = advance()
|
||||||
|
val name = if (check(TokenType.Identifier)) advance().lexeme else null
|
||||||
|
val params = parameters()
|
||||||
|
val body = block()
|
||||||
|
return FunctionExpr(name, params, body, keyword.line, keyword.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isStatementEnd(): Boolean =
|
||||||
|
check(TokenType.Newline) || check(TokenType.Semicolon) ||
|
||||||
|
check(TokenType.RightBrace) || check(TokenType.Eof)
|
||||||
|
|
||||||
|
private fun matchElse(): Boolean {
|
||||||
|
val saved = current
|
||||||
|
skipNewlines()
|
||||||
|
if (check(TokenType.Else)) {
|
||||||
|
advance()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
current = saved
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun skipSeparators() {
|
||||||
|
while (check(TokenType.Newline) || check(TokenType.Semicolon)) advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun skipNewlines() {
|
||||||
|
while (check(TokenType.Newline)) advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun match(type: TokenType): Boolean {
|
||||||
|
if (check(type)) {
|
||||||
|
advance()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun consume(type: TokenType, message: String): Token {
|
||||||
|
if (check(type)) return advance()
|
||||||
|
val found = peek()
|
||||||
|
throw ParseError("$message, got ${describe(found)}", found.line, found.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun describe(token: Token): String = when (token.type) {
|
||||||
|
TokenType.Newline -> "end of line"
|
||||||
|
TokenType.Eof -> "end of input"
|
||||||
|
else -> "'${token.lexeme.ifEmpty { token.type.name }}'"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun check(type: TokenType): Boolean = peek().type == type
|
||||||
|
|
||||||
|
private fun advance(): Token = tokens[current++]
|
||||||
|
|
||||||
|
private fun peek(): Token = tokens[current]
|
||||||
|
|
||||||
|
private fun peekNext(): Token = if (current + 1 < tokens.size) tokens[current + 1] else tokens.last()
|
||||||
|
}
|
||||||
Reference in new issue
Block a user