Изучение Haskell Parsec и работа с необязательными парсерами

Я переделываю старое домашнее задание, чтобы развлечься, чтобы узнать, как использовать Parsec, и у меня возникли проблемы со структурированием моих синтаксических анализаторов для выходов (и включенного типа данных). Итак, сначала мы получаем файл со списком комнат. Каждая комната содержит название комнаты (ниже - Комната -), некоторое описание или историю, а затем список выходов в формате (direction, destination). В конце концов человек выбирал направление, и вы смотрели на название комнаты и переносили игрока в следующую комнату.

-- 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.

Как видите, в некоторых комнатах нет выходов (я не храню их так, как это было). Так что возможны выходы.

Я дошел до части выхода, и все мои парсеры до этого шага, кажется, работают. Проблема заключается в том, что не может быть выходов или больше одного выхода. Также то, как я обрабатываю выходы, которые я думаю, влияет на то, как я обрабатываю свои типы выходов (также известный как выход типа может стать Может быть [Выход]

В любом случае вот мой код:

--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

В настоящее время я закомментировал комнату, когда тестировал парсеры меньшего размера.

Мой подход:

  1. Сделайте parseExits, которые анализируют - Exits - и «многие из parseExit».
  2. Если --Exit-- не найден, то парсер не работает (тогда я думаю, что он ничего не возвращает)
  3. В парсере oneRoom я ищу 0 или много parseExits или eof (так как я предварительно разбиваю на \ n \ n)

Вопросы:

  1. Как сделать вариант "none" или "many" в соответствии с parsec docs? Может быть, или необязательно, но где это применимо? В выходах или в одной комнате? Документы для легкого доступа
  2. Является ли мой подход к мини-синтаксическим анализаторам правильным способом решения этой проблемы в haskell и parsec?
  3. Наконец, oneRoom в настоящее время получает строки файла, разделенные на \ n \ n, но я думаю, что могу включить это в свой синтаксический анализатор как последнюю строку синтаксического анализатора oneRoom, правильно?
  4. В моем парсере oneRoom я в настоящее время разбираю историю как элемент, заканчивающийся на - Exit - но я не уверен, что история не потребляет следующий - Exits - или eof? Как заставить анализатор истории заканчиваться либо на первом - Exits - он находит, либо на eof (или \ n \ n, если я разбираю весь файл)

Я надеюсь, что мои вопросы и код достаточно ясны, если нет, дайте мне знать, как я могу уточнить. Заранее спасибо.


person prussiap    schedule 19.08.2013    source источник
comment
Просто используйте список для выходов. [] так же хорош, как Nothing. Введение Maybe только усложняет ситуацию, и, кроме того, что будет означать Just []?   -  person firefrorefiddle    schedule 20.08.2013


Ответы (2)


Я не хочу писать для вас весь синтаксический анализатор, поэтому я просто пытаюсь ответить на отдельные вопросы.

  • Как сделать "ни одного" или "много" [...]

Просто используйте many вместо many1.

Чтобы сделать весь блок «- Выходы -» необязательным, вы можете попробовать что-то вроде

exits <- parseExists <|> return []
  • Является ли мой подход к мини-синтаксическим анализаторам правильным способом решения этой проблемы в haskell и parsec?

да. Как правило, старайтесь написать хотя бы один синтаксический анализатор для каждого типа данных, а часто и больше.

  • Наконец, oneRoom в настоящее время получает строки файла, разделенные на \ n \ n, но я думаю, что могу включить это в свой синтаксический анализатор как последнюю строку синтаксического анализатора oneRoom, правильно?

Я думаю, вам не следует использовать split в функции main, а вместо этого просто используйте newline в ваших синтаксических анализаторах везде, где вы хотите иметь символ новой строки.

  • В моем парсере oneRoom я в настоящее время разбираю историю как элемент, заканчивающийся на - Exit - но я не уверен, что история не потребляет следующий - Exits - или eof? Как заставить анализатор истории заканчиваться либо на первом - Exits - он находит, либо на eof (или \ n \ n, если я разбираю весь файл)

Может быть, вам нужно что-то вроде следующего:

-- 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

С такой функцией вы можете использовать manyTill ... endOfStory.

person Toxaris    schedule 20.08.2013

Прошу прощения за задержку с ответом. Я занимался этим в свободное время. Большое спасибо за предложения. После работы с GHCI и изменения некоторых типов данных приведенное ниже решение сработало. Я хотел опубликовать ответ здесь в надежде, что он поможет другим.

    --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

Если вы хотите попробовать несколько комнат, обязательно добавьте новую строку между комнатами.

person prussiap    schedule 28.08.2013