Parsec:使用以' $'开头的变量解析表达式(没有空格)

时间:2015-08-31 14:23:46

标签: haskell parsec

我想使用Parsec的Token和Expr模块解析包含以$开头的变量的表达式(如在$a=$b中)。这是我的代码的简化版本:

module Main where

import Control.Monad.Identity
import Control.Applicative

import Text.Parsec
import Text.Parsec.String

import qualified Text.Parsec.Token    as Tok
import qualified Text.Parsec.Language as Tok
import qualified Text.Parsec.Expr     as Ex

data Expr
  = BinaryOp String Expr Expr
  | Var String
  deriving (Show)

lexer :: Tok.TokenParser ()
lexer = Tok.makeTokenParser style
  where
    style = Tok.emptyDef
      { Tok.reservedOpNames = ["="]
      , Tok.reservedNames   = []
      , Tok.identStart      = letter
      , Tok.identLetter     = alphaNum
      }

reservedOp = Tok.reservedOp lexer
identifier = Tok.identifier lexer
whiteSpace = Tok.whiteSpace lexer

parseExpr :: String -> Either ParseError Expr
parseExpr = parse (whiteSpace *> expr <* eof) ""

expr :: Parser Expr
expr = Ex.buildExpressionParser opTable terms <?> "expression"
  where
    opTable =
      [ [ Ex.Infix (reservedOp "=" >> return (BinaryOp "=")) Ex.AssocLeft ] ]
    terms =
      try var

var :: Parser Expr
var = Var <$> (char '$' >> identifier)

--

main :: IO ()
main = case parseExpr "$a=$b" of
  Left err -> print err
  Right expr -> print expr

这适用于运算符周围有空格的表达式(如$a = $b),但没有空格($a=$b)我得到错误:

(line 1, column 5):
unexpected '$'
expecting operator

此外,如果我修改解析器以解析不以$开头的变量,则解析器可以使用和不使用空格。因此,$和运算符的组合似乎存在问题,它们之间没有空格。

1 个答案:

答案 0 :(得分:3)

问题是默认令牌解析器中opStartopLetter的定义:

emptyDef   :: LanguageDef st
emptyDef    = LanguageDef
               { commentStart   = ""
               ...
               , opStart        = opLetter emptyDef
               , opLetter       = oneOf ":!#$%&*+./<=>?@\\^|-~"
               ...
               }

令牌解析器使用opStartopLetter贪婪地匹配运营商名称,因此$a=$b解析与$a =$ b相同。由于=$不是运算符,因此会出现语法错误。

如果您从$移除opLetter,则应该有效,例如:

lexer :: Tok.TokenParser ()
lexer = Tok.makeTokenParser style
  where
    style = Tok.emptyDef
      { Tok.reservedOpNames = ["="]
      , Tok.reservedNames   = []
      , Tok.identStart      = letter
      , Tok.identLetter     = alphaNum
      , Tok.opLetter        = oneOf ":!#%"  -- add this line
      }