使用FParsec解析含括号与函数调用的语法时遇空引用异常求助
FParsec解析器修复:处理分组表达式与函数调用
问题根源
你碰到的NullReferenceException核心原因是递归语法的转发引用未正确初始化。在FParsec中,像表达式作为函数调用主体/参数这类递归场景,必须用createParserForwardedToRef创建占位解析器后再赋值,否则会出现空引用。
修复后的完整代码
open FParsec // AST定义 type Expr = | Identifier of string | And of Expr * Expr | Or of Expr * Expr | Call of Expr * Expr list let parseExpr = // 创建转发引用,用于递归解析表达式 let exprRef = createParserForwardedToRef<Expr, unit>() // 解析标识符:字母开头,可包含字母数字 let identifier = many1Satisfy2 isLetter isLetterOrDigit |>> Identifier // 括号包裹的表达式 let parenExpr = between (pchar '(') (pchar ')') exprRef.Value // 函数调用解析:主体(标识符/括号表达式)+ 参数列表 let call = pipe2 (identifier <|> parenExpr) (between (pchar '(') (pchar ')') (sepBy exprRef.Value (pchar ',' >> spaces))) (fun target args -> Call(target, args)) // 原子表达式:函数调用 > 括号表达式 > 标识符(优先级匹配顺序) let atomicExpr = call <|> parenExpr <|> identifier // 初始化运算符优先级解析器 let opp = OperatorPrecedenceParser<Expr, unit, unit>() opp.TermParser <- atomicExpr // 添加运算符:&优先级高于| opp.AddOperator(InfixOperator("&", spaces, 2, Associativity.Left, fun a b -> And(a, b))) opp.AddOperator(InfixOperator("|", spaces, 1, Associativity.Left, fun a b -> Or(a, b))) // 给转发引用赋值完整的表达式解析器 exprRef.Value <- opp.ExpressionParser // 返回最终解析器(确保解析到输入结尾) opp.ExpressionParser .>> eof // 测试示例1 test parseExpr "(A & B) | F(C)" // 输出:Or (And (Identifier "A", Identifier "B"), Call (Identifier "F", [Identifier "C"])) // 测试示例2 test parseExpr "(A | B)(C & D(E, F))" // 输出:Call (Or (Identifier "A", Identifier "B"), [And (Identifier "C", Call (Identifier "D", [Identifier "E"; Identifier "F"]))])
关键修复说明
- 转发引用正确初始化:先通过
createParserForwardedToRef创建exprRef占位,所有依赖递归表达式的子解析器(如parenExpr、call)都使用exprRef.Value,最后将完整的运算符解析器赋值给exprRef.Value,闭环递归路径。 - 原子表达式顺序调整:把
call放在最前面,因为函数调用的主体可以是任意表达式(比如括号包裹的Or表达式),需要优先匹配避免解析错误。 - 运算符优先级配置:给
&设置更高优先级(2),|设置较低优先级(1),确保表达式运算顺序符合预期。
排查要点
如果仍有异常,检查:
- 所有递归场景是否都使用了
exprRef.Value,而非直接引用未初始化的解析器变量。 - 运算符解析器的
TermParser是否正确绑定到atomicExpr,没有遗漏任何原子表达式分支。
内容的提问来源于stack exchange,提问作者Richiban
相关产品推荐
相关产品推荐

