我对Haskell相对较新,主要编程背景来自OO语言。我正在尝试使用解析器编写一个解释器来编写简单的编程语言。到目前为止,我有一个解释器处于一个我很满意的状态,但我正在与解析器稍微挣扎。
以下是我遇到问题的代码
data IntExp
= IVar Var
| ICon Int
| Add IntExp IntExp
deriving (Read, Show)
whitespace = many1 (char ' ')
parseICon :: Parser IntExp
parseICon =
do x <- many (digit)
return (ICon (read x :: Int))
parseIVar :: Parser IntExp
parseIVar =
do x <- many (letter)
prime <- string "'" <|> string ""
return (IVar (x ++ prime))
parseIntExp :: Parser IntExp
parseIntExp =
do x <- try(parseICon)<|>try(parseIVar)<|>parseAdd
return x
parseAdd :: Parser IntExp
parseAdd =
do x <- parseIntExp
whitespace
string "+"
whitespace
y <- parseIntExp
return (Add x y)
runP :: Show a => Parser a -> String -> IO ()
runP p input
= case parse p "" input of
Left err ->
do putStr "parse error at "
print err
Right x -> print x
语言稍微复杂一点,但这足以说明我的问题。
因此在类型IntExp中ICon是一个常量,而IVar是一个变量,但现在问题就出现了。这例如成功运行
runP parseAdd“5 + 5”
给出(Add(ICon 5)(ICon 5)),这是预期的结果。使用IVars而不是ICons时会出现问题,例如
runP parseAdd“n + m”
这导致程序错误地说有一个意外的“n”,其中有一个数字是预期的。这让我相信parseIntExp没有像我预期的那样工作。我的意图是它将尝试解析ICon,如果失败则尝试解析IVar等等。
所以我认为parseIntExp中存在问题,或者我在parseIVar和parseICon中遗漏了一些东西。
我希望我已经提供了足够的关于我的问题的信息,我很清楚。
感谢您的帮助,您可以给我!
答案 0 :(得分:13)
您的问题实际上在parseICon
:
parseICon =
do x <- many (digit)
return (ICon (read x :: Int))
many
组合子匹配零或更多次出现,因此匹配零位后它在“m”上成功,然后在read
失败时可能会死亡。
虽然我很喜欢,因为你是Haskell的新手,这里有一些未经请求的建议:
请勿使用虚假括号。 many (digit)
应为many digit
。这里的括号只是对事物进行分组,它们对功能应用来说不是必需的。
您无需执行ICon (read x :: Int)
。数据构造函数ICon
只能使用Int
,因此编译器可以自行确定您的意思。
try
中的前两个选项不需要parseIntExp
,因为它没有输入会导致任何一个输入在失败前消耗掉一些输入。它们要么立即失败(不需要try
),要么在匹配单个字符后成功。
在解析之前首先进行标记化通常是个好主意。在语法处理的同时处理空白是一件令人头痛的问题。
在Haskell中使用($)
运算符来避免使用括号。它只是函数应用程序,但优先级非常低,因此many1 (char ' ')
之类的内容可以写成many1 $ char ' '
。
此外,做这种事情是多余的和不必要的:
parseICon :: Parser IntExp
parseICon =
do x <- many digit
return (ICon (read x))
当您所做的只是将常规函数应用于解析器的结果时,您可以使用fmap
:
parseICon :: Parser IntExp
parseICon = fmap (ICon . read) (many digit)
他们是完全一样的。如果您导入Control.Applicative
模块,可以使事情看起来更好,这会为您提供fmap
的运算符版本,称为(<$>)
,以及另一个允许(<*>)
的运算符(<*)
你用多个参数的函数做同样的事情。还有运算符(*>)
和whitespace = many1 $ char ' '
parseICon :: Parser IntExp
parseICon = ICon . read <$> many1 digit
parseIVar :: Parser IntExp
parseIVar = IVar <$> parseVarName
parseVarName :: Parser String
parseVarName = (++) <$> many1 letter <*> parsePrime
parsePrime :: Parser String
parsePrime = option "" $ string "'"
parseIntExp :: Parser IntExp
parseIntExp = parseICon <|> parseIVar <|> parseAdd
parsePlusWithSpaces :: Parser ()
parsePlusWithSpaces = whitespace *> string "+" *> whitespace *> pure ()
parseAdd :: Parser IntExp
parseAdd = Add <$> parseIntExp <* parsePlusWithSpaces <*> parseIntExp
分别丢弃右值或左值,在这种情况下,您可以在丢弃结果时解析某些内容,例如空格等。
以下是您的代码的轻微修改版本,其中包含上述一些建议以及其他一些小的风格调整:
{{1}}
答案 1 :(得分:1)
我也是Haskell的新手,只是想知道:
parseIntExp会不会让它变成parseAdd?
在达到'parseAdd'之前,ICon或IVar似乎总是被解析。
e.g。 runP parseIntExp“3 + m”
会尝试parseICon,然后成功,给予
(ICon 3)代替(Add(ICon 3)(IVar m))
对不起,如果我在这里很蠢,我只是不确定。