diff --git a/src/main/kotlin/kestrel/sql/parser/Parser.kt b/src/main/kotlin/kestrel/sql/parser/Parser.kt new file mode 100644 index 0000000..aa132a3 --- /dev/null +++ b/src/main/kotlin/kestrel/sql/parser/Parser.kt @@ -0,0 +1,457 @@ +package kestrel.sql.parser + +import kestrel.ParseException +import kestrel.catalog.Column +import kestrel.sql.ast.Assignment +import kestrel.sql.ast.BeginStatement +import kestrel.sql.ast.BinaryExpression +import kestrel.sql.ast.BinaryOperator +import kestrel.sql.ast.ColumnExpression +import kestrel.sql.ast.CommitStatement +import kestrel.sql.ast.CreateIndexStatement +import kestrel.sql.ast.CreateTableStatement +import kestrel.sql.ast.DeleteStatement +import kestrel.sql.ast.DropIndexStatement +import kestrel.sql.ast.DropTableStatement +import kestrel.sql.ast.Expression +import kestrel.sql.ast.ExpressionProjection +import kestrel.sql.ast.ExplainStatement +import kestrel.sql.ast.FunctionExpression +import kestrel.sql.ast.InsertStatement +import kestrel.sql.ast.JoinClause +import kestrel.sql.ast.JoinType +import kestrel.sql.ast.LiteralExpression +import kestrel.sql.ast.OrderBy +import kestrel.sql.ast.Projection +import kestrel.sql.ast.RollbackStatement +import kestrel.sql.ast.SelectItem +import kestrel.sql.ast.SelectStatement +import kestrel.sql.ast.StarProjection +import kestrel.sql.ast.Statement +import kestrel.sql.ast.UnaryExpression +import kestrel.sql.ast.UnaryOperator +import kestrel.sql.ast.UpdateStatement +import kestrel.sql.lexer.Token +import kestrel.sql.lexer.TokenType +import kestrel.storage.ColumnType +import kestrel.storage.Value + +class Parser(private val tokens: List) { + private var position = 0 + + fun parseProgram(): List { + val statements = mutableListOf() + while (!check(TokenType.EndOfInput)) { + if (match(TokenType.Semicolon)) continue + statements.add(parseStatement()) + if (!check(TokenType.EndOfInput)) { + expect(TokenType.Semicolon, "expected ';' after statement") + } + } + return statements + } + + private fun parseStatement(): Statement { + val token = peek() + return when (token.type) { + TokenType.Create -> parseCreate() + TokenType.Insert -> parseInsert() + TokenType.Select -> parseSelect() + TokenType.Delete -> parseDelete() + TokenType.Update -> parseUpdate() + TokenType.Drop -> parseDrop() + TokenType.Explain -> parseExplain() + TokenType.Begin -> parseBegin() + TokenType.Commit -> parseCommit() + TokenType.Rollback -> parseRollback() + else -> throw error("expected a statement but found '${describe(token)}'", token) + } + } + + private fun parseCreate(): Statement { + expect(TokenType.Create, "expected CREATE") + return when { + check(TokenType.Table) -> parseCreateTable() + check(TokenType.Index) -> parseCreateIndex() + else -> throw error("expected TABLE or INDEX after CREATE but found '${describe(peek())}'", peek()) + } + } + + private fun parseCreateIndex(): CreateIndexStatement { + expect(TokenType.Index, "expected INDEX") + val indexName = expectIdentifier("expected index name") + expect(TokenType.On, "expected ON after index name") + val tableName = expectIdentifier("expected table name") + expect(TokenType.LeftParen, "expected '(' before indexed column") + val columnName = expectIdentifier("expected column name") + expect(TokenType.RightParen, "expected ')' after indexed column") + return CreateIndexStatement(indexName, tableName, columnName) + } + + private fun parseExplain(): ExplainStatement { + expect(TokenType.Explain, "expected EXPLAIN") + return ExplainStatement(parseSelect()) + } + + private fun parseBegin(): Statement { + expect(TokenType.Begin, "expected BEGIN") + match(TokenType.Transaction) + return BeginStatement + } + + private fun parseCommit(): Statement { + expect(TokenType.Commit, "expected COMMIT") + match(TokenType.Transaction) + return CommitStatement + } + + private fun parseRollback(): Statement { + expect(TokenType.Rollback, "expected ROLLBACK") + match(TokenType.Transaction) + return RollbackStatement + } + + private fun parseCreateTable(): CreateTableStatement { + expect(TokenType.Table, "expected TABLE after CREATE") + val tableName = expectIdentifier("expected table name") + expect(TokenType.LeftParen, "expected '(' after table name") + val columns = mutableListOf() + do { + columns.add(parseColumnDefinition()) + } while (match(TokenType.Comma)) + expect(TokenType.RightParen, "expected ')' after column definitions") + return CreateTableStatement(tableName, columns) + } + + private fun parseColumnDefinition(): Column { + val columnName = expectIdentifier("expected column name") + val typeToken = advance() + val type = when (typeToken.type) { + TokenType.Integer -> ColumnType.Integer + TokenType.Text -> ColumnType.Text + TokenType.Boolean -> ColumnType.Boolean + TokenType.Real -> ColumnType.Real + else -> throw error("expected a column type but found '${describe(typeToken)}'", typeToken) + } + var notNull = false + var primaryKey = false + while (true) { + when { + check(TokenType.Not) -> { + advance() + expect(TokenType.Null, "expected NULL after NOT") + notNull = true + } + check(TokenType.Primary) -> { + advance() + expect(TokenType.Key, "expected KEY after PRIMARY") + primaryKey = true + notNull = true + } + else -> break + } + } + return Column(columnName, type, notNull, primaryKey) + } + + private fun parseInsert(): InsertStatement { + expect(TokenType.Insert, "expected INSERT") + expect(TokenType.Into, "expected INTO after INSERT") + val tableName = expectIdentifier("expected table name") + var columns: List? = null + if (match(TokenType.LeftParen)) { + val names = mutableListOf() + do { + names.add(expectIdentifier("expected column name")) + } while (match(TokenType.Comma)) + expect(TokenType.RightParen, "expected ')' after column list") + columns = names + } + expect(TokenType.Values, "expected VALUES") + val rows = mutableListOf>() + do { + expect(TokenType.LeftParen, "expected '(' before value list") + val values = mutableListOf() + do { + values.add(parseExpression()) + } while (match(TokenType.Comma)) + expect(TokenType.RightParen, "expected ')' after value list") + rows.add(values) + } while (match(TokenType.Comma)) + return InsertStatement(tableName, columns, rows) + } + + private fun parseSelect(): SelectStatement { + expect(TokenType.Select, "expected SELECT") + val projection = parseProjection() + expect(TokenType.From, "expected FROM") + val (tableName, tableAlias) = parseTableRef() + val joins = parseJoins() + val where = if (match(TokenType.Where)) parseExpression() else null + val groupBy = parseGroupBy() + val having = if (match(TokenType.Having)) parseExpression() else null + val orderBy = parseOrderBy() + val limit = parseLimit() + return SelectStatement(projection, tableName, tableAlias, joins, where, groupBy, having, orderBy, limit) + } + + private fun parseProjection(): Projection { + if (match(TokenType.Star)) return StarProjection + val items = mutableListOf() + do { + val expression = parseExpression() + val alias = if (match(TokenType.As)) expectIdentifier("expected alias after AS") else null + items.add(SelectItem(expression, alias)) + } while (match(TokenType.Comma)) + return ExpressionProjection(items) + } + + private fun parseTableRef(): Pair { + val tableName = expectIdentifier("expected table name") + val alias = when { + match(TokenType.As) -> expectIdentifier("expected alias after AS") + check(TokenType.Identifier) -> advance().lexeme + else -> null + } + return tableName to alias + } + + private fun parseJoins(): List { + val joins = mutableListOf() + while (true) { + val type = when { + match(TokenType.Inner) -> { + expect(TokenType.Join, "expected JOIN after INNER"); JoinType.Inner + } + match(TokenType.Left) -> { + match(TokenType.Outer); expect(TokenType.Join, "expected JOIN after LEFT"); JoinType.Left + } + match(TokenType.Join) -> JoinType.Inner + else -> break + } + val (tableName, alias) = parseTableRef() + expect(TokenType.On, "expected ON after joined table") + joins.add(JoinClause(type, tableName, alias, parseExpression())) + } + return joins + } + + private fun parseGroupBy(): List { + if (!match(TokenType.Group)) return emptyList() + expect(TokenType.By, "expected BY after GROUP") + val expressions = mutableListOf() + do { + expressions.add(parseExpression()) + } while (match(TokenType.Comma)) + return expressions + } + + private fun parseOrderBy(): OrderBy? { + if (!match(TokenType.Order)) return null + expect(TokenType.By, "expected BY after ORDER") + val expression = parseExpression() + val descending = when { + match(TokenType.Desc) -> true + match(TokenType.Asc) -> false + else -> false + } + return OrderBy(expression, descending) + } + + private fun parseLimit(): Int? { + if (!match(TokenType.Limit)) return null + return expect(TokenType.IntLiteral, "expected an integer after LIMIT").intValue!!.toInt() + } + + private fun parseDelete(): DeleteStatement { + expect(TokenType.Delete, "expected DELETE") + expect(TokenType.From, "expected FROM after DELETE") + val tableName = expectIdentifier("expected table name") + val where = if (match(TokenType.Where)) parseExpression() else null + return DeleteStatement(tableName, where) + } + + private fun parseUpdate(): UpdateStatement { + expect(TokenType.Update, "expected UPDATE") + val tableName = expectIdentifier("expected table name") + expect(TokenType.Set, "expected SET after table name") + val assignments = mutableListOf() + do { + val columnName = expectIdentifier("expected column name") + expect(TokenType.Equal, "expected '=' in assignment") + assignments.add(Assignment(columnName, parseExpression())) + } while (match(TokenType.Comma)) + val where = if (match(TokenType.Where)) parseExpression() else null + return UpdateStatement(tableName, assignments, where) + } + + private fun parseDrop(): Statement { + expect(TokenType.Drop, "expected DROP") + return when { + match(TokenType.Table) -> DropTableStatement(expectIdentifier("expected table name")) + match(TokenType.Index) -> DropIndexStatement(expectIdentifier("expected index name")) + else -> throw error("expected TABLE or INDEX after DROP but found '${describe(peek())}'", peek()) + } + } + + private fun parseExpression(): Expression = parseOr() + + private fun parseOr(): Expression { + var left = parseAnd() + while (match(TokenType.Or)) { + left = BinaryExpression(BinaryOperator.Or, left, parseAnd()) + } + return left + } + + private fun parseAnd(): Expression { + var left = parseNot() + while (match(TokenType.And)) { + left = BinaryExpression(BinaryOperator.And, left, parseNot()) + } + return left + } + + private fun parseNot(): Expression { + if (match(TokenType.Not)) { + return UnaryExpression(UnaryOperator.Not, parseNot()) + } + return parseComparison() + } + + private fun parseComparison(): Expression { + var left = parseAdditive() + while (true) { + val operator = when { + check(TokenType.Equal) -> BinaryOperator.Equal + check(TokenType.NotEqual) -> BinaryOperator.NotEqual + check(TokenType.Less) -> BinaryOperator.Less + check(TokenType.LessEqual) -> BinaryOperator.LessEqual + check(TokenType.Greater) -> BinaryOperator.Greater + check(TokenType.GreaterEqual) -> BinaryOperator.GreaterEqual + else -> break + } + advance() + left = BinaryExpression(operator, left, parseAdditive()) + } + return left + } + + private fun parseAdditive(): Expression { + var left = parseMultiplicative() + while (true) { + val operator = when { + check(TokenType.Plus) -> BinaryOperator.Add + check(TokenType.Minus) -> BinaryOperator.Subtract + else -> break + } + advance() + left = BinaryExpression(operator, left, parseMultiplicative()) + } + return left + } + + private fun parseMultiplicative(): Expression { + var left = parseUnary() + while (true) { + val operator = when { + check(TokenType.Star) -> BinaryOperator.Multiply + check(TokenType.Slash) -> BinaryOperator.Divide + else -> break + } + advance() + left = BinaryExpression(operator, left, parseUnary()) + } + return left + } + + private fun parseUnary(): Expression { + if (match(TokenType.Minus)) { + return UnaryExpression(UnaryOperator.Negate, parseUnary()) + } + return parsePrimary() + } + + private fun parsePrimary(): Expression { + val token = peek() + return when (token.type) { + TokenType.IntLiteral -> { + advance(); LiteralExpression(Value.IntegerValue(token.intValue!!)) + } + TokenType.RealLiteral -> { + advance(); LiteralExpression(Value.RealValue(token.realValue!!)) + } + TokenType.TextLiteral -> { + advance(); LiteralExpression(Value.TextValue(token.textValue!!)) + } + TokenType.True -> { + advance(); LiteralExpression(Value.BooleanValue(true)) + } + TokenType.False -> { + advance(); LiteralExpression(Value.BooleanValue(false)) + } + TokenType.Null -> { + advance(); LiteralExpression(Value.NullValue) + } + TokenType.Identifier -> { + advance() + when { + check(TokenType.LeftParen) -> parseFunctionCall(token.lexeme) + match(TokenType.Dot) -> ColumnExpression(expectIdentifier("expected column name after '.'"), token.lexeme) + else -> ColumnExpression(token.lexeme) + } + } + TokenType.LeftParen -> { + advance() + val expression = parseExpression() + expect(TokenType.RightParen, "expected ')' after expression") + expression + } + else -> throw error("expected an expression but found '${describe(token)}'", token) + } + } + + private fun parseFunctionCall(name: String): Expression { + expect(TokenType.LeftParen, "expected '(' after function name") + if (match(TokenType.Star)) { + expect(TokenType.RightParen, "expected ')' after '*'") + return FunctionExpression(name, argument = null, star = true) + } + val argument = parseExpression() + expect(TokenType.RightParen, "expected ')' after function argument") + return FunctionExpression(name, argument, star = false) + } + + private fun peek(): Token = tokens[position] + + private fun check(type: TokenType): Boolean = peek().type == type + + private fun advance(): Token { + val token = tokens[position] + if (token.type != TokenType.EndOfInput) position++ + return token + } + + private fun match(type: TokenType): Boolean { + if (check(type)) { + advance() + return true + } + return false + } + + private fun expect(type: TokenType, message: String): Token { + if (check(type)) return advance() + val token = peek() + throw error("$message but found '${describe(token)}'", token) + } + + private fun expectIdentifier(message: String): String = + expect(TokenType.Identifier, message).lexeme + + private fun error(message: String, token: Token): ParseException = + ParseException(message, token.line, token.column) + + private fun describe(token: Token): String = + if (token.type == TokenType.EndOfInput) "end of input" else token.lexeme +}