Parsing 在Haskell中将字符串解析为数据类型
我在学校学习Haskell课程,我必须在Haskell中定义一个逻辑命题数据类型。到目前为止,一切都很好(定义和函数),我已经将其声明为Ord、Eq和show的一个实例。当我需要定义一个与用户交互的程序时,问题就出现了:我必须将用户的输入解析为我的数据类型:Parsing 在Haskell中将字符串解析为数据类型,parsing,haskell,Parsing,Haskell,我在学校学习Haskell课程,我必须在Haskell中定义一个逻辑命题数据类型。到目前为止,一切都很好(定义和函数),我已经将其声明为Ord、Eq和show的一个实例。当我需要定义一个与用户交互的程序时,问题就出现了:我必须将用户的输入解析为我的数据类型: type Var = String data FProp = V Var | No FProp | Y FProp FProp | O FProp FProp
type Var = String
data FProp = V Var
| No FProp
| Y FProp FProp
| O FProp FProp
| Si FProp FProp
| Sii FProp FProp
式中,-q^p为:(Y(否(V“q”)(V“p”))
我一直在研究,发现我可以将我的数据类型声明为Read的实例
这样做明智吗?如果是,我可以得到一些帮助来定义解析方法吗?REPL解释器的读取部分通常如下所示
repl :: ForthState -> IO () -- parser definition
repl state
= do putStr "> " -- puts a > character to indicate it's waiting for input
input <- getLine -- this is what you're looking for, to read a line.
if input == "quit" -- allows user to quit the interpreter
then do putStrLn "Bye!"
return ()
else let (is, cs, d, output) = eval (words input) state -- your grammar definition is somewhere down the chain when eval is called on input
in do mapM_ putStrLn output
repl (is, cs, d, [])
main = do putStrLn "Welcome to your very own interpreter!"
repl initialForthState -- runs the parser, starting with read
repl::ForthState->IO()--解析器定义
repl状态
=do putStr“>”--放置一个>字符,表示它正在等待输入
inputREPL解释器的读取部分通常如下所示
repl :: ForthState -> IO () -- parser definition
repl state
= do putStr "> " -- puts a > character to indicate it's waiting for input
input <- getLine -- this is what you're looking for, to read a line.
if input == "quit" -- allows user to quit the interpreter
then do putStrLn "Bye!"
return ()
else let (is, cs, d, output) = eval (words input) state -- your grammar definition is somewhere down the chain when eval is called on input
in do mapM_ putStrLn output
repl (is, cs, d, [])
main = do putStrLn "Welcome to your very own interpreter!"
repl initialForthState -- runs the parser, starting with read
repl::ForthState->IO()--解析器定义
repl状态
=do putStr“>”--放置一个>字符,表示它正在等待输入
输入不是一个完整的答案,因为这是一个家庭作业问题,但这里有一些提示
另一个答案建议getLine
,然后在words
处拆分。听起来你想要一个更像传统标记器的东西,它可以让你写一些东西,比如:
(Y
(No (V q))
(V p))
下面是一个实现,它将字符串转换为字母数字字符字符串或单个非字母数字可打印字符的标记。您需要扩展它以支持带引号的字符串:
import Data.Char
type Token = String
tokenize :: String -> [Token]
{- Here, a token is either a string of alphanumeric characters, or else one
- non-spacing printable character, such as "(" or ")".
-}
tokenize [] = []
tokenize (x:xs) | isSpace x = tokenize xs
| not (isPrint x) = error $
"Invalid character " ++ show x ++ " in input."
| not (isAlphaNum x) = [x]:(tokenize xs)
| otherwise = let (token, rest) = span isAlphaNum (x:xs)
in token:(tokenize rest)
它将示例转换为[(“,”Y“,”(“,”否“,”(“,”V“,”q“,”,”,”,”,”,“(“,”V“,”p“,”)”)]
。请注意,您可以访问整个Unicode指令集
以交互方式对此进行计算的main
函数可能如下所示:
main = interact ( unlines . map show . map evaluate . parse . tokenize )
其中,parse
将令牌列表转换为AST列表,evaluate
将AST转换为可打印表达式
至于实现解析器,您的语言似乎具有与LISP相似的语法,LISP是最简单的解析语言之一;你甚至不需要优先规则。递归下降解析器可以做到这一点,而且可能是最容易手工实现的。您可以在parse(“(”:xs)=
上进行模式匹配,但是模式匹配语法也可以非常容易地实现前瞻,例如parse(“(”:x1:xs)=
来前瞻一个令牌
如果以递归方式调用解析器,则需要定义一个只使用单个表达式的帮助函数,该函数的类型签名类似于:[Token]->(AST,[Token])
。这样可以解析内部表达式,检查下一个标记是否为”
,然后继续解析。但是,在外部,您需要使用所有令牌并返回AST或它们的列表
编写解析器的时尚方式是使用一元解析器组合器(也许有人会发布一个示例)。工业级解决方案是一个类似于Parsec的库,但这可能有些过头了。不过,解析(主要是!)一个已解决的问题,如果你只是想按时完成作业,使用现成的图书馆是个好主意。这不是一个完整的答案,因为这是一个家庭作业问题,但这里有一些提示
另一个答案建议使用getLine
,然后在words
处拆分。听起来你想要的是更像传统标记器的东西,它可以让你编写如下内容:
(Y
(No (V q))
(V p))
下面是一个实现,它将字符串转换为字母数字字符字符串或单个非字母数字可打印字符的标记。您需要对其进行扩展以支持带引号的字符串:
import Data.Char
type Token = String
tokenize :: String -> [Token]
{- Here, a token is either a string of alphanumeric characters, or else one
- non-spacing printable character, such as "(" or ")".
-}
tokenize [] = []
tokenize (x:xs) | isSpace x = tokenize xs
| not (isPrint x) = error $
"Invalid character " ++ show x ++ " in input."
| not (isAlphaNum x) = [x]:(tokenize xs)
| otherwise = let (token, rest) = span isAlphaNum (x:xs)
in token:(tokenize rest)
它将示例转换为[(“,”Y“,”(“,”否“,”(“,”V“,”q“,”,”,”,”,“(“,”V“,”p“,”)”)]
。请注意,您可以访问整个Unicode库
以交互方式对此进行计算的main
函数可能如下所示:
main = interact ( unlines . map show . map evaluate . parse . tokenize )
其中,parse
将令牌列表转换为AST列表,evaluate
将AST转换为可打印表达式
至于实现解析器,您的语言似乎具有与LISP相似的语法,LISP是最简单的解析语言之一;您甚至不需要优先规则。递归下降解析器可以做到这一点,并且可能是最容易手动实现的。您可以在parse((“:xs)上进行模式匹配=
,但是模式匹配语法也可以非常容易地实现前瞻,例如解析((“:x1:xs)=
来前瞻一个令牌
如果以递归方式调用解析器,则需要定义一个只使用单个表达式的帮助函数,该函数的类型签名类似于:[Token]->(AST,[Token])
。这样可以解析内部表达式,检查下一个标记是否为”
,然后继续解析。但是,在外部,您需要使用所有令牌并返回AST或它们的列表
编写解析器的时尚方式是使用一元解析器组合器(也许有人会发布一个示例)。工业级解决方案是一个类似于Parsec的库,但这可能有些过头了。不过,解析(主要是!)一个已解决的问题,如果你只是想按时完成作业,使用现成的库是个好主意。通常Read
用于将(Y(No(V\'q\)))(V\'p\)之类的字符串转换成这样的值。用于解析“人类可读”文本,一个通常使用另一个函数。那么getLine呢?工业级的解决方案是使用类似Parsec的东西,但是Lisp风格的语言很容易手工编写语法分析器。你能澄清一下吗:你想把用户提供的看起来像“-q^p”的字符串解析成FProp