1

I need to parse this syntax for function declaration

foo x = 1 
Func "foo" (Ident "x") = 1

foo (x = 1) = 1 
Func "foo" (Label "x" 1) = 1

foo x = y = 1 
Func "foo" (Ident "x") = (Label "y" 1)

I have written this parser

module SimpleParser where
import Text.Parsec.String (Parser)
import Text.Parsec.Language (emptyDef)
import Text.Parsec
import qualified Text.Parsec.Token as Tok
import Text.Parsec.Char
import Prelude

lexer :: Tok.TokenParser ()
lexer = Tok.makeTokenParser style
  where
    style = emptyDef {
              Tok.identLetter    = alphaNum
             }

parens :: Parser a -> Parser a
parens = Tok.parens lexer

commaSep :: Parser a -> Parser [a]
commaSep = Tok.commaSep1 lexer

commaSep1 :: Parser a -> Parser [a]
commaSep1 = Tok.commaSep1 lexer


identifier :: Parser String
identifier = Tok.identifier lexer

reservedOp :: String -> Parser ()
reservedOp = Tok.reservedOp lexer

data Expr = IntLit Int | Ident String | Label String Expr | Func String Expr Expr | ExprList [Expr] deriving (Eq, Ord, Show)


integer :: Parser Integer
integer = Tok.integer lexer

litInt :: Parser Expr
litInt = do
  n <- integer
  return $ IntLit (fromInteger n)

ident :: Parser Expr
ident = Ident <$> identifier

paramLabelItem = litInt <|> paramLabel

paramLabel :: Parser Expr
paramLabel = do
  lbl <- try (identifier <* reservedOp "=")
  body <- paramLabelItem
  return $ Label lbl body

paramItem :: Parser Expr
paramItem = parens paramRecord <|> litInt <|> try paramLabel <|> ident

paramRecord :: Parser Expr
paramRecord = ExprList <$> commaSep1 paramItem

func :: Parser Expr
func = do
  name <- identifier
  params <- paramRecord
  reservedOp "="
  body <- paramRecord
  return $ (Func name params body)


parseExpr :: String -> Either ParseError Expr
parseExpr s = parse func "" s

I can parse foo (x) = 1 but I cannot parse foo x = 1

parseExpr "foo x = 1"
Left (line 1, column 10):
unexpected end of input
expecting digit, "," or "="

I understand it tries to parse this code like Func "foo" (Label "x" 1) and fails. But after fail why it cannot try to parse it like Func "foo" (Ident "x") = 1

Is there any way to do it?

Also I have tried to swap ident and paramLabel

paramItem = parens paramRecord <|> litInt <|> try paramLabel <|> ident
paramItem = parens paramRecord <|> litInt <|> try ident <|> paramLabel

In this case I can parse foo x = 1 but I cannot parse foo (x = 1) = 2

parseExpr "foo (x = 1) = 2"
Left (line 1, column 8):
unexpected "="
expecting "," or ")"
ais
  • 2,514
  • 2
  • 17
  • 24

1 Answers1

2

Here is how I understand how Parsec backtracking works (and doesn't work):

In:

(try a <|> try b <|> c)

if a fails, b will be tried, and if b subsequently fails c will be tried.

However, in:

(try a <|> try b <|> c) >> d

if a succeeds but d fails, Parsec does not go back and try b. Once a succeeded Parsec considers the whole choice as parsed and it moves on to d. It will never go back to try b or c.

This doesn't work either for the same reason:

(try (try a <|> try b <|> c)) >> d

Once a or b succeeds, the whole choice succeeds, and therefor the outer try succeeds. Parsing then moves on to d.

A solution is to distribute d to within the choice:

try (a >> d) <|> try (b >> d) <|> (c >> d)

Now if a succeeds but d fails, b >> d will be tried.

ErikR
  • 51,541
  • 9
  • 73
  • 124