Note that there are some explanatory texts on larger screens.

plurals
  1. POHow do you use parsec in a greedy fashion?
    primarykey
    data
    text
    <p>In my work I come across a lot of gnarly sql, and I had the bright idea of writing a program to parse the sql and print it out neatly. I made most of it pretty quickly, but I ran into a problem that I don't know how to solve.</p> <p>So let's pretend the sql is "select foo from bar where 1". My thought was that there is always a keyword followed by data for it, so all I have to do is parse a keyword, and then capture all gibberish before the next keyword and store that for later cleanup, if it is worthwhile. Here's the code:</p> <pre><code>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) &lt;?&gt; "keywordparser") stuffparser :: Parsec String u String stuffparser = manyTill anyChar (eof &lt;|&gt; (lookAhead keywordparser &gt;&gt; return ())) statementparser = do key &lt;- keywordparser stuff &lt;- stuffparser return $ Branch key [Leaf (trim stuff)] &lt;?&gt; "statementparser" tp = parse (many statementparser) "" </code></pre> <p>The key here is the stuffparser. That is the stuff in between the keywords that could be anything from column lists to where criteria. This function catches all characters leading up to a keyword. But it needs something else before it is finished. What if there is a subselect? "select id,(select product from products) from bar". Well in that case if it hits that keyword, it screws everything up, parses it wrong and screws up my indenting. Also where clauses can have parenthesis as well.</p> <p>So I need to change that anyChar into another combinator that slurps up characters one at a time but also tries to look for parenthesis, and if it finds them, traverse and capture all that, but also if there are more parenthesis, do that until we have fully closed the parenthesis, then concatenate it all and return it. Here's what I've tried, but I can't quite get it to work.</p> <pre><code>stuffparser :: Parsec String u String stuffparser = fmap concat $ manyTill somechars (eof &lt;|&gt; (lookAhead keywordparser &gt;&gt; return ())) where somechars = parens &lt;|&gt; fmap (\c -&gt; [c]) anyChar parens= between (char '(') (char ')') somechars </code></pre> <p>This will error like so:</p> <pre><code>&gt; tp "select asdf(qwerty) from foo where 1" Left (line 1, column 14): unexpected "w" expecting ")" </code></pre> <p>But I can't think of any way to rewrite this so that it works. I've tried to use manyTill on the parenthesis part, but I end up having trouble getting it to typecheck when I have both string producing parens and single chars as alternatives. Does anyone have any suggestions on how to go about this?</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

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