% ソース解析
Rustプログラムのコンパイルにおける最初の段階は、トークン化です。ここでは、ソーステキストがトークン(すなわち 分割不可能な字句単位。プログラミング言語における「単語」に相当するもの)の列へと変換されます。Rustには、次のようなさまざまな種類のトークンがあります。
- 識別子:
foo,Bambous,self,we_can_dance,LaCaravane, … - 整数:
42,72u32,0_______0, … - キーワード:
_,fn,self,match,yield,macro, … - ライフタイム:
'a,'b,'a_rare_long_lifetime_name, … - 文字列:
"","Leicester",r##"venezuelan beaver"##, … - シンボル:
[,:,::,->,@,<-, …
…などです。上記について、いくつか注意すべき点があります。まず、selfは識別子でもあり、キーワードでもあります。ほとんどすべての場合において、selfはキーワードですが、識別子として扱われることも可能であり、これについては後ほど(多くの罵倒とともに)出てきます。次に、キーワードの一覧には、yieldやmacroのような、言語には実際には存在しないものの、コンパイラによって解析される不審な項目が含まれています。これらは将来の利用のために予約されています。第三に、シンボルの一覧にも、その言語で使われていない項目が含まれています。<-の場合、それは痕跡的なものです。文法からは削除されましたが、レキサーからは削除されていません。最後の点として、::は独立したトークンであり、単に2つの隣接した:トークンではないことに注意してください。Rust 1.2時点では、Rustにおけるすべての複数文字シンボルトークンについて同じことが当てはまります。1
比較対象として、一部の言語では、この段階にマクロ層がありますが、Rustにはありません。たとえば、C/C++のマクロは、この時点で実質的に処理されます。2 これが、次のコードが機能する理由です。3
#define SUB void
#define BEGIN {
#define END }
SUB main() BEGIN
printf("Oh, the horror!\n");
END
次の段階は解析であり、トークンのストリームが抽象構文木(AST)に変換されます。これには、プログラムの構文構造をメモリ上に構築することが含まれます。たとえば、トークン列1 + 2は、次のものに相当する形へと変換されます。
┌─────────┐ ┌─────────┐
│ BinOp │ ┌╴│ LitInt │
│ op: Add │ │ │ val: 1 │
│ lhs: ◌ │╶┘ └─────────┘
│ rhs: ◌ │╶┐ ┌─────────┐
└─────────┘ └╴│ LitInt │
│ val: 2 │
└─────────┘
ASTにはプログラム全体の構造が含まれますが、それは純粋に字句的な情報に基づいています。たとえば、コンパイラは特定の式が「a」という名前の変数を参照していることを知っているかもしれませんが、この段階では、「a」が何であるか、あるいはそれがどこから来たのかさえ知る手段がありません。
マクロが処理されるのは、ASTが構築された後です。しかし、それについて議論する前に、トークンツリーについて話す必要があります。
トークンツリー
トークンツリーは、トークンとASTの中間に位置するものです。第一に、ほとんどすべてのトークンはトークンツリーでもあります。より具体的には、それらは葉です。トークンツリーの葉になり得るものはもう1種類ありますが、それについては後で戻ってきます。
葉ではない唯一の基本トークンは、「グループ化」トークン、すなわち(...)、[...]、{...}です。これら3つはトークンツリーの内部ノードであり、それらに構造を与えるものです。具体例を挙げると、このトークン列は次のようになります。
a + b + (c + d[0]) + e
次のトークンツリーへと解析されます。
«a» «+» «b» «+» «( )» «+» «e»
╭────────┴──────────╮
«c» «+» «d» «[ ]»
╭─┴─╮
«0»
これは、その式が生成するASTとは何の関係もないことに注意してください。単一のルートノードではなく、ルートレベルには9個のトークンツリーがあります。参考までに、ASTは次のようになります。
┌─────────┐
│ BinOp │
│ op: Add │
┌╴│ lhs: ◌ │
┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐
│ Var │╶┘ └─────────┘ └╴│ BinOp │
│ name: a │ │ op: Add │
└─────────┘ ┌╴│ lhs: ◌ │
┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐
│ Var │╶┘ └─────────┘ └╴│ BinOp │
│ name: b │ │ op: Add │
└─────────┘ ┌╴│ lhs: ◌ │
┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐
│ BinOp │╶┘ └─────────┘ └╴│ Var │
│ op: Add │ │ name: e │
┌╴│ lhs: ◌ │ └─────────┘
┌─────────┐ │ │ rhs: ◌ │╶┐ ┌─────────┐
│ Var │╶┘ └─────────┘ └╴│ Index │
│ name: c │ ┌╴│ arr: ◌ │
└─────────┘ ┌─────────┐ │ │ ind: ◌ │╶┐ ┌─────────┐
│ Var │╶┘ └─────────┘ └╴│ LitInt │
│ name: d │ │ val: 0 │
└─────────┘ └─────────┘
ASTとトークンツリーの違いを理解することは重要です。マクロを書くときには、両方を別々のものとして扱う必要があります。
これについて注意すべきもう1つの側面があります。対応のない丸括弧、角括弧、中括弧を持つことは不可能です。また、トークンツリー内でグループが誤ってネストされることも不可能です。