I'm redoing an old homework assignment for fun to learn how to use Parsec and I'm having trouble structuring my parsers for Exits (and the included datatype). So first we get a file with a list of rooms. Each room contains a room name (below -- Room --), some description or story and then a list of Exits in the format of (direction, destination)
. Eventually a person would choose a direction and you'd look up room name and bring the player to the next room.
-- Room --
Center
You are in a square room. There are doors to the
north, south, east, and west.
-- Exits --
North: North Hall
South: South Room
East: East Room
West: West Room
-- Room --
North Hall
You are in a hallway. There are doors to the north and south.
As you can see some Rooms have No exits (Nothing is how i'm storing it). So there could be Maybe Exits.
I have gotten as far as the exits part and all my parsers before that step seem to work. The problem is handling the fact that there could be no exits or more than one exit. Also how I handle the Exits I think affects how I handle my Exits types (aka type Exits might become Maybe [ Exit ]
Anyway here is my code:
--module Loader
-- Game parser will go here
--
import Text.ParserCombinators.Parsec
import Data.List.Split
astory = unlines [" -- Room --",
"Cell",
"You have been locked in a dungeon cell. The prisoner next",
"to you whipsers, there's a tunnel behind the bed on the",
"south wall. Good Luck!",
"-- Exits --",
"South: Tunnel"]
type Rooms = [Room]
type Story = String
type Destination = String
data Exit = Exit { direction :: Direction, destination :: Destination } deriving (Ord, Show, Eq)
type Exits = [ Exit ]
data Room = Room { name :: String
, story :: String
, exits :: Exits
} deriving (Ord, Show, Eq)
data Direction = Nothing | North | South | East | West | Quit deriving (Ord, Show, Eq)
an_exit = "North: Tunnel \n"
a_full_exit = "-- Exits --\nSouth: Tunnel"
directionParser :: Parser Direction
directionParser = (string "South:" >> return South)
<|> (string "North:" >> return North)
<|> (string "East:" >> return East)
<|> (string "West:" >> return West)
parseExit :: Parser Exit
parseExit = do
direction <- directionParser
spaces
destination <- many (noneOf "\n")
return $ Exit direction destination
parseExits :: Parse Exits
parseExits = do
string "-- Exits --\n"
--oneRoom :: Parser Room
--oneRoom = do
-- string "--Room--\n"
-- name <- many (noneOf "\n")
-- newline
-- story <- manyTill anyChar (string "-- Exits --")
-- optionMaybe (string "-- Exits --")
-- exits <- optionMaybe $ many1 anyExits
-- return $ Room name story exits
--main = do
-- file <- readFile "cave.adventure"
-- let stories = splitOn "\n\n" file
-- mapM putStrLn stories
I currently commented out the room as I was testing the smaller parsers.
My Approach:
Questions:
I hope my questions and code is clear enough if not please let me know how I can clarify. Thanks in advance.
I don't want to write the whole parser for you, so I'm just trying to answer the individual questions.
- How do you do the none or many [...]
Just use many
instead of many1
.
To make the whole "-- Exits --" block optional, you can try something like
exits <- parseExists <|> return []
- Is my approach of dealing with the mini parsers a correct way to deal with this problem in haskell and with parsec?
Yes. As a rule of thumb, try to write at least one parser for every datatype, often even more.
- Finally oneRoom currently receives the file strings split on \n\n but I think I can include that in my parser as the last line of the oneRoom parser correct?
I think you shouldn't use split
in the main
function but instead just use newline
in your parsers wherever you want to have a newline character.
- In my oneRoom parser I'm currently parsing Story as the element that ends at -- Exit -- but i'm not convinced that story doesn't consume the next -- Exits -- or the eof? How do you get your Story parser to end at either the first -- Exits -- it finds or the eof(or \n\n if I parse the full file)
Maybe you want something like the following:
-- succeeds if we're at the end of story
-- never consumes any input
endOfStory :: Parser ()
endOfStory = lookAhead $
try (string "-- Room --" >> newline) <|>
try (string "-- Exits --" >> newline) <|>
try (newline >> newline) <|>
eof
With a function like this, you could use manyTill ... endOfStory
.