在Haskell中将字符串解析为数据类型

时间:2018-02-06 18:24:53

标签: parsing haskell

我正在学校学习Haskell课程,我必须在Haskell中定义一个Logical Proposition数据类型。到目前为止一切工作正常(定义和功能),我已经将它声明为Ord,Eq和show的实例。当我需要定义一个与用户交互的程序时,问题出现了:我必须将用户的输入解析为我的数据类型:

type Var = String
data FProp = V Var
           | No FProp
           | Y FProp FProp
           | O FProp FProp
           | Si FProp FProp
           | Sii FProp FProp

其中公式:¬q^ p将是:(Y(No(V“q”))(V“p”))

我一直在研究,发现我可以将我的数据类型声明为Read的实例。

这是否可取?如果是,我可以获得一些帮助来定义解析方法吗?

2 个答案:

答案 0 :(得分:1)

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

你的eval方法将有各种循环,堆栈操作,条件等,以实际找出用户输入的内容。希望这至少可以帮助你阅读输入部分。

答案 1 :(得分:1)

不是一个完整的答案,因为这是一个家庭作业问题,但这里有一些提示。

另一个答案建议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","(","No","(","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或其列表。

编写解析器的时尚方法是使用monadic解析器组合器。 (也许有人会发布一个例子。)工业级解决方案将是像Parsec这样的库,但这可能是一种过度杀伤力。尽管如此,解析(主要是!)是一个已解决的问题,如果您只是想按时完成任务,那么使用现成的库是一个好主意。