我想使用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
此外,如果我修改解析器以解析不以$
开头的变量,则解析器可以使用和不使用空格。因此,$
和运算符的组合似乎存在问题,它们之间没有空格。
答案 0 :(得分:3)
问题是默认令牌解析器中opStart
和opLetter
的定义:
emptyDef :: LanguageDef st
emptyDef = LanguageDef
{ commentStart = ""
...
, opStart = opLetter emptyDef
, opLetter = oneOf ":!#$%&*+./<=>?@\\^|-~"
...
}
令牌解析器使用opStart
和opLetter
贪婪地匹配运营商名称,因此$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
}