我正在尝试使用Parsec实现日期打印和解析系统。
我已经成功实现了
类型的打印功能 showDate :: String -> Date -> Parser String
它需要解析格式化字符串,并根据格式化字符串显示的标记创建一个新字符串。
例如
showDate "%d-%m-%Y" $ Date 2015 3 17
的输出为Right "17-3-2015"
我已经编写了一个在showDate
函数中使用的tokenizer,所以我认为我可以使用它的输出以某种方式使用函数readDate :: [Token] -> Parser Date
生成一个解析器。我的想法很快就停止了,因为我意识到我不知道如何实现它。
假设我们有以下功能和类型(实现无关紧要):
data Token = DayNumber | Year | MonthNumber | DayOrdinal | Literal String
-- Parses four digits and returns an integer
pYear :: Parser Integer
-- Parses two digits and returns an integer
pMonthNum :: Parser Int
-- Parses two digits and returns an integer
pDayNum :: Parser Int
-- Parses two digits and an ordinal suffix and returns an integer
pDayOrd :: Parser Int
-- Parses a string literal
pLiteral :: String -> Parser String
解析器readDate [DayNumber,Literal "-",MonthNumber,Literal "-",Year]
应该等同于
do
d <- pDayNum
pLiteral "-"
m <- pMonthNum
pLiteral "-"
y <- pYear
return $ Date y m d
同样,解析器readDate [Literal "~~", MonthNumber,Literal "hello",DayNumber,Literal " ",Year]
应该等同于
do
pLiteral "~~"
m <- pMonthNum
pLiteral "hello"
d <- pDayNum
pLiteral " "
y <- pYear
return $ Date y m d
我的直觉表明使用monad绑定有一种concat / map / fold,我可以用它,但我不知道。
parsec是否是正确的工具?
我的方法是错综复杂还是无效?
如果没有,我该如何实现此功能?
如果是这样,我应该尝试做什么?
答案 0 :(得分:4)
您的Token
是日语格式[Token]
的小语言说明。
import Data.Functor
import Text.Parsec
import Text.Parsec.String
data Date = Date Int Int Int deriving (Show)
data Token = DayNumber | Year | MonthNumber | Literal String
为了解释这种语言,我们需要一种表示解释器状态的类型。我们开始不了解Date
的任何组件,然后在遇到DayNumber
,Year
或MonthNumber
时发现它们。以下DateState
表示了解或不了解Date
的每个组件的状态。
data DateState = DateState {dayState :: (Maybe Int), monthState :: (Maybe Int), yearState :: (Maybe Int)}
我们将开始使用[Token]
解释DateState Nothing Nothing Nothing
。
每个Token
都将转换为一个读取DateState
的函数,并生成一个计算新DateState
的解析器。
readDateToken :: Token -> DateState -> Parser DateState
readDateToken (DayNumber) ds =
do
day <- pNatural
return ds {dayState = Just day}
readDateToken (MonthNumber) ds =
do
month <- pNatural
return ds {monthState = Just month}
readDateToken (Year) ds =
do
year <- pNatural
return ds {yearState = Just year}
readDateToken (Literal l) ds = string l >> return ds
pNatural :: Num a => Parser a
pNatural = fromInteger . read <$> many1 digit
要阅读解释[Token]
的日期,我们将首先将其转换为函数列表,以决定如何根据map readDateToken :: [Token] -> [DateState -> Parser DateState]
的当前状态解析新状态。然后,从以初始状态return (DateState Nothing Nothing Nothing)
成功的解析器开始,我们将所有这些函数与>>=
绑定在一起。如果生成的DateState
没有完全定义Date
,我们会抱怨[Token]
s无效。我们也可以提前检查一下。如果您希望将无效日期错误包括为解析错误,那么也可以检查Date
是否有效并且不代表4月31日之前不存在的日期。
readDate :: [Token] -> Parser Date
readDate tokens =
do
dateState <- foldl (>>=) (return (DateState Nothing Nothing Nothing)) . map readDateToken $ tokens
case dateState of
DateState (Just day) (Just month) (Just year) -> return (Date day month year)
_ -> fail "Date format is incomplete"
我们将举几个例子。
runp p s = runParser p () "runp" s
main = do
print . runp (readDate [DayNumber,Literal "-",MonthNumber,Literal "-",Year]) $ "12-3-456"
print . runp (readDate [Literal "~~", MonthNumber,Literal "hello",DayNumber,Literal " ",Year]) $ "~~3hello12 456"
print . runp (readDate [DayNumber,Literal "-",MonthNumber,Literal "-",Year,Literal "-",Year]) $ "12-3-456-789"
print . runp (readDate [DayNumber,Literal "-",MonthNumber]) $ "12-3"
这导致以下输出。请注意,当我们要求阅读Year
两次时,两年中的第二年用于Date
。您可以通过修改readDateToken
的定义并可能修改DateState
类型来选择不同的行为。当[Token]
没有指定如何阅读其中一个日期字段时,我们会收到错误Date format is incomplete
并略有不正确的描述;这可以改进。
Right (Date 12 3 456)
Right (Date 12 3 456)
Right (Date 12 3 789)
Left "runp" (line 1, column 5):
unexpected end of input
expecting digit
Date format is incomplete