1
votes

A noob Haskell question.

I have had fun writing a few parsers with both Parsec and AttoParsec. I now want to gather information during the parsing process (basically build a symbol table) and using the WriterT monad transformer seems like a good option.

I have this working now in this simple example:

module NewParse where

import qualified Text.ParserCombinators.Parsec as A
import Control.Monad.Trans (lift)
import Control.Monad.Writer (WriterT, tell,  runWriterT)
type WParser a = WriterT [String] A.Parser a 

data StoryToken = StoryToken Char deriving (Show)

getToken :: WParser StoryToken
getToken = do
  tell ["hello from Writer T"]
  c <- lift A.anyChar 
  return $ StoryToken c 

test = A.parse (runWriterT getToken) "story" "#"

It works great. Calling test in ghci gives me this:

*NewParse> test
Right (StoryToken '#',["hello"])

What I'm kind of dreading when applying this to my parser code having to lift the parser monad for every every call it. I'll be calling tell relatively infrequently but parser functions a lot. The code is going to get a lot uglier.

Is there a way of easily exposing certain functions from Parsec so I don't have to use lift in the code?

A solution that worked for me was creating my own function

anyChar = lift A.anyChar

Which is cool but would require creating similar shadow functions for every function I use from parsec.

And I don't mind doing that but just wondered if there was a better, less boiler-platey way of achieving the same thing.

Any help gratefully received :)

1
Generally speaking you wouldn't bother creating a symbol table when parsing with Haskell. With Haskell coding ASTs is easy because algebraic types correspond to trees, and mapping from symbols to anything except monomorphic types is hard. In post processing it is common to have an environment which stores bindings (similar to a symbol table), but environments can usually map identifiers to monotypes - e.g. when implementing a functional language the environment would map to identifiers to expressions. In short I'd recommend you try to do more with your AST and avoid a separate symbol table.stephen tetley

1 Answers

2
votes

Parsec allows for users to carry state through parsers just use

addSym :: String -> Parsec String [String] ()
addSym = void . updateState . (:) -- append a string to the symbol list

type MyParser = Parsec String [String]

This will handle backtracking correctly.