Question

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:

  1. Make parseExits that parses -- Exits -- and "many of parseExit".
  2. If --Exit-- isn't found that parser fails (i think it returns Nothing then)
  3. In oneRoom parser I look for 0 or many parseExits or eof (since i'm pre splitting on \n\n)

Questions:

  1. How do you do the none or many according to parsec docs optionMaybe or optional does the trick but where is that applied ? In Exits or one room ? The docs for easy access
  2. Is my approach of dealing with the mini parsers a correct way to deal with this problem in haskell and with parsec?
  3. 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 ?
  4. 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)

I hope my questions and code is clear enough if not please let me know how I can clarify. Thanks in advance.

Was it helpful?

Solution

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.

OTHER TIPS

I apologize for the delay in response. I was doing this in spare time. Many thanks for the suggestions. After puttsing around with GHCI and modifying some Data types the below solution worked. I wanted to post the answer here in the hopes that it helps others.

    --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\n"]

a_new_story = unlines [
      "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 --"]

type Story = String
type Destination = String
data Exit = Exit { direction :: Direction, destination :: Destination } deriving (Ord, Show, Eq)
data Exits = Exits [ Exit ] deriving (Ord, Show, Eq)
data Name = Name String deriving (Ord, Show, Eq)

data Room = Room { name  :: Name
                 , story :: String
                 , exits :: Exits
                 } deriving (Ord, Show, Eq)

data Rooms = Rooms [ Room ] 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\n"

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")
    newline
    return $ Exit direction destination

parseEol :: Parser ()
parseEol = do
    newline
    return $ ()

parseExits :: Parser Exits
parseExits = do
    string "-- Exits --\n"
    exits <- many parseExit
    return $ Exits exits

endOfStory :: Parser ()
endOfStory = lookAhead $
    try (string "-- Room --" >> parseEol) <|>
    try (string "-- Exits --" >> parseEol) <|>
    try (parseEol >> parseEol) <|> eof

roomname = "-- Room --\nCell\n"

parseName :: Parser Name
parseName = do
    string "-- Room --\n"
    name <- many (noneOf "\n")
    newline
    return $ Name name

oneRoom :: Parser Room
oneRoom = do
    name <- parseName
    story <- manyTill anyChar endOfStory
    exits <- parseExits <|> return (Exits [])
    newline
    return $ Room name story exits

manyRooms :: Parser Rooms
manyRooms = do
    rooms <- many oneRoom
    return $ Rooms rooms

If you want to try a few rooms make sure to add a newline between the rooms

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top