在我的工作中,我遇到了很多粗糙的 sql,我有一个聪明的想法,那就是编写一个程序来解析 sql 并整齐地打印出来。我很快就完成了大部分工作,但遇到了一个我不知道如何解决的问题。
所以让我们假设 sql 是“select foo from bar where 1”。我的想法是,总是有一个关键字后跟数据,所以我所要做的就是解析一个关键字,然后在下一个关键字之前捕获所有乱码并将其存储起来以供以后清理,如果值得的话。这是代码:
import Text.Parsec
import Text.Parsec.Combinator
import Text.Parsec.Char
import Data.Text (strip)
newtype Statement = Statement [Atom]
data Atom = Branch String [Atom] | Leaf String deriving Show
trim str = reverse $ trim' (reverse $ trim' str)
where
trim' (' ':xs) = trim' xs
trim' str = str
printStatement atoms = mapM_ printAtom atoms
printAtom atom = loop 0 atom
where
loop depth (Leaf str) = putStrLn $ (replicate depth ' ') ++ str
loop depth (Branch str atoms) = do
putStrLn $ (replicate depth ' ') ++ str
mapM_ (loop (depth + 2)) atoms
keywords :: [String]
keywords = [
"select",
"update",
"delete",
"from",
"where"]
keywordparser :: Parsec String u String
keywordparser = try ((choice $ map string keywords) <?> "keywordparser")
stuffparser :: Parsec String u String
stuffparser = manyTill anyChar (eof <|> (lookAhead keywordparser >> return ()))
statementparser = do
key <- keywordparser
stuff <- stuffparser
return $ Branch key [Leaf (trim stuff)]
<?> "statementparser"
tp = parse (many statementparser) ""
这里的关键是 stuffparser。那是关键字之间的东西,可以是从列列表到条件的任何内容。此函数捕获通向关键字的所有字符。但在完成之前它还需要其他东西。如果有子选择怎么办?“从栏选择 id,(从产品中选择产品)”。好吧,在那种情况下,如果它碰到那个关键字,它就会搞砸一切,解析错误并搞砸我的缩进。where 子句也可以有括号。
因此,我需要将 anyChar 更改为另一个组合器,该组合器一次吞一个字符,但也尝试查找括号,如果找到它们,遍历并捕获所有这些,但如果有更多括号,则这样做直到我们已完全关闭括号,然后将其全部连接并返回。这是我尝试过的,但我无法让它发挥作用。
stuffparser :: Parsec String u String
stuffparser = fmap concat $ manyTill somechars (eof <|> (lookAhead keywordparser >> return ()))
where
somechars = parens <|> fmap (\c -> [c]) anyChar
parens= between (char '(') (char ')') somechars
这将像这样错误:
> tp "select asdf(qwerty) from foo where 1"
Left (line 1, column 14):
unexpected "w"
expecting ")"
但是我想不出任何方法来重写它以使其起作用。我尝试在括号部分使用 manyTill,但是当我同时使用字符串生成括号和单个字符作为替代时,我最终无法对其进行类型检查。有人对如何解决这个问题有任何建议吗?