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

Haskell中使用readP库构建左结合算术解析器的问题

解决ReadP构建左结合算术解析器的问题

你的核心问题在于递归下降解析器无法直接处理左递归语法,同时当前的右递归写法自然会产生右结合的AST。要实现左结合,需要通过语法分层+迭代折叠的方式改造解析器结构。

问题根源分析

  1. 当你把pOp里的e1改成pExpr时,会触发左递归:pExpr调用pOp,pOp又调用pExpr,导致无限循环,这是递归下降解析器的固有局限。
  2. 当前代码中pOp用numConst作为左操作数,pExpr作为右操作数,本质是右递归语法,所以解析2+4+6+8时会生成2+(4+(6+8))这种右结合结构。

解决方案:语法分层+左折叠

正确的做法是将表达式拆分为原子表达式和后缀运算符链,通过迭代收集所有运算符和右操作数,再用左折叠将它们组合成左结合的AST。

修改后的完整代码:

import Data.Char
import Text.ParserCombinators.ReadP
import Control.Applicative ((<|>))

type Parser a = ReadP a 

data Value =
  IntVal Int
  deriving (Eq, Show, Read)

data Exp =
    Const Value
  | Oper Op Exp Exp
  deriving (Eq, Show, Read)

data Op = Plus
  deriving (Eq, Show, Read)

space :: Parser Char
space = satisfy isSpace

spaces :: Parser String 
spaces = many space

space1 :: Parser String
space1 = many1 space

symbol :: String -> Parser String
symbol = token . string

token :: Parser a -> Parser a
token combinator = do
  spaces
  combinator

parseString input = readP_to_S (do                         
  e <- pExpr 
  token eof
  return e) input

-- 原子表达式:最基础的表达式单元(这里是数字,后续可扩展括号表达式)
pAtom :: Parser Exp
pAtom = do
  skipSpaces
  digits <- munch isDigit            
  return $ Const (IntVal (read digits))

-- 主表达式:先解析一个原子,再迭代解析后续的运算符+原子,左折叠实现左结合
pExpr :: Parser Exp
pExpr = do
  first <- pAtom
  rest <- many parseOpAndAtom
  return $ foldl combine first rest
  where
    combine exp1 (op, exp2) = Oper op exp1 exp2
    parseOpAndAtom = do
      skipSpaces
      _ <- symbol "+"
      exp2 <- pAtom
      return (Plus, exp2)

关键代码解释

  1. pAtom:负责解析最基础的表达式单元(比如整数常量),这是递归的终止点,避免左递归。
  2. pExpr:
    • 先解析第一个原子表达式作为初始值。
    • 用many迭代解析所有后续的+ 原子组合,得到一个[(Op, Exp)]列表。
    • 用foldl左折叠这个列表:每次将当前的左结合表达式与新的右操作数组合,最终生成((2+4)+6)+8这种左结合的AST。

验证效果

在GHCi中测试:

ghci> parseString "2+4+6+8"
[((((Const (IntVal 2) `Oper` Plus) (Const (IntVal 4)) `Oper` Plus) (Const (IntVal 6)) `Oper` Plus) (Const (IntVal 8)),"")]

输出的AST是标准的左结合形式。

为什么不需要用look前瞻

你尝试的look方案是绕远路,根本问题是语法结构设计不当,而非需要前瞻处理。通过语法分层消除左递归,再用迭代折叠实现左结合,是递归下降解析器处理这类问题的标准方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 08:35:24