read
- это частичная функция с типом Read a => String -> a
, которая выдает исключение при сбое синтаксического анализа.Обычно вы хотите избежать этого (используйте readMaybe
вместо этого, если у вас есть строка).String
и L.Text
- это разные типы, поэтому вы получаете сообщение об ошибке.
В вашем примере кода отсутствует дополнительный )
после trans-func
.
IЯ использую пакет Megaparsec, который обеспечивает простой способ работы с комбинаторами синтаксического анализа.Автор библиотеки написал более длинное руководство здесь .
Основная идея состоит в том, что Parser a
- это тип значения, которое может анализировать что-то типа a
.В Text.Megaparsec
есть несколько функций, которые вы можете использовать (parse
, parseMaybe
и т. Д.), Чтобы «запустить» синтаксический анализатор для «строкового» типа данных (например, String
или строго / ленивый Text
).
Когда вы используете do
обозначение для IO
, это означает «выполнять одно действие за другим».Точно так же вы можете использовать do
нотацию с Parser
, это означает «разобрать эту вещь, а затем проанализировать следующую вещь».
p1 *> p2
означает запустить анализатор p1
, запустить p2
и вернуть результат выполнения p2
.p1 <* p2
означает запустить анализатор p1
, запустить p2
и вернуть результат выполнения p1
.Вы также можете посмотреть документацию по Hoogle , если у вас возникли проблемы с пониманием чего-либо.
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
-- In practice, many of these imports would be unqualified, but I've
-- opted for explicitness for clarity.
import Control.Applicative (empty, many, some, (<*), (*>))
import Control.Exception (try, IOException)
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import Data.Text (Text)
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import qualified Text.Megaparsec as MP
import qualified Text.Megaparsec.Char as MPC
import qualified Text.Megaparsec.Char.Lexer as MPCL
type Q = Text
type E = Char
data EntryDFA = EntryDFA
{ state :: Set Q
, alpha :: Set E
, delta :: Set (Q,E,Q)
, start :: Q
, final :: Set Q
} deriving Show
inputFile = "foo.sexp"
main :: IO ()
main = do
-- read file and check for exception instead of checking if
-- it exists and then trying to read it
result <- try (TIO.readFile inputFile)
case result of
Left e -> print (e :: IOException)
Right txt -> do
case MP.parse dfaParser inputFile txt of
Left e -> print e
Right dfa -> print dfa
type Parser = MP.Parsec () Text
-- There are no comments in the S-exprs, so leave those empty
spaceConsumer :: Parser ()
spaceConsumer = MPCL.space MPC.space1 empty empty
symbol :: Text -> Parser Text
symbol txt = MPCL.symbol spaceConsumer txt
parens :: Parser a -> Parser a
parens p = MP.between (symbol "(") (symbol ")") p
setP :: Ord a => Parser a -> Parser (Set a)
setP p = do
items <- parens (p `MP.sepBy1` (symbol ","))
return (Set.fromList items)
pair :: Parser a -> Parser b -> Parser (a, b)
pair p1 p2 = parens $ do
x1 <- p1
x2 <- symbol "," *> p2
return (x1, x2)
stateP :: Parser Text
stateP = do
c <- MPC.letterChar
cs <- many MPC.alphaNumChar
return (T.pack (c:cs))
dfaParser :: Parser EntryDFA
dfaParser = do
() <- spaceConsumer
(_, state) <- pair (symbol "states") (setP stateP)
(_, alpha) <- pair (symbol "alpha") (setP alphaP)
(_, delta) <- pair (symbol "trans-func") (setP transFuncP)
(_, start) <- pair (symbol "start") valP
(_, final) <- pair (symbol "final") (setP valP)
return (EntryDFA {state, alpha, delta, start, final})
where
alphaP :: Parser Char
alphaP = MPC.letterChar <* spaceConsumer
transFuncP :: Parser (Text, Char, Text)
transFuncP = parens $ do
s1 <- stateP
a <- symbol "," *> alphaP
s2 <- symbol "," *> stateP
return (s1, a, s2)
valP :: Parser Text
valP = fmap T.pack (some MPC.digitChar)