我正在学校学习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的实例。
这是否可取?如果是,我可以获得一些帮助来定义解析方法吗?
答案 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这样的库,但这可能是一种过度杀伤力。尽管如此,解析(主要是!)是一个已解决的问题,如果您只是想按时完成任务,那么使用现成的库是一个好主意。