Да, between
может не работать для того, что вы ищете. Конечно, для вашего случая использования я бы последовал совету Хаммара и взял бы готовый анализатор SQL. (личное мнение: или старайтесь не использовать SQL, если в этом нет особой необходимости; идея использовать строки для запросов к базе данных была исторической ошибкой).
Примечание. Я добавляю оператор с именем <++>
, который объединяет результаты двух анализаторов, будь то строки или символы. (код внизу.)
Во-первых, для задачи разбора скобок: верхний уровень будет разбирать некоторые вещи между соответствующими символами, что в точности соответствует коду,
parseParen = char '(' <++> inner <++> char ')'
Затем функция inner
должна анализировать что-либо еще: непаренс, возможно, включая другой набор скобок, и непаренский мусор, который следует.
parseParen = char '(' <++> inner <++> char ')' where
inner = many (noneOf "()") <++> option "" (parseParen <++> inner)
Я сделаю предположение, что для остальной части решения то, что вы хотите сделать, является аналогом разделения на ключевые слова SQL верхнего уровня. (т.е. игнорируя те, что в скобках). А именно, у нас будет парсер, который будет вести себя так,
Main> parseTest parseSqlToplevel "select asdf(select m( 2) fr(o)m w where n) from b where delete 4"
[(Select," asdf(select m( 2) fr(o)m w where n) "),(From," b "),(Where," "),(Delete," 4")]
Предположим, у нас есть синтаксический анализатор parseKw
, который получит значения, подобные select
и т. Д. После того, как мы используем ключевое слово, нам нужно прочитать до следующего ключевого слова [верхнего уровня]. Последний трюк с моим решением - использовать комбинатор lookAhead
, чтобы определить, является ли следующее слово ключевым словом, и вернуть его, если так. Если это не так, то мы потребляем скобки или другой символ, а затем возвращаемся к остальным.
-- consume spaces, then eat a word or parenthesis
parseOther = many space <++>
(("" <$ lookAhead (try parseKw)) <|> -- if there's a keyword, put it back!
option "" ((parseParen <|> many1 (noneOf "() \t")) <++> parseOther))
Мое полное решение выглядит следующим образом
-- overloaded operator to concatenate string results from parsers
class CharOrStr a where toStr :: a -> String
instance CharOrStr Char where toStr x = [x]
instance CharOrStr String where toStr = id
infixl 4 <++>
f <++> g = (\x y -> toStr x ++ toStr y) <$> f <*> g
data Keyword = Select | Update | Delete | From | Where deriving (Eq, Show)
parseKw =
(Select <$ string "select") <|>
(Update <$ string "update") <|>
(Delete <$ string "delete") <|>
(From <$ string "from") <|>
(Where <$ string "where") <?>
"keyword (select, update, delete, from, where)"
-- consume spaces, then eat a word or parenthesis
parseOther = many space <++>
(("" <$ lookAhead (try parseKw)) <|> -- if there's a keyword, put it back!
option "" ((parseParen <|> many1 (noneOf "() \t")) <++> parseOther))
parseSqlToplevel = many ((,) <$> parseKw <*> (space <++> parseOther)) <* eof
parseParen = char '(' <++> inner <++> char ')' where
inner = many (noneOf "()") <++> option "" (parseParen <++> inner)
edit - версия с поддержкой цитат
вы можете сделать то же самое, что и с паренами для поддержки кавычек,
import Control.Applicative hiding (many, (<|>))
import Text.Parsec
import Text.Parsec.Combinator
-- overloaded operator to concatenate string results from parsers
class CharOrStr a where toStr :: a -> String
instance CharOrStr Char where toStr x = [x]
instance CharOrStr String where toStr = id
infixl 4 <++>
f <++> g = (\x y -> toStr x ++ toStr y) <$> f <*> g
data Keyword = Select | Update | Delete | From | Where deriving (Eq, Show)
parseKw =
(Select <$ string "select") <|>
(Update <$ string "update") <|>
(Delete <$ string "delete") <|>
(From <$ string "from") <|>
(Where <$ string "where") <?>
"keyword (select, update, delete, from, where)"
-- consume spaces, then eat a word or parenthesis
parseOther = many space <++>
(("" <$ lookAhead (try parseKw)) <|> -- if there's a keyword, put it back!
option "" ((parseParen <|> parseQuote <|> many1 (noneOf "'() \t")) <++> parseOther))
parseSqlToplevel = many ((,) <$> parseKw <*> (space <++> parseOther)) <* eof
parseQuote = char '\'' <++> inner <++> char '\'' where
inner = many (noneOf "'\\") <++>
option "" (char '\\' <++> anyChar <++> inner)
parseParen = char '(' <++> inner <++> char ')' where
inner = many (noneOf "'()") <++>
(parseQuote <++> inner <|> option "" (parseParen <++> inner))
Я попробовал это с parseTest parseSqlToplevel "select ('a(sdf'())b"
. веселит