【问题标题】:Haskell parsec prefix operator issueHaskell parsec 前缀运算符问题
【发布时间】:2013-04-24 00:01:19
【问题描述】:

我使用 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]&gt;-1 &amp;&amp; 1) 这样更复杂的表达式似乎给出了不正确的结果。

x = arr[4]&gt;-1 给出[Assign (Just '=') (Var "arr")] 但应该是[Assign (Just "x") (Op "&gt;" (Var "arr") (UnOp "-" (IntLit 1)))]

x=5+-3 奇怪地给出了[Assign (Just "=" (IntLit 5)),而它应该是[Assign (Just "x") (Op "+" (IntLit 5) (UnOp "-" (IntLit 3)))]

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

谢谢!

【问题讨论】:

    标签: haskell parsec


    【解决方案1】:

    几个问题:

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

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

    parse (whiteSpace >> many stat) "" input
    

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

    最后,在输入 "x = arr[4]&gt;-1" 上,我很确定词法分析器将 &gt;- 视为单个标记,Haskell 自己的语法也是如此。所以在这种情况下,解析器是正确的(如果你添加我建议的 eof 会给出错误)。请注意,赋值语句不会发生这种情况,因为 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 ⊢
    

    【讨论】:

    • 能否让表达式解析器相信 >- 不是运算符?
    • @kvanberendonck:可能。不过,我对它不是很熟悉。
    • 这有点离题,但我喜欢这个提示!我在哪里可以找到它的含义?我猜这与逻辑有关?
    • @ocharles: 的意思是“证明”或“因此”,有点像元含义。它被称为turnstile∀x. x 是“forall x, x”,这是undefined 的最通用类型,并且是逻辑不一致/图灵完备语言中唯一的真/居住类型。引用 IRC 上的 roconnor 的话,“cmccann 的意思是,如果一切都有人居住,那么他将要写的任何废话都会随之而来。”如果您不知道逻辑与类型有什么关系,请查找“Curry-Howard 对应”。 :]
    【解决方案2】:

    你的第 128 行:

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

    我想专注于这一点:

    variable >> symbol "="
    

    这是做什么的:

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

    你想要发生的事情:

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

    将代码的 sn-p 更改为:

    variable <* symbol "="
    

    (您需要从 Control.Applicative 导入 (&lt;*)

    除了不是那么简单:表达式有这些类型:

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

    您必须自己确定variable 是否真的是此时调用的正确解析器,或者Align 构造函数的第一个字段是否应该改为Maybe Expr,或者您是否应该用其他方法修复它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-12
      • 1970-01-01
      • 1970-01-01
      • 2022-11-24
      相关资源
      最近更新 更多