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

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

このコミットは、Go言語の初期開発段階における重要な変更を記録しています。主に、Go言語の構文解析器(パーサー)において、セミコロンの扱いを「オプション」にするための文法規則の改訂と、それに伴う型変換ロジックの追加が行われています。具体的には、src/cmd/gc/go.hsrc/cmd/gc/go.ysrc/cmd/gc/walk.c の3つのファイルが変更されています。

コミット

commit 8200a0b08883f27c6fd014d68f1dc2eb9feece82
Author: Ken Thompson <ken@golang.org>
Date:   Sun Jun 8 12:48:37 2008 -0700

    optional semicolons
    
    SVN=121604

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

https://github.com/golang/go/commit/8200a0b08883f27c6fd014d68f1dc2eb9feece82

元コミット内容

このコミットの元々の内容は、Go言語の構文においてセミコロンをオプションにするというものです。これは、Go言語の設計思想の一つである「簡潔さ」を追求する上で非常に重要な変更でした。また、それに付随して、配列の型変換に関する新しい関数 arrayconv が追加されています。

変更の背景

Go言語は、C言語のような構文を持ちながらも、より現代的なプログラミングパラダイムを取り入れることを目指して設計されました。C言語やJavaなどの多くの言語では、文の終わりにはセミコロンが必須ですが、これはコードの冗長性を高める要因の一つと見なされることがあります。

Go言語の設計者たちは、コードの可読性と記述効率を向上させるため、セミコロンを明示的に記述する手間を省くことを検討しました。その結果、Goコンパイラが特定のルールに基づいて自動的にセミコロンを挿入する「自動セミコロン挿入 (Automatic Semicolon Insertion, ASI)」というメカニズムが導入されることになりました。このコミットは、そのASIを実現するためのパーサーの根本的な変更を反映しています。

また、Go言語は強力な型システムを持つため、異なる型間での変換(キャスト)は厳密に管理されます。配列の型変換もその一つであり、このコミットで導入された arrayconv 関数は、配列リテラルや配列の型変換を正確に処理するための基盤を築くものです。

前提知識の解説

構文解析器 (Parser) と Yacc/Bison

Go言語のコンパイラは、ソースコードを解析して機械語に変換する過程で、構文解析器を使用します。構文解析器は、ソースコードが言語の文法規則に則っているかを検証し、抽象構文木 (Abstract Syntax Tree, AST) を構築します。

go.y ファイルは、Go言語の文法規則を定義するためにYacc (Yet Another Compiler Compiler) または Bison (GNU Parser Generator) というツールで使用されるファイルです。これらのツールは、文法定義ファイルからC言語の構文解析器のソースコードを生成します。%token はトークン(キーワード、識別子など)、%type は非終端記号(文、式など)の型を定義し、その後に続くルールが文法規則を記述します。

自動セミコロン挿入 (Automatic Semicolon Insertion, ASI)

Go言語のASIは、特定の状況下で改行文字の後に自動的にセミコロンを挿入するルールです。主なルールは以下の通りです。

  1. 識別子、整数、浮動小数点数、虚数、ルーン、文字列リテラル、キーワード (break, continue, fallthrough, return)、演算子 (++, --)、または閉じ括弧 (], ), }, _) の直後に改行がある場合。
  2. if, for, switch などの制御構造のブロックの閉じ括弧 } の直後に改行がある場合。

このコミットは、これらのルールをパーサーに組み込むための初期の実装を示しています。

型システムと型変換

Go言語は静的型付け言語であり、変数は使用前に型を宣言する必要があります。異なる型の値を扱う場合、明示的または暗黙的な型変換が必要になることがあります。このコミットで言及されている OCONV は、コンパイラ内部で型変換を表す操作コードであり、arrayconv は配列の型変換を処理するための具体的なロジックを提供します。

技術的詳細

このコミットの技術的な核心は、Go言語のパーサーにおける文法規則の抜本的な変更と、それに伴う型変換ロジックの追加です。

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

  • セミコロンのオプション化:
    • 従来の stmt (文) の定義から、明示的なセミコロン (';') の記述が削除されています。これは、パーサーが改行に基づいてセミコロンを推論するように変更されたことを意味します。
    • empty_stmt (空の文) の定義も削除されています。これは、単独のセミコロンが空の文として扱われる必要がなくなったためです。
    • else_stmtelse_stmt1else_stmt2 に分割され、else_stmt2 には semi_stmt や単独のセミコロンが含まれるようになりました。これは、else 節の後に続く文の形式がより柔軟になったことを示唆しています。
    • stmt_list_r (文のリスト) の定義が大幅に複雑化し、Astmt, Bstmt, Cstmt という新しい非終端記号が導入されています。これらは、セミコロンの有無にかかわらず、異なる種類の文を正しく解析するための新しい文法規則です。特に、Astmt_list_r';' を含む可能性のある文のリストを、Bstmt_list_r はセミコロンを必要としない文のリストを扱うように設計されています。この複雑な構造は、ASIのロジックを文法レベルで表現するためのものです。
  • 型変換の一般化 (typeconv):
    • latype '(' oexpr_list ')' というルールが typeconv '(' oexpr_list ')' に変更され、typeconv という新しい非終端記号が導入されました。
    • typeconv は、配列 ('[' ']' typeconv)、チャネル (LCHAN chandir typeconv)、マップ (LMAP '[' typeconv ']' typeconv)、そして LANY (interface{}) など、様々な型を表現できるようになりました。これにより、Go言語の型変換構文がより統一的かつ強力になったことを示しています。特に、配列リテラルや複合リテラルの型指定がこの新しい typeconv ルールによって処理されるようになります。

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

  • void arrayconv(Type*, Node*); の関数プロトタイプが追加されています。これは、walk.c で実装される arrayconv 関数が、コンパイラの他の部分から呼び出されることを示しています。

src/cmd/gc/walk.c の変更

  • arrayconv 関数の追加:
    • この関数は、配列の型変換を処理するために新しく追加されました。
    • walk.c は、抽象構文木 (AST) を走査し、最適化や型チェックなどの処理を行うコンパイラのバックエンド部分です。
    • arrayconv は、配列リテラルや配列の型変換が行われる際に呼び出され、要素の型チェックや配列の境界(長さ)の検証を行います。
    • 特に、convlit(l, t->type); はリテラル値をターゲットの型に変換し、ascompat(l->type, t->type) は要素の型が互換性があるかをチェックします。
    • if(t->bound == 0) の部分は、配列の長さが明示的に指定されていない場合に、要素の数から長さを推論するロジックを示しています。
  • OCONV 処理の変更:
    • walk.cloop 関数内で、OCONV (型変換) 操作が処理される際に、n->type->etype == TARRAY の場合に arrayconv が呼び出されるようになりました。これは、配列の型変換がこの新しい関数によって一元的に処理されることを意味します。

これらの変更は、Go言語の構文と型システムが、より洗練され、柔軟になるための初期のステップを示しています。特に、セミコロンのオプション化は、Go言語のコードスタイルに大きな影響を与え、その後のGo言語の普及に貢献しました。

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

src/cmd/gc/go.y (文法定義ファイル)

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -30,12 +30,14 @@
 %type	<lint>		chandir
 %type	<node>		xdcl xdcl_list_r oxdcl_list common_dcl
 %type	<node>		oarg_type_list arg_type_list_r arg_type
-%type	<node>		stmt empty_stmt else_stmt
-%type	<node>		complex_stmt compound_stmt stmt_list_r ostmt_list
+%type	<node>		else_stmt1 else_stmt2
+%type	<node>		complex_stmt compound_stmt ostmt_list
+%type	<node>		stmt_list_r Astmt_list_r Bstmt_list_r
+%type	<node>		Astmt Bstmt Cstmt
 %type	<node>		for_stmt for_body for_header
 %type	<node>		if_stmt if_body if_header
 %type	<node>		range_header range_body range_stmt
-%type	<node>		simple_stmt osimple_stmt
+%type	<node>		simple_stmt osimple_stmt semi_stmt
 %type	<node>		expr uexpr pexpr expr_list oexpr oexpr_list expr_list_r
 %type	<node>		name name_name new_name new_name_list_r
 %type	<node>		vardcl_list_r vardcl
@@ -48,7 +50,7 @@
 %type	<node>		fnres fnliteral xfndcl fndcl
 %type	<node>		keyval_list_r keyval
 
-%type	<type>		type fntypeh fntype fnlitdcl intype new_type
+%type	<type>		type fntypeh fntype fnlitdcl intype new_type typeconv
 
 %left			LOROR
 %left			LANDAND
@@ -206,40 +208,16 @@ typedcl:
 	dodcltype($1, $2);
 	}
 
-/*
- * statements
- */
-stmt:
-	error ';'
-	{
-		$$ = N;
-		context = nil;
-	}
-|	common_dcl ';'
-	{
-		$$ = $1;
-	}
-|	simple_stmt ';'
-|	complex_stmt
+else_stmt1:
+	complex_stmt
 |	compound_stmt
-|\tempty_stmt
-
-empty_stmt:
-	';'
-	{
-		$$ = nod(OEMPTY, N, N);
-	}
-
-else_stmt:
-	stmt
+
+else_stmt2:
+	simple_stmt
+|	semi_stmt
+|	';'
 	{
-\t\t$$ = $1;
-\t\tswitch($$->op) {
-\t\tcase OLABEL:
-\t\tcase OXCASE:
-\t\tcase OXFALL:
-\t\t\tyyerror("statement cannot be labeled");
-\t\t}
+\t\t$$ = N;
 	}
 
 simple_stmt:
@@ -295,7 +273,7 @@ complex_stmt:
 		popdcl("if/switch");
 		$$ = $2;
 	}
-|\tLIF if_stmt LELSE else_stmt
+|\tLIF if_stmt LELSE else_stmt1
 	{
 		popdcl("if/switch");
 		$$ = $2;
@@ -306,10 +284,6 @@ complex_stmt:
 		popdcl("range");
 		$$ = $2;
 	}
-|\tLRETURN oexpr_list ';'
-	{
-		$$ = nod(ORETURN, $2, N);
-	}
 |\tLCASE expr_list ':'
 	{
 		// will be converted to OCASE
@@ -323,38 +297,50 @@ complex_stmt:
 		poptodcl();
 		$$ = nod(OXCASE, N, N);
 	}
-|\tLFALL ';'
+|\tnew_name ':'
+	{
+		$$ = nod(OLABEL, $1, N);
+	}
+
+semi_stmt:
+	LFALL
 	{
 		// will be converted to OFALL
 		$$ = nod(OXFALL, N, N);
 	}
-|\tLBREAK oexpr ';'
+|\tLBREAK oexpr
 	{
 		$$ = nod(OBREAK, $2, N);
 	}
-|\tLCONTINUE oexpr ';'
+|\tLCONTINUE oexpr
 	{
 		$$ = nod(OCONTINUE, $2, N);
 	}
-|\tLGO pexpr '(' oexpr_list ')' ';'
+|\tLGO pexpr '(' oexpr_list ')'
 	{
 		$$ = nod(OPROC, $2, $4);
 	}
-|\tLPRINT expr_list ';'
+|\tLPRINT expr_list
 	{
 		$$ = nod(OPRINT, $2, N);
 	}
-|\tLPANIC oexpr_list ';'
+|\tLPANIC oexpr_list
 	{
 		$$ = nod(OPANIC, $2, N);
 	}
-|\tLGOTO new_name ';'
+|\tLGOTO new_name
 	{
 		$$ = nod(OGOTO, $2, N);
 	}
-|\tnew_name ':'
+|\tLRETURN oexpr_list
 	{
-\t\t$$ = nod(OLABEL, $1, N);
+\t\t$$ = nod(ORETURN, $2, N);
+\t}
+|\tLIF if_stmt LELSE else_stmt2
+	{
+\t\tpopdcl("if/switch");
+\t\t$$ = $2;
+\t\t$$->nelse = $4;
 	}
 
 compound_stmt:
@@ -657,11 +643,11 @@ pexpr:
 		// map literal
 		$$ = N;
 	}
-|\tlatype '(' oexpr_list ')'
+|\ttypeconv '(' oexpr_list ')'
 	{
 		// struct literal and conversions
 		$$ = nod(OCONV, $3, N);
-\t\t$$->type = $1->otype;
+\t\t$$->type = $1;
 	}
 |\tLCONVERT '(' type ',' expr ')'
 	{
@@ -738,6 +724,32 @@ name:
 		$$ = oldname($1);
 	}
 
+typeconv:
+	latype
+	{
+		$$ = oldtype($1);
+	}
+|\t'[' ']' typeconv
+	{
+		$$ = aindex(N, $3);
+	}
+|\tLCHAN chandir typeconv
+	{
+		$$ = typ(TCHAN);
+		$$->type = $3;
+		$$->chan = $2;
+	}
+|\tLMAP '[' typeconv ']' typeconv
+	{
+		$$ = typ(TMAP);
+		$$->down = $3;
+		$$->type = $5;
+	}
+|\tLANY
+	{
+		$$ = typ(TANY);
+	}
+
 type:
 	latype
 	{
@@ -1046,15 +1058,48 @@ arg_type_list_r:
 		$$ = nod(OLIST, $1, $3);
 	}
 
-stmt_list_r:
-	stmt
+Astmt:
+	complex_stmt
+
+Bstmt:
+	semi_stmt
+|\tcommon_dcl
+
+Cstmt:
+	simple_stmt
+
+Astmt_list_r:
+	Astmt
+|\tAstmt_list_r Astmt
 	{
-\t\t$$ = $1;
+\t\t$$ = nod(OLIST, $1, $2);
+\t}
+|\tBstmt_list_r ';'
+|\tAstmt_list_r ';'
+|\t';'
+	{
+\t\t$$ = N;
 	}
-|\tstmt_list_r stmt
+
+Bstmt_list_r:
+	Bstmt
+|\tCstmt
+|\tBstmt_list_r Bstmt
 	{
 	\t$$ = nod(OLIST, $1, $2);
 	}
+|\tAstmt_list_r Cstmt
+	{
+\t\t$$ = nod(OLIST, $1, $2);
+\t}
+|\tAstmt_list_r Bstmt
+	{
+\t\t$$ = nod(OLIST, $1, $2);
+\t}
+
+stmt_list_r:
+	Astmt_list_r
+|\tBstmt_list_r
 
 expr_list_r:
  texpr

src/cmd/gc/walk.c (AST走査と型チェック)

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -264,6 +264,7 @@ loop:
 		}
 
 		// simple fix-float
+\t\tif(n->left->type != T)
 		if(isint[n->left->type->etype] || isfloat[n->left->type->etype])
 		if(isint[n->type->etype] || isfloat[n->type->etype]) {
 			evconst(n);
@@ -283,6 +284,11 @@ loop:
 			}
 		}
 
+\t\tif(n->type->etype == TARRAY) {
+\t\t\tarrayconv(n->type, n->left);
+\t\t\tgoto ret;
+\t\t}
+\
 		badtype(n->op, n->left->type, n->type);
 		goto ret;
 
@@ -1276,3 +1282,31 @@ reorder(Node *n)\n {\n \treturn n;\n }\n+\n+void\n+arrayconv(Type *t, Node *n)\n+{\n+\tint c;\n+\tIter save;\n+\tNode *l;\n+\n+\tl = listfirst(&save, &n);\n+\tc = 0;\n+\n+loop:\n+\tif(l == N) {\n+\t\tif(t->bound == 0)\n+\t\t\tt->bound = c;\n+\t\tif(t->bound == 0 || t->bound < c)\n+\t\t\tyyerror("error with array convert bounds");\n+\t\treturn;\n+\t}\n+\n+\tc++;\n+\twalktype(l, 0);\n+\tconvlit(l, t->type);\n+\tif(!ascompat(l->type, t->type))\n+\t\tbadtype(OARRAY, l->type, t->type);\n+\tl = listnext(&save);\n+\tgoto loop;\n+}\

src/cmd/gc/go.h (ヘッダーファイル)

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -530,6 +530,7 @@ Node*	newcompat(Node*);\n Node*	stringop(Node*);\n Node*	convas(Node*);\n Node*	reorder(Node*);\n+void	arrayconv(Type*, Node*);\n 
 /*
  *\tconst.c

コアとなるコードの解説

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

このファイルはGo言語の文法規則を定義しており、変更の大部分はセミコロンのオプション化と型変換の一般化に関するものです。

  • セミコロンのオプション化:
    • stmt ルールから明示的なセミコロンが削除され、empty_stmt も廃止されました。これは、Goコンパイラが改行に基づいてセミコロンを自動的に挿入する(ASI)ための基盤を構築するものです。
    • else_stmtelse_stmt1else_stmt2 に分割され、else_stmt2semi_stmt や単独のセミコロンが許容されるようになりました。これにより、else 節の後の文の記述がより柔軟になります。
    • 最も重要な変更は、stmt_list_r の再定義です。Astmt, Bstmt, Cstmt という新しい非終端記号が導入され、これらを組み合わせて文のリストを解析する複雑なルールが追加されました。
      • Astmt: complex_stmt (複合文、例: if, for) を表します。
      • Bstmt: semi_stmt (セミコロンで終わる文、例: break, continue, fallthrough など) や common_dcl (一般的な宣言) を表します。
      • Cstmt: simple_stmt (単純な文、例: 代入、関数呼び出し) を表します。
      • Astmt_list_rBstmt_list_r は、これらの文をセミコロンの有無に応じて適切に連結するためのルールです。この複雑な構造は、Go言語のASIのロジックを文法レベルで表現するために不可欠です。これにより、開発者は多くの場所でセミコロンを省略できるようになります。
  • 型変換の一般化 (typeconv):
    • %type <type> typeconv が追加され、typeconv という新しい非終端記号が導入されました。
    • typeconv は、latype (基本的な型)、'[' ']' typeconv (配列型)、LCHAN chandir typeconv (チャネル型)、LMAP '[' typeconv ']' typeconv (マップ型)、LANY (空インターフェース型) など、Go言語の様々な型を表現できるようになりました。
    • これにより、latype '(' oexpr_list ')'typeconv '(' oexpr_list ')' に変更され、構造体リテラルや型変換の構文がより統一的になりました。$$->type = $1; の行は、typeconv ルールによって解析された型が、OCONV ノードのターゲット型として設定されることを示しています。

src/cmd/gc/walk.c の変更点

このファイルは、コンパイラのセマンティック分析と中間コード生成を担当しています。

  • arrayconv 関数の追加:
    • arrayconv(Type *t, Node *n) 関数が新しく追加されました。この関数は、配列の型変換を処理するためのものです。
    • t は変換先の配列型、n は変換元のノード(通常は配列リテラルや配列式のリスト)を表します。
    • 関数内では、listfirstlistnext を使って n が表す要素のリストを走査します。
    • walktype(l, 0); は各要素の型を走査し、convlit(l, t->type); は要素をターゲットの配列要素型に変換します。
    • if(!ascompat(l->type, t->type)) は、変換後の要素の型がターゲットの配列要素型と互換性があるかをチェックします。互換性がない場合はエラー (badtype) を報告します。
    • if(t->bound == 0) の部分は、配列の長さが明示的に指定されていない場合(例: [...]int{1, 2, 3})、要素の数 (c) から配列の長さを推論し、t->bound に設定します。これはGo言語の配列リテラルの重要な機能です。
  • OCONV 処理の変更:
    • walk.cloop 関数内で、OCONV (型変換) 操作が処理される際に、変換先の型が配列 (n->type->etype == TARRAY) である場合に、新しく追加された arrayconv 関数が呼び出されるようになりました。これにより、配列の型変換がこの専用の関数によって適切に処理されることが保証されます。

src/cmd/gc/go.h の変更点

  • void arrayconv(Type*, Node*); の宣言が追加されました。これは、arrayconv 関数がコンパイラの他の部分から利用可能であることを示します。

これらの変更は、Go言語の構文をより簡潔にし、型システムをより堅牢にするための初期の重要なステップでした。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコードリポジトリ (GitHub)
  • コンパイラ理論に関する一般的な知識