feat(parser): extend Parser with parseIsSuffix and parseInSuffix

This commit is contained in:
dualfroz committed 2026-09-04 15:11:23 +02:00
1 parent 89c9c7e6ca
commit 1f216ebd5d
1 file changed
+142 -8
+142 -8
View File
@@ -1348,6 +1348,14 @@ class Parser(private val tokens: List<Token>) {
private fun parseComparison(): Expression {
var left = parseAdditive()
while (true) {
val negated = match(TokenType.Not)
left = when {
check(TokenType.In) -> { advance(); parseInSuffix(left, negated) }
check(TokenType.Between) -> { advance(); parseBetweenSuffix(left, negated) }
check(TokenType.Like) -> { advance(); parseLikeSuffix(left, negated) }
negated -> throw error("expected IN, BETWEEN, or LIKE after NOT", peek())
check(TokenType.Is) -> { advance(); parseIsSuffix(left) }
else -> {
val operator = when {
check(TokenType.Equal) -> BinaryOperator.Equal
check(TokenType.NotEqual) -> BinaryOperator.NotEqual
@@ -1355,12 +1363,46 @@ class Parser(private val tokens: List<Token>) {
check(TokenType.LessEqual) -> BinaryOperator.LessEqual
check(TokenType.Greater) -> BinaryOperator.Greater
check(TokenType.GreaterEqual) -> BinaryOperator.GreaterEqual
else -> break
else -> return left
}
advance()
left = BinaryExpression(operator, left, parseAdditive())
BinaryExpression(operator, left, parseAdditive())
}
return left
}
}
}
private fun parseIsSuffix(left: Expression): Expression {
val negated = match(TokenType.Not)
expect(TokenType.Null, "expected NULL after IS" + if (negated) " NOT" else "")
return IsNullExpression(left, negated)
}
private fun parseInSuffix(left: Expression, negated: Boolean): Expression {
expect(TokenType.LeftParen, "expected '(' after IN")
if (check(TokenType.Select)) {
val select = parseSelect()
expect(TokenType.RightParen, "expected ')' after IN subquery")
return InSubqueryExpression(left, select, negated)
}
val values = mutableListOf<Expression>()
do {
values.add(parseExpression())
} while (match(TokenType.Comma))
expect(TokenType.RightParen, "expected ')' after IN list")
return InExpression(left, values, negated)
}
private fun parseBetweenSuffix(left: Expression, negated: Boolean): Expression {
val low = parseAdditive()
expect(TokenType.And, "expected AND in BETWEEN")
val high = parseAdditive()
return BetweenExpression(left, low, high, negated)
}
private fun parseLikeSuffix(left: Expression, negated: Boolean): Expression {
val pattern = parseAdditive()
return LikeExpression(left, pattern, negated)
}
private fun parseAdditive(): Expression {
@@ -1378,7 +1420,7 @@ class Parser(private val tokens: List<Token>) {
}
private fun parseMultiplicative(): Expression {
var left = parseUnary()
var left = parseCollate()
while (true) {
val operator = when {
check(TokenType.Star) -> BinaryOperator.Multiply
@@ -1386,11 +1428,30 @@ class Parser(private val tokens: List<Token>) {
else -> break
}
advance()
left = BinaryExpression(operator, left, parseUnary())
left = BinaryExpression(operator, left, parseCollate())
}
return left
}
/**
* `expr COLLATE name` - sits between unary and multiplicative in precedence, matching
* SQLite's own grammar (binds tighter than every arithmetic/comparison operator, but looser
* than unary `-`/`NOT`, so `-x COLLATE NOCASE` is `(-x) COLLATE NOCASE` and
* `a || b COLLATE NOCASE` would apply to `b` alone). Left-associative chaining
* (`x COLLATE A COLLATE B`) is accepted like SQLite accepts it - only the last one written
* has any effect, since each wraps the previous [CollateExpression] as its own operand.
*/
private fun parseCollate(): Expression {
var expression = parseUnary()
while (match(TokenType.Collate)) {
val nameToken = expect(TokenType.Identifier, "expected a collation name after COLLATE")
val collation = Collation.fromName(nameToken.lexeme)
?: throw error("no such collating sequence: ${nameToken.lexeme}", nameToken)
expression = CollateExpression(expression, collation)
}
return expression
}
private fun parseUnary(): Expression {
if (match(TokenType.Minus)) {
return UnaryExpression(UnaryOperator.Negate, parseUnary())
@@ -1419,6 +1480,27 @@ class Parser(private val tokens: List<Token>) {
TokenType.Null -> {
advance(); LiteralExpression(Value.NullValue)
}
TokenType.Parameter -> {
advance()
val index = token.intValue!!
if (index < 1) throw error("parameter index must be >= 1 but was $index", token)
ParameterExpression(index.toInt())
}
TokenType.Case -> parseCase()
TokenType.Exists -> parseExists()
TokenType.Cast -> parseCast()
// LEFT/RIGHT are otherwise reserved (LEFT/RIGHT JOIN), but LEFT(str, n)/RIGHT(str, n) are
// real, commonly expected scalar functions - only valid here as a call, never as a bare
// column reference, so a missing '(' is unconditionally an error rather than falling back
// to ColumnExpression like the TokenType.Identifier branch below does.
TokenType.Left -> {
advance()
if (check(TokenType.LeftParen)) parseFunctionCall(token.lexeme) else throw error("expected '(' after LEFT", peek())
}
TokenType.Right -> {
advance()
if (check(TokenType.LeftParen)) parseFunctionCall(token.lexeme) else throw error("expected '(' after RIGHT", peek())
}
TokenType.Identifier -> {
advance()
when {
@@ -1429,19 +1511,71 @@ class Parser(private val tokens: List<Token>) {
}
TokenType.LeftParen -> {
advance()
if (check(TokenType.Select)) {
val select = parseSelect()
expect(TokenType.RightParen, "expected ')' after subquery")
ScalarSubqueryExpression(select)
} else {
val expression = parseExpression()
expect(TokenType.RightParen, "expected ')' after expression")
expression
}
}
else -> throw error("expected an expression but found '${describe(token)}'", token)
}
}
private fun parseCase(): Expression {
expect(TokenType.Case, "expected CASE")
val operand = if (!check(TokenType.When)) parseExpression() else null
val branches = mutableListOf<CaseBranch>()
while (match(TokenType.When)) {
val condition = parseExpression()
expect(TokenType.Then, "expected THEN after CASE condition")
val result = parseExpression()
branches.add(CaseBranch(condition, result))
}
if (branches.isEmpty()) throw error("expected at least one WHEN clause in CASE expression", peek())
val elseExpression = if (match(TokenType.Else)) parseExpression() else null
expect(TokenType.End, "expected END to close CASE expression")
return CaseExpression(operand, branches, elseExpression)
}
private fun parseCast(): Expression {
expect(TokenType.Cast, "expected CAST")
expect(TokenType.LeftParen, "expected '(' after CAST")
val operand = parseExpression()
expect(TokenType.As, "expected AS in CAST")
val spec = parseTypeSpec()
expect(TokenType.RightParen, "expected ')' after CAST target type")
return CastExpression(operand, spec.type, spec.precision, spec.scale)
}
private fun parseExists(): Expression {
expect(TokenType.Exists, "expected EXISTS")
expect(TokenType.LeftParen, "expected '(' after EXISTS")
val select = parseSelect()
expect(TokenType.RightParen, "expected ')' after EXISTS subquery")
return ExistsExpression(select)
}
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 upperName = name.uppercase()
if (upperName == "STRING_AGG") return parseStringAggCall(name)
val isAggregate = upperName in AGGREGATE_FUNCTIONS
var star = false
var distinct = false
val arguments = mutableListOf<Expression>()
if (isAggregate && match(TokenType.Distinct)) {
distinct = true
arguments.add(parseExpression())
} else if (isAggregate && match(TokenType.Star)) {
star = true
} else if (!check(TokenType.RightParen)) {
do {
arguments.add(parseExpression())
} while (match(TokenType.Comma))
}
val argument = parseExpression()
expect(TokenType.RightParen, "expected ')' after function argument")