DesignPatterns、通訳テンプレート

コンパイラーと言語のインタープリターは通常、他の言語で作成されます(少なくとも最初はそうでした)。 たとえば、PhPはCで作成されました。PhPで推測したように、言語用に独自のコンパイラを定義、設計、および作成できます。 もちろん、私たちが作成する言語はゆっくりと機能が制限されますが、非常に便利です。



そして、解決する必要がある問題を作成します


原則として、アプリケーションを開発するとき、ユーザーにある種の機能(api)を提供します。インターフェイスを開発するとき、機能と使いやすさの間の妥協点を見つける必要があります。 アプリケーションの機能を「トリミング」せずに、おそらくインターフェイスを単純化できます。 ユーザーにドメイン固有のプログラミング言語(通常、DSLまたはドメイン固有言語と呼ばれます)を提供します。アプリケーションの機能を実際に拡張できます。

もちろん、中間プログラミング言語なしで、ユーザーがシステムで独自のスクリプトを作成できるようにすることができます。



$input = $_REQUEST['input']; // «print file_get_contents('/etc/passwd'); eval($input);
      
      







明らかに、このメソッドを通常と呼ぶことはできません。 しかし、それがあなたにとって明らかでない場合、これには2つの理由があります:セキュリティと複雑さ。



しかし、私たちのミニ、中間言語は、そのような問題を解決し、柔軟性を高め、ユーザーの能力を低下させ、システムを壊さずに済みます。

小さなテストシステムを開発するためのアプリケーションが必要だとします。 開発者は質問を考え出し、応答を評価するためのルールを確立します。 ユーザーはテキストボックスにいくつかの回答を入力できますが、質問への回答は人間の介入なしに評価する必要があるという要件があります。



質問例:

私たちの太陽系にはいくつの惑星がありますか?

正解として「8」、「8」の回答を受け入れることができます。 もちろん、これはすべてのお気に入りの正規表現^ 8 | eight $を使用して解決できます。

しかし、残念なことに、すべての開発者がそれらを知っているわけではありません。 したがって、よりわかりやすいインターフェイスを実装しましょう。



$input equals «8» or $input equals «»







変数、等号演算子、ブール論理(またはand)をサポートする言語を作成する必要があります。 私たちはすべてのものを呼び出すのが大好きなので、言語をGoLogicと呼びましょう。

より複雑な機能に対する多くの要求が予想されるため、言語を明確に拡張する必要があります。 入力データの分析の問題は別として、これらの要素を接続して応答を生成するメカニズムのアーキテクチャに進みます。



実装


私たちの小さな言語でさえ、多くの要素を追跡する必要があります:変数、文字列リテラル、ブールAND、ブールOR、および等価チェック。

それらをクラスに分けます:

画像



小さな図を描いてみましょう。

画像



コードのビット:

 abstract class Expression { private static $keycount = 0; private $key; abstract function interpret(InterpreterContext $context); function getKey() { if (!isset($this->key)) { self::$keycount++; $this->key = self::$keycount; } return $this->key; } } class LitralExpr extends Expression { private $value; function __construct($value) { $this->value = $value; } public function interpret(InterpreterContext $context) { $context->replace($this, $this->value); } } class InterpreterContext { private $exprstore = array(); function replace(Expression $exp, $value) { $this->exprstore[$exp->getKey()] = $value; } function lookup(Expression $exp) { return $this->exprstore[$exp->getKey()]; } }
      
      







InterpreterContextは、データの保存に使用する連想配列$ exprstoreのインターフェイスのみを提供します。 キーと値をreplace()メソッドに渡し、lookup()メソッドも実装してデータを取得します。

Expressionクラスは、抽象interpre()メソッドと特定のgetKey()メソッドを定義します。 静的なカウンター値で操作すると、式記述子として返されます;このメソッドを使用して、データにインデックスを付けます。

LiteralExprクラスは、値引数が渡される構造を定義します。 interept()メソッドには、InterpreterContex型のオブジェクトを渡す必要があり、単にreplace()メソッドを呼び出します

次に、残りのVariableExprクラスを定義します。

 class VariableExpr extends Expression { private $name; private $val; public function __construct($name, $val = null) { $this->name = $name; $this->val = $val; } public function interpret(InterpreterContext $context) { if (!is_null($this->val)) { $context->replace($this, $this->val); $this->val = null; } } function setValue($val) { $this->val = $val; } function getKey() { return $this->name; } }
      
      







コンストラクターには2つの引数(名前と値)が渡され、これらはオブジェクトのプロパティに保存されます。 クラスにsetValue()メソッドが実装されているため、クライアントコードはいつでも値を変更できます。

interept()メソッドは、$ valプロパティにゼロ値があるかどうかをチェックし、値がある場合、その値はInterpreterContextオブジェクトに格納されます。 次に、変数$ valをnullに設定して、メソッド呼び出しが、VariableExprオブジェクトの別のインスタンスによってInterpreterContextオブジェクトに格納された同じ名前の変数の値を損なわないようにします。

次に、いくつかのロジックを追加します。



 abstract class OperatorExpr extends Expression { protected $l_op; protected $r_op; function __construct(Expression $l_op, Expression $r_op) { $this->l_op = $l_op; $this->r_op = $r_op; } function interpret(InterpreterContext $context) { $this->l_op->interpret($context); $this->r_op->interpret($context); $result_l = $context->lookup($this->l_op); $result_r = $context->lookup($this->r_op); $this->doInterpret($context, $result_l, $result_r); } protected abstract function doInterpret(InterpreterContext $context, $result_l, $result_r); }
      
      







doInterpret()メソッドは、テンプレートメソッドのインスタンスです。 このテンプレートでは、抽象メソッドが親クラスで定義および呼び出され、その実装は子クラスに任されています。 これにより、特定のクラスの開発を簡素化できます。これは、スーパークラスが共有機能を管理し、子が明確で理解可能な目標に専念できるようにするためです。



 class EqualsExpr extends OperatorExpr { protected function doInterpret(InterpreterContext $context, $result_l, $result_r) { $context->replace($this, $result_l == $result_r); } } class BoolOrExpr extends OperatorExpr { protected function doInterpret(InterpreterContext $context, $result_l, $result_r) { $context->replace($this, $result_l || $result_r); } } class BoolAndExpr extends OperatorExpr { protected function doInterpret(InterpreterContext $context, $result_l, $result_r) { $context->replace($this, $result_l && $result_r); } }
      
      







クラスと小さなシステムを準備したので、少しのコードを実行する準備ができました。 あなたはまだ彼を覚えていますか?

$input equals «8» or $input equals «»







 $context = new InterpreterContext(); $input = new VariableExpr('input'); $stats = new BoolOrExpr(new EqualsExpr($input, new LitralExpr('')), new EqualsExpr($input, new LitralExpr('8'))); foreach (array("", "8", "666") as $val) { $input->setValue($val); print "$val:\n"; $stats->interpret($context); if($context->lookup($stats)) { print '\n\n'; } else { print " \n\n"; } }
      
      







私たちのシステムにはまだパーサーがありません。独自のパーサーを作成するか、既存のものを使用できます。 複雑なシステムの場合は、既存のエンジンを使用することをお勧めします。



画像

通訳パターンの問題


Interpreterテンプレートを実装するためのメインクラスを準備したら、それを簡単に拡張できます。 そのために支払わなければならない価格は、作成する必要があるクラスの数だけです。 したがって、このパターンは比較的小さな言語により適しています。 また、本格的なプログラミング言語が必要な場合は、サードパーティ企業のツールを探す方が良いでしょう。



Interpreterクラスは非常によく似たタスクを実行することが多いため、作成されるクラスを追跡して重複を避けることは価値があります。



ソース:

4つのテンプレートのギャング



All Articles