解析は、実際には 2 つのフェーズで構成されています。1 つ目は「字句解析」です。これは、文字の生の文字列を、プログラムがより容易に理解できるもの (一般にトークンと呼ばれます) に変換します。
簡単な例では、lex は次のように変換します。
(a + b > 2) の場合
の中へ:
IF_TOKEN LEFT_PAREN IDENTIFIER(a) PLUS_SIGN IDENTIFIER(b) GREATER_THAN NUMBER(2) RIGHT_PAREN THEN_TOKEN
パースはそのトークンのストリームを取得し、それらをさらに意味のあるものにしようとします。この場合、それらのトークンを IF_STATEMENT に一致させようとします。解析すると、IF _STATEMENT は次のようになります。
IF (ブール式) THEN
字句解析フェーズの結果がトークン ストリームである場合、解析フェーズの結果は解析ツリーです。
したがって、パーサーは上記を次のように変換できます。
if_ステートメント
| |
v
boolean_expression.operator = GREATER_THAN
| | | |
| | v
V numeric_constant.string="2"
式.演算子 = PLUS_SIGN
| | | |
| | v
v identifier.string = "b"
identifier.string = "a"
IF_STATEMENT があることがわかります。IF_STATEMENT には、BOOLEAN_EXPRESSION である単一の引数があります。これは、何らかの方法でパーサーに説明されました。パーサーがトークン ストリームを変換するとき、IF がどのように見えるかを「認識」し、BOOLEAN_EXPRESSION がどのように見えるかを認識しているため、コードを確認したときに適切な割り当てを行うことができます。
たとえば、次のような場合:
(a + b) の場合
パーサーは、それがブール式ではないことを認識でき (+ は算術演算子であり、ブール演算子ではないため)、解析はこの時点でエラーをスローする可能性があります。
次に、BOOLEAN_EXPRESSION には 3 つのコンポーネント、演算子 (GREATER_THAN)、および左側と右側の 2 つの辺があることがわかります。
左側では、さらに別の式 "a + b" を指し、右側では NUMERIC_CONSTANT (この場合は文字列 "2") を指しています。繰り返しになりますが、パーサーはこれが NUMERIC 定数であることを「認識」しています。数字でない場合は、IDENTIFIER になります ("a" や "b" など)。
次のようなものがある場合に注意してください。
(a + b > "XYZ") の場合
それは問題なく「解析」します(左側の式、右側の文字列定数)。これを見ただけでは、これが有効な式かどうかわかりません。この時点で、「a」または「b」が文字列または数値を参照しているかどうかはわかりません。したがって、これはパーサーが判断できないものであり、単に認識していないため、エラーとしてフラグを立てることはできません。これは、IF ステートメントを評価する (実行するか、コードにコンパイルしようとする) ときに発生します。
もしそうなら:
[a > b ) の場合
パーサーは、その構文エラーを問題としてすぐに認識し、エラーをスローします。そのトークンの文字列は、それが知っているもののようには見えません。
つまり、完全な解析ツリーを取得すると、最初は「コードが適切に見える」という保証が得られるということです。実行中に、他のエラーが発生する可能性があります。
解析ツリーを評価するには、ツリーをたどるだけです。コンパイルまたは評価の部分で、解析ツリーの主要なノードに関連付けられたコードがいくつかあります。通訳がいるとしましょう。
public void execute_if_statment(ParseTreeNode node) {
// We already know we have a IF_STATEMENT node
Value value = evaluate_expression(node.getBooleanExpression());
if (value.getBooleanResult() == true) {
// we do the "then" part of the code
}
}
public Value evaluate_expression(ParseTreeNode node) {
Value result = null;
if (node.isConstant()) {
result = evaluate_constant(node);
return result;
}
if (node.isIdentifier()) {
result = lookupIdentifier(node);
return result;
}
Value leftSide = evaluate_expression(node.getLeftSide());
Value rightSide = evaluate_expression(node.getRightSide());
if (node.getOperator() == '+') {
if (!leftSide.isNumber() || !rightSide.isNumber()) {
throw new RuntimeError("Must have numbers for adding");
}
int l = leftSide.getIntValue();
int r = rightSide.getIntValue();
int sum = l + r;
return new Value(sum);
}
if (node.getOperator() == '>') {
if (leftSide.getType() != rightSide.getType()) {
throw new RuntimeError("You can only compare values of the same type");
}
if (leftSide.isNumber()) {
int l = leftSide.getIntValue();
int r = rightSide.getIntValue();
boolean greater = l > r;
return new Value(greater);
} else {
// do string compare instead
}
}
}
ここに再帰的評価器があることがわかります。実行時の型をチェックし、基本的な評価を実行する方法がわかります。
何が起こるかというと、execute_if_statement がその主な式を評価するということです。解析で BOOLEAN_EXPRESION のみが必要であったとしても、すべての式は目的のためにほとんど同じです。したがって、execute_if_statement は evaluate_expression を呼び出します。
私たちのシステムでは、すべての式に演算子と左辺と右辺があります。式の各辺も式であるため、実際の値を取得するために、それらもすぐに評価して評価する方法を確認できます。1 つの注意点は、式が CONSTANT で構成されている場合は、単に定数値を返し、それが識別子の場合は変数として検索することです (これは、"I can't find変数 'a'" メッセージ)、それ以外の場合は左側/右側の問題に戻ります。
パーサーからトークン ストリームを取得したら、単純なエバリュエーターがどのように機能するかを理解していただければ幸いです。評価中に、言語の主要な要素がどのように配置されているかに注意してください。そうしないと、構文エラーが発生し、このフェーズに到達できませんでした。たとえば PLUS 演算子がある場合、左側と右側の 2 つの式があることを「知っている」と単純に期待できます。または、IF ステートメントを実行すると、評価するブール式が既にあることになります。解析は、私たちのためにその重労働を行うものです。
新しい言語を使い始めるのは大変かもしれませんが、慣れてしまえば残りはとても簡単で、最終的にはほとんど「魔法」のようにうまくいくことがわかるでしょう。
書式設定はご容赦ください。ただし、アンダースコアは混乱を招きます。まだ明確であることを願っています。