Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/three.js/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
String 根据规则集,将字符串标记为字符串列表_String_Haskell_Compiler Construction - Fatal编程技术网

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))” 输出: [ “(”,

我有一个字符串需要按照以下规则标记到列表中:

  • “(”和“)”括号
  • 可能有小数点的数字
  • 运算符(+、/、*、>=,等等)
  • 以字母开头的标识符,字符串的其余部分可以是字母或数字
为此,我在Haskell代码中将上述规则创建为有限状态机:

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末尾使用
反向