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

Haskell结合State与Alternative:解析器及生成式规则验证问题

嘿,看起来你已经搭好了解析器的核心基础框架——用ExceptT处理错误、State管理Token流,还有consumeToken和peekToken这两个关键工具函数,接下来要做的就是用这些来实现生成式规则的验证,也就是经典的递归下降解析啦!我给你梳理下具体的实现思路和实用例子:

核心思路:递归下降解析

每个生成式规则对应一个独立的Parser函数,比如规则Expr → Term ( '+' Term )*就对应parseExpr :: Parser Expr。我们用peekToken预判当前Token是否符合规则的起始符号,符合就用consumeToken消耗它,再递归调用子规则的Parser来组合最终结果。

先封装辅助工具函数

先写一个matchToken函数,把peekToken+consumeToken的逻辑封装起来,避免重复代码:

matchToken :: (Token -> Bool) -> Parser Token
matchToken predicate = do
  currentTok <- peekToken
  if predicate currentTok
    then consumeToken  -- 匹配成功就消耗该Token
    else throwE $ UnexpectedToken currentTok (expectedFromPredicate predicate)
-- 这里需要你补充`expectedFromPredicate`函数,根据传入的匹配逻辑返回对应的ExpectedToken,方便错误提示
示例:实现算术表达式生成式

假设我们有这样的生成式规则:

Expr → Term ( ( '+' | '-' ) Term )*
Term → Factor
Factor → Number | '(' Expr ')'

先定义对应的AST类型(你可以根据需求调整):

data Expr = Literal Int | Add Expr Expr | Subtract Expr Expr deriving (Show)

然后逐个实现规则的Parser:

解析Factor

parseFactor :: Parser Expr
parseFactor = do
  tok <- peekToken
  case tok of
    -- 匹配数字:消耗Token并返回Literal节点
    Number n -> do
      consumeToken
      return $ Literal n
    -- 匹配括号:消耗左括号后递归解析Expr,再确保右括号存在
    LParen -> do
      consumeToken
      innerExpr <- parseExpr
      _ <- matchToken (== RParen)
      return innerExpr
    -- 既不是数字也不是左括号,抛出明确错误
    _ -> throwE $ UnexpectedToken tok ExpectedNumberOrLParen

解析Term

因为Term直接对应Factor,逻辑非常简单:

parseTerm :: Parser Expr
parseTerm = parseFactor

解析Expr

这里要处理重复的(+/- Term)部分,用many组合子匹配零次或多次:

parseExpr :: Parser Expr
parseExpr = do
  -- 先解析第一个Term
  firstTerm <- parseTerm
  -- 解析后续所有的"+ Term"或"- Term"
  restTerms <- many parseOpTerm
  -- 把所有Term用运算符组合成完整的Expr
  return $ foldl combineExpr firstTerm restTerms
  where
    combineExpr acc (op, term) = case op of
      Plus -> Add acc term
      Minus -> Subtract acc term
      _ -> error "Unexpected operator (这部分实际不会触发,因为parseOpTerm已做匹配校验)"

-- 解析单个"+ Term"或"- Term"
parseOpTerm :: Parser (Token, Expr)
parseOpTerm = do
  -- 匹配+或-运算符
  op <- matchToken (\t -> t == Plus || t == Minus)
  -- 解析运算符后的Term
  term <- parseTerm
  return (op, term)
处理分支与可选规则

如果生成式有分支(比如Stmt → ExprStmt | VarDecl),可以用<|>组合子(需要导入Control.Applicative):

parseStmt :: Parser Stmt
parseStmt = parseExprStmt <|> parseVarDecl

如果有可选部分(比如OptionalExpr → Expr | ε),用optional组合子:

parseOptionalExpr :: Parser (Maybe Expr)
parseOptionalExpr = optional parseExpr
运行解析器

最后写一个顶层函数来启动解析,把Token流转换成AST或者错误信息:

runParser :: Parser a -> [Token] -> Either ParseError a
runParser parser tokens = case runState (runExceptT parser) tokens of
  (Left err, _) -> Left err
  (Right result, _) -> Right result
关键注意点
  • 错误信息要明确:抛出错误时尽量携带预期的Token类型,调试时能快速定位问题
  • 避免左递归:递归下降解析不支持左递归规则(比如Expr → Expr '+' Term),要改成Expr → Term ( '+' Term )*这种右递归形式
  • 利用Monad特性:Parser是Monad,用do语法可以轻松组合子Parser,处理顺序依赖的规则

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:13:49