feat(parser): extend Parser with parseIsSuffix and parseInSuffix
This commit is contained in:
1 parent
89c9c7e6ca
commit
1f216ebd5d
1 file changed
+153
-19
@@ -1348,19 +1348,61 @@ class Parser(private val tokens: List<Token>) {
|
||||
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
|
||||
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
|
||||
check(TokenType.Less) -> BinaryOperator.Less
|
||||
check(TokenType.LessEqual) -> BinaryOperator.LessEqual
|
||||
check(TokenType.Greater) -> BinaryOperator.Greater
|
||||
check(TokenType.GreaterEqual) -> BinaryOperator.GreaterEqual
|
||||
else -> return left
|
||||
}
|
||||
advance()
|
||||
BinaryExpression(operator, left, parseAdditive())
|
||||
}
|
||||
}
|
||||
advance()
|
||||
left = 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()
|
||||
val expression = parseExpression()
|
||||
expect(TokenType.RightParen, "expected ')' after expression")
|
||||
expression
|
||||
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")
|
||||
|
||||
Reference in new issue
Block a user