Search code examples
parsinghaskelllogicexpressionparsec

Haskell parsing queries (Parsec)


I'm trying to make a queries parser in Haskell, but don't understand how I'm supposed to allow different optional paths of parser logic. My attempt:

query :: Parser Query
query = do
    -- add more queries
    reserved "SELECT"
    select <- sequenceOfExpr
    reserved "FROM"
    table <- identifier
    semi
    return $ Select select table (BoolConst True)
    <|> do
    reserved "SELECT"
    select <- sequenceOfExpr
    reserved "FROM"
    table <- identifier
    reserved "WHERE"
    whereQ <- bExpression
    semi
    return $ Select select table whereQ
    <|> do
    reserved "INSERT"
    insert <- sequenceOfExpr
    reserved "INTO"
    table <- identifier
    semi
    return $ Insert insert table
    <|> do
    reserved "REMOVE"
    reserved "FROM"
    table <- identifier
    reserved "WHERE"
    whereQ <- bExpression
    semi
    return $ Remove table whereQ
    <|> do
    reserved "CREATE"
    table <- identifier
    fields <- sequenceOfExpr
    semi
    return $ Create table fields
    <|> do
    reserved "DROP"
    table <- identifier
    semi
    return $ Drop table

Which works when parsing a string that corresponds to the first do stmt structure, e.g.:

"SELECT testField FROM testTable;"

but not for the others. E.g. when parsing:

"SELECT testField FROM testTable WHERE TRUE"

Instead of trying the other paths, it returns:

unexpected "W"
expecting ";"

In other words it seems like it only tries the first logic. What am I doing wrong?

Any help would be much appreciated!


Solution

  • This happens because the SELECT FROM alternative has succeeded and returned its result, the parsing never got to trying the SELECT FROM WHERE alternative.

    In this specific case, I would just flip their order: try SELECT FROM WHERE first, and if that doesn't work, fall back to SELECT FROM. You would also need to wrap it in a try in order for the parser to roll back to the beginning of the query.

    Alternatively, you could make the WHERE parsing a conditional part of the SELECT FROM parser, something like this:

    do
        reserved "SELECT"
        select <- sequenceOfExpr
        reserved "FROM"
        table <- identifier
        whereQ <- try (reserved "WHERE" *> bExpression) <|> (pure $ BoolConst True)
        semi
        return $ Select select table whereQ