我正在开发一个包含小型 DSL 的项目。用这种语言对字符串进行词法分析和解析会产生一个解析树,该解析树实现为一个名为 Expr 的抽象类,然后它具有许多常见的派生类,例如 AssignmentExpr、InvokeExpr、AdditionExpr 等,它们对应于作为分配的解析树节点、函数调用、添加等。该项目是用 C# 实现的。
我目前正在考虑为此 DSL 实现类型推断。这意味着我希望能够获取 Expr 类的实例并返回有关树中不同节点类型的编码信息。此类型信息取决于符号表(变量类型)和函数表(函数签名)。因此,我想做类似的事情:
TypedExpr typedExpr = inferTypes(expr, symbolTable, functionTable)
在这里,TypedExpr 理想情况下与 Expr 类似,除了 Type 属性给出表达式的类型。但是,这会带来以下设计问题:
TypedExpr 从 Expr 继承并简单地实现一个附加属性 Type 是有意义的。但是,这将创建两个并行继承层次结构,一个用于 TypedExpr(TypedAssignmentExpr、TypedInvokeExpr 等),一个用于 Expr(AssignmentExpr、InvokeExpr 等)。这不方便维护,如果需要进一步扩展解析树,问题就会扩大。我不确定如何减轻这种情况。一种可能性是桥接设计模式,但我认为这不能完全解决问题。
或者,Expr 可以简单地实现一个 Type 属性,然后在解析器构造时该属性为 null,然后由类型推断算法填充。但是,传递具有空字段的对象会引发 NullReferenceExceptions。TypedExpr 的想法可以缓解这种情况。此外,鉴于 Expr 类的想法是表示解析树,类型信息实际上并不是树的一部分:类型是上下文相关的,并且需要特定的符号和函数表。
第三,类型推断方法也可以简单地返回一个 Dictionary<Expr, Type>,它对所有节点的类型信息进行编码。这意味着 Expr 仍然只代表解析树。这样做的缺点是构造的字典对象没有任何明显的属性表明它专门链接到传递给类型推断方法的 Expr 对象。
我对上面给出的三个解决方案中的任何一个都不完全满意。
我的问题是:解决这个问题的各种方法的优缺点是什么?类型信息应该直接在解析树中编码,还是应该使用并行树类?还是 Dictionary 解决方案是最好的?是否有公认的“最佳实践”解决方案?