I am writing a Parsec parser for a C-like language and I have several datas that
look more or less like this one: > import Control.Monad( liftM ) > import Text.ParserCombinators.Parsec > data FooBar = Foo | Bar > deriving (Show,Read,Bounded,Enum) Looking at these, it seems that there should be no need to write a parser for this guy by hand. We already know that it is bounded and enumerable, so we can get a list of all possible FooBars: > enumAll :: (Bounded a, Enum a) => [a] > enumAll = enumFromTo minBound maxBound Also, we know how to show and read each FooBar. Therefore, I should get a free parser! > freeParser :: (Enum a, Bounded a, Show a, Read a) => Parser a Here is one use of freeParser: > paramMod = option Foo freeParser > test = parseTest $ do { x <- paramMod; eof; return x } Not suprisingly: test "Foo" => Foo test "Bar" => Bar test "" => Foo I had a little hard time figuring out how this parser should look. The best I came up with was: > freeParser = freeParser' minBound > where enumAll' :: (Bounded a, Enum a) => a -> [a] > enumAll' _ = enumAll > freeParser' :: (Enum a, Bounded a, Show a, Read a) => a -> Parser a > freeParser' x = liftM read $ choice (map (string . show) (enumAll' x)) [Actually, in my code I use reserved' (reserved' x = reserved x >> return x) instead of string, where reserved is from Parsec's builtin tokenizer (which does some neat things behind the curtains). Here string is used just to illustrate the expamle.] The problem is that freeParser, although useful, is far from elegant. It's something that I came up with by trial and error. In short: it's a hack. I would like to hear your suggestions about how it can be beautified. Thank you in advance. Cheers! -- Slavomir Kaslev _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
Slavomir Kaslev wrote:
>> freeParser = freeParser' minBound >> where enumAll' :: (Bounded a, Enum a) => a -> [a] >> enumAll' _ = enumAll >> freeParser' :: (Enum a, Bounded a, Show a, Read a) => a -> Parser a >> freeParser' x = liftM read $ choice (map (string . show) (enumAll' x)) 1. I would use an explicit function argument instead of "Show" to allow strings starting with lower case. 2. Calling read after parsing looks stupid. Just return the value shown as parser result (within map). 3. Instead of the "string" parser, it should be checked if a further alphaNum or '_' follows (for the longest match). And don't forget "try"! Cheers Christian > [Actually, in my code I use reserved' (reserved' x = reserved x >> return x) > instead of string, where reserved is from Parsec's builtin tokenizer (which does > some neat things behind the curtains). Here string is used just to > illustrate the > expamle.] > > The problem is that freeParser, although useful, is far from elegant. It's > something that I came up with by trial and error. In short: it's a hack. > > I would like to hear your suggestions about how it can be beautified. > > Thank you in advance. > > Cheers! > Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
On Mon, Oct 6, 2008 at 8:07 PM, Christian Maeder
<[hidden email]> wrote: > Slavomir Kaslev wrote: >>> freeParser = freeParser' minBound >>> where enumAll' :: (Bounded a, Enum a) => a -> [a] >>> enumAll' _ = enumAll >>> freeParser' :: (Enum a, Bounded a, Show a, Read a) => a -> Parser a >>> freeParser' x = liftM read $ choice (map (string . show) (enumAll' x)) > > 1. I would use an explicit function argument instead of "Show" to allow > strings starting with lower case. > You are right. But that was not the problem. The problem was that I wrestled with Haskell's type system quite a bit to make freeParser work. What I want to write is freeParser :: (Enum a, Bounded a, Show a, Read a) => Parser a freeParser = liftM read $ choice (map (string . show) enumAll) but it doesn't compile. How can I make this piece code work? > 2. Calling read after parsing looks stupid. Just return the value shown > as parser result (within map). > Good point. It is silly =-) > 3. Instead of the "string" parser, it should be checked if a further > alphaNum or '_' follows (for the longest match). And don't forget "try"! > Sure. I actually use Parsec's reserved, which is kind enough to manage all this stuff for me. > Cheers Christian > >> [Actually, in my code I use reserved' (reserved' x = reserved x >> return x) >> instead of string, where reserved is from Parsec's builtin tokenizer (which does >> some neat things behind the curtains). Here string is used just to >> illustrate the >> expamle.] >> >> The problem is that freeParser, although useful, is far from elegant. It's >> something that I came up with by trial and error. In short: it's a hack. >> >> I would like to hear your suggestions about how it can be beautified. >> >> Thank you in advance. >> >> Cheers! >> > -- Slavomir Kaslev _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
At Mon, 6 Oct 2008 20:20:57 +0300,
Slavomir Kaslev wrote: > > On Mon, Oct 6, 2008 at 8:07 PM, Christian Maeder > <[hidden email]> wrote: > > Slavomir Kaslev wrote: > >>> freeParser = freeParser' minBound > >>> where enumAll' :: (Bounded a, Enum a) => a -> [a] > >>> enumAll' _ = enumAll > >>> freeParser' :: (Enum a, Bounded a, Show a, Read a) => a -> Parser a > >>> freeParser' x = liftM read $ choice (map (string . show) (enumAll' x)) > > > > 1. I would use an explicit function argument instead of "Show" to allow > > strings starting with lower case. > > > > You are right. But that was not the problem. The problem was that I > wrestled with Haskell's type system quite a bit to make freeParser > work. What I want to write is > > freeParser :: (Enum a, Bounded a, Show a, Read a) => Parser a > freeParser = liftM read $ choice (map (string . show) enumAll) > > but it doesn't compile. How can I make this piece code work? I would start by adding this to the top of the file: > {-# LANGUAGE ScopedTypeVariables, FlexibleContexts #-} Then add 'forall a.' to the type signature of freeParser. This causes freeParser' and freeParser to have the same value for 'a'. Now you can add an explicit type signature to (enumAll :: [a]) > freeParser :: forall a. (Enum a, Bounded a, Show a, Read a) => Parser a > freeParser = freeParser' minBound > where freeParser' :: (Enum a, Bounded a, Show a, Read a) => a -> Parser a > freeParser' x = liftM read $ choice (map (string . show) (enumAll :: [a])) The reseason you have to explicitly type enumAll is because you do a show and then a read. Looking at a simplified case, we can see that the types in this expression are ambigious: read (show 1.0) show :: (Show a) => a -> String read :: (Read a) => String -> a It is perfectly valid typewise to do,: read (show 1.0) :: Char Of course, that will result in a runtime error: *Main> read (show 1.0) :: Char *** Exception: Prelude.read: no parse If we rewrite freeParser like this, then we don't need any special extensions: > freeParser :: (Enum a, Bounded a, Show a, Read a) => Parser a > freeParser = freeParser' minBound > where freeParser' :: (Enum a, Bounded a, Show a, Read a) => a -> Parser a > freeParser' x = choice (map (\x -> string (show x) >> return x) enumAll) Some might consider this prettier: > freeParser :: (Enum a, Bounded a, Show a, Read a) => Parser a > freeParser = freeParser' minBound > where freeParser' :: (Enum a, Bounded a, Show a, Read a) => a -> Parser a > freeParser' x = choice [ string (show x) >> return x | x <- enumAll ] Anyway, there is another problem -- if you extend you datatype with a constructor Foomatic: > data FooBar = Foo | Foomatic | Bar > deriving (Show,Read,Bounded,Enum) you get the error: test "Foomatic" parse error at (line 1, column 4): unexpected "m" expecting end of input This is because the parser wil successfully parse Foo and so it won't even try parsing foomatic. As a cheap hack we can do this: > freeParser :: (Ord a, Enum a, Bounded a, Show a, Read a) => Parser a > freeParser = freeParser' minBound > where freeParser' :: (Ord a, Enum a, Bounded a, Show a, Read a) => a -> Parser a > freeParser' x = choice [ try (string (show x)) >> return x | x <- reverse $ sort enumAll ] We sort the constructors by reverse alphabetical order so that the parser will try Foomatic before trying Foo. We also need to use the 'try' function so that if Foomatic fails it will still try Foo. This is not a particularily efficient fix though. j. _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
In reply to this post by Slavomir Kaslev
On Mon, Oct 6, 2008 at 9:36 PM, Christian Maeder
<[hidden email]> wrote: > import Text.ParserCombinators.Parsec > > freeParser :: (Enum a, Bounded a, Show a) => Parser a > freeParser = choice $ map (\ a -> try $ do > string $ show a > notFollowedBy (alphaNum <|> char '_') > return a) [minBound .. maxBound] > This is exactly what I needed. Though I still don't quite understand why this works and mine doesn't. > I see not need for extensions. (And I don't like > Text.ParserCombinators.Parsec.Token) > > Where did you get enumAll from? > Mine definition of enumAll was equivаlent to [minBound .. maxBound]: enumAll :: (Bounded a, Enum a) => [a] еnumAll = enumFromTo minBound maxBound > The type of the Parser must be deducible from the context (or given by a > type annotation). > > Cheers C. > > Slavomir Kaslev wrote: >> On Mon, Oct 6, 2008 at 8:07 PM, Christian Maeder >> <[hidden email]> wrote: >>> Slavomir Kaslev wrote: >>>>> freeParser = freeParser' minBound >>>>> where enumAll' :: (Bounded a, Enum a) => a -> [a] >>>>> enumAll' _ = enumAll >>>>> freeParser' :: (Enum a, Bounded a, Show a, Read a) => a -> Parser a >>>>> freeParser' x = liftM read $ choice (map (string . show) (enumAll' x)) >>> 1. I would use an explicit function argument instead of "Show" to allow >>> strings starting with lower case. >>> >> >> You are right. But that was not the problem. The problem was that I >> wrestled with Haskell's type system quite a bit to make freeParser >> work. What I want to write is >> >> freeParser :: (Enum a, Bounded a, Show a, Read a) => Parser a >> freeParser = liftM read $ choice (map (string . show) enumAll) >> >> but it doesn't compile. How can I make this piece code work? >> >>> 2. Calling read after parsing looks stupid. Just return the value shown >>> as parser result (within map). >>> >> >> Good point. It is silly =-) >> >>> 3. Instead of the "string" parser, it should be checked if a further >>> alphaNum or '_' follows (for the longest match). And don't forget "try"! >>> >> >> Sure. I actually use Parsec's reserved, which is kind enough to manage >> all this stuff for me. >> >>> Cheers Christian >>> >>>> [Actually, in my code I use reserved' (reserved' x = reserved x >> return x) >>>> instead of string, where reserved is from Parsec's builtin tokenizer (which does >>>> some neat things behind the curtains). Here string is used just to >>>> illustrate the >>>> expamle.] >>>> >>>> The problem is that freeParser, although useful, is far from elegant. It's >>>> something that I came up with by trial and error. In short: it's a hack. >>>> >>>> I would like to hear your suggestions about how it can be beautified. >>>> >>>> Thank you in advance. >>>> >>>> Cheers! >>>> >> >> >> > Slavomir Kaslev _______________________________________________ Haskell-Cafe mailing list [hidden email] http://www.haskell.org/mailman/listinfo/haskell-cafe |
Free forum by Nabble | Edit this page |