ホームページ >バックエンド開発 >PHPチュートリアル >1 時間の表現言語
このブログ投稿は、元の形式で表示するのが最適です。
この投稿は、1 時間の式言語 というタイトルのプレゼンテーションを要約し、概念とコードの両方をレビューします。1
式言語2は、このコンテキストでは、式 (バイトのシーケンス、おそらく UTF-8 文字) を評価します。3 例は次のとおりです。
1 1
//article[@title="foobar"]//image
.items[].foo|select(.bar = "foo")
a.comments > 1 and a.category not in ["misc"]
式言語 (または DSL4) の例は次のとおりです:
なぜ独自の表現言語を構築するのでしょうか? なぜだめですか? 忙しすぎますか? 心配しないで!数か月、数週間、さらには数日も必要ありません。 One Hour Expression Language を使用して 1 時間で 1 つ作成しましょう!5
私たちは、2000 年以降に向けた次世代の非科学算術計算機である ProCalc2000 式言語を構築します。
1 1
や 1 2
などの式を評価し、1 3 2 / 2
などの除算問題を処理できます。
言語は数字 (例: 1、2) と演算子 (、、-、) で構成されます。 演算子の優先順位 (付録 I を参照) や除算は*サポートされません。
そのシンプルさにもかかわらず、変数、関数、パイプ演算子、サフィックス、文字列の連結、さらには (ゴジラの希望に反して) 除算などの機能を追加するための基盤を提供します。
バイト シーケンスを評価するには多くの方法がありますが、ここではトークナイザー、パーサー、およびエバリュエーターを使用します。
<code> +-----------+ tokens +--------+ ast +-----------+ EXPRESSION ==>| Tokenizer |--------->| Parser |------>| Evaluator | => VALUE +-----------+ +--------+ +-----------+</code>
レクサーまたはスキャナーとも呼ばれます。このクラスは、文字列をトークンと呼ばれる分類されたチャンクに分割します。
<code class="language-php">class Tokenizer { public function tokenize(string $expression): Tokens { // ... } }</code>
たとえば、1 2 3
は 5 つのトークンを生成します。
<code>Token(Integer, 1) Token(Plus) Token(Integer, 2) Token(Plus) Token(Integer, 3)</code>
トークナイザーは左から右にスキャンして、興味深いチャンク (正の整数、 、 - 、および * 演算子) を識別します。空白は無視されます。他の文字を使用するとエラーが発生します。 トークンのタイプは、整数、プラス、マイナス、乗算です。
トークナイザーは式の有効性をチェックしません。チャンクを分類するだけです。6 トークンはパーサーに渡されます。
パーサーはトークンを解釈し、抽象構文ツリー (AST) に変換します。
<code> +-----------+ tokens +--------+ ast +-----------+ EXPRESSION ==>| Tokenizer |--------->| Parser |------>| Evaluator | => VALUE +-----------+ +--------+ +-----------+</code>
トークン リストが与えられると、パーサーは AST (ツリーのルート ノード) を返します。 各ノードは評価可能な式です。ノード タイプは BinaryOp と Integer です。
二項演算には 2 つのオペランドがあります (たとえば、
foo or bar
はBinaryOp(Variable('foo'), 'or', Variable('bar'))
である可能性があります)。単項演算にはオペランドが 1 つあります (例:
-1
)。三項演算には 3 つのオペランドがあります (例:
foo ? bar : baz
)。
式 1 1 / 5
は、演算子として
を持つ BinaryOp で、一方のオペランドは 1 で、もう一方のオペランドは別の BinaryOp (1 / 5
) です。
<code class="language-php">class Tokenizer { public function tokenize(string $expression): Tokens { // ... } }</code>
エバリュエーターはノードを受け入れ、値 (ここでは整数) を返します。 木歩き通訳です
<code>Token(Integer, 1) Token(Plus) Token(Integer, 2) Token(Plus) Token(Integer, 3)</code>
このコードは PHPSW ミートアップで誕生し、単体テストによって推進されました (ここでは省略)。リポジトリを参照してください。
まず、Token
列挙型とオプションの値を持つ TokenType
クラス:
<code class="language-php">class Parser { public function parse(Tokens $tokens): Node { // ... } }</code>
<code> +-------------+ | Binary Op + | <p>In PHP:</p> ```php $ast = new BinaryOp( left: new Integer(1), operator: '+', right: new BinaryOp( left: new Integer(1), operator: '/', right: new Integer(5), ) );</code>
トークンは次のようになります:
<code class="language-php">class Evaluator { public function evaluate(Node $node): int { // ... } }</code>
Tokenizer
クラスは次の作業を行います:7
<code class="language-php">class Token { public function __construct( public TokenType $type, public ?string $value = null ) {} }</code>
Tokens
コレクション:
<code class="language-php">enum TokenType { case Plus; case Minus; case Multiply; case Integer; }</code>
<code class="language-php">[ new Token(TokenType::Integer, 50), new Token(TokenType::Plus), // ... ]</code>
ここに、演算子の優先順位、サフィックス解析、パイプ演算子が追加されます。 たとえば、サフィックス解析では、「5 マイル」のような表現を処理します。
<code class="language-php">class Tokenizer { public function tokenize(string $expression): Tokens { $offset = 0; $tokens = []; while (isset($expression[$offset])) { $char = $expression[$offset++]; if (is_numeric($char)) { while (is_numeric($expression[$offset] ?? null)) { $char .= $expression[$offset++]; } $tokens[] = new Token(TokenType::Integer, $char); continue; } $token = match ($char) { '+' => new Token(TokenType::Plus), '-' => new Token(TokenType::Minus), '*' => new Token(TokenType::Multiply), ' ' => null, default => throw new RuntimeException(sprintf( 'Invalid operator: "%s"', $char )), }; if ($token === null) { continue; } $tokens[] = $token; } return new Tokens($tokens); } }</code>
このコードは、テストを含めてライブコーディングされました。 完全なコードはリポジトリで入手できます。
式 1 * 3 4
は (1 * 3) 4 = 7
である必要がありますが、解析方法により、言語はそれを 1 * (3 4) = 7
として評価します。8 Pratt パーサーはこれを修正します。
<code> +-----------+ tokens +--------+ ast +-----------+ EXPRESSION ==>| Tokenizer |--------->| Parser |------>| Evaluator | => VALUE +-----------+ +--------+ +-----------+</code>
preg_
メソッドの方がパフォーマンスが高い可能性があります。以上が1 時間の表現言語の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。