> {-# OPTIONS_HADDOCK hide,show-extensions #-} > {-| > Module : LTK.Porters.EggBox > Copyright : (c) 2022-2023 Dakotah Lambert > License : MIT > > This module provides a mechanism to display the egg-box representation > for a syntactic monoid. This is an export-only format, as information > is lost. > > @since 1.1 > -} > module LTK.Porters.EggBox ( exportEggBox ) where > import Data.List (intercalate, nub) > import Data.Maybe (mapMaybe) > import Data.Set (Set) > import qualified Data.Set as Set > import LTK.FSA > import LTK.Algebra (SynMon, idempotents) > -- |Draw the egg-box representation of the given monoid > -- in GraphViz @dot@ format. > exportEggBox :: (Ord n, Ord e, Show e) => SynMon n e -> String > exportEggBox m > = unlines > ([ "digraph {", "node [shape=plaintext]", "edge [dir=none]" ] > ++ sts > ++ map (uncurry showtr) (reduce g) > ++ ["}"] > ) > where js = zip (map show [1::Integer ..]) . Set.toList > $ jEquivalence m > sts = map > (\(x,y) -> x ++ "[label=<" > ++ constructTable m y ++ ">];") > js > ps = pairs js > g = mapMaybe (uncurry f) ps > f x y > | x2 `Set.isSubsetOf` y2 = Just (fst y, fst x) > | y2 `Set.isSubsetOf` x2 = Just (fst x, fst y) > | otherwise = Nothing > where x2 = primitiveIdeal2 m (Set.findMin $ snd x) > y2 = primitiveIdeal2 m (Set.findMin $ snd y) > showtr x y = x ++ " -> " ++ y ++ ";" > pairs :: [a] -> [(a,a)] > pairs (x:xs) = map ((,) x) xs ++ pairs xs > pairs _ = [] > constructTable :: (Ord n, Ord e, Show e) => > SynMon n e -> Set (State ([Maybe n], [Symbol e])) > -> String > constructTable m j > = unlines ([ ""] > ++ concatMap (lines . constructRow m) rs > ++ [ "
" ]) > where rs = Set.toList $ partitionBy (primitiveIdealR m) j > attrs = [ "BORDER=\"0\"" > , "CELLBORDER=\"1\"" > , "CELLSPACING=\"0\"" > ] A row is an R-class. But a column is an L-class, so we have to make certain that the cells are generated in a consistent order. > constructRow :: (Ord n, Ord e, Show e) => > SynMon n e -> Set (State ([Maybe n], [Symbol e])) > -> String > constructRow m r > = unlines ([""] > ++ map (constructCell m) ls > ++ [""]) > where ls = map (Set.map snd) . Set.toAscList $ partitionBy fst ls' > ls' = Set.map (\x -> (primitiveIdealL m x, x)) r A cell is an H-class. Idempotent elements are marked by a star. The most intensive part of `constructCell` is one of design principle: I want to visually see if identity is reachable in a star-free system. If there is a nonsalient symbol, that symbol is used for identity. Otherwise, we use □ symbol. > constructCell :: (Ord n, Ord e, Show e) => > SynMon n e -> Set (State ([Maybe n], [Symbol e])) > -> String > constructCell m h = "" ++ intercalate "
" h' ++ "" > where h' = map (display . snd . nodeLabel) $ Set.toList h > display x > | not (null x) = intercalate "\x2009" > (mapMaybe (toMaybe . fmap showish) x) > ++ if x `Set.member` i then "*" else "" > | otherwise = (case t of > ((Symbol n):_) -> showish n > _ -> "□") ++ "*" > where t = map edgeLabel > . filter ((/= Epsilon) . edgeLabel) > . filter ((`Set.member` initials m) > . destination) > . filter ((`Set.member` initials m) . source) > . Set.toList $ transitions m > toMaybe (Symbol a) = Just a > toMaybe _ = Nothing > showish x = deescape . filter (/= '"') $ show x > i = Set.map (snd . nodeLabel) > (initials m `Set.union` idempotents m) If you show a string, quotes and some other symbols get escaped. Undo that. A better approach would be to not use Show to begin with, but that makes the system less generic, so we accept the burden. > deescape :: String -> String > deescape ('\\' : '&' : xs) = deescape xs > deescape ('\\' : x : xs) > | isEmpty digits = x : deescape xs > | otherwise = toEnum (read digits) : deescape others > where (digits, others) = span (isIn "0123456789") (x:xs) > deescape (x:xs) = x : deescape xs > deescape _ = [] Compute the transitive reduction of an acyclic graph which is specified by source/destination pairs. The precondition, that the graph be acyclic, is not checked. > reduce :: (Eq a) => [(a,a)] -> [(a,a)] > reduce ps = [(x,y) | x <- nodes, y <- nodes, y `elem` expand x, > all ((`notElem` ps) . flip (,) y) $ expand x] > where nodes = nub $ map fst ps ++ map snd ps > expand p = let n = map snd $ filter ((p ==) . fst) ps > in n ++ concatMap expand n