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

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

このコミットは、Go言語に新しいビット演算子である「AND NOT (&^)」とその複合代入演算子「AND NOT ASSIGN (&^=)」を追加するものです。これにより、Go言語の表現力が向上し、特定のビット操作がより簡潔に記述できるようになりました。

コミット

commit 1b141ca068166bb236f1b0d2c53840e5398485f6
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Mar 16 14:20:08 2009 -0700

    added &^ and &^=
    
    R=rsc
    DELTA=14  (12 added, 0 deleted, 2 changed)
    OCL=26278
    CL=26348
---
 src/lib/go/scanner.go      | 8 +++++++-
 src/lib/go/scanner_test.go | 2 ++\
 src/lib/go/token.go        | 6 +++++-\
 3 files changed, 14 insertions(+), 2 deletions(-)

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

https://github.com/golang/go/commit/1b141ca068166bb236f1b0d2c53840e5398485f6

元コミット内容

&^ (AND NOT) および &^= (AND NOT ASSIGN) 演算子を追加しました。

変更の背景

Go言語は、設計当初からシンプルさと効率性を重視していました。ビット演算はシステムプログラミングや低レベルの操作において非常に重要であり、Go言語もこれらの操作をサポートしています。&^ (AND NOT) 演算子は、特定のビットをクリアする(0にする)操作を簡潔に表現するために導入されました。

従来のビット操作では、ある変数の特定のビットをクリアするには、その変数をビット反転したマスクとAND演算する必要がありました(例: x = x & (^mask))。&^ 演算子を導入することで、この一般的な操作を x &^ mask と直接的かつ読みやすく記述できるようになります。これは、コードの可読性と記述効率の向上に寄与します。

このコミットが行われた2009年3月は、Go言語がまだ活発に開発され、初期の言語仕様が固められている段階でした。この時期には、言語の表現力を高め、開発者がより効率的にコードを書けるようにするための様々な演算子や機能が追加されていました。&^ の追加も、そうした言語設計の進化の一環として行われたものです。

前提知識の解説

ビット演算子

ビット演算子は、数値の個々のビットに対して操作を行う演算子です。Go言語を含む多くのプログラミング言語でサポートされています。

  • AND (&): 両方のビットが1の場合にのみ1を返します。
    • 例: 0101 & 0011 = 0001
  • OR (|): どちらかのビットが1の場合に1を返します。
    • 例: 0101 | 0011 = 0111
  • XOR (^): ビットが異なる場合に1を返します。
    • 例: 0101 ^ 0011 = 0110
  • NOT (^ または ~): ビットを反転させます(0は1に、1は0に)。Go言語では単項の ^ がビット反転です。
    • 例: ^0101 = 1010 (型によって上位ビットが埋められる)

AND NOT (&^) 演算子

&^ 演算子は、Go言語に特有のビット演算子で、「AND NOT」または「ビットクリア」と呼ばれます。これは x & (^y) と同等です。つまり、x のビットのうち、y の対応するビットが0であるビットのみを保持し、y の対応するビットが1であるビットをクリア(0にする)します。

  • 動作: A &^ B は、A のビットのうち、B の対応するビットが 0 のものだけを残し、B の対応するビットが 1 のものは 0 にします。
  • : 0101 &^ 0011
    • 0101 (A)
    • 0011 (B)
    • ^B (Bのビット反転) は 1100 (仮に4ビット整数と仮定)
    • A & (^B)0101 & 1100 = 0100
    • したがって、0101 &^ 0011 = 0100

複合代入演算子

複合代入演算子は、演算と代入を組み合わせたものです。例えば、+=a = a + b と同等です。

  • &^= (AND NOT ASSIGN): x &^= yx = x &^ y と同等です。

Go言語の字句解析 (Lexical Analysis) とトークン (Tokens)

Go言語のコンパイラは、ソースコードを処理する際に、まず字句解析(スキャン)を行います。字句解析器(スキャナー)は、ソースコードの文字列を意味のある最小単位である「トークン」に分割します。

  • トークン (Token): 予約語(func, varなど)、識別子(変数名、関数名など)、リテラル(数値、文字列など)、演算子(+, =, &など)、区切り文字((, {など)などがトークンです。
  • src/lib/go/scanner.go: このファイルはGo言語の字句解析器(スキャナー)の実装を含んでいます。ソースコードを読み込み、トークンを識別する役割を担います。
  • src/lib/go/token.go: このファイルはGo言語のトークン定義を含んでいます。各トークンに一意の定数を割り当て、その文字列表現や優先順位などを管理します。

新しい演算子を追加するということは、これらのファイルに変更を加え、スキャナーが新しい演算子を正しく認識し、トークンとして分類できるようにする必要があることを意味します。

技術的詳細

このコミットは、Go言語の字句解析器とトークン定義に、新しいビット演算子 &^ とその複合代入演算子 &^= を追加するものです。

  1. src/lib/go/token.go の変更:

    • 新しいトークン定数 AND_NOTAND_NOT_ASSIGN が追加されます。これにより、コンパイラがこれらの演算子を内部的に識別できるようになります。
    • これらのトークンに対応する文字列表現 ("&^""&^=") がマップに追加されます。これは、デバッグやエラーメッセージの生成などでトークンの文字列表現が必要な場合に使用されます。
    • AND_NOT 演算子の優先順位が定義されます。Go言語の演算子優先順位ルールに従い、MUL, QUO, REM, SHL, SHR, AND と同じ優先順位(6)に設定されています。これは、これらの演算子が同じ結合性を持つことを意味します。
  2. src/lib/go/scanner.go の変更:

    • 字句解析器の scan メソッド(または関連するスイッチ文)が修正され、文字 & を読み込んだ際に、その次の文字が ^ であるかどうかをチェックするロジックが追加されます。
    • もし & の次に ^ が続く場合、スキャナーは &^ を一つのトークン token.AND_NOT として認識します。さらに、その後に = が続く場合は &^=token.AND_NOT_ASSIGN として認識します。
    • これにより、ソースコード中の &^&^= が正しく単一の演算子トークンとして扱われるようになります。
  3. src/lib/go/scanner_test.go の変更:

    • 新しい演算子 &^&^= が正しくスキャンされることを検証するためのテストケースが追加されます。これにより、字句解析器の変更が意図通りに機能し、既存の機能に悪影響を与えないことが保証されます。

これらの変更により、Goコンパイラは &^&^= を有効な演算子として認識し、それらを含むGoプログラムを正しく解析・コンパイルできるようになります。

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

src/lib/go/scanner.go

--- a/src/lib/go/scanner.go
+++ b/src/lib/go/scanner.go
@@ -447,7 +447,13 @@ scan_again:
 		case '>': tok = S.switch4(token.GTR, token.GEQ, '>', token.SHR, token.SHR_ASSIGN);
 		case '=': tok = S.switch2(token.ASSIGN, token.EQL);
 		case '!': tok = S.switch2(token.NOT, token.NEQ);
-		case '&': tok = S.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND);
+		case '&':
+			if S.ch == '^' {
+				S.next();
+				tok = S.switch2(token.AND_NOT, token.AND_NOT_ASSIGN);
+			} else {
+				tok = S.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND);
+			}
 		case '|': tok = S.switch3(token.OR, token.OR_ASSIGN, '|', token.LOR);
 		default: S.error(loc, "illegal character " + charString(ch));
 		}

src/lib/go/scanner_test.go

--- a/src/lib/go/scanner_test.go
+++ b/src/lib/go/scanner_test.go
@@ -76,6 +76,7 @@ var tokens = [...]elt{
 	elt{ token.XOR, "^", operator },
 	elt{ token.SHL, "<<", operator },
 	elt{ token.SHR, ">>", operator },
+	elt{ token.AND_NOT, "&^", operator },
 
 	elt{ token.ADD_ASSIGN, "+=", operator },
 	elt{ token.SUB_ASSIGN, "-=", operator },
@@ -88,6 +89,7 @@ var tokens = [...]elt{
 	elt{ token.XOR_ASSIGN, "^=", operator },
 	elt{ token.SHL_ASSIGN, "<<=", operator },
 	elt{ token.SHR_ASSIGN, ">>=", operator },
+	elt{ token.AND_NOT_ASSIGN, "&^=", operator },
 
 	elt{ token.LAND, "&&", operator },
 	elt{ token.LOR, "||", operator },

src/lib/go/token.go

--- a/src/lib/go/token.go
+++ b/src/lib/go/token.go
@@ -40,6 +40,7 @@ const (
 	XOR;
 	SHL;
 	SHR;
+	AND_NOT;
 
 	ADD_ASSIGN;
 	SUB_ASSIGN;
@@ -52,6 +53,7 @@ const (
 	XOR_ASSIGN;
 	SHL_ASSIGN;
 	SHR_ASSIGN;
+	AND_NOT_ASSIGN;
 
 	LAND;
 	LOR;
@@ -145,6 +147,7 @@ var tokens = map [int] string {
 	XOR : "^",
 	SHL : "<<",
 	SHR : ">>",
+	AND_NOT : "&^",
 
 	ADD_ASSIGN : "+=",
 	SUB_ASSIGN : "-=",
@@ -157,6 +160,7 @@ var tokens = map [int] string {
 	XOR_ASSIGN : "^=",
 	SHL_ASSIGN : "<<=",
 	SHR_ASSIGN : ">>=",
+	AND_NOT_ASSIGN : "&^=",
 
 	LAND : "&&",
 	LOR : "||",
@@ -264,7 +268,7 @@ func Precedence(tok int) int {
 		return 4;
 	case ADD, SUB, OR, XOR:
 		return 5;
-\tcase MUL, QUO, REM, SHL, SHR, AND:\
+\tcase MUL, QUO, REM, SHL, SHR, AND, AND_NOT:\
 		return 6;
 	}\
 	return LowestPrec;

コアとなるコードの解説

src/lib/go/scanner.go

このファイルはGo言語の字句解析器(スキャナー)の実装です。変更の中心は、& 文字を検出した際の処理ロジックです。

  • 変更前: case '&': tok = S.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND);
    • これは、& を検出した場合に、& (AND), &= (AND_ASSIGN), && (LAND) のいずれかであるかを判断する既存のロジックです。
  • 変更後:
    case '&':
        if S.ch == '^' { // 次の文字が '^' かどうかをチェック
            S.next(); // '^' を消費
            tok = S.switch2(token.AND_NOT, token.AND_NOT_ASSIGN); // '&^' または '&^=' を判断
        } else {
            tok = S.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND); // 既存のロジック
        }
    
    • & を読み込んだ後、スキャナーは次の文字 S.ch を確認します。
    • もし S.ch^ であれば、それは &^ 演算子の始まりであると判断し、S.next()^ を消費します。
    • その後、S.switch2(token.AND_NOT, token.AND_NOT_ASSIGN) を呼び出します。この関数は、さらに次の文字が = であるかをチェックし、&^= であれば token.AND_NOT_ASSIGN を、そうでなければ token.AND_NOT を返します。
    • S.ch^ でない場合は、既存の &, &=, && の判断ロジックにフォールバックします。
    • この変更により、スキャナーは &^&^= を正しく単一のトークンとして認識できるようになります。

src/lib/go/scanner_test.go

このファイルはスキャナーのテストケースを含んでいます。

  • 新しい elt 構造体が tokens 配列に追加されています。
    • elt{ token.AND_NOT, "&^", operator }
    • elt{ token.AND_NOT_ASSIGN, "&^=", operator }
  • これらの追加により、テストスイートは &^&^= がそれぞれ token.AND_NOTtoken.AND_NOT_ASSIGN として正しく識別されることを検証します。これは、字句解析器の変更が期待通りに機能していることを保証するために不可欠です。

src/lib/go/token.go

このファイルはGo言語のトークン定義と関連するユーティリティ関数を含んでいます。

  • トークン定数の追加:

    const (
        // ...
        SHR;
        AND_NOT; // 新しいAND NOT演算子のトークン
    
        // ...
        SHR_ASSIGN;
        AND_NOT_ASSIGN; // 新しいAND NOT複合代入演算子のトークン
    
        // ...
    )
    
    • AND_NOTAND_NOT_ASSIGN という新しい定数が token 型の列挙に追加されました。これにより、これらの演算子に一意の数値IDが割り当てられ、コンパイラの内部で参照できるようになります。
  • 文字列表現のマップへの追加:

    var tokens = map [int] string {
        // ...
        SHR : ">>",
        AND_NOT : "&^", // トークンIDと文字列表現のマッピング
    
        // ...
        SHR_ASSIGN : ">>=",
        AND_NOT_ASSIGN : "&^=", // トークンIDと文字列表現のマッピング
    
        // ...
    }
    
    • tokens マップは、トークンの数値IDとその文字列表現を関連付けます。この変更により、token.AND_NOT"&^" として、token.AND_NOT_ASSIGN"&^=" として認識され、エラーメッセージやデバッグ出力などで利用できるようになります。
  • 演算子優先順位の定義:

    --- a/src/lib/go/token.go
    +++ b/src/lib/go/token.go
    @@ -264,7 +268,7 @@ func Precedence(tok int) int {
     		return 4;
     	case ADD, SUB, OR, XOR:
     		return 5;
    -\tcase MUL, QUO, REM, SHL, SHR, AND:\
    +\tcase MUL, QUO, REM, SHL, SHR, AND, AND_NOT:\
     		return 6;
     	}\
     	return LowestPrec;
    
    • Precedence 関数は、与えられたトークンの演算子優先順位を返します。
    • AND_NOTMUL, QUO, REM, SHL, SHR, AND と同じ優先順位グループ(6)に追加されました。これは、Go言語の演算子優先順位ルールに従い、これらのビット演算子が同じ結合性を持つことを意味します。

これらの変更は、Go言語のコンパイラが &^&^= を言語の正規の演算子として認識し、処理するための基盤を構築します。

関連リンク

  • Go言語仕様 (Operators): Go言語の演算子に関する公式仕様は、&^ 演算子の定義を含んでいます。
  • Go言語のビット演算子に関する議論: Go言語の初期開発におけるビット演算子の選択や設計に関する議論は、メーリングリストやIssueトラッカーで見つけることができるかもしれません。

参考にした情報源リンク

  • Go言語の公式ドキュメントおよび言語仕様
  • Go言語のソースコード(特に src/go/token/token.gosrc/go/scanner/scanner.go の現在のバージョンも参照し、進化を比較)
  • ビット演算に関する一般的なプログラミング知識
  • Go言語の初期開発に関するブログ記事やメーリングリストのアーカイブ(具体的なURLは特定できませんでしたが、背景理解のために参照しました)