【问题标题】:How to make the Parsec chainl1 function follow operator precedence rules如何使 Parsec chainl1 函数遵循运算符优先规则
【发布时间】:2013-05-03 20:35:48
【问题描述】:

我正在编写标准数学符号 -> DC POSIX 兼容格式转换器。它获取输入字符串,将其解析为中间数据类型,然后通过showing 将其转换为输出字符串。

这是使用的数据类型。我对数据类型没有问题 -> 输出字符串转换,它完美无缺:

data Expression = Expression :+ Expression
                | Expression :- Expression
                | Expression :* Expression
                | Expression :/ Expression
                | Expression :^ Expression
                | Cons String

infixr 0 :+
infixr 0 :-
infixr 1 :*
infixr 1 :/
infixr 2 :^

instance Show Expression where
  show (x :+ y) = unwords [show x, show y, "+"]
  show (x :- y) = unwords [show x, show y, "-"] 
  show (x :* y) = unwords [show x, show y, "*"]
  show (x :/ y) = unwords [show x, show y, "/"]
  show (x :^ y) = unwords [show x, show y, "^"]
  show (Cons y) = y

然而,Parsec 解析器部分拒绝遵守定义的运算符优先级规则。显然是因为chainl1subexpression 解析器定义中的使用方式:

expression :: Parser Expression
expression = do
  spaces
  x <- subexpression
  spaces >> eof >> return x

subexpression :: Parser Expression
subexpression = (
    (bracketed subexpression) <|>
    constant
  ) `chainl1` (
    try addition              <|>
    try substraction          <|>
    try multiplication        <|>
    try division              <|>
    try exponentiation
  )

addition       = operator '+' (:+)
substraction   = operator '-' (:-)
multiplication = operator '*' (:*)
division       = operator '/' (:/)
exponentiation = operator '^' (:^)

operator :: Char -> (a -> a -> a) -> Parser (a -> a -> a)
operator c op = do
  spaces >> char c >> spaces
  return op

bracketed :: Parser a -> Parser a
bracketed parser = do
  char '('
  x <- parser
  char ')'
  return x

constant :: Parser Expression
constant = do
  parity <- optionMaybe $ oneOf "-+"
  constant <- many1 (digit <|> char '.')
  return (if parity == Just '-'
    then (Cons $ '_':constant)
    else  Cons       constant)

有没有一种方法可以让解析器考虑运算符优先规则,而不必重写我的整个代码?

【问题讨论】:

    标签: haskell parsec combinators


    【解决方案1】:

    好吧,您不需要重写您的整个代码,但由于您的 subexpression 解析器根本没有考虑优先级,因此您必须重写 - 基本上。 p>

    一种可能性是从具有相同优先级的顶级运算符的子表达式的解析器构建它,

    atom :: Parser Expression
    atom = bracketed subexpression <|> constant
    
    -- highest precedence operator is exponentiation, usually that's
    -- right-associative, hence I use chainr1 here
    powers :: Parser Expression
    powers = atom `chainr1` try exponentiation
    
    -- a multiplicative expression is a product or quotient of powers,
    -- left-associative
    multis :: Parser Expression
    multis = powers `chainl1` (try multiplication <|> try division)
    
    -- a subexpression is a sum (or difference) of multiplicative expressions
    subexpression :: Parser Expression
    subexpression = multis `chainl1` (try addition <|> try substraction)
    

    另一种选择是让库处理优先级和关联性并使用Text.Parsec.Expr,即buildExpressionParser

    table = [ [binary "^" (:^) AssocRight]
            , [binary "*" (:*) AssocLeft, binary "/" (:/) AssocLeft]
            , [binary "+" (:+) AssocLeft, binary "-" (:-) AssocLeft]
            ]
    
    binary  name fun assoc = Infix (do{ string name; spaces; return fun }) assoc
    
    subexpression = buildExpressionParser table atom
    

    (要求bracketed parserconstant 占用已用标记后的空格)。

    【讨论】:

    • 谢谢。这个解析器封装正是我所需要的。到目前为止,我只使用过正则表达式,所以 Parsec 之类的上下文相关功能仍然让我有点头晕。
    • 那时你将不得不重新学习一点,但是一旦你开始涉足,即使是稍微复杂的事情,编写解析器也比正则表达式容易得多。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-14
    • 1970-01-01
    • 2015-05-23
    • 2013-10-02
    • 2011-01-09
    • 1970-01-01
    相关资源
    最近更新 更多