首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在算术表达式中解析表达式

在算术表达式中解析表达式
EN

Stack Overflow用户
提问于 2018-05-09 20:44:40
回答 1查看 1K关注 0票数 2

我想解析算术表达式。

这里是我当前的解析器:

代码语言:javascript
复制
data AExpr
    = ExprAsAExpr Expr
    | IntConst Integer
    | Neg AExpr
    | ABinary ABinOp AExpr AExpr
    deriving (Show, Eq)

aExpr :: Parser AExpr
aExpr = makeExprParser aTerm aOperators

aTerm :: Parser AExpr
aTerm
    =   parens aExpr
    <|> IntConst <$> integerParser

aOperators :: [[Operator Parser AExpr]]
aOperators =
    [ [Prefix (Neg <$ symbol "-") ]
    , [ InfixL (ABinary Multiply <$ symbol "*")
      , InfixL (ABinary Divide   <$ symbol "/") ]
    , [ InfixL (ABinary Add      <$ symbol "+")
      , InfixL (ABinary Subtract <$ symbol "-") ]
    ]

使用它,我可以正确地解析以下内容:

代码语言:javascript
复制
1 + 2

产生这样的AST。

代码语言:javascript
复制
(ABinary Add (IntConst 1) (IntConst 2))

我可以解析的另一件事是通用表达式。这些可以是诸如变量、方法调用、三元等。

,例如

标识符:

代码语言:javascript
复制
varName

这就产生了:

代码语言:javascript
复制
(Identifier (Name "varName"))

方法调用:

代码语言:javascript
复制
methodCall()

这就产生了:

代码语言:javascript
复制
(MethodCall (Name "methodCall") (BlockExpr []))

下面是一个解析通用表达式的示例。

代码语言:javascript
复制
expressionParser :: Parser Expr
expressionParser
    =   methodCallParser
    <|> identifierParser

这很好,但我也想在这里解析算术表达式。

代码语言:javascript
复制
expressionParser :: Parser Expr
expressionParser
    =   newClassInstanceParser
    <|> methodCallParser
    <|> AExprAsExpr <$> aExpr
    <|> identifierParser

这意味着使用expressionParser,我现在可以解析所有不同的表达式,包括算术表达式。如果它恰好是一个算术表达式,它将被包装在AExprAsExpr中。

问题

我想解析包含其他表达式的算术表达式。

例如。

代码语言:javascript
复制
x + y

要做到这一点,我最初的想法是修改算术解析器,以便它也能解析表达式。

代码语言:javascript
复制
data AExpr
    = ExprAsAExpr Expr
    | IntConst Integer
    | Neg AExpr
    | ABinary ABinOp AExpr AExpr
    deriving (Show, Eq)

aExpr :: Parser AExpr
aExpr = makeExprParser aTerm aOperators

aTerm :: Parser AExpr
aTerm
    =   parens aExpr
    <|> IntConst <$> integerParser
    <|> ExprAsAExpr <$> expressionParser

aOperators :: [[Operator Parser AExpr]]
aOperators =
    [ [Prefix (Neg <$ symbol "-") ]
    , [ InfixL (ABinary Multiply <$ symbol "*")
      , InfixL (ABinary Divide   <$ symbol "/") ]
    , [ InfixL (ABinary Add      <$ symbol "+")
      , InfixL (ABinary Subtract <$ symbol "-") ]
    ]

这方面的问题是存在一个递归循环,因为aTerm调用表达式解析器,表达式解析器调用aExpr。这将导致无限循环。还有一个问题是,所有的identifiers现在都将封装在一个AExprAsExpr中。

在算术表达式中解析表达式的正确方法是什么?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-05-09 22:27:00

编辑我刚刚意识到您正在使用makeExpressionParser,而我的答案并不适用于此。不管怎样,也许这个答案还是有帮助的?

Parsec是一种递归下降解析器,这意味着它不能处理左递归,正如您所看到的。你需要把它算出来,如果语法是无上下文的,这总是可以完成的。您看到这种分解的一种方法是为每个优先级级别创建一个产品。下面是一个简单算术的语法示例:

代码语言:javascript
复制
expr ::= addExpr
addExpr ::= mulExpr '+' addExpr
          | mulExpr '-' addExpr
          | mulExpr
mulExpr ::= term '*' mulExpr
          | term '/' mulExpr
          | term
term ::= '(' expr ')'
       | number

注意模式:每个产品中的第一个符号调用到下一个更具体的符号。然后,显式括号允许我们重新进入顶级产品。这通常是在递归下降中表示操作符优先级的方式。

上述语法只能产生正确的嵌套表达式.虽然它将接受完全正确的右字符串,但当解释是左关联时,它不会正确地解析。特别地,

代码语言:javascript
复制
1 - 2 - 3 - 4

将被解析为

代码语言:javascript
复制
1 - (2 - (3 - 4))

根据我们的惯例,这是不正确的。在一般的递归下降解析器中,您必须做一些技巧才能在这里正确地关联。然而,在Parsec中,我们有many组合子,我们可以利用它来实现我们的优势。例如,要解析左关联减法,我们可以说

代码语言:javascript
复制
subExpr = foldl1 (-) <$> many1 mulExpr

这里的下一个层次显然是chainl组合器,它似乎就是为了这个目的而设计的(尽管我刚刚了解到它--我想我应该更仔细地阅读文档)。使用此方法的一个示例是

代码语言:javascript
复制
addExpr = chainl1 mulExpr oper
    where
    oper = choice [ (+) <$ symbol '+'
                  , (-) <$ symbol '-'
                  ]

我喜欢在Haskell写解析器。祝好运!

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50261752

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档