Files
ChuBlog/chusql-engine/src/ChuSQL/Syntax/Parser.hs
T
2026-09-24 21:28:49 +08:00

327 lines
8.1 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{-# LANGUAGE ImportQualifiedPost #-}
module ChuSQL.Syntax.Parser (parseStatement) where
import ChuSQL.Model (Column (..))
import ChuSQL.Syntax.AST
import Control.Monad.Combinators.Expr (Operator (..), makeExprParser)
import Data.Char (isAlpha, isAlphaNum, toLower)
import Data.List (intercalate)
import Data.Maybe (fromMaybe)
import Data.Void (Void)
import Text.Megaparsec
import Text.Megaparsec.Char
import Text.Megaparsec.Char.Lexer qualified as Lxr
-- SQL 解析:词法 + 语法,把文本变成语句树。
-- * 词法
-- | 解析器类型
type Parser = Parsec Void String
-- | 跳过空白
sc :: Parser ()
sc = Lxr.space space1 empty empty
-- | 词后面吃掉空白
lexeme :: Parser a -> Parser a
lexeme = Lxr.lexeme sc
-- | 读一个符号
symbol :: String -> Parser String
symbol = Lxr.symbol sc
{- | 大小写不敏感的关键字;其后不能紧跟标识符字符,避免把 @selection@ 读成 @select@。
整体用 'try' 包住:否则 @ORDER@ 会被 @keyword "or"@ 吃掉前缀 @OR@ 再报错,导致无法回溯。
-}
-- | 读关键字(不分大小写)
keyword :: String -> Parser ()
keyword k = lexeme $ try $ do
_ <- string' k
notFollowedBy (satisfy isIdentChar)
-- | 读一个名字
identifier :: Parser String
identifier = lexeme $ do
first <- satisfy (\ch -> isAlpha ch || ch == '_')
rest <- many (satisfy isIdentChar)
return (first : rest)
-- | 名字里允许的字符
isIdentChar :: Char -> Bool
isIdentChar ch = isAlphaNum ch || ch == '_'
-- | 读一个整数
integer :: Parser Int
integer = lexeme Lxr.decimal
-- | 读一个字符串字面量
stringLit :: Parser String
stringLit = lexeme $ do
_ <- char '\''
s <- many stringChar
_ <- char '\''
return s
{- | 字符串内的一个字符:@''@ 折叠成一个单引号,其余字符(含反斜杠)原样保留。
两个分支都不会在失败时消耗输入,因此可以安全地放进 'many'。
-}
-- | 字符串里的一个字符
stringChar :: Parser Char
stringChar =
choice
[ '\'' <$ try (string "''")
, satisfy (/= '\'')
]
-- * 子句
-- | 升序或降序
sortDir :: Parser SortDir
sortDir = (Desc <$ keyword "desc") <|> (Asc <$ keyword "asc") <|> pure Asc
-- | LIMIT 后面的整数
limitClause :: Parser Int
limitClause = do
keyword "limit"
integer
-- | 能带表名的列名
qualifiedName :: Parser String
qualifiedName = do
first <- identifier
rest <- many (symbol "." *> identifier)
return (intercalate "." (first : rest))
-- * 表达式
-- | 读一个表达式
expr :: Parser Expr
expr = makeExprParser atom operatorTable
{- | 运算符表。注意 'makeExprParser' 要求各层按优先级从高到低排列:
比较运算符绑定最紧,'AND' 次之,'OR' 最松。
-}
-- | 运算符优先级(从紧到松)
operatorTable :: [[Operator Parser Expr]]
operatorTable =
[
[ InfixN (Gt <$ symbol ">") -- 最高优先级
, InfixN (Lt <$ symbol "<")
, InfixN (Eq <$ symbol "=")
]
, [InfixL (And <$ keyword "AND")]
, [InfixL (Or <$ keyword "OR")] -- 最低优先级
]
-- | 最小的表达式单位
atom :: Parser Expr
atom =
choice
[ LitInt <$> integer
, LitStr <$> stringLit
, Col <$> qualifiedName
, between (symbol "(") (symbol ")") expr
]
-- | 一个排序列
orderItem :: Parser (String, SortDir)
orderItem = do
col <- qualifiedName
dir <- sortDir
return (col, dir)
-- | ORDER BY 的列表
orderByClause :: Parser [(String, SortDir)]
orderByClause = do
keyword "order"
keyword "by"
sepBy1 orderItem (symbol ",")
-- | SET 里的一条赋值
assignment :: Parser (String, Expr)
assignment = do
col <- identifier
_ <- symbol "="
e <- expr
return (col, e)
-- | 保留字清单
reservedWords :: [String]
reservedWords =
[ "select"
, "from"
, "where"
, "order"
, "by"
, "limit"
, "join"
, "on"
, "and"
, "or"
, "asc"
, "desc"
, "insert"
, "into"
, "values"
, "delete"
, "update"
, "set"
, "create"
, "table"
]
{- | 表别名。别名不能是保留字:否则 @FROM users WHERE age > 18@ 会把 @WHERE@
当成 @users@ 的别名吃掉,后面真正的 WHERE 子句就再也解析不到。
用 'try' 包住,一旦命中保留字就整体回溯,让 'optional' 正常返回 'Nothing'。
-}
-- | 别名(不许用保留字)
aliasName :: Parser String
aliasName = try $ do
name <- identifier
if map toLower name `elem` reservedWords then empty else return name
-- | 表名 + 可选别名
tableRef :: Parser (Maybe String, String)
tableRef = do
tbl <- identifier
mAlias <- optional aliasName
return (mAlias, tbl)
-- | 一个 JOIN ... ON
joinClause :: Parser (Maybe String, String, Expr)
joinClause = do
keyword "join"
(mAlias, tbl) <- tableRef
keyword "on"
cond <- expr
return (mAlias, tbl, cond)
-- | FROM 子句(可含多个 JOIN)
fromClause :: Parser FromClause
fromClause = do
(mAlias, tbl) <- tableRef
joins <- many joinClause
return (foldl (\acc (a, t, c) -> FromJoin acc a t c) (FromTable mAlias tbl) joins)
-- | SELECT 的列清单
selectList :: Parser [String]
selectList =
choice
[ ["*"] <$ symbol "*"
, (:) <$> qualifiedName <*> many (symbol "," *> qualifiedName)
]
-- | 读 CREATE TABLE
createTableStatement :: Parser Statement
createTableStatement = do
keyword "create"
keyword "table"
name <- identifier
cols <- between (symbol "(") (symbol ")") (sepBy1 columnDef (symbol ","))
return (CreateTable name cols)
-- | 读 DROP TABLE
dropTableStatement :: Parser Statement
dropTableStatement = do
keyword "drop"
keyword "table"
name <- identifier
return (DropTable name)
-- | 建表时的一列
columnDef :: Parser (String, Column)
columnDef = do
name <- identifier
ty <- columnType
return (name, ty)
-- | 列类型关键字
columnType :: Parser Column
columnType =
(keyword "integer" >> pure TInt)
<|> (keyword "int" >> pure TInt)
<|> (keyword "varchar" >> pure TStr)
<|> (keyword "text" >> pure TStr)
<|> (keyword "str" >> pure TStr)
<|> (keyword "boolean" >> pure TBool)
<|> (keyword "bool" >> pure TBool)
-- * 语句
-- | 读 SELECT
selectStatement :: Parser Statement
selectStatement = do
keyword "select"
cols <- selectList
keyword "from"
fromC <- fromClause
mWhere <- optional (keyword "where" *> expr)
orderBy <- fromMaybe [] <$> optional orderByClause
mLimit <- optional limitClause
return
Select
{ selectCols = cols
, selectFrom = fromC
, selectWhere = mWhere
, selectOrderBy = orderBy
, selectLimit = mLimit
}
-- | 读 INSERT
insertStatement :: Parser Statement
insertStatement = do
keyword "INSERT"
keyword "INTO"
tbl <- identifier
cols <-
between
(symbol "(")
(symbol ")")
(sepBy1 identifier (symbol ","))
keyword "VALUES"
vals <-
between
(symbol "(")
(symbol ")")
(sepBy1 atom (symbol ","))
return (Insert tbl cols vals)
-- | 读 DELETE
deleteStatement :: Parser Statement
deleteStatement = do
keyword "DELETE"
keyword "FROM"
tbl <- identifier
mWhere <- optional (keyword "WHERE" *> expr)
return (Delete tbl mWhere)
-- | 读 UPDATE
updateStatement :: Parser Statement
updateStatement = do
keyword "update"
tbl <- identifier
keyword "set"
assigns <- sepBy1 assignment (symbol ",")
mWhere <- optional (keyword "where" *> expr)
return (Update tbl assigns mWhere)
-- * 入口
-- | 解析总入口
parseStatement :: String -> Either String Statement
parseStatement input =
case runParser (sc *> statementP <* sc <* eof) "<query>" input of
Left err -> Left (errorBundlePretty err)
Right q -> Right q
where
-- \| 依次尝试各种语句
statementP =
selectStatement
<|> insertStatement
<|> deleteStatement
<|> updateStatement
<|> createTableStatement
<|> dropTableStatement