给定标记列表生成解析器
Generating a parser given a list of tokens
背景
我正在尝试使用 Parsec 实现日期打印和解析系统。
我成功实现了
类型的打印功能
showDate :: String -> Date -> Parser String
它会解析一个格式化字符串,并根据格式化字符串所呈现的标记创建一个新字符串。
例如
showDate "%d-%m-%Y" $ Date 2015 3 17
有输出Right "17-3-2015"
我已经编写了一个分词器用于 showDate
函数,所以我认为我可以使用它的输出以某种方式使用函数 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
我的直觉表明我可以使用某种 concat/map/fold 使用 monad 绑定,但我不知道。
问题
parsec 是正确的工具吗?
我的方法是复杂的还是无效的?
如果没有,我该如何实现这个功能?
如果是这样,我应该尝试做什么?
您的 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)}
我们将开始用 DateState Nothing Nothing Nothing
解释 [Token]
。
每个 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]
无效。我们也可以提前检查一下。如果您想将无效日期错误包含为解析错误,这也是检查 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
背景
我正在尝试使用 Parsec 实现日期打印和解析系统。
我成功实现了
类型的打印功能showDate :: String -> Date -> Parser String
它会解析一个格式化字符串,并根据格式化字符串所呈现的标记创建一个新字符串。
例如
showDate "%d-%m-%Y" $ Date 2015 3 17
有输出Right "17-3-2015"
我已经编写了一个分词器用于 showDate
函数,所以我认为我可以使用它的输出以某种方式使用函数 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
我的直觉表明我可以使用某种 concat/map/fold 使用 monad 绑定,但我不知道。
问题
parsec 是正确的工具吗?
我的方法是复杂的还是无效的?
如果没有,我该如何实现这个功能?
如果是这样,我应该尝试做什么?
您的 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)}
我们将开始用 DateState Nothing Nothing Nothing
解释 [Token]
。
每个 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]
无效。我们也可以提前检查一下。如果您想将无效日期错误包含为解析错误,这也是检查 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