Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

% ソース解析

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はキーワードですが、識別子として扱われることも可能であり、これについては後ほど(多くの罵倒とともに)出てきます。次に、キーワードの一覧には、yieldmacroのような、言語には実際には存在しないものの、コンパイラによって解析される不審な項目が含まれています。これらは将来の利用のために予約されています。第三に、シンボルの一覧にも、その言語で使われていない項目が含まれています。<-の場合、それは痕跡的なものです。文法からは削除されましたが、レキサーからは削除されていません。最後の点として、::は独立したトークンであり、単に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つの側面があります。対応のない丸括弧、角括弧、中括弧を持つことは不可能です。また、トークンツリー内でグループが誤ってネストされることも不可能です。


  1. @には目的がありますが、ほとんどの人はそれを完全に忘れているようです。これはパターン内で、パターンの非終端部分を名前に束縛するために使われます。この章を校正していたRustコアチームのメンバーでさえ、まさにこの節を話題にしていたにもかかわらず、@に目的があることを覚えていませんでした。かわいそうな、かわいそうな渦巻きくん。

  2. 実際には、CプリプロセッサはC自体とは異なる字句構造を使用しますが、その違いは大まかには重要ではありません。

  3. それが機能すべきかどうかは、まったく別の問題です。