含まれるもの
インタープリター パターンは、動作デザイン パターンを指します。このパターンを使用すると、AST ツリーを操作して独自のプログラミング言語を実装できます。AST ツリーの頂点は、言語の機能を提供する Interpret メソッドを実装する終端式と非終端式です。
- 終端式 – 例: 文字列定数 – 「Hello World」
- 非終端式 – たとえば、Print(“Hello World”) には、Print と終端式 “Hello World” からの引数が含まれます
違いは何ですか?違いは、解釈は終端式で終了しますが、非終端式の場合は、入力されるすべての頂点/引数にわたって深く継続されることです。 AST ツリーが非終端式のみで構成されている場合、アプリケーションは決して完了しません。どのプロセスにも特定の有限性が必要です。この有限性が終端式の正体であり、通常、終端式には文字列などのデータが含まれます。
AST ツリーの例を以下に示します。

Dcoetzee、CC0、ウィキメディア コモンズ経由
ご覧のとおり、終端式は定数と変数であり、残りは非終端式です。
含まれないもの
インタープリタの実装には、AST ツリーに入力された言語文字列の解析は含まれません。終端式と非終端式のクラスを実装し、入力で Context 引数を使用してメソッドを解釈し、式の AST ツリーを作成し、ルート式で Interpret メソッドを実行するだけで十分です。コンテキストを使用して、実行時にアプリケーションの状態を保存できます。
実装
パターンには以下が含まれます:
- クライアント – AST ツリーを返し、ルート ノード (クライアント) に対して Interpret(context) を実行します
- コンテキスト – アプリケーションの状態が含まれており、解釈時に式に渡されます (コンテキスト)
- 抽象式 – Interpret(context) (Expression) メソッドを含む抽象クラス
- ターミナル式は最終式であり、抽象式 (ターミナル式) の子孫です
- 非終端式は有限式ではありません。これには 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 という Web 用の FP 言語を取り上げてみましょう。 Elm にはクラスはありませんが、レコードとタイプがあるため、次のレコードとタイプが実装に関係します。
- 式 – 考えられるすべての言語式のリスト (式)
- 従属式 – 非終端式 (ExpressionLeaf) に従属する式
- コンテキスト – アプリケーションの状態 (コンテキスト) を保存するレコード
- 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 のインタープリタの実装では、頂点の解析と下位頂点の解析という 2 つの関数で構成される単純なパーサーを 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/