Haskell parsec前缀运算符问题

时间:2013-04-24 00:01:19

标签: haskell parsec

我使用GHC在Windows上编译。这是我的代码(also available here):

module GMC.GMLParser (parseGML) where

import Control.Applicative ((<$>), (<*>))
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Expr
import Text.ParserCombinators.Parsec.Language
import qualified Text.ParserCombinators.Parsec.Token as P

type VarIdent = String
type FunIdent = String

data Expr =
      Var VarIdent
    | IntLit Integer
    | StringLit String
    | BiLit Bool
    | Op String Expr Expr
    | UnOp String Expr
    | Call FunIdent [Expr]
    deriving (Eq, Show)

data Stat =
      Seq [Stat]
    | Skip
    | Assign (Maybe VarIdent) Expr
    | If Expr Stat (Maybe Stat)
    deriving (Eq, Show)

lexer = P.makeTokenParser gmlDef

parens          = P.parens lexer    
braces          = P.braces lexer    
semi            = P.semi lexer
semiSep         = P.semiSep lexer  
semiSep1        = P.semiSep1 lexer    
commaSep        = P.commaSep lexer
commaSep1       = P.commaSep1 lexer
brackets        = P.brackets lexer
whiteSpace      = P.whiteSpace lexer    
symbol          = P.symbol lexer    
identifier      = P.identifier lexer    
reserved        = P.reserved lexer    
reservedOp      = P.reservedOp lexer
integer         = P.integer lexer    
charLiteral     = P.charLiteral lexer    
stringLiteral   = P.stringLiteral lexer


operators =
    [ [ prefix "-" ]
    , [ op "*"  AssocLeft, op "/"  AssocLeft ]
    , [ op "+"  AssocLeft, op "-"  AssocLeft ]
    , [ op "=" AssocNone, op "<>" AssocNone, op "<="  AssocNone
      , op "<" AssocNone, op ">="  AssocNone, op ">" AssocNone ]
    , [ op "&" AssocRight, op "&&" AssocRight ] -- Right for shortcircuiting
    , [ op "|" AssocRight, op "||" AssocRight ] -- Right for shortcircuiting
    , [ op ":=" AssocRight ]
    ]
    where
      op name assoc   = Infix (do{ reservedOp name
                                  ; return (\x y -> Op name x y) 
                                  }) assoc
      prefix name     = Prefix  (do{ reservedOp name
                                  ; return (\x -> UnOp name x)
                                  })


gmlDef :: LanguageDef st
gmlDef = emptyDef
    { commentStart    = "/*"
    , commentEnd      = "*/"
    , commentLine     = "//"
    , nestedComments  = True
    , identStart      = letter
    , identLetter     = alphaNum <|> oneOf "_"
    , reservedNames   = []
    , reservedOpNames = []
    , caseSensitive   = True
    }

parseGML :: String -> Either ParseError [Stat]
parseGML input = parse (whiteSpace >> many stat) "" input

intLit :: Parser Expr
intLit = IntLit <$> integer

strLit :: Parser Expr
strLit = StringLit <$> stringLiteral

variable :: Parser Expr
variable = do ident <- identifier
              memb <- optional $ symbol "." -- ignored for now, only parse its existance
              vname <- optional identifier -- ignored for now, only parse its existance
              indx <- optional $ brackets expr -- ignored for now, only parse its existance
              return (Var ident)

expr :: Parser Expr
expr = buildExpressionParser operators genExpr

genExpr :: Parser Expr
genExpr = choice [ intLit
                 , strLit
                 , try callExpr
                 , variable
                 , parens expr
                 ]

callExpr :: Parser Expr
callExpr = Call <$> identifier <*> parens (commaSep expr)

stat :: Parser Stat
stat =  do optional $ skipMany1 semi
           choice [ ifStat
                  , assignStat
                  , seqStat
                  ]

seqStat :: Parser Stat
seqStat = do stmts <- braces $ many stat
             return $ if null stmts then Skip else Seq stmts

ifStat :: Parser Stat
ifStat = If <$> (reserved "if" >> expr)
            <*> (optional (reserved "then") >> stat)
            <*> (optionMaybe $ reserved "else" >> stat)

assignStat :: Parser Stat
assignStat = do ident <- (optionMaybe $ try $ variable >> symbol "=")
                stmt <- case ident of
                    Just x -> expr
                    Nothing -> callExpr
                return (Assign ident stmt)

问题在于解析前缀实数和变量会产生奇怪的结果。

x=-3给出[Assign (Just "=") (UnOp "-" (IntLit 3))]这是正确的。然而,更复杂的表达式(如x=5+-3x = (arr[4]>-1 && 1))似乎会产生错误的结果。

x = arr[4]>-1[Assign (Just '=') (Var "arr")],但应该是[Assign (Just "x") (Op ">" (Var "arr") (UnOp "-" (IntLit 1)))]

x=5+-3奇怪的是[Assign (Just "=" (IntLit 5))应该是[Assign (Just "x") (Op "+" (IntLit 5) (UnOp "-" (IntLit 3)))]

我认为这是因为与我的运算符优先级有关,或者,通常我的前缀-运算符的实现似乎是不可靠的。我非常感谢指导。

谢谢!

2 个答案:

答案 0 :(得分:4)

一些问题:

ident <- (optionMaybe $ try $ variable >> symbol "=")

这是解析并忽略variable,然后返回symbol "="的结果。此外,variable在这里会出现类型错误。我会在这里使用identifier进行测试,但你可能想要更高级的东西。

parse (whiteSpace >> many stat) "" input

您的测试输入表明您打算解析整个问题。你可能应该在最后吃空格,然后使用eof来确保它消耗整个输入。

最后,在输入"x = arr[4]>-1"上我很确定词法分析器将>-视为单个标记,Haskell自己的语法也是如此。所以在这种情况下解析器是正确的(如果你添加我建议的eof,将会出错)。请注意,这与赋值语句一起发生 not ,因为Parsec的表达式解析器没有解析它。

这是我在做出这些更改后得到的输出(请原谅我奇怪的GHCi提示):

∀x. x ⊢ parseGML "x=-3"
Right [Assign (Just "x") (UnOp "-" (IntLit 3))]
∀x. x ⊢ parseGML "x = arr[4]>-1"
Left (line 1, column 11):
unexpected '>'
expecting ";", "if", identifier, "{" or end of input
∀x. x ⊢ parseGML "x = arr[4]> -1"
Right [Assign (Just "x") (Op ">" (Var "arr") (UnOp "-" (IntLit 1)))]
∀x. x ⊢ parseGML "x = 5+-3"
Left (line 1, column 6):
unexpected '+'
expecting ";", "if", identifier, "{" or end of input
∀x. x ⊢ parseGML "x = 5+ -3"
Right [Assign (Just "x") (Op "+" (IntLit 5) (UnOp "-" (IntLit 3)))]
∀x. x ⊢

答案 1 :(得分:3)

你的第128行:

assignStat = do ident <- (optionMaybe $ try $ variable >> symbol "=")

我想专注于这一点:

variable >> symbol "="

这是做什么的:

  • 解析变量,丢弃解析结果
  • 解析=令牌
  • 返回解析=作为整体解析结果的结果

你想要发生什么:

  • 解析变量
  • 解析=令牌,丢弃解析结果
  • 返回解析变量的结果作为整体解析的结果

如何将代码片段更改为:

variable <* symbol "="

(您需要从Control.Applicative中导入(<*)

除非它不那么简单:表达式有以下类型:

variable >> symbol "=" :: Parser String
variable <* symbol "=" :: Parser Expr

您必须自己解决variable是否真的是此时调用的正确解析器,或者Align构造函数的第一个字段是否应该是{{1}或者你是否应该以其他方式修复它。