This repository was archived by the owner on Dec 21, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 114
This repository was archived by the owner on Dec 21, 2025. It is now read-only.
Help with reduce/reduce conflict while parsing SQL #108
Copy link
Copy link
Closed
Description
I'm trying parse some simple SQL clauses and build them into SQLAlchemy objects. While parsing the example expressions below seem to work fine, I can't solve the generated reduce/reduce conflict. Can someone help me elucidate this?
This is a very simplified version to exemplify the problem I'm having:
from secrets import token_hex
from typing import Any
import sqlalchemy as sa
from sly import Lexer, Parser
from sqlalchemy.sql.elements import BindParameter, ClauseElement, ColumnClause
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable,PyPep8Naming
class SQLLexer(Lexer):
tokens = {
BETWEEN,
NUMBER,
BOOLEAN,
AND,
COLUMN,
}
ignore = " \n"
BETWEEN = r"BETWEEN"
NUMBER = r"(\d+([.]\d*)?([eE][+-]?\d+)?|[.]\d+([eE][+-]?\d+)?)"
BOOLEAN = r"TRUE|FALSE"
AND = r"AND"
COLUMN = r'"(?:[^"]|"")*"|[a-zA-Z_][a-zA-Z0-9_]*'
# noinspection PyUnresolvedReferences,PyUnusedLocal
class SQLParser(Parser):
debugfile = "parser.txt"
tokens = SQLLexer.tokens
precedence = (
("left", AND),
("nonassoc", BETWEEN),
)
# Schema Item
@_("COLUMN")
def expr(self, p: tuple[str]) -> ColumnClause:
return sa.column(p[0])
# Number
@_("NUMBER")
def expr(self, p: tuple[str]) -> BindParameter:
try:
return wrap_value(int(p[0]))
except ValueError:
return wrap_value(float(p[0]))
# Boolean
@_("BOOLEAN")
def expr(self, p: tuple[str]) -> BindParameter:
if p[0].lower() == "true":
return wrap_value(True)
elif p[0].lower() == "false":
return wrap_value(False)
else:
raise NotImplementedError("unknown boolean value")
# AND
@_("expr AND expr")
def expr(
self, p: tuple[ClauseElement, str, ClauseElement]
) -> tuple[ClauseElement, ClauseElement]:
return sa.and_(p[0], p[2])
# BETWEEN
@_("expr BETWEEN expr AND expr")
def expr(
self, p: tuple[ClauseElement, str, ClauseElement, str, ClauseElement]
) -> tuple[ClauseElement, ClauseElement]:
return p[0].between(p[2], p[4])
def wrap_value(value: Any) -> BindParameter:
return sa.bindparam("param_" + token_hex(4), value)
if __name__ == "__main__":
samples = [
"myFlag AND FALSE",
"rowNumber BETWEEN 1 AND 10",
]
lexer = SQLLexer()
parser = SQLParser()
for sample in samples:
print(f"input: {sample}", flush=True)
tokens = list(lexer.tokenize(sample))
print(f"tokens: {[(t.type, t.value) for t in tokens]}", flush=True)
generated = parser.parse(iter(tokens))
print(f"generated: {generated}", flush=True)
print(flush=True)And here's the output:
WARNING: 2 reduce/reduce conflicts
Parser debugging for SQLParser written to parser.txt
input: myFlag AND FALSE
tokens: [('COLUMN', 'myFlag'), ('AND', 'AND'), ('BOOLEAN', 'FALSE')]
generated: "myFlag" AND :param_da45c008
input: rowNumber BETWEEN 1 AND 10
tokens: [('COLUMN', 'rowNumber'), ('BETWEEN', 'BETWEEN'), ('NUMBER', '1'), ('AND', 'AND'), ('NUMBER', '10')]
generated: "rowNumber" BETWEEN :param_199c82be AND :param_01991b92
The conflict info:
Conflicts:
reduce/reduce conflict in state 10 resolved using rule expr -> expr AND expr [precedence=left, level=1]
rejected rule (expr -> expr BETWEEN expr AND expr [precedence=left, level=1]) in state 10
And state 10 info:
state 10
(1) expr -> expr BETWEEN expr AND expr .
(2) expr -> expr AND expr .
(1) expr -> expr . BETWEEN expr AND expr
(2) expr -> expr . AND expr
! reduce/reduce conflict for AND resolved using rule 2 (expr -> expr AND expr .)
! reduce/reduce conflict for BETWEEN resolved using rule 2 (expr -> expr AND expr .)
$end reduce using rule 1 (expr -> expr BETWEEN expr AND expr .)
AND reduce using rule 2 (expr -> expr AND expr .)
BETWEEN shift and go to state 5
I've had similar issues with expr IS expr and expr IS NOT expr which I wanted to treat as a.is_(b) and a.isnot(b) respectively, which I tried to solve with %prec with no success. But I'm assuming that figuring out this more complex (above) case will also handle the simpler one.
Metadata
Metadata
Assignees
Labels
No labels