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

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

このコミットは、Go言語のコンパイラ(gc)とランタイム(runtime)における複数の重要な変更を導入しています。主な目的は、Go言語に「型スイッチ(type switches)」構文を導入し、インターフェースの型アサーションをより簡潔かつ安全に行えるようにすることです。これに伴い、typeofキーワードの削除、およびインターフェースから型への変換(I2T)におけるランタイムのバグ修正も含まれています。

コミット

commit a4a10ed856509b9e1f8f2a53b90696e428a51014
Author: Ken Thompson <ken@golang.org>
Date:   Fri Mar 6 17:50:43 2009 -0800

    1. type switches
    2. fixed fault on bug128
    3. got rid of typeof
    4. fixed bug in t,ok = I2T
    
    R=r
    OCL=25873
    CL=25873

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

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

元コミット内容

    1. type switches
    2. fixed fault on bug128
    3. got rid of typeof
    4. fixed bug in t,ok = I2T
    
    R=r
    OCL=25873
    CL=25873

変更の背景

このコミットが行われた2009年3月は、Go言語がまだ一般公開される前の初期開発段階にありました。Go言語の設計思想の一つに、シンプルさと安全性があります。インターフェースはGoの強力な機能の一つですが、インターフェース値が実際にどのような具象型を持っているかを検査し、それに基づいて異なる処理を行う必要が頻繁に生じます。

従来の型アサーション(value, ok := interfaceValue.(Type))は単一の型チェックには有効ですが、複数の型に対して分岐処理を行う場合には冗長になりがちでした。このような背景から、より簡潔で読みやすい構文として「型スイッチ」が提案され、このコミットでそのコンパイラとランタイムのサポートが実装されました。

また、typeofキーワードは、おそらく初期の実験的な機能として存在していたものの、型スイッチの導入によりその必要性がなくなり、言語のシンプルさを保つために削除されました。

さらに、インターフェースの内部処理におけるバグ修正も含まれており、これは言語の安定性と正確性を確保するための継続的な改善の一環です。特にt,ok = I2Tに関するバグ修正は、インターフェース値から具象型への変換処理の信頼性を高めるものです。

前提知識の解説

Go言語のインターフェース

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。具象型がインターフェースで定義されたすべてのメソッドを実装していれば、その具象型は自動的にそのインターフェースを満たします(暗黙的な実装)。インターフェース値は、内部的に具象型の値と、その具象型の型情報(型ディスクリプタ)の2つの要素を保持しています。これにより、実行時にインターフェース値が保持する具象型を特定することが可能になります。

型アサーション (i.(T))

型アサーションは、インターフェース値が特定の具象型であるかどうかを検査し、もしそうであればその具象型の値として取り出すための構文です。 例: value, ok := myInterface.(MyStruct) okは、アサーションが成功したかどうかを示す真偽値です。

型スイッチ (switch v := i.(type))

型スイッチは、インターフェース値が取りうる複数の具象型に対して、それぞれ異なる処理を記述するための制御構造です。これは、複数のif-else ifと型アサーションを組み合わせたものよりも、はるかに簡潔で読みやすいコードを提供します。 例:

switch v := myInterface.(type) {
case MyStruct:
    // v は MyStruct 型
case MyOtherStruct:
    // v は MyOtherStruct 型
default:
    // v はその他の型
}

型スイッチのcase節では、型アサーションのようにvがその型として扱われます。

Goコンパイラ (gc) の構造

Goコンパイラ(gc)は、Go言語のソースコードを機械語に変換する役割を担っています。その主要なコンポーネントは以下の通りです。

  • 字句解析 (Lexer): lex.cなどで実装され、ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。
  • 構文解析 (Parser): go.y(Yacc/Bisonの文法定義ファイル)で定義され、トークンのストリームを抽象構文木(AST: Abstract Syntax Tree)に変換します。ASTはプログラムの構造を階層的に表現したものです。
  • 意味解析 (Semantic Analysis): ASTを走査し、型チェック、名前解決、定数畳み込みなどの意味的な検証を行います。
  • コード生成 (Code Generation): 意味解析後のASTから、最終的な機械語コードを生成します。

このコミットでは、主に構文解析(go.y)と意味解析/コード生成(swt.c)の層に大きな変更が加えられています。

Goランタイム (runtime)

Goランタイムは、Goプログラムの実行をサポートする低レベルなコードの集合です。ガベージコレクション、スケジューラ、インターフェースの内部処理、メモリ管理などが含まれます。src/runtime/iface.cは、インターフェースの内部表現と操作に関するコードを含んでいます。

メモリのアライメント (rnd関数)

コンピュータのメモリは、特定のバイト境界にデータを配置することで、効率的なアクセスが可能になります。この配置の規則を「アライメント」と呼びます。rnd関数は、与えられた値を指定された境界にアライメントするために使用されることが多いです。例えば、rnd(value, 8)valueを8バイト境界にアライメントします。アライメントが正しくないと、パフォーマンスの低下や、場合によってはクラッシュなどのバグにつながることがあります。

技術的詳細

型スイッチの実装

このコミットの最も重要な変更は、型スイッチの導入です。

  1. ASTノードの追加: src/cmd/gc/go.hに新しいASTノードタイプOTYPESWが追加されました。これは、型スイッチ構文をAST上で表現するために使用されます。また、typeswvarというNode*型のグローバル変数も追加され、型スイッチの対象となる変数を管理するために使われます。

  2. 文法解析の変更 (src/cmd/gc/go.y):

    • pexpr '.' '(' LTYPE ')'という新しい文法規則が追加され、これがOTYPESWノードを生成するように定義されました。これは、interfaceValue.(type)という構文を解析するためのものです。
    • LCASE type ':'という規則がcomplex_stmtに追加され、型スイッチのcase節の文法が定義されました。
    • if_headerif_bodyの規則が修正され、型スイッチの構文がif文の内部でどのように扱われるか(特にswitch v := i.(type)のような初期化文を含む場合)が考慮されています。
  3. 字句解析の変更 (src/cmd/gc/lex.c):

    • typeofキーワードが字句解析器から完全に削除されました。これにより、Go言語のキーワードセットからtypeofがなくなりました。
  4. オペレーション名の追加 (src/cmd/gc/subr.c):

    • OTYPESWに対応する文字列"TYPESW"opnames配列に追加され、デバッグやエラーメッセージでOTYPESWノードが適切に表示されるようになりました。
  5. スイッチ処理ロジックの変更 (src/cmd/gc/swt.c):

    • このファイルは、Goコンパイラにおけるswitch文のセマンティック解析とコード生成の大部分を担っています。
    • enum { Snorm, Strue, Sfalse, Stype }が導入され、switch文の種類(通常の式スイッチ、真偽値定数スイッチ、型スイッチ)を区別できるようになりました。
    • sw0, sw1, sw2, sw3, walkcasesといった既存の関数が、新しいargパラメータを受け取るように変更されました。これにより、これらの関数はスイッチの種類に応じて異なる振る舞いをすることができます。
    • typeswitch関数の導入: この関数が型スイッチの核心的な処理を行います。
      • 型スイッチの対象がインターフェース型であることを検証します。
      • 型スイッチは、内部的には一連のif文と型アサーションに変換されます。例えば、switch v := i.(type) { case T1: ... case T2: ... }は、if _, ok := i.(T1); ok { ... } else if _, ok := i.(T2); ok { ... }のような構造に変換されます。
      • ODOTTYPEノード(interface.(type))とOASノード(代入)を組み合わせて、各case節に対応する型チェックと変数への代入を表現するASTを生成します。
    • walkswitch関数のリファクタリング: walkswitch関数は、与えられたswitchノードが型スイッチであるかどうかをsw->ntest->op == OTYPESWで判断し、もしそうであれば新しく導入されたtypeswitch関数に処理を委譲します。そうでなければ、従来の通常のスイッチ処理(prepswなど)を続行します。
    • prepsw関数もargパラメータを受け取るように変更され、スイッチの種類に応じたコード生成の準備を行うようになりました。

typeofキーワードの削除

typeofキーワードは、Go言語の初期段階で型の情報を取得するために検討された可能性のある機能ですが、型スイッチやリフレクションといったより強力でGoらしいメカニズムが導入されたことで、その必要性がなくなりました。このコミットで字句解析器から削除されたことで、Go言語の構文から正式に姿を消しました。

t,ok = I2Tバグ修正

src/runtime/iface.csys·ifaceI2T2関数におけるバグ修正は、インターフェースから型への変換(Interface to Type)処理に関するものです。具体的には、ok変数のメモリ配置を計算するrnd関数の引数がrnd(wid, 8)からrnd(wid, 1)に変更されました。

  • sys·ifaceI2T2は、インターフェース値iを特定の型stに変換しようとするランタイム関数です。
  • retは変換後の値が格納されるポインタ、okは変換が成功したかどうかを示す真偽値が格納されるポインタです。
  • widは変換後の型のサイズ(幅)を示します。
  • ok = (bool*)(ret+rnd(wid, 8))は、retポインタからwidバイト進んだ後、さらに8バイト境界にアライメントされた位置にok変数を配置しようとしていました。
  • しかし、bool型は通常1バイトであり、8バイト境界にアライメントする必要はありません。また、ok変数は変換後の値の直後に配置されるべきであり、過剰なアライメントはメモリの無駄や、場合によっては誤ったメモリ位置へのアクセスを引き起こす可能性があります。
  • rnd(wid, 1)への変更は、ok変数をwidバイトの直後に、1バイト境界(つまり、アライメントなし)で配置するように修正したものです。これにより、メモリの効率的な利用と、ok変数が正しい位置に書き込まれることが保証されます。これは、インターフェースの型アサーションがvalue, ok := interfaceValue.(Type)の形式で、okが正しく設定されるために重要な修正です。

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

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -329,7 +329,7 @@ enum
 	OKEY, OPARAM,
 	OCOMPOS,
 	OCONV,
-	ODOTTYPE,
+	ODOTTYPE, OTYPESW,
 	OBAD,
 
 	OEXTEND,	// 6g internal
@@ -526,6 +526,7 @@ EXTERN	Node*	retnil;
 EXTERN	Node*	fskel;
 
 EXTERN	Node*	addtop;
+EXTERN	Node*	typeswvar;
 
 EXTERN	char*	context;
 EXTERN	char*	pkgcontext;

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -18,7 +18,7 @@
 %token	<sym>		LPACKAGE LIMPORT LDEFER
 %token	<sym>		LMAP LCHAN LINTERFACE LFUNC LSTRUCT
 %token	<sym>		LCOLAS LFALL LRETURN LDDD
-%token	<sym>		LLEN LCAP LTYPEOF LPANIC LPANICN LPRINT LPRINTN
+%token	<sym>		LLEN LCAP LPANIC LPANICN LPRINT LPRINTN
 %token	<sym>		LVAR LTYPE LCONST LCONVERT LSELECT LMAKE LNEW
 %token	<sym>		LFOR LIF LELSE LSWITCH LCASE LDEFAULT
 %token	<sym>		LBREAK LCONTINUE LGO LGOTO LRANGE
@@ -507,6 +511,18 @@ complex_stmt:
 		$$ = nod(OXCASE, $$, N);\n\t\taddtotop($$);\n\t}\n+|\tLCASE type ':'\n+\t{\n+\t\tpoptodcl();\n+\t\tif(typeswvar == N || typeswvar->right == N) {\n+\t\t\tyyerror("type case not in a type switch");\n+\t\t\t$$ = N;\n+\t\t} else\n+\t\t\t$$ = old2new(typeswvar->right, $2);\n+\t\t$$ = nod(OTYPESW, $$, N);\n+\t\t$$ = nod(OXCASE, $$, N);\n+\t\taddtotop($$);\n+\t}\n |\tLDEFAULT ':'
 	{\n 	\tpoptodcl();
@@ -836,6 +862,10 @@ pexpr:
 		$$ = nod(ODOTTYPE, $1, N);\n\t\t$$->type = $4;\n 	}\n+|\tpexpr '.' '(' LTYPE ')'\n+\t{\n+\t\t$$ = nod(OTYPESW, $1, N);\n+\t}\n |\tpexpr '[' expr ']'
 	{\n 	\t$$ = nod(OINDEX, $1, $3);\
@@ -858,11 +888,6 @@ pexpr:
 	{\n 	\t$$ = nod(OCAP, $3, N);\n 	}\n-|\tLTYPEOF '(' type ')'\n-\t{\n-\t\t$$ = nod(OTYPEOF, N, N);\n-\t\t$$->type = $3;\n-\t}\n |\tLNEW '(' type ')'
 	{\n 	\t$$ = nod(ONEW, N, N);\
@@ -1001,7 +1026,6 @@ sym3:
 |\tLNEW
 |\tLMAKE
 |\tLBASETYPE
-|\tLTYPEOF
 
 /*
  * keywords that we can

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1110,7 +1110,6 @@ static	struct
 	"switch",	LSWITCH,	Txxx,
 	"true",		LTRUE,		Txxx,
 	"type",		LTYPE,		Txxx,
-	"typeof",	LTYPEOF,	Txxx,
 	"var",		LVAR,		Txxx,
 
 	"notwithstanding",		LIGNORE,	Txxx,

src/cmd/gc/subr.c

--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -715,6 +715,7 @@ opnames[] =
 	[OSELECT]	= "SELECT",
 	[OSWITCH]	= "SWITCH",
 	[OTYPE]		= "TYPE",
+	[OTYPESW]	= "TYPESW",
 	[OVAR]		= "VAR",
 	[OIMPORT]	= "IMPORT",
 	[OXOR]		= "XOR",

src/cmd/gc/swt.c

--- a/src/cmd/gc/swt.c
+++ b/src/cmd/gc/swt.c
@@ -299,35 +332,141 @@ loop:
 }
 
+/*
+ * convert switch of the form
+ *\tswitch v := i.(type) { case t1: ..; case t2: ..; }
+ * into if statements
+ */
 void
-walkswitch(Node *n)
+typeswitch(Node *sw)
 {
-\tType *t;\n+\tIter save;\n+\tNode *face, *bool, *cas;\n+\tNode *t, *a, *b;\n \n-\tcasebody(n);\n-\tif(n->ntest == N)\n-\t\tn->ntest = booltrue;\n+//dump("typeswitch", sw);\n+\n+\twalktype(sw->ntest->right, Erv);\n+\tif(!istype(sw->ntest->right->type, TINTER)) {\n+\t\tyyerror("type switch must be on an interface");\n+\t\treturn;\n+\t}\n+\twalkcases(sw, sw0, Stype);\n \n-\twalkstate(n->ninit);\n-\twalktype(n->ntest, Erv);\n-\twalkstate(n->nbody);\n+\t/*\n+\t * predeclare variables for the interface var\n+\t * and the boolean var\n+\t */\n+\tface = nod(OXXX, N, N);\n+\ttempname(face, sw->ntest->right->type);\n+\tcas = nod(OAS, face, sw->ntest->right);\n \n-\t// walktype\n-\twalkcases(n, sw0);\n+\tbool = nod(OXXX, N, N);\n+\ttempname(bool, types[TBOOL]);\n \n-\t// find common type\n-\tt = n->ntest->type;\n-\tif(t == T)\n-\t\tt = walkcases(n, sw1);\n+\tt = listfirst(&save, &sw->nbody->left);\n \n-\t// if that fails pick a type\n-\tif(t == T)\n-\t\tt = walkcases(n, sw2);\n+loop:\n+\tif(t == N) {\n+\t\tsw->nbody->left = rev(cas);\n+\t\twalkstate(sw->nbody);\n+//dump("done", sw->nbody->left);\n+\t\treturn;\n+\t}\n \n-\t// set the type on all literals\n-\tif(t != T) {\n-\t\twalkcases(n, sw3);\n-\t\tconvlit(n->ntest, t);\n-\t\tprepsw(n);\n+\tif(t->left == N) {\n+\t\tcas = list(cas, t->right);\t\t// goto default\n+\t\tt = listnext(&save);\n+\t\tgoto loop;\n \t}\n+\tif(t->left->op != OTYPESW) {\n+\t\tt = listnext(&save);\n+\t\tgoto loop;\n+\t}\n+\n+\ta = t->left->left;\t\t// var\n+\ta = nod(OLIST, a, bool);\t// var,bool\n+\n+\tb = nod(ODOTTYPE, face, N);\n+\tb->type = t->left->left->type;\t// interface.(type)\n+\n+\ta = nod(OAS, a, b);\t\t// var,bool = interface.(type)\n+\tcas = list(cas, a);\n+\n+\ta = nod(OIF, N, N);\n+\ta->ntest = bool;\n+\ta->nbody = t->right;\t\t// if bool { goto l }\n+\tcas = list(cas, a);\n+\n+\tt = listnext(&save);\n+\tgoto loop;\n+}\n+\n+void\n+walkswitch(Node *sw)\n+{\n+\tType *t;\n+\tint arg;\n+\n+//dump("walkswitch", sw);\n+\n+\t/*\n+\t * reorder the body into (OLIST, cases, statements)\n+\t * cases have OGOTO into statements.\n+\t * both have inserted OBREAK statements\n+\t */\n+\twalkstate(sw->ninit);\n+\tif(sw->ntest == N)\n+\t\tsw->ntest = booltrue;\n+\tcasebody(sw);\n+\n+\t/*\n+\t * classify the switch test\n+\t * Strue or Sfalse if the test is a bool constant\n+\t *\tthis allows cases to be map/chan/interface assignments\n+\t *\tas well as (boolean) expressions\n+\t * Stype if the test is v := interface.(type)\n+\t *\tthis forces all cases to be types\n+\t * Snorm otherwise\n+\t *\tall cases are expressions\n+\t */\n+\tif(sw->ntest->op == OTYPESW) {\n+\t\ttypeswitch(sw);\n+\t\treturn;\n+\t}\n+\targ = Snorm;\n+\tif(whatis(sw->ntest) == Wlitbool) {\n+\t\targ = Strue;\n+\t\tif(sw->ntest->val.u.xval == 0)\n+\t\t\targ = Sfalse;\n+\t}\n+\n+\t/*\n+\t * init statement is nothing important\n+\t */\n+\twalktype(sw->ntest, Erv);\n+//print("after walkwalks\\n");\n+\n+\t/*\n+\t * pass 0,1,2,3\n+\t * walk the cases as appropriate for switch type\n+\t */\n+\twalkcases(sw, sw0, arg);\n+\tt = sw->ntest->type;\n+\tif(t == T)\n+\t\tt = walkcases(sw, sw1, arg);\n+\tif(t == T)\n+\t\tt = walkcases(sw, sw2, arg);\n+\tif(t == T)\n+\t\treturn;\n+\twalkcases(sw, sw3, arg);\n+\tconvlit(sw->ntest, t);\n+//print("after walkcases\\n");\n+\n+\t/*\n+\t * convert the switch into OIF statements\n+\t */\n+\tprepsw(sw, arg);\n+\twalkstate(sw->nbody);\n+//print("normal done\\n");\n }\n```

### `src/runtime/iface.c`

```diff
--- a/src/runtime/iface.c
+++ b/src/runtime/iface.c
@@ -315,7 +315,7 @@ sys·ifaceI2T2(Sigt *st, Iface i, ...)
 	ret = (byte*)(&i+1);
 	alg = st->hash & 0xFF;
 	wid = st->offset;
-	ok = (bool*)(ret+rnd(wid, 8));
+	ok = (bool*)(ret+rnd(wid, 1));
 
 	if(iface_debug) {
 		prints("I2T2 sigt=");

コアとなるコードの解説

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

  • ODOTTYPE, OTYPESW,: enumOTYPESWが追加されました。これは、switch i.(type)のような型スイッチ構文を抽象構文木(AST)で表現するための新しいノードタイプです。ODOTTYPEi.(T)のような型アサーションを表す既存のノードです。
  • EXTERN Node* typeswvar;: typeswvarというNode*型のグローバル変数が宣言されました。これは、型スイッチの対象となるインターフェース変数を一時的に保持するために使用されます。

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

  • %token ... LTYPEOF ... の行から LTYPEOF が削除されました。これは、typeofキーワードがGo言語の構文から削除されたことを示します。
  • LCASE type ':' の新しい規則が追加されました。これは、型スイッチのcase T:節を解析するためのものです。この規則では、typeswvar(型スイッチの対象変数)と$2caseで指定された型)を使って新しいOTYPESWノードを生成し、それをOXCASEノードの子として追加しています。
  • pexpr '.' '(' LTYPE ')' の新しい規則が追加されました。これは、interfaceValue.(type)という構文を解析し、OTYPESWノードを生成するためのものです。このノードは、型スイッチのテスト式として使用されます。

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

  • "typeof", LTYPEOF, Txxx, の行が削除されました。これは、字句解析器がtypeofという文字列を特別なキーワードとして認識しなくなったことを意味します。

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

  • [OTYPESW] = "TYPESW", の行がopnames配列に追加されました。これにより、OTYPESWノードがデバッグ出力などで"TYPESW"という文字列として表示されるようになります。

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

  • typeswitch(Node *sw) 関数の追加:
    • この関数は、型スイッチのセマンティック解析とコード生成の主要なロジックを含んでいます。
    • sw->ntest->rightがインターフェース型であることを確認します。そうでない場合はエラーを報告します。
    • face = nod(OXXX, N, N); tempname(face, sw->ntest->right->type); cas = nod(OAS, face, sw->ntest->right);
      • これは、型スイッチの対象となるインターフェース変数を一時変数faceに代入するコードを生成しています。
    • bool = nod(OXXX, N, N); tempname(bool, types[TBOOL]);
      • 型アサーションの結果(成功したかどうか)を保持するための一時的な真偽値変数boolを生成しています。
    • a = t->left->left; // var
    • a = nod(OLIST, a, bool); // var,bool
    • b = nod(ODOTTYPE, face, N); b->type = t->left->left->type; // interface.(type)
    • a = nod(OAS, a, b); // var,bool = interface.(type)
      • これらの行は、各case節に対応する型アサーションのコードを生成しています。具体的には、var, bool = face.(type)のような形式の代入をASTで表現しています。ODOTTYPEは型アサーションを表し、その結果がvarboolに代入されます。
    • a = nod(OIF, N, N); a->ntest = bool; a->nbody = t->right; // if bool { goto l }
      • 型アサーションが成功した場合(boolが真の場合)に、対応するcase節のコードブロックにジャンプするためのif文を生成しています。
  • walkswitch(Node *sw) 関数の変更:
    • if(sw->ntest->op == OTYPESW) { typeswitch(sw); return; }
      • この部分が、switch文のテスト式がOTYPESWノード(つまりinterfaceValue.(type))である場合に、新しく追加されたtypeswitch関数を呼び出すように変更されました。これにより、型スイッチの処理が専用の関数に委譲されます。
    • それ以外の場合は、従来の通常のスイッチ処理(argパラメータを使って真偽値スイッチや通常の式スイッチを区別)が続行されます。

src/runtime/iface.c の変更

  • ok = (bool*)(ret+rnd(wid, 8)); から ok = (bool*)(ret+rnd(wid, 1)); への変更。
    • sys·ifaceI2T2関数は、インターフェースから具象型への変換を行うランタイム関数です。
    • retは変換された具象値へのポインタ、okは変換が成功したかを示す真偽値へのポインタです。
    • rnd(wid, 8)は、wid(具象型のサイズ)を8バイト境界にアライメントした値を返していました。これは、ok変数を8バイト境界に配置しようとしていたことを意味します。
    • しかし、bool型は通常1バイトであり、8バイト境界にアライメントする必要はありません。この修正は、ok変数のメモリ配置をwidの直後(1バイト境界)に修正することで、メモリの無駄をなくし、ok変数が正しく書き込まれるようにするためのバグ修正です。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • Go言語のコンパイラ設計に関する一般的な情報源 (例: Go compiler internals, Go AST)
  • Yacc/Bisonの文法定義に関する一般的な情報