私の仕事では、多くの厄介なSQLに出くわし、SQLを解析してきれいに出力するプログラムを作成するという素晴らしいアイデアを思いつきました。私はそれをかなり早く作りましたが、解決方法がわからない問題に遭遇しました。
それでは、SQLが「1のバーからfooを選択」であるとしましょう。私の考えでは、キーワードの後にデータが続くので、キーワードを解析し、次のキーワードの前にすべてのジブリッシュをキャプチャして、価値がある場合は後でクリーンアップするために保存するだけです。コードは次のとおりです。
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) ""
ここで重要なのはスタッフパーサーです。これは、キーワードの間にあるものであり、列リストからwhere基準まで何でもかまいません。この関数は、キーワードに至るまでのすべての文字をキャッチします。しかし、それが完了する前に何か他のものが必要です。副選択がある場合はどうなりますか?「selectid、(select product from products)frombar」。その場合、そのキーワードにヒットすると、すべてが台無しになり、解析が間違って、インデントが台無しになります。また、句に括弧を付けることもできます。
したがって、anyCharを別のコンビネータに変更する必要があります。このコンビネータは、文字を1つずつ丸呑みするだけでなく、括弧を探します。見つかった場合は、すべてをトラバースしてキャプチャします。さらに括弧がある場合は、それを実行します。括弧を完全に閉じてから、すべてを連結して返します。これが私が試したことですが、うまく機能させることができません。
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を使用しようとしましたが、代わりに文字列生成括弧と単一文字の両方がある場合、タイプチェックを取得するのに問題が発生します。誰かがこれについて行く方法について何か提案がありますか?