Early explorations into building a reverse tree from the recipe
Getty Ritter
8 years ago
1 | 1 | module Main where |
2 | 2 | |
3 | import Data.List (intercalate) | |
4 | import qualified Data.Text as T | |
5 | import Data.Text (Text) | |
6 | ||
7 | import AST | |
3 | 8 | import Parser (parseFile) |
4 | 9 | |
5 | 10 | main :: IO () |
6 | 11 | main = do |
7 | 12 | cs <- getContents |
8 |
|
|
13 | case parseFile "[stdin]" cs of | |
14 | Left err -> putStrLn err | |
15 | Right recipes -> | |
16 | putStrLn (unlines (map showRecipeGraph recipes)) | |
17 | ||
18 | -- An 'ActionChunk' represents a set of actions in between two join | |
19 | -- points. This is 'reversed' from what we'd expect: the 'name' is | |
20 | -- actually the name of the join point at the end of a sequence of | |
21 | -- actions, or the string DONE, while the 'prev' is the name of the | |
22 | -- join point that came at the beginning, or the ingredients list | |
23 | -- that started the rule. The actions also will appear in reverse | |
24 | -- order. | |
25 | ||
26 | -- Maybe an explanation is in order: this rule | |
27 | -- ingredients -> a -> $x -> b -> c -> $y; | |
28 | -- will produce two ActionChunks: | |
29 | -- ActionChunk $y [c, b] (Right $x) | |
30 | -- and | |
31 | -- ActionChunk $x [a] (Left ingredients) | |
32 | data ActionChunk = ActionChunk | |
33 | { acName :: Text | |
34 | , acRules :: [Text] | |
35 | , acPrev :: Either IngredientList Text | |
36 | } deriving (Eq, Show) | |
37 | ||
38 | -- This is the function that actually splits apart the action into | |
39 | -- ActionChunks. It's grosser than I'd hoped, but it's mostly a lot | |
40 | -- of fiddly but straightforward traversing. | |
41 | splitApart :: Either IngredientList Text -> [Action] -> [ActionChunk] | |
42 | splitApart i = toChunk [] . reverse | |
43 | where toChunk cs (Join t:xs) = | |
44 | gather t xs [] cs | |
45 | toChunk cs (Action "DONE" _:xs) = | |
46 | gather "DONE" xs [] cs | |
47 | toChunk cs (Done:xs) = | |
48 | gather "DONE" xs [] cs | |
49 | toChunk _ (Action _ _:_) = | |
50 | error "expected chunk to end with a join or DONE" | |
51 | toChunk cs [] = cs | |
52 | gather n xs@(Join t:_) as cs = | |
53 | toChunk (ActionChunk n (reverse as) (Right t) : cs) xs | |
54 | gather n (Action t _:xs) as cs = | |
55 | gather n xs (t:as) cs | |
56 | gather _ (Done:_) _ _ = | |
57 | error "unsure how to handle this case" | |
58 | gather n [] as cs = | |
59 | ActionChunk n (reverse as) i : cs | |
60 | ||
61 | -- Here we take a recipe and pull all the ActionChunks into a single | |
62 | -- list. | |
63 | getChunks :: Recipe -> [ActionChunk] | |
64 | getChunks Recipe { rRecipe = st } = | |
65 | mconcat (map getActions st) | |
66 | where getActions (Step (InpJoin t) as) = splitApart (Right t) as | |
67 | getActions (Step (InpIngredients is) as) = splitApart (Left is) as | |
68 | ||
69 | -- The ReverseGraph is a tree rooted at the DONE node. The 'children' | |
70 | -- are actually the steps leading up to a given node. Only childless | |
71 | -- nodes should have an IngredientList associated with them, but we | |
72 | -- don't encode this invariant in the type. | |
73 | data ReverseGraph = ReverseGraph | |
74 | { rStep :: Either IngredientList Text | |
75 | , rPrevs :: [ReverseGraph] | |
76 | } deriving (Eq, Show) | |
77 | ||
78 | -- Take a list of ActionChunks and stitch them back together so that | |
79 | -- we can build a ReverseGraph of them. Again, fiddly but straightforward | |
80 | -- traversing of the data structures. | |
81 | buildReverseGraph :: [ActionChunk] -> ReverseGraph | |
82 | buildReverseGraph as = ReverseGraph (Right "DONE") | |
83 | (concat (map buildFrom (findChunks "DONE"))) | |
84 | where findChunks n = [ chunk | chunk <- as, acName chunk == n ] | |
85 | buildFrom (ActionChunk _ rs p) = go rs p | |
86 | go [] (Right p) = concat $ map buildFrom (findChunks p) | |
87 | go [] (Left i) = [ReverseGraph (Left i) []] | |
88 | go (r:rs) p = [ReverseGraph (Right r) (go rs p)] | |
89 | ||
90 | -- Prettily convert a ReverseGraph to a readable tree. This will give | |
91 | -- us a recipe tree in reverse order, starting with the DONE, and | |
92 | -- gradually going back to the ingredients. | |
93 | prettyGraph :: ReverseGraph -> String | |
94 | prettyGraph = go 0 | |
95 | where go n (ReverseGraph t rs) = | |
96 | indent n ++ stepName t ++ "\n" ++ concat (map (go (n+2)) rs) | |
97 | indent n = replicate n ' ' | |
98 | stepName (Right t) = T.unpack t | |
99 | stepName (Left (IngredientList is)) = | |
100 | intercalate "; " [ ingName i | i <- is ] | |
101 | ingName (Ingredient (Just amt) name) = | |
102 | T.unpack amt ++ " " ++ T.unpack name | |
103 | ingName (Ingredient Nothing name) = | |
104 | T.unpack name | |
105 | ||
106 | showRecipeGraph :: Recipe -> String | |
107 | showRecipeGraph = prettyGraph . buildReverseGraph . getChunks |