feat(parser): extend Parser with parseIsSuffix and parseInSuffix
This commit is contained in:
1 parent
89c9c7e6ca
commit
1f216ebd5d
1 file changed
+142
-8
@@ -1348,6 +1348,14 @@ class Parser(private val tokens: List<Token>) {
|
|||||||
private fun parseComparison(): Expression {
|
private fun parseComparison(): Expression {
|
||||||
var left = parseAdditive()
|
var left = parseAdditive()
|
||||||
while (true) {
|
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 {
|
val operator = when {
|
||||||
check(TokenType.Equal) -> BinaryOperator.Equal
|
check(TokenType.Equal) -> BinaryOperator.Equal
|
||||||
check(TokenType.NotEqual) -> BinaryOperator.NotEqual
|
check(TokenType.NotEqual) -> BinaryOperator.NotEqual
|
||||||
@@ -1355,12 +1363,46 @@ class Parser(private val tokens: List<Token>) {
|
|||||||
check(TokenType.LessEqual) -> BinaryOperator.LessEqual
|
check(TokenType.LessEqual) -> BinaryOperator.LessEqual
|
||||||
check(TokenType.Greater) -> BinaryOperator.Greater
|
check(TokenType.Greater) -> BinaryOperator.Greater
|
||||||
check(TokenType.GreaterEqual) -> BinaryOperator.GreaterEqual
|
check(TokenType.GreaterEqual) -> BinaryOperator.GreaterEqual
|
||||||
else -> break
|
else -> return left
|
||||||
}
|
}
|
||||||
advance()
|
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 {
|
private fun parseAdditive(): Expression {
|
||||||
@@ -1378,7 +1420,7 @@ class Parser(private val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMultiplicative(): Expression {
|
private fun parseMultiplicative(): Expression {
|
||||||
var left = parseUnary()
|
var left = parseCollate()
|
||||||
while (true) {
|
while (true) {
|
||||||
val operator = when {
|
val operator = when {
|
||||||
check(TokenType.Star) -> BinaryOperator.Multiply
|
check(TokenType.Star) -> BinaryOperator.Multiply
|
||||||
@@ -1386,11 +1428,30 @@ class Parser(private val tokens: List<Token>) {
|
|||||||
else -> break
|
else -> break
|
||||||
}
|
}
|
||||||
advance()
|
advance()
|
||||||
left = BinaryExpression(operator, left, parseUnary())
|
left = BinaryExpression(operator, left, parseCollate())
|
||||||
}
|
}
|
||||||
return left
|
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 {
|
private fun parseUnary(): Expression {
|
||||||
if (match(TokenType.Minus)) {
|
if (match(TokenType.Minus)) {
|
||||||
return UnaryExpression(UnaryOperator.Negate, parseUnary())
|
return UnaryExpression(UnaryOperator.Negate, parseUnary())
|
||||||
@@ -1419,6 +1480,27 @@ class Parser(private val tokens: List<Token>) {
|
|||||||
TokenType.Null -> {
|
TokenType.Null -> {
|
||||||
advance(); LiteralExpression(Value.NullValue)
|
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 -> {
|
TokenType.Identifier -> {
|
||||||
advance()
|
advance()
|
||||||
when {
|
when {
|
||||||
@@ -1429,19 +1511,71 @@ class Parser(private val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
TokenType.LeftParen -> {
|
TokenType.LeftParen -> {
|
||||||
advance()
|
advance()
|
||||||
|
if (check(TokenType.Select)) {
|
||||||
|
val select = parseSelect()
|
||||||
|
expect(TokenType.RightParen, "expected ')' after subquery")
|
||||||
|
ScalarSubqueryExpression(select)
|
||||||
|
} else {
|
||||||
val expression = parseExpression()
|
val expression = parseExpression()
|
||||||
expect(TokenType.RightParen, "expected ')' after expression")
|
expect(TokenType.RightParen, "expected ')' after expression")
|
||||||
expression
|
expression
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else -> throw error("expected an expression but found '${describe(token)}'", token)
|
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 {
|
private fun parseFunctionCall(name: String): Expression {
|
||||||
expect(TokenType.LeftParen, "expected '(' after function name")
|
expect(TokenType.LeftParen, "expected '(' after function name")
|
||||||
if (match(TokenType.Star)) {
|
val upperName = name.uppercase()
|
||||||
expect(TokenType.RightParen, "expected ')' after '*'")
|
if (upperName == "STRING_AGG") return parseStringAggCall(name)
|
||||||
return FunctionExpression(name, argument = null, star = true)
|
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()
|
val argument = parseExpression()
|
||||||
expect(TokenType.RightParen, "expected ')' after function argument")
|
expect(TokenType.RightParen, "expected ')' after function argument")
|
||||||
|
|||||||
Reference in new issue
Block a user