Haskell 中有理数和算术表达式之间的二元运算解析器

Parser for binary operations between Rationals and Arithmetic expressions in Haskell

受到以下 project 的启发,我正在使用线性表达式并定义了以下结构和解析器。

data AExp
  = Lit Rational
  | Var String
  | AExp :+: AExp
  | Rational :*: AExp
  deriving (Eq)

import           Text.Parsec
import           Text.Parsec.Char
import           Text.Parsec.Expr
import           Text.Parsec.Language (javaStyle)
import           Text.Parsec.String
import           Control.Monad (void, ap)
import qualified Text.Parsec.Token    as Token

Token.TokenParser {..} = Token.makeTokenParser javaStyle

binary name fun = Infix (fun <$ reservedOp name) AssocLeft

whitespace :: Parser ()
whitespace = void $ many $ oneOf " \n\t"

regularParse :: Parser a -> String -> Either ParseError a
regularParse p = parse p ""

rational :: Parser Rational
rational = do
    whitespace
    num <- many1 digit
    void $ char '/'
    den <- many1 digit
    whitespace
    return $ toRational $ (read num)/ (read den)

aexp :: Parser AExp
aexp = buildExpressionParser table term 
 where term =  Lit  <$> rational
           <|> Var <$> identifier
           <|> try ((:*:) <$> (rational <* reservedOp "*") <*> aexp)
           <|> try (parens aexp)
       table = [ [ binary "+" (:+:)]]

我的问题是乘法 (:*:) 和两种不同类型之间的一般二元运算 (Rational - AExp)。以下示例显示了我的结果。

Main>regularParse aexp "10/1 * x"
Right (Lit 10 % 1)

即不匹配乘法(*),匹配字面量变量Lit

我找了一些表达式解析器的例子,但是我总是发现其中的乘法是AExp之间的二元运算,即结构和解析器是这样的:

data AExp
  = Lit Rational
  | Var String
  | AExp:+: AExp
  | AExp:*: AExp
  deriving (Eq)

aexp :: Parser AExp
aexp = buildExpressionParser table term
 where term = Lit <$> rational
           <|> Var <$> identifier
           <|> try ((:*:) <$> (rational <* reservedOp "*") <*> aexp)
           <|> try (parens aexp)
       table = [[binary "+" (:+:)],
                  [binary "*" (:*:)]]

我关注的项目就是这样定义的

我可以尝试将另一个重点放在解析器上,但对我来说,遵循项目指南更容易,因为我的许多结构都非常相似。

我如何定义乘法解析器 (:*:) 或者有任何示例可以指导我吗?

提前致谢

问题出在这里:

term =  Lit  <$> rational
       <|> ...
       <|> try ((:*:) <$> (rational <* reservedOp "*") <*> aexp)
       <|> ...

这将首先尝试解析一个单独的 rational;如果成功,将不会尝试剩余的分支。选择这种承诺第一次成功解析的策略是为了提高效率。只需更改顺序即可帮助您克服困难,并解决您的下一个问题(总是有一个,不是吗??)。

term = try ((:*:) <$> (rational <* reservedOp "*") <*> aexp)
      <|> Lit <$> rational
      <|> ...

我对我原来的问题想得更多了。我想为线性表达式制作一个解析器。 一个有限但足够的解决方案是:

varAExp :: Parser AExp
varAExp = do
  x <- identifier
  return $ Var x

aexp :: Parser AExp
aexp = buildExpressionParser table term 
where term = try ((:*:) <$> (rational <* reservedOp "*") <*> varAExp)
       <|> Lit  <$> rational
       <|> Var <$> identifier
       <|> try (parens aexp)
   table = [[binary "+" (:+:) ]]

有了这个解析器,我可以处理这种类型的表达式:

arit_1 = regularParse aexp "10/1*x + 3/2*y + 1/1 + 3/1"

我无法表达数字的乘法,但正如我之前所说,这对我来说已经足够了。 感谢您的帮助