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

[インデックス 1281] ファイルの概要

このコミットは、Go言語のコンパイラ(gc)における変数(var)および定数(const)の宣言処理を大幅に改善し、特にiotaキーワードを用いた定数宣言のメカニズムを導入したものです。これにより、Go言語の宣言構文がより柔軟で強力になり、コンパイラの内部構造もよりモジュール化されました。

コミット

このコミットは、Go言語の初期開発段階における重要な変更点の一つであり、言語の構文とコンパイラの内部処理の進化を示しています。変数と定数の宣言ロジックが、文法定義ファイル(go.y)から宣言処理を担当するC言語のファイル(dcl.c)に分離され、コードのモジュール化と保守性が向上しています。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c597845e1302a4ad58b00bbc927bc17bfb97495f

元コミット内容

commit c597845e1302a4ad58b00bbc927bc17bfb97495f
Author: Ken Thompson <ken@golang.org>
Date:   Thu Dec 4 15:33:40 2008 -0800

    const/var/iota declarations as discussed

    R=r
    OCL=20506
    CL=20506

変更の背景

このコミットの背景には、Go言語の設計初期段階における変数および定数宣言の構文とセマンティクスの洗練があります。特に、複数の定数をまとめて宣言する際に、自動的に連番を生成するiotaの概念を導入し、より簡潔で強力な定数宣言メカニズムを提供することが目的でした。

従来の宣言処理は、Yacc/Bisonの文法定義ファイル(go.y)内に直接記述されており、宣言のロジックが文法解析と密結合していました。これにより、宣言の複雑性が増すにつれてgo.yファイルが肥大化し、可読性や保守性が低下する可能性がありました。このコミットは、宣言処理のロジックを専用のC言語関数(dcl.c内のvariterconstiter)に分離することで、この問題を解決し、コードの構造を改善しています。この変更は、Go言語の構文が進化し、より表現豊かな宣言形式をサポートするための基盤を築くものでした。

前提知識の解説

Go言語のコンパイラ (gc)

Go言語の公式コンパイラは、初期にはgc(Go Compiler)と呼ばれていました。これは、C言語で書かれたコンパイラであり、Go言語のソースコードを機械語に変換する役割を担っていました。src/cmd/gc/ディレクトリには、このコンパイラのソースコードが含まれています。このコミットは、そのコンパイラの重要な部分である宣言処理に手を入れています。

Yacc/Bison (.yファイル)

go.yファイルは、Go言語の文法を定義するYacc(Yet Another Compiler Compiler)またはBisonの入力ファイルです。Yacc/Bisonは、文法定義からパーサー(構文解析器)を自動生成するツールです。.yファイルには、文法規則と、それぞれの規則が認識されたときに実行されるアクション(C言語のコード)が記述されています。このコミットでは、これらのアクションから宣言ロジックが分離され、よりクリーンな文法定義が実現されました。

iota

iotaはGo言語における特別な識別子で、const宣言ブロック内で使用されます。iotaは、const宣言ブロック内で連続する定数に自動的に連番を割り当てるために使用される、プリデクレアされた(事前に宣言された)定数です。constブロックの最初のiota0に評価され、それ以降のconst宣言ごとに1ずつ増加します。これにより、列挙型のような連続した値を簡単に定義できます。

例:

const (
    A = iota // A = 0
    B        // B = 1 (implicitly B = iota)
    C        // C = 2 (implicitly C = iota)
)

const (
    D = iota * 10 // D = 0 * 10 = 0
    E             // E = 1 * 10 = 10
    F             // F = 2 * 10 = 20
)

このコミットは、このiotaのセマンティクスをコンパイラに実装する初期段階を示しており、Go言語の表現力を高める上で不可欠な機能となりました。

変数と定数の宣言

Go言語では、varキーワードを用いて変数を宣言し、constキーワードを用いて定数を宣言します。

  • 変数宣言: var name type = expression または name := expression (短縮宣言)
  • 定数宣言: const name type = expression

このコミットは、特に複数の変数や定数をまとめて宣言する際の内部処理に焦点を当てています。例えば、var a, b = 1, 2const C, D = 3, 4のような宣言形式を効率的に処理するための基盤を構築しています。

技術的詳細

このコミットの主要な技術的変更点は、Goコンパイラのフロントエンドにおける構文解析と意味解析の分離を強化した点にあります。

  1. 宣言ロジックの分離:

    • 以前はsrc/cmd/gc/go.y(文法定義ファイル)内で直接行われていた変数および定数の宣言処理が、src/cmd/gc/dcl.c内の新しい関数variterconstiterに集約されました。
    • これにより、文法定義は純粋に構文の認識に特化し、具体的な宣言のセマンティクス(型チェック、シンボルテーブルへの登録、iotaの管理など)はdcl.c内の関数が担当するようになりました。これは、コンパイラの設計における「関心の分離」の原則に従った改善であり、コードの保守性と拡張性を大幅に向上させます。
  2. variter関数の導入:

    • variter(Node *vv, Type *t, Node *ee)は、複数の変数名(vv)、型(t)、および初期化式(ee)のリストを受け取り、それぞれの変数宣言を処理します。
    • この関数は、変数名と式のリストを逆順に処理し(rev(vv)rev(ee))、listfirstlistnextを使ってイテレーションを行います。これにより、リストの要素を効率的に処理できます。
    • 各変数について、nod(OAS, v, N)で代入ノードを作成し、型が指定されていない場合は式の型から推論し(gettype(e, a))、dodclvarを呼び出して変数を宣言します。これは、Go言語の型推論機能の初期実装の一部です。
    • エラーハンドリングとして、変数名と式の数が一致しない場合にyyerror("shape error in var dcl")を出力し、コンパイルエラーを報告します。
  3. constiter関数の導入とiotaの管理:

    • constiter(Node *vv, Type *t, Node *cc)は、複数の定数名(vv)、型(t)、および初期化式(cc)のリストを受け取ります。
    • constiterの最も重要な役割は、iotaのインクリメントを管理することです。const宣言ブロックの各定数宣言の終わりにiota += 1;が実行されるようになりました。これにより、iotaが自動的に増加し、連続する定数に異なる値を割り当てることが可能になります。
    • cc == Nの場合、前回のconst宣言の式(lastconst)を再利用するロジックが含まれており、これはconst (A; B)のように、式を省略して前の定数宣言の式を再利用するGo言語の構文をサポートするためのものです。
    • 定数式に対してgettype(c, N)で型を取得し、型が明示されている場合はconvlit(c, t)で型変換を行い、dodclconst(v, c)を呼び出して定数を宣言します。
    • variterと同様に、定数名と式の数が一致しない場合にyyerror("shape error in var dcl")(このエラーメッセージはvar dclとありますが、実際には定数宣言のエラーを示しています)を出力します。
  4. go.yの簡素化:

    • go.yファイルでは、Bvardcl(変数ブロック宣言)とconstdcl(定数宣言)の文法規則が大幅に簡素化されました。
    • 以前は文法規則のアクション内に直接記述されていた宣言ロジックが、variterconstiterの呼び出しに置き換えられました。
    • 例えば、Bvardclのルールは$$ = variter($1, $2, $4);のように、constdclのルールはconstiter($1, $2, $4);のように変更され、文法定義の可読性が向上しました。
    • また、new_namenew_name_list_rに置き換えられたことで、文法レベルで複数の名前を一度に宣言する構文がサポートされるようになりました。

これらの変更により、Go言語のコンパイラは、よりクリーンなアーキテクチャを持ち、将来の言語機能の追加や変更に対する拡張性が向上しました。特にiotaの導入は、Go言語の定数宣言をより強力で表現豊かなものにする上で不可欠なステップでした。

コアとなるコードの変更箇所

src/cmd/gc/dcl.c

  • variter関数が新規追加されました。
    • 変数名のリストと式のリストを反復処理し、各変数に対して型推論と宣言を行います。
  • constiter関数が新規追加されました。
    • 定数名のリストと式のリストを反復処理し、各定数に対して型変換と宣言を行います。
    • iota変数をインクリメントするロジックが含まれています。

src/cmd/gc/go.h

  • variterconstiter関数のプロトタイプ宣言が追加されました。これにより、これらの関数がコンパイラの他の部分から呼び出せるようになります。

src/cmd/gc/go.y

  • Bvardcl(変数ブロック宣言)の文法規則が変更され、宣言ロジックがvariter関数呼び出しに置き換えられました。
    • 例: 変更前は複雑な宣言ロジックが直接記述されていましたが、変更後は$$ = variter($1, $2, $4);のように簡潔になりました。
  • constdcl(定数宣言)およびconstdcl1(連続する定数宣言)の文法規則が変更され、宣言ロジックがconstiter関数呼び出しに置き換えられました。
    • 例: constiter($1, $2, $4);
    • iota += 1;のロジックがgo.yから削除され、constiter内に移動しました。これにより、iotaの管理が一元化されました。
  • new_namenew_name_list_rに置き換えられ、複数の名前を一度に宣言する構文に対応するようになりました。

コアとなるコードの解説

src/cmd/gc/dcl.cvariter 関数

Node*
variter(Node *vv, Type *t, Node *ee)
{
    Iter viter, eiter;
    Node *v, *e, *r, *a;

    vv = rev(vv); // 変数名のリストを逆順にする (リストの処理を容易にするため)
    ee = rev(ee); // 式のリストを逆順にする

    v = listfirst(&viter, &vv); // 最初の変数名を取得
    e = listfirst(&eiter, &ee); // 最初の式を取得
    r = N; // 結果のノードリスト (宣言された変数を格納)

loop:
    if(v == N && e == N) // 変数名と式の両方が尽きたらループ終了
        return rev(r);

    if(v == N || e == N) { // 変数名と式の数が一致しない場合のエラーチェック
        yyerror("shape error in var dcl"); // 文法エラーを報告
        return rev(r);
    }

    a = nod(OAS, v, N); // 代入ノードを作成 (OAS: Operator ASsign)
                        // 左辺は変数名v、右辺は後で設定
    if(t == T) { // 型が指定されていない場合 (型推論が必要な場合)
        gettype(e, a); // 式eから型を推論し、代入ノードaに設定
        defaultlit(e); // リテラルのデフォルト型を適用 (例: 整数リテラルはint)
        dodclvar(v, e->type); // 推論された型で変数を宣言
    } else { // 型が明示的に指定されている場合
        dodclvar(v, t); // 指定された型で変数を宣言
    }
    a->right = e; // 代入ノードの右辺に初期化式を設定

    r = list(r, a); // 結果リストに代入ノードを追加

    v = listnext(&viter); // 次の変数名を取得
    e = listnext(&eiter); // 次の式を取得
    goto loop; // ループを継続
}

この関数は、var (a, b = 1, 2)のような複数変数の一括宣言を処理します。変数名と初期化式のリストを反復処理し、それぞれの変数に対して適切な型を決定し、コンパイラの内部表現で変数を宣言します。型が明示されていない場合は、初期化式の型から変数の型を推論します。

src/cmd/gc/dcl.cconstiter 関数

void
constiter(Node *vv, Type *t, Node *cc)
{
    Iter viter, citer;
    Node *v, *c, *a;

    if(cc == N) // 式が指定されていない場合 (例: const (A; B) のB)
        cc = lastconst; // 前回の定数式を再利用
    lastconst = cc; // 現在の定数式をlastconstとして保存 (次の省略記法のために)
    vv = rev(vv); // 定数名のリストを逆順にする
    cc = rev(treecopy(cc)); // 式のリストを逆順にし、コピーを作成 (元の式ノードを破壊しないため)

    v = listfirst(&viter, &vv); // 最初の定数名を取得
    c = listfirst(&citer, &cc); // 最初の式を取得

loop:
    if(v == N && c == N) { // 定数名と式の両方が尽きたらループ終了
        iota += 1; // iotaをインクリメント (constブロック内の各定数宣言の終わりに実行)
        return;
    }

    if(v == N || c == N) { // 定数名と式の数が一致しない場合のエラーチェック
        yyerror("shape error in var dcl"); // 文法エラーを報告 (メッセージはvar dclだが、const dclのエラー)
        iota += 1; // エラー時でもiotaはインクリメントされる
        return;
    }

    gettype(c, N); // 式の型を取得
    if(t != T) // 型が明示的に指定されている場合
        convlit(c, t); // リテラルを型変換 (例: 1をfloat64に変換)
    dodclconst(v, c); // 定数を宣言

    v = listnext(&viter); // 次の定数名を取得
    c = listnext(&citer); // 次の式を取得
    goto loop; // ループを継続
}

この関数は、const (A, B = 1, 2)const (A = iota; B)のような複数定数の一括宣言を処理します。特に、iotaのインクリメントをこの関数内で管理することで、constブロック内でのiotaの自動増加を実現しています。また、式が省略された場合に前回の式を再利用するロジックも含まれており、Go言語の簡潔な定数宣言を可能にしています。

src/cmd/gc/go.y の変更

go.yでは、Bvardclconstdclといった宣言に関する文法規則のアクション部分が、上記で説明したvariterconstiterの呼び出しに置き換えられています。これにより、文法定義がより簡潔になり、宣言のセマンティクスはC言語の関数に委譲されることで、コンパイラの構造がより明確になりました。この変更は、Go言語のパーサーがよりクリーンで保守しやすい形に進化したことを示しています。

関連リンク

参考にした情報源リンク