Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Yeah, <code>between</code> might not work for what you're looking for. Of course, for your use case, I'd follow hammar's suggestion and grab an off-the-shelf SQL parser. (personal opinion: or, try not to use SQL unless you really have to; the idea to use strings for database queries was imho a historical mistake).</p> <p><em>Note: I add an operator called <code>&lt;++&gt;</code> which will concatenate the results of two parsers, whether they are strings or characters. (code at bottom.)</em></p> <p>First, for the task of parsing parenthesis: the top level will parse some stuff between the relevant characters, which is exactly what the code says,</p> <pre><code>parseParen = char '(' &lt;++&gt; inner &lt;++&gt; char ')' </code></pre> <p>Then, the <code>inner</code> function should parse anything else: non-parens, possibly including another set of parenthesis, and non-paren junk that follows.</p> <pre><code>parseParen = char '(' &lt;++&gt; inner &lt;++&gt; char ')' where inner = many (noneOf "()") &lt;++&gt; option "" (parseParen &lt;++&gt; inner) </code></pre> <p>I'll make the assumption that for the rest of the solution, what you want to do is analgous to splitting things up by top-level SQL keywords. (i.e. ignoring those in parenthesis). Namely, we'll have a parser that will behave like so,</p> <pre><code>Main&gt; 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")] </code></pre> <p>Suppose we have a <code>parseKw</code> parser that will get the likes of <code>select</code>, etc. After we consume a keyword, we need to read until the next [top-level] keyword. The last trick to my solution is using the <code>lookAhead</code> combinator to determine whether the next word is a keyword, and put it back if so. If it's not, then we consume a parenthesis or other character, and then recurse on the rest.</p> <pre><code>-- consume spaces, then eat a word or parenthesis parseOther = many space &lt;++&gt; (("" &lt;$ lookAhead (try parseKw)) &lt;|&gt; -- if there's a keyword, put it back! option "" ((parseParen &lt;|&gt; many1 (noneOf "() \t")) &lt;++&gt; parseOther)) </code></pre> <p>My entire solution is as follows</p> <pre><code>-- overloaded operator to concatenate string results from parsers class CharOrStr a where toStr :: a -&gt; String instance CharOrStr Char where toStr x = [x] instance CharOrStr String where toStr = id infixl 4 &lt;++&gt; f &lt;++&gt; g = (\x y -&gt; toStr x ++ toStr y) &lt;$&gt; f &lt;*&gt; g data Keyword = Select | Update | Delete | From | Where deriving (Eq, Show) parseKw = (Select &lt;$ string "select") &lt;|&gt; (Update &lt;$ string "update") &lt;|&gt; (Delete &lt;$ string "delete") &lt;|&gt; (From &lt;$ string "from") &lt;|&gt; (Where &lt;$ string "where") &lt;?&gt; "keyword (select, update, delete, from, where)" -- consume spaces, then eat a word or parenthesis parseOther = many space &lt;++&gt; (("" &lt;$ lookAhead (try parseKw)) &lt;|&gt; -- if there's a keyword, put it back! option "" ((parseParen &lt;|&gt; many1 (noneOf "() \t")) &lt;++&gt; parseOther)) parseSqlToplevel = many ((,) &lt;$&gt; parseKw &lt;*&gt; (space &lt;++&gt; parseOther)) &lt;* eof parseParen = char '(' &lt;++&gt; inner &lt;++&gt; char ')' where inner = many (noneOf "()") &lt;++&gt; option "" (parseParen &lt;++&gt; inner) </code></pre> <h3>edit - version with quote support</h3> <p>you can do the same thing as with the parens to support quotes,</p> <pre><code>import Control.Applicative hiding (many, (&lt;|&gt;)) import Text.Parsec import Text.Parsec.Combinator -- overloaded operator to concatenate string results from parsers class CharOrStr a where toStr :: a -&gt; String instance CharOrStr Char where toStr x = [x] instance CharOrStr String where toStr = id infixl 4 &lt;++&gt; f &lt;++&gt; g = (\x y -&gt; toStr x ++ toStr y) &lt;$&gt; f &lt;*&gt; g data Keyword = Select | Update | Delete | From | Where deriving (Eq, Show) parseKw = (Select &lt;$ string "select") &lt;|&gt; (Update &lt;$ string "update") &lt;|&gt; (Delete &lt;$ string "delete") &lt;|&gt; (From &lt;$ string "from") &lt;|&gt; (Where &lt;$ string "where") &lt;?&gt; "keyword (select, update, delete, from, where)" -- consume spaces, then eat a word or parenthesis parseOther = many space &lt;++&gt; (("" &lt;$ lookAhead (try parseKw)) &lt;|&gt; -- if there's a keyword, put it back! option "" ((parseParen &lt;|&gt; parseQuote &lt;|&gt; many1 (noneOf "'() \t")) &lt;++&gt; parseOther)) parseSqlToplevel = many ((,) &lt;$&gt; parseKw &lt;*&gt; (space &lt;++&gt; parseOther)) &lt;* eof parseQuote = char '\'' &lt;++&gt; inner &lt;++&gt; char '\'' where inner = many (noneOf "'\\") &lt;++&gt; option "" (char '\\' &lt;++&gt; anyChar &lt;++&gt; inner) parseParen = char '(' &lt;++&gt; inner &lt;++&gt; char ')' where inner = many (noneOf "'()") &lt;++&gt; (parseQuote &lt;++&gt; inner &lt;|&gt; option "" (parseParen &lt;++&gt; inner)) </code></pre> <p>I tried it with <code>parseTest parseSqlToplevel "select ('a(sdf'())b"</code>. cheers</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload