venzone-1.0.0.0: src/Parse.hs
{-# Language LambdaCase #-}
module Parse where
import Tile
import Room
import Story
import Plant
import Meeple.Operate
import Input
import Terminal.Game
import Text.Megaparsec hiding (State)
import Text.Megaparsec.Char
import Lens.Micro.Platform
import qualified Codec.Binary.UTF8.Light as U8
import qualified Control.Monad.State as S
import qualified Data.Void as V
import qualified Data.List.NonEmpty as LN
import qualified Data.Set as DS
-- proparse: we are in between ®®
type PropParse = Bool
data ParseState = ParseState { psCoords :: Coords,
psProp :: PropParse }
deriving (Eq, Show)
-- coords: for map parsing
type Parser a = ParsecT V.Void String (S.State ParseState) a
---------------
-- INTERFACE --
---------------
readStory :: FilePath -> IO Story
readStory fp =
parseRun fp pstory <$> U8.readUTF8File fp
-- run parse and crash on error
parseRun :: FilePath -> Parser a -> String -> a
parseRun fp p t =
let eps = runParserT p fp t
ep = fst $ S.runState eps pstate
a = either (error . (errProl ++) . errorBundlePretty) id ep
in a
where
pstate = ParseState (1, 1) False
errProl = unlines
["",
"",
"Error in parsing Story file!",
"Read what's written below; if the error is unclear or you",
"found a bug, contact me at <fa-ml@ariis.it>.",
""]
-----------
-- STORY --
-----------
pstory :: Parser Story
pstory = option () noncode *> st
<?> "Story file"
where
ps = proom <* (noncode <|> eof)
<?> "room (and maybe noncode)"
st = buildStory <$> pstoryname
<*> (proomsize <* eol)
<*> pstartroom
<* noncode
<*> some ps
<* eof
<?> "Story body"
pstoryname :: Parser String
pstoryname = string "story_name:" *> space1 *> someTill anySingle eol
<?> "story_name instruction"
proomsize :: Parser Coords
proomsize = string "room_size:" >>
space1 >> num >>= \r ->
space1 >> num >>= \c ->
return (r, c)
<?> "room_size instruction"
where
num :: Parser Integer
num = read <$> some digitChar
pstartroom :: Parser String
pstartroom = string "start_room:" *> space1 *> someTill anySingle eol
<?> "start_room instruction"
pcomment :: Parser ()
pcomment = string "@@" *>
manyTill anySingle (() <$ eol <|> eof) *>
pure ()
<?> "Comment"
-- a number of comments/whitespace
noncode :: Parser ()
noncode = () <$ some (space1 <|> pcomment)
<?> "Whitespace or Comment"
----------
-- ROOM --
----------
proom :: Parser Room
proom = ptitle >>= \t ->
S.lift (S.put initState) >>
pelements >>= \es ->
let r = elemsRoom es in
some pexit >>= \xs ->
return (r & title .~ t
& exits .~ xs)
<?> "Room"
where
initState = ParseState (1, 1) False
-- title
ptitle :: Parser String
ptitle = char '#' *> space1 *> someTill anySingle eol
<?> "Room title"
pelements :: Parser [RoomElem]
pelements = some pelem
<?> "Room ASCII elements"
-----------
-- PLANT --
-----------
-- fill architecture/player/baddies
elemsRoom :: [RoomElem] -> Room
elemsRoom ces = foldr elemRoom defaultRoom ces
elemRoom :: RoomElem -> Room -> Room
elemRoom Blank r = r
elemRoom (ETile t cs) r = r & plant %~ addTile cs t
elemRoom (EMeeple m) r = addMeeple m r
-----------
-- EXITS --
-----------
pexit :: Parser Exit
pexit = string ">>>" *> space1 >>
pcard <* space1 >>= \c ->
string "->" *> space1 >>
someTill anySingle fin >>= \t ->
return (Exit c t)
<?> "Room exit (>>>)"
where
fin = () <$ eol <|> eof
pcard :: Parser Cardinal
pcard = choice [N <$ char' 'N',
S <$ char' 'S',
E <$ char' 'E',
W <$ char' 'W']
<?> "Cardinal direction (NWSE)"
--------------
-- ELEMENTS --
--------------
data RoomElem = ETile Tile Coords
| EMeeple Meeple
| Blank
deriving (Show, Eq)
-- to be invoked after succesfull parsing of a char
pcoords :: Parser Coords
pcoords = S.lift (S.gets psCoords) >>= \cs ->
return cs
<?> "ASCII Room coordinates"
pelem :: Parser RoomElem
pelem = choice [try epropmode, try pprop,
try etile, try emeeple,
try eNewLine, espace]
<?> "Room ASCII element (simple)"
eNewLine :: Parser RoomElem
eNewLine = eol >>
advance ARow >>
return Blank
<?> "enew line"
espace :: Parser RoomElem
espace = Blank <$ char ' ' <* advance ACol
<?> "space (none) element"
epropmode :: Parser RoomElem
epropmode = Blank <$ char '®' <*
(toggleprop <* advance ACol)
<?> "text-mode marker"
emeeple :: Parser RoomElem
emeeple = EMeeple <$> pmeeple <* advance ACol
<?> "meeple (creature)"
etile :: Parser RoomElem
etile = ETile <$> ltile <*> pcoords <* advance ACol
<?> "Tile element"
pprop :: Parser RoomElem
pprop = anySingle >>= \c ->
S.lift (S.gets psProp) >>= \case
True -> ETile (creaProp c) <$> pcoords <* advance ACol
False -> expected [c] "prop element (we're not in prop mode)"
<?> "Prop (background) char"
toggleprop :: Parser ()
toggleprop = let tog (ParseState c p) = ParseState c (not p) in
S.lift (S.modify tog)
<?> "Toggle Prop character (after ®)"
-------------------
-- TILES/MEEPLES --
-------------------
ltile :: Parser Tile
ltile = anySingle >>= \c ->
case charTile c of
Just t -> return t
Nothing -> expected [c] "tile"
<?> "Single tile"
pmeeple :: Parser Meeple
pmeeple = anySingle >>= \c ->
pcoords >>= \cs ->
case charMeeple c cs of
Just m -> return m
Nothing -> expected [c] "meeple"
<?> "Meeple"
-- wrong element / expected one
expected :: String -> String -> Parser a
expected cs t = failure (Just . Tokens $ LN.fromList cs)
(DS.fromList [Label $ LN.fromList t])
<?> "expected error"
-----------------
-- ANCILLARIES --
-----------------
data Adv = ARow | ACol
advance :: Adv -> Parser ()
advance ARow = S.lift . S.modify $
\(ParseState (r, _) b) -> ParseState (r+1, 1) b
advance ACol = S.lift . S.modify $
\(ParseState (r, c) b) -> ParseState (r, c+1) b