@onmach см редактировать
ей работе я сталкиваюсь с множеством скучных SQL-запросов, и у меня появилась блестящая идея написать программу, которая будет анализировать SQL-файл и аккуратно его распечатывать. Я сделал это довольно быстро, но столкнулся с проблемой, которую не знаю, как решить.
Итак, давайте притворимся, что sql «select foo from bar, где 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) ""
Ключевым моментом здесь является материал-парсер. Это то, что находится между ключевыми словами, которые могут быть чем угодно, от списков столбцов до критериев где. Эта функция ловит все символы, ведущие к ключевому слову. Но для этого нужно что-то еще, прежде чем оно закончится. Что делать, если есть подвыбор? msgstr "выбрать идентификатор, (выбрать продукт из продуктов) из бара". Ну, в этом случае, если он попадет в это ключевое слово, он все испортит, неправильно его проанализирует и испортит мой отступ. Также, где пункты могут иметь круглые скобки.
Поэтому мне нужно изменить этот 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 в скобках, но у меня возникли проблемы с проверкой типов, когда в качестве альтернативы у меня есть как строковые выражения, так и одиночные символы. У кого-нибудь есть какие-либо предложения по поводу этого?