3
votes

Here I have in mind that a possible configuration is a tree of specifications, each specification has a corresponding keyword (the string) and type. Something like this:

data Select = And | Or
data ConfigTree = Node Select [ConfigTree] | Leaf (String, *)

I'm not sure how to write this properly, given that there's no "type of types", but nevermind that for the moment.

Now, given such a tree, I want to build a parser that can read a possible valid configuration; I assume I already have sub-parsers that can parse keyword/type pairs.

For instance, a possible configuration tree is:

Node And [ Leaf ("width", Double)
         , Node Or [ Leaf ("height", Double) , Leaf ("aspectratio", Double)
         ]

which can specify the size of a rectangle. A possible configuration file would be, say:

aspectratio = 2
width = 10

(Let's assume that a configuration file is just a list of newline separated pairs, keyword = blah, where blah is something the corresponding parser for that keyword can deal with; but they can be in any order, and just have to match up with one possible "valid subset" of the tree, where a valid subset is any subset containing the top node, that contains all the children of an "and" node it contains, and say exactly one child of an "or" node it contains.)

I have no idea how to even start building such a parser. Can anyone give some tips about how to proceed, or a way to completely restructure the above ConfigTree datatype to something more amenable to parsing?

1

1 Answers

2
votes

The problem with building a parser for this, is that your input format doesn't match your data type at all. The input format is a simple, easily parsable list of key-value pairs while your data type is a tree. E.g. to determine if all the sub-trees in an And node are valid you have to know the complete input.

So, instead of doing validating the list of key-value pairs directly in the parser, just do it afterwards.

I've put together a small example to show what I mean:

data Type = TDouble | TString 
data Select = And | Or 
data ConfigTree = Node Select [ConfigTree] | Leaf (String, Type)

-- matches a list of key-value pairs against a tree
match :: [(String, String)] -> ConfigTree -> Bool
match sts (Leaf (s, t)) = case filter ((== s) . fst) sts of
                            -- we don't want multiple occurences of a key
                            [(_, v)] -> if valid v t then True else False 
                            _        -> False
match sts (Node And cfgs) = and . map (match sts) $ cfgs
-- not completely what you described, because it will match 1 or more
match sts (Node Or cfgs)  = or  . map (match sts) $ cfgs

-- validates a string against a type
valid :: String -> Type -> Bool
valid s TDouble = case reads s :: [(Double, String)] of
                    [(_, "")] -> True
                    _         -> False
valid _ TString = True

-- this is what you actually parsed
config = [ ("aspectratio", "2")
         , ("width", "123")
         , ("name", "Sam")
         ]

-- the example tree
cfgTree = Node And [ Leaf ("width", TDouble)
                   , Node Or [ Leaf ("height", TDouble), Leaf ("aspectratio", TDouble)]
                   ]

I don't think that this is a particularly useful example, because all it does is check if your config data are valid, it doesn't extract them, but I hope it demonstrates what I meant.