在Haskell中解析一个简单的解释器

时间:2010-11-06 01:43:53

标签: parsing haskell

我对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中遗漏了一些东西。

我希望我已经提供了足够的关于我的问题的信息,我很清楚。

感谢您的帮助,您可以给我!

2 个答案:

答案 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))

对不起,如果我在这里很蠢,我只是不确定。