You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在算术表达式中解析其他表达式?(Haskell解析器问题)

解决Haskell解析器中算术表达式与普通表达式的递归循环问题

嘿,我之前写表达式解析器的时候也踩过这个递归循环的坑,咱们一步步拆解解决它~

首先得揪出问题根源:你原来的设计里,expressionParser包含了AExprAsExpr(把算术表达式包装成普通表达式),现在又想让aTerm(算术表达式的原子项)调用expressionParser,这就形成了循环依赖——expressionParser依赖aExprParser,aExprParser依赖aTerm,aTerm又依赖expressionParser,解析器直接陷入无限递归。另外,标识符被错误包裹在AExprAsExpr里,是因为解析器优先把标识符当成了算术表达式的一部分,没直接解析成普通表达式的标识符项。

核心思路:拆分解析器层级,明确边界

我们需要把解析器拆成三个层级,既打破循环,又清晰划分基础项与组合表达式:

  1. 原子表达式解析器:处理最基础的普通表达式单元,是所有表达式的底层依赖,不碰算术表达式。
  2. 算术表达式解析器:建立在原子表达式之上,专门处理算术运算逻辑。
  3. 完整表达式解析器:整合算术表达式(包装成AExprAsExpr)和其他普通表达式。

具体代码实现示例

假设你的表达式类型大概是这样:

data Expr = Identifier String
          | MethodCall String [Expr]
          | AExprAsExpr AExpr
          -- 其他普通表达式构造器...
          deriving (Show)

data AExpr = ANumber Integer
           | AVar Expr  -- 用普通表达式作为算术变量载体
           | AAdd AExpr AExpr
           | AMul AExpr AExpr
           -- 其他算术表达式构造器...
           deriving (Show)

第一步:定义原子表达式解析器

这部分处理最基础的普通表达式单元,比如标识符、方法调用、括号包裹的完整表达式:

import Text.Parsec
import Text.Parsec.String (Parser)

primaryExprParser :: Parser Expr
primaryExprParser = choice
  [ -- 直接解析标识符为普通Expr,不会被包装成算术表达式
    Identifier <$> identifierParser
    -- 解析方法调用,参数支持完整表达式(递归调用expressionParser是安全的,括号是明确终止符)
  , MethodCall <$> identifierParser <*> parens (commaSep expressionParser)
    -- 括号里的完整表达式
  , parens expressionParser
    -- 可添加其他原子项,比如布尔字面量、常量等
  ]
  where
    identifierParser = (:) <$> letter <*> many (alphaNum <|> char '_')

第二步:修改算术表达式解析器,依赖原子表达式

让aTerm基于primaryExprParser构建,不再依赖整个expressionParser:

-- 定义算术运算符的优先级与结合性
aOperators :: [[Operator String () AExpr]]
aOperators =
  [ [Infix (AMul <$ char '*') AssocLeft]
  , [Infix (AAdd <$ char '+') AssocLeft]
  ]

aTerm :: Parser AExpr
aTerm = choice
  [ -- 把原子表达式转成算术变量(AVar)
    AVar <$> primaryExprParser
    -- 解析数字字面量
  , ANumber <$> read <$> many1 digit
    -- 括号里的算术表达式
  , parens aExprParser
  ]

aExprParser :: Parser AExpr
aExprParser = buildExpressionParser aOperators aTerm

第三步:整合完整表达式解析器

现在expressionParser可以安全地整合算术表达式和普通原子表达式,彻底打破循环:

expressionParser :: Parser Expr
expressionParser = choice
  [ -- 把算术表达式包装成普通Expr
    AExprAsExpr <$> aExprParser
    -- 普通原子表达式(标识符、方法调用等)
  , primaryExprParser
    -- 可添加其他普通表达式类型,比如赋值、条件表达式等
  ]

为什么这样能解决问题?

  • 打破循环依赖:expressionParser依赖aExprParser,aExprParser依赖primaryExprParser,primaryExprParser仅在括号场景递归调用expressionParser——这是合法递归,因为括号提供了明确的语法边界,不会无限循环。
  • 避免错误包裹:标识符会被primaryExprParser直接解析成Identifier Expr,只有在解析算术表达式时,才会把原子表达式转成AVar AExpr,再通过AExprAsExpr包装成Expr,完全符合预期逻辑。

额外小提示

如果你的语法里算术表达式是普通表达式的子集(比如x+y本身就是合法普通表达式),要注意解析顺序:expressionParser里先尝试解析aExprParser,再解析primaryExprParser,这样复杂的算术表达式会被优先识别,不会被拆成单个标识符和运算符。

内容的提问来源于stack exchange,提问作者Michael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:47:54