String 根据规则集,将字符串标记为字符串列表
我有一个字符串需要按照以下规则标记到列表中:String 根据规则集,将字符串标记为字符串列表,string,haskell,compiler-construction,String,Haskell,Compiler Construction,我有一个字符串需要按照以下规则标记到列表中: “(”和“)”括号 可能有小数点的数字 运算符(+、/、*、>=,等等) 以字母开头的标识符,字符串的其余部分可以是字母或数字 为此,我在Haskell代码中将上述规则创建为有限状态机: data FsaState = R | Q -- start state: Q; success state R; deriving Show 输入: tokenize “( (23.5+age) ∗ (20.99+adres))” 输出: [ “(”,
- “(”和“)”括号
- 可能有小数点的数字
- 运算符(+、/、*、>=,等等)
- 以字母开头的标识符,字符串的其余部分可以是字母或数字
data FsaState = R | Q -- start state: Q; success state R;
deriving Show
输入:
tokenize “( (23.5+age) ∗ (20.99+adres))”
输出:
[ “(”, “ ”, “(”, “23.5”, “+”, “age”, “)”, “ ”, “∗”, “ ”, “(”, “20.99”, “+”, “adres”, “)”, “)” ]
(可能只使用空格过滤掉字符串)
我应该如何开始?我陷入了命令式思维,因为Haskell不是我的主要语言。如果您担心效率,您可能应该定义一个令牌数据类型(
数据令牌=…
)。这就是说,这里有一个最小的标记器,它大致完成了您想要的功能。它以递归方式工作(tail),为每个递归调用咀嚼一个标记(或空格)
我选择放弃空白,而不是将其作为标记
import Data.Char
tokenize :: String -> [String]
tokenize "" = []
tokenize (c:cs)
| isSpace c = tokenize cs
| isAlpha c = let (i,cs') = span isAlphaNum cs in (c : i) : tokenize cs'
| isDigit c = let (n,cs') = span isDigit cs
in case cs' of
('.':cs'') -> let (m,cs''') = span isDigit cs''
in (c : n ++ "." ++ m) : tokenize cs'''
_ -> (c : n) : tokenize cs'
| c `elem` "+/*-()" = [c] : tokenize cs
| otherwise = error $ "unexpected character " ++ show c
以下是行动:
ghci> tokenize "( (23.5+age) ∗ (20.99+adres))"
["(","(","23.5","+","age",")","*","(","20.99","+","adres",")",")"]
这就是说:我强烈建议您要么编写一个解析monad(类似于
数据解析器a=Parser{runParser::String->Maybe(a,String)}
),这样您就可以单体编写解析器,要么使用现有的库/工具(具体请参见Alex和megaparsec
)如果您担心效率,您可能应该定义一个令牌数据类型(数据令牌=…
)。这就是说,这里有一个最小的标记器,它大致完成了您想要的功能。它以递归方式工作(tail),为每个递归调用咀嚼一个标记(或空格)
我选择放弃空白,而不是将其作为标记
import Data.Char
tokenize :: String -> [String]
tokenize "" = []
tokenize (c:cs)
| isSpace c = tokenize cs
| isAlpha c = let (i,cs') = span isAlphaNum cs in (c : i) : tokenize cs'
| isDigit c = let (n,cs') = span isDigit cs
in case cs' of
('.':cs'') -> let (m,cs''') = span isDigit cs''
in (c : n ++ "." ++ m) : tokenize cs'''
_ -> (c : n) : tokenize cs'
| c `elem` "+/*-()" = [c] : tokenize cs
| otherwise = error $ "unexpected character " ++ show c
以下是行动:
ghci> tokenize "( (23.5+age) ∗ (20.99+adres))"
["(","(","23.5","+","age",")","*","(","20.99","+","adres",")",")"]
这就是说:我强烈建议您要么编写一个解析monad(类似于
数据解析器a=Parser{runParser::String->Maybe(a,String)}
),这样您就可以单体编写解析器,要么使用现有的库/工具(具体请参见Alex和megaparsec
)表示状态机的一个好方法是使用折叠。下面是来自数据的foldl'
类型。列表
,一个严格的左折叠:
这种类型非常通用,因此它可能有助于看到它的专门性,使用t~[]
和a~Char
:
foldl' :: (b -> Char -> b) -> b -> [Char] -> b
(还记得type String=[Char]
)
因此foldl'步骤开始输入
将对输入
的每个字符运行步骤
功能,从开始
状态开始,并返回最终状态。我们可以介绍一种表示机器可能状态的类型,正如您在问题中开始所做的:
data State
= StartState
| NameState String
| IntegerState String
| DecimalState String
| OperatorState String
| …
但是,我们的step
函数不能只是类型State->Char->State
,因为我们还希望累积一个令牌列表作为输出。我们可以只使用一对状态和累加器:
step :: (State, [Token]) -> Char -> (State, [Token])
(为了简单起见,我假设type Token=String
,但实际上您可能希望使用代数数据类型,例如data Token=LeftParen | RightParen | Name String | Integer Int |……
)
因此,目前的基本结构是:
import Data.Char
import Data.List (foldl')
data State = …
tokenize :: String -> [Token]
tokenize input = reverse $ snd $ foldl' step (StartState, []) input
where
step :: (State, [Token]) -> Char -> (State, [Token])
(我们在末尾使用reverse
,因为我们将通过将令牌预先添加到列表(O(1))而不是附加(O(n))来累积令牌。)
现在您只需填写案例即可继续操作-步骤定义机器中状态之间的转换。当我们想要发出令牌时,我们返回一个新的令牌列表:
step (StartState, tokens) '(' = (StartState, "(" : tokens)
step (StartState, tokens) ')' = (StartState, ")" : tokens)
当我们想要执行状态转换时,我们会返回一个新状态:
step (StartState, tokens) char
-- Whitespace is ignored, looping back to the start state.
| isSpace char = (StartState, tokens)
-- Letters cause a transition to the “name” state.
| isLetter char = (NameState [char], tokens)
-- Digits, to the “integer” state.
| isDigit char = (IntegerState [char], tokens)
-- And so on.
| char `elem` "+-*/^" = (OperatorState [char], tokens)
| …
然后,它只是为所有其他状态实现转换的问题。为简化此操作,打开-Wall
将生成有关您未处理的案例的警告
例如,下面是如何实现NameState
转换:
step (NameState name, tokens) char
| isLetter char || isDigit char = (NameState (char : name), tokens)
| otherwise = step (StartState, reverse name : tokens) char
当我们得到一个字母或数字时,我们将其累加到名称状态中
;当我们得到其他东西时,我们发出令牌并返回到StartState
。但请注意,我们并不是简单地说否则=(StartState,反向名称:tokens)
,因为这将丢弃字符!相反,我们通过再次调用step
重试处于新状态的当前字符
我将留给您来解决如何处理输入的结尾;提示:折叠完成后,有些东西我们忘记检查了
这种方法相当有限;如果您想添加一元效果,例如使用或字符串[Token]
报告解析错误,那么您可能会发现它很难处理。在这一点上,我建议研究一元解析库是如何工作的。表示状态机的一个好方法是使用折叠。下面是来自数据的foldl'
类型。列表
,一个严格的左折叠:
这种类型非常通用,因此它可能有助于看到它的专门性,使用t~[]
和a~Char
:
foldl' :: (b -> Char -> b) -> b -> [Char] -> b
(还记得type String=[Char]
)
因此foldl'步骤开始输入
将对输入
的每个字符运行步骤
功能,从开始
状态开始,并返回最终状态。我们可以介绍一种表示机器可能状态的类型,正如您在问题中开始所做的:
data State
= StartState
| NameState String
| IntegerState String
| DecimalState String
| OperatorState String
| …
但是,我们的step
函数不能只是类型State->Char->State
,因为我们还希望累积一个令牌列表作为输出。我们可以只使用一对状态和累加器:
step :: (State, [Token]) -> Char -> (State, [Token])
(为了简单起见,我假设type Token=String
,但实际上您可能希望使用代数数据类型,例如data Token=LeftParen | RightParen | Name String | Integer Int |……
)
因此,目前的基本结构是:
import Data.Char
import Data.List (foldl')
data State = …
tokenize :: String -> [Token]
tokenize input = reverse $ snd $ foldl' step (StartState, []) input
where
step :: (State, [Token]) -> Char -> (State, [Token])
(我们在bec末尾使用反向
)