gdritter repos apicius / 29604bd
Basic working parser for Apicus files Getty Ritter 7 years ago
10 changed file(s) with 317 addition(s) and 0 deletion(s). Collapse all Expand all
1 *~
2 dist-newstyle
3 dist
1 Copyright (c) 2016, Getty Ritter
2 All rights reserved.
3
4 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
6 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
8 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
10 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
12 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1 name: apicus
2 version: 0.1.0.0
3 -- synopsis:
4 -- description:
5 license: BSD3
6 license-file: LICENSE
7 author: Getty Ritter <gettyritter@gmail.com>
8 maintainer: Getty Ritter <gettyritter@gmail.com>
9 copyright: ©2016 Getty Ritter
10 category: Cookery
11 build-type: Simple
12 cabal-version: >= 1.12
13
14 executable apicus
15 hs-source-dirs: src
16 main-is: Main.hs
17 other-modules: Lexer,
18 Parser,
19 AST
20 build-tools: happy, alex
21 default-extensions: OverloadedStrings,
22 ScopedTypeVariables
23 ghc-options: -Wall
24 build-depends: base >=4.7 && <4.9,
25 text,
26 array
27 default-language: Haskell2010
1 egg curry {
2 [8] eggs -> boil 10m -> $a;
3 [2] onions -> mince -> brown &oil -> stir -> $b;
4 [2] garlic cloves + [1] piece ginger + [1] hot pepper + salt + pepper -> grind -> $b;
5 [4] ripe tomatoes -> chop -> $c;
6 $b -> stir &thyme -> $c;
7 $c -> simmer 5m &saffron -> $a;
8 $a -> stir 10m -> simmer 5m &water -> halve -> DONE;
9 }
1 eggplant rougail {
2 [2] eggplants -> scoop flesh, discard skin -> mash -> $a;
3 [2] white onions or shallots -> mince -> $a;
4 [2] hot peppers -> mince -> $a;
5 $a -> mix &[4tbsp] oil -> DONE;
6 }
1 soondubu jigae {
2 [1/2] yellow onion
3 -> dice
4 -> cook 5m
5 -> $chili
6 -> cook 1m
7 -> $zucchini
8 -> stir &salt
9 -> $kimchi
10 -> simmer 2m
11 -> $broth
12 -> boil &salt
13 -> $tofu
14 -> cover with broth, simmer
15 -> $eggs
16 -> cook 2m
17 -> DONE;
18 [2 tbsp] chili paste -> $chili;
19 [1] zucchini -> dice -> $zucchini;
20 [1 cup] kimchi -> chop coarsely -> $kimchi;
21 [2 cups] beef or chicken broth + [1 tsp] soy sauce
22 -> $broth;
23 [16oz] silken tofu -> $tofu;
24 [3] eggs -> $eggs;
25 }
1 module AST where
2
3 import Data.Text (Text)
4
5 data Recipe = Recipe
6 { rName :: Text
7 , rRecipe :: [Step]
8 } deriving (Eq, Show)
9
10 data Step = Step
11 { sInputs :: Input
12 , sActions :: [Action]
13 } deriving (Eq, Show)
14
15 data Input
16 = InpIngredients IngredientList
17 | InpJoin Text
18 deriving (Eq, Show)
19
20 data IngredientList = IngredientList
21 { fromIngredientList :: [Ingredient]
22 } deriving (Eq, Show)
23
24 data Ingredient = Ingredient
25 { iAmount :: Maybe Text
26 , iType :: Text
27 } deriving (Eq, Show)
28
29 data Action
30 = Action Text (Maybe IngredientList)
31 | Join Text
32 | Done
33 deriving (Eq, Show)
1 {
2 {-# OPTIONS -w #-}
3 {-# LANGUAGE OverloadedStrings #-}
4
5 module Lexer where
6
7 import Control.Monad (liftM)
8 import Data.Text (Text)
9 import qualified Data.Text as T
10
11 import Prelude hiding (lex)
12 }
13
14 %wrapper "monadUserState"
15
16 tokens :-
17 $white+ ;
18 "#".* ;
19
20 DONE { lex' TkDone }
21
22 \{ { lex' TkLCurl }
23 \} { lex' TkRCurl }
24 \[ { lex' TkLBrac }
25 \] { lex' TkRBrac }
26 \( { lex' TkLParn }
27 \) { lex' TkRParn }
28 \; { lex' TkSemi }
29 \, { lex' TkComma }
30 \+ { lex' TkPlus }
31 \& { lex' TkAnd }
32
33 \-\> { lex' TkArrow }
34
35 [A-Za-z0-9_$white\,\/]+ { lex (TkText . T.strip) }
36
37 \$[A-Za-z0-9_]+ { lex TkJoin }
38
39 {
40 data Token = Token AlexPosn TkType deriving (Eq, Show)
41
42 data TkType
43 = TkLCurl
44 | TkRCurl
45 | TkLBrac
46 | TkRBrac
47 | TkLParn
48 | TkRParn
49 | TkArrow
50 | TkComma
51 | TkAnd
52 | TkSemi
53 | TkDone
54 | TkPlus
55 | TkText Text
56 | TkJoin Text
57 | TkEOF
58 deriving (Eq, Show)
59
60 data AlexUserState = AlexUserState
61 { filePath :: FilePath
62 } deriving (Eq, Show)
63
64 alexInitUserState :: AlexUserState
65 alexInitUserState = AlexUserState "<unknown>"
66
67 getFilePath :: Alex FilePath
68 getFilePath = liftM filePath alexGetUserState
69
70 setFilePath :: FilePath -> Alex ()
71 setFilePath = alexSetUserState . AlexUserState
72
73 alexMonadScan' :: Alex Token
74 alexMonadScan' = do
75 inp <- alexGetInput
76 sc <- alexGetStartCode
77 case alexScan inp sc of
78 AlexEOF -> alexEOF
79 AlexError (p, _, _, s) ->
80 alexError' p ("lexical error at character '" ++ take 1 s ++ "'")
81 AlexSkip inp' len -> do
82 alexSetInput inp'
83 alexMonadScan'
84 AlexToken inp' len action -> do
85 alexSetInput inp'
86 action (ignorePendingBytes inp) len
87
88 alexError' :: AlexPosn -> String -> Alex a
89 alexError' (AlexPn _ l c) msg = do
90 fp <- getFilePath
91 alexError (fp ++ ":" ++ show l ++ ":" ++ show c ++ ": " ++ msg)
92
93 alexEOF :: Alex Token
94 alexEOF = do
95 (p,_,_,_) <- alexGetInput
96 return (Token p TkEOF)
97
98
99 lex :: (Text -> TkType) -> AlexAction Token
100 lex f = \(p,_,_,s) i -> return (Token p (f (T.pack (take i s))))
101
102 lex' :: TkType -> AlexAction Token
103 lex' = lex . const
104
105 runAlex' :: Alex a -> FilePath -> String -> Either String a
106 runAlex' mote fp input = runAlex input (setFilePath fp >> mote)
107 }
1 module Main where
2
3 import Parser (parseFile)
4
5 main :: IO ()
6 main = do
7 cs <- getContents
8 print (parseFile "[stdin]" cs)
1 {
2 {-# LANGUAGE OverloadedStrings #-}
3
4 module Parser where
5
6 import AST
7 import Lexer
8
9 }
10
11 %name parse
12 %tokentype { Token }
13 %monad { Alex }
14 %lexer { lexwrap } { Token _ TkEOF }
15 %error { happyError }
16
17 %token
18 '{' { Token _ TkLCurl }
19 '}' { Token _ TkRCurl }
20 '[' { Token _ TkLBrac }
21 ']' { Token _ TkRBrac }
22 '(' { Token _ TkLParn }
23 ')' { Token _ TkRParn }
24 ';' { Token _ TkSemi }
25 ',' { Token _ TkComma }
26 '+' { Token _ TkPlus }
27 '&' { Token _ TkAnd }
28
29 '->' { Token _ TkArrow }
30 done { Token _ TkDone }
31
32 text { Token _ (TkText $$) }
33 join { Token _ (TkJoin $$) }
34
35 %%
36
37 file
38 : recipe file { $1 : $2 }
39 | { [] }
40
41 recipe
42 : text '{' steps '}' { Recipe $1 $3 }
43
44 steps
45 : step ';' steps { $1 : $3 }
46 | { [] }
47
48 step
49 : input '->' actions { Step $1 $3 }
50
51 input
52 : ilist { InpIngredients $1 }
53 | join { InpJoin $1 }
54
55 ilist
56 : ingredients { IngredientList $1 }
57
58 ingredients
59 : ingredient '+' ingredients { $1 : $3 }
60 | ingredient { [$1] }
61
62 ingredient
63 : '[' text ']' text { Ingredient (Just $2) $4 }
64 | text { Ingredient Nothing $1 }
65
66 actions
67 : action '->' actions { $1 : $3 }
68 | action { [$1] }
69
70 action
71 : text { Action $1 Nothing }
72 | text '&' ilist { Action $1 (Just $3) }
73 | join { Join $1 }
74 | done { Done }
75
76 {
77
78 lexwrap :: (Token -> Alex a) -> Alex a
79 lexwrap = (alexMonadScan' >>=)
80
81 happyError :: Token -> Alex a
82 happyError (Token p t) =
83 alexError' p ("parse error at token " ++ show t)
84
85 parseFile :: FilePath -> String -> Either String [Recipe]
86 parseFile = runAlex' parse
87 }