包含什么
解释器模式是指行为设计模式。此模式允许您通过使用 AST 树来实现您自己的编程语言,该树的顶点是实现 Interpret 方法的终端和非终端表达式,该方法提供了该语言的功能。
- 终端表达式 – 例如,字符串常量 – “你好世界”
- 非终端表达式 – 例如 Print(“Hello World”),包含 Print 和来自终端表达式“Hello World”的参数
有什么区别?不同之处在于,解释在终端表达式上结束,但对于非终端表达式,它会在所有传入的顶点/参数上继续深入。如果 AST 树仅由非终结符表达式组成,那么应用程序将永远无法完成,因为任何过程都需要一定的有限性,这种有限性就是终端表达式,它们通常包含数据,例如字符串。
AST 树的示例如下:

Dcoetzee,CC0,来自维基共享资源
如您所见,终端表达式是常量和变量,其余的是非终端表达式。
不包括什么
解释器实现不包括解析输入到 AST 树中的语言字符串。实现终结符和非终结符表达式的类、在输入处使用 Context 参数的 Interpret 方法、创建表达式的 AST 树并在根表达式处运行 Interpret 方法就足够了。上下文可用于在运行时存储应用程序状态。
实施
该模式涉及:
- Client – 返回 AST 树并为根节点(Client)运行 Interpret(context)
- 上下文 – 包含应用程序的状态,在解释时传递给表达式(上下文)
- 抽象表达式 – 包含 Interpret(context) (Expression) 方法的抽象类
- 终端表达式是最终表达式,是抽象表达式 (TerminalExpression) 的后代
- 非终结表达式不是有限表达式;它包含指向 AST 树深处顶点的指针;下级顶点通常会影响非终结表达式 (NonTerminalExpression) 的解释结果
C# 中的客户端示例
static void Main(string[] args)
{
var context = new Context();
var initialProgram = new PerformExpression(
new IExpression[] {
new SetExpression("alpha", "1"),
new GetExpression("alpha"),
new PrintExpression(
new IExpression[] {
new ConstantExpression("Hello Interpreter Pattern")
}
)
}
);
System.Console.WriteLine(initialProgram.interpret(context));
}
}
C# 中的抽象表达式示例
{
String interpret(Context context);
}
C# 中的终端表达式示例(字符串常量)
{
private String constant;
public ConstantExpression(String constant) {
this.constant = constant;
}
override public String interpret(Context context) {
return constant;
}
}
C# 中非终结符表达式的示例(使用分隔符“;”开始并连接从属顶点的结果
{
public PerformExpression(IExpression[] leafs) : base(leafs) {
this.leafs = leafs;
}
override public String interpret(Context context) {
var output = "";
foreach (var leaf in leafs) {
output += leaf.interpret(context) + ";";
}
return output;
}
}
你能从功能上做到这一点吗?
众所周知,所有图灵完备的语言都是等价的。是否可以将面向对象模式转移到函数式编程语言?
作为实验,我们采用一种名为 Elm 的 FP 网络语言。 Elm中没有类,但是有Records和Types,因此实现中涉及到以下记录和类型:
- 表达式 – 列出所有可能的语言表达式(Expression)
- 从属表达式 – 从属于非终结符表达式 (ExpressionLeaf) 的表达式
- Context – 存储应用程序状态的记录(Context)
- 实现 Interpret(context) 方法的函数 – 实现终端和非终端表达式功能的所有必要函数
- 解释器状态的辅助记录 – 解释器正确操作所必需的,它们存储解释器状态、上下文
在 Elm 中实现对整个可能表达式集的解释的函数示例:
case input.expression of
Constant text ->
{
output = text,
context = input.context
}
Perform leafs ->
let inputs = List.map (\leaf -> { expressionLeaf = leaf, context = input.context } ) leafs in
let startLeaf = { expressionLeaf = (Node (Constant "")), context = { variables = Dict.empty } } in
let outputExpressionInput = List.foldl mergeContextsAndRunLeafs startLeaf inputs in
{
output = (runExpressionLeaf outputExpressionInput).output,
context = input.context
}
Print printExpression ->
run
{
expression = printExpression,
context = input.context
}
Set key value ->
let variables = Dict.insert key value input.context.variables in
{
output = "OK",
context = { variables = variables }
}
Get key ->
{
output = Maybe.withDefault ("No value for key: " ++ key) (Dict.get key input.context.variables),
context = input.context
}
解析怎么样?
解释器模式中不包含将源代码解析为 AST 树;有多种解析源代码的方法,稍后会详细介绍。
在Elm解释器的实现中,我在AST树中编写了一个简单的解析器,由两个函数组成——解析顶点,解析从属顶点。
parseLeafs state =
let tokensQueue = state.tokensQueue in
let popped = pop state.tokensQueue in
let tokensQueueTail = tail state.tokensQueue in
if popped == "Nothing" then
state
else if popped == "Perform(" then
{
tokensQueue = tokensQueue,
result = (state.result ++ [Node (parse tokensQueue)])
}
else if popped == ")" then
parseLeafs {
tokensQueue = tokensQueueTail,
result = state.result
}
else if popped == "Set" then
let key = pop tokensQueueTail in
let value = pop (tail tokensQueueTail) in
parseLeafs {
tokensQueue = tail (tail tokensQueueTail),
result = (state.result ++ [Node (Set key value)])
}
else if popped == "Get" then
let key = pop tokensQueueTail in
parseLeafs {
tokensQueue = tail tokensQueueTail,
result = (state.result ++ [Node (Get key)])
}
else
parseLeafs {
tokensQueue = tokensQueueTail,
result = (state.result ++ [Node (Constant popped)])
}
parse tokensQueue =
let popped = pop tokensQueue in
let tokensQueueTail = tail tokensQueue in
if popped == "Perform(" then
Perform (
parseLeafs {
tokensQueue = tokensQueueTail,
result = []
}
).result
else if popped == "Set" then
let key = pop tokensQueueTail in
let value = pop (tail tokensQueueTail) in
Set key value
else if popped == "Print" then
Print (parse tokensQueueTail)
else
Constant popped
链接
https://gitlab.com/demensdeum /patterns/-/tree/master/interpreter/elm
https://gitlab.com/demensdeum/patterns/-/tree/master/interpreter/csharp
来源
https://en.wikipedia.org/wiki/Interpreter_pattern
https://elm-lang.org/
https://docs.microsoft.com/en-us/dotnet/csharp/