views:

150

answers:

4

This function generates simple .dot files for visualizing automata transition functions using Graphviz. It's primary purpose is debugging large sets of automatically generated transitions (e.g., the inflections of Latin verbs).

prepGraph :: ( ... ) => NFA c b a -> [String]
prepGraph nfa = "digraph finite_state_machine {"
              : wrapSp "rankdir = LR"
              : wrapSp ("node [shape = circle]" ++ (mapSp (states nfa \\ terminal nfa)))
              : wrapSp ("node [shape = doublecircle]" ++ (mapSp $ terminal nfa))
              : formatGraph nfa ++ ["}"]

formatGraph :: ( ... ) => NFA c b a -> [String]
formatGraph = map formatDelta . deltaTuples
 where formatDelta (a, a', bc) = wrapSp (mkArrow a a' ++ " " ++ mkLabel bc)
       mkArrow x y   = show x ++ " -> " ++ show y
       mkLabel (y, z) = case z of
         (Just t) -> "[ label = \"(" ++ show y ++ ", " ++ show t ++ ")\" ]"
         Nothing  -> "[ label = \"(" ++ show y ++ ", " ++ "Null" ++ ")\" ]"

where wrap, wrapSp and mapSp are formatting functions, as is deltaTuples.

The problem is that formatGraph retains double quotes around Strings, which causes errors in Graphviz. E.g., when I print unlines $ prepGraph to a file, I get things like:

0 -> 1 [ label = "('a', "N. SF")" ];

instead of

0 -> 1 [ label = "('a', N. SF)" ];

(However, "Null" seems to work fine, and outputs perfectly well). Now of course the string "N. SF" isn't the actual form I use to store inflections, but that form does include a String or two. So how can I tell Haskell: when you show a String values, don't double-quote it?

+2  A: 

Use dotgen package - it has special safeguards in place to prevent forbidden chars from sneaking into attribute values.

ADEpt
A: 

It seems a little ugly, but you could apply a filter to show t

filter (/='"') (show t)
Matt Ellen
Shouldn't it be `filter (!='"') (show t)`? BTW, this also strips escaped `"` from the input, seems to be not expected like this.
FUZxxl
@FUZxxl: The question just asks for " to be removed. It makes no mention of escaped ".
Matt Ellen
@FUZxxl: remember `(/=) /= (!=)`!
sreservoir
@sreservoir: Yeah...
FUZxxl
+1  A: 

You could define your own typeClass like this:

class GShow a where
   gShow :: a -> String
   gShow = show

instance GShow String where
   show = id

instance GShow Integer
instance GShow Char
-- And so on for all the types you need.

The default implementation for "gShow" is "show", so you don't need a "where" clause for every instance. But you do need all the instances, which is a bit of a drag.

Alternatively you could use overlapping instances. I think (although I haven't tried it) that this will let you replace the list of instances using the default "gShow" by a single line:

instance (Show a) => GShow a

The idea is that with overlapping instances the compiler will chose the most specific instance available. So for strings it will pick the string instance over the more general one, and for everything else the general one is the only one that matches.

Paul Johnson
+1  A: 

Check out how Martin Erwig handled the same problem in Data.Graph.Inductive.Graphviz:

http://hackage.haskell.org/packages/archive/fgl/5.4.2.3/doc/html/src/Data-Graph-Inductive-Graphviz.html

The function you're looking for is "sq" at the bottom:

sq :: String -> String
sq s@[c]                     = s
sq ('"':s)  | last s == '"'  = init s
            | otherwise      = s
sq ('\'':s) | last s == '\'' = init s
            | otherwise      = s
sq s                         = s

(check out the context and adapt for your own code, of course)

Will