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

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

このコミットは、Go言語のcmd/yaccツールにおいて、トークン名に先頭アンダースコア(_)の使用を許可するように変更を加えるものです。これにより、yaccで生成されるパーサーが、アンダースコアで始まるトークン名を正しく認識できるようになります。

コミット

commit cbc9ab75cb0da5c5848fd495ef2a2ff87f345735
Author: Rob Pike <r@golang.org>
Date:   Fri Sep 7 09:31:51 2012 -0700

    cmd/yacc: allow leading underscore in token name
    Fixes #4037.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6488093
---
 src/cmd/yacc/units.y | 10 +++++-----
 src/cmd/yacc/yacc.go |  6 +++---
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/cmd/yacc/units.y b/src/cmd/yacc/units.y
index eaa3fb18a4..32d37e5039 100644
--- a/src/cmd/yacc/units.y
+++ b/src/cmd/yacc/units.y
@@ -78,7 +78,7 @@ var vflag bool
 
 %token	<vval>	VAL
 %token	<vvar>	VAR
-%token	<numb>	SUP
+%token	<numb>	_SUP // tests leading underscore in token name
 %%
 prog:
 	':' VAR expr
@@ -159,7 +159,7 @@ expr3:
 
 expr2:
 	expr1
-|	expr2 SUP
+|	expr2 _SUP
 	{
 		xpn(&$$, &$1, $2)
 	}
@@ -236,13 +236,13 @@ loop:
 		return '/'
 	case '¹', 'ⁱ':
 		yylval.numb = 1
-		return SUP
+		return _SUP
 	case '²', '⁲':
 		yylval.numb = 2
-		return SUP
+		return _SUP
 	case '³', '⁳':
 		yylval.numb = 3
-		return SUP
+		return _SUP
 	}
 	return int(c)
 
diff --git a/src/cmd/yacc/yacc.go b/src/cmd/yacc/yacc.go
index cca5570fb8..a4ae35349a 100644
--- a/src/cmd/yacc/yacc.go
+++ b/src/cmd/yacc/yacc.go
@@ -988,7 +988,7 @@ func gettok() int {
 
 func getword(c rune) {
 	tokname = ""
-	for isword(c) || isdigit(c) || c == '_' || c == '.' || c == '$' {
+	for isword(c) || isdigit(c) || c == '.' || c == '$' {
 		tokname += string(c)
 		c = getrune(finput)
 	}
@@ -1338,7 +1338,7 @@ loop:
 			if j >= max {
 				errorf("Illegal use of $%v", j)
 			}
-		} else if isword(c) || c == '_' || c == '.' {
+		} else if isword(c) || c == '.' {
 			// look for $name
 			ungetrune(finput, c)
 			if gettok() != IDENTIFIER {
@@ -3090,7 +3090,7 @@ var peekrune rune
 func isdigit(c rune) bool { return c >= '0' && c <= '9' }
 
 func isword(c rune) bool {
-	return c >= 0xa0 || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+	return c >= 0xa0 || c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
 }
 
 func mktemp(t string) string { return t }

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

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

元コミット内容

cmd/yacc: トークン名に先頭アンダースコアを許可する。 Issue #4037 を修正。

変更の背景

このコミットは、Go言語のcmd/yaccツールが、トークン名の先頭にアンダースコア(_)を持つことを許可していなかった問題(Issue #4037)を修正するために行われました。

yacc(Yet Another Compiler Compiler)は、文法定義からパーサーを生成するツールです。パーサーは、入力されたテキストが特定の文法規則に沿っているかを解析し、その構造を理解します。このプロセスにおいて、文法内で定義される「トークン」は、プログラムの最小単位(キーワード、識別子、演算子など)を表します。

従来のcmd/yaccの実装では、トークン名を識別する際に、先頭にアンダースコアを持つ文字列を正しく認識できない、または許可しない制約がありました。これは、特に内部的なトークンや、特定の命名規則に従う必要がある場合に問題となります。例えば、_SUPのようなトークンを定義しようとすると、yaccツールがエラーを発生させるか、意図しない挙動を示す可能性がありました。

この修正は、yaccツールがより柔軟なトークン命名規則をサポートし、開発者がより自由に文法を定義できるようにするために不可欠でした。

前提知識の解説

Yacc (Yet Another Compiler Compiler)

Yaccは、プログラミング言語のコンパイラやインタープリタを作成する際に使用される、パーサー生成ツールです。文脈自由文法(Context-Free Grammar)の定義ファイル(通常は.y拡張子)を読み込み、その文法を解析するためのC言語(または他の言語)のコードを生成します。生成されたコードは、入力ストリームを読み込み、文法規則に従って構文木を構築します。

字句解析 (Lexical Analysis) と構文解析 (Parsing)

コンパイラのフロントエンドは、主に以下の2つのフェーズに分けられます。

  1. 字句解析 (Lexical Analysis):

    • ソースコードを読み込み、意味のある最小単位である「トークン(Token)」に分割するプロセスです。この処理を行うプログラムを「字句解析器(Lexer)」または「スキャナー(Scanner)」と呼びます。
    • 例えば、int x = 10;というコードは、int(キーワード)、x(識別子)、=(演算子)、10(整数リテラル)、;(区切り文字)といったトークンに分割されます。
    • トークンは、その種類(例: KEYWORD, IDENTIFIER, OPERATOR)と、そのトークンが持つ値(例: int, x, =, 10)で構成されます。
  2. 構文解析 (Parsing):

    • 字句解析器によって生成されたトークンのシーケンスを読み込み、それが文法規則に適合しているかを確認し、プログラムの構造を表す「構文木(Syntax Tree)」を構築するプロセスです。この処理を行うプログラムを「構文解析器(Parser)」と呼びます。
    • Yaccは、この構文解析器を生成するためのツールです。

トークン名と識別子

  • トークン名: Yaccの文法定義ファイル内で、特定の種類のトークンを識別するために使用される名前です。例えば、%token IDENTIFIERのように定義されます。
  • 識別子: プログラミング言語において、変数名、関数名、クラス名などを指す一般的な用語です。通常、アルファベット、数字、アンダースコアで構成され、先頭はアルファベットまたはアンダースコアであることが多いです。

このコミットの文脈では、cmd/yaccがトークン名を解析する際に、識別子としての命名規則(特に先頭文字の制約)が問題となっていました。

技術的詳細

このコミットの技術的な変更は、主にcmd/yaccツールがトークン名を識別する際のロジックを修正することにあります。具体的には、src/cmd/yacc/yacc.go内のisword関数と、トークンを解析するgetword関数、そしてsrc/cmd/yacc/units.y内の文法定義が変更されています。

  1. src/cmd/yacc/yacc.goの変更:

    • getword関数は、トークン名を構成する文字を読み取る役割を担っています。この関数内のループ条件から、アンダースコア(_)が明示的に除外されていました。これは、アンダースコアが単独でトークンの一部として扱われることを防ぐためのものでしたが、結果として先頭アンダースコアを持つトークン名を正しく認識できない原因となっていました。この変更では、c == '_'の条件が削除され、アンダースコアの扱いがisword関数に委ねられるようになりました。
    • isword関数は、与えられた文字が「単語」の一部として有効かどうかを判断します。この関数は、アルファベット(a-z, A-Z)と特定のUnicode文字(c >= 0xa0)を有効な文字としていましたが、アンダースコア(_)は含まれていませんでした。この変更により、isword関数にc == '_'が追加され、アンダースコアが有効な「単語」文字として認識されるようになりました。
    • loop関数内のisword(c) || c == '_'の条件もisword(c)のみに変更されています。これは、isword関数自体がアンダースコアを考慮するようになったため、冗長なチェックを削除したものです。
  2. src/cmd/yacc/units.yの変更:

    • units.yは、cmd/yaccツール自身のテストに使用される文法定義ファイルです。このファイル内で、SUPというトークンが定義されていましたが、このコミットではその名前が_SUPに変更されています。
    • これは、先頭アンダースコアを持つトークン名が正しく処理されることをテストするための具体的な変更です。文法定義内で%token <numb> _SUPと変更され、それに伴い、文法規則内でSUPが使用されていた箇所もすべて_SUPに更新されています。また、字句解析部分(yylex関数に相当する部分)でSUPトークンを返す箇所も_SUPを返すように変更されています。

これらの変更により、cmd/yaccは、トークン名の先頭にアンダースコアを持つことを許可し、そのようなトークンを正しく字句解析および構文解析できるようになります。

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

src/cmd/yacc/units.y

--- a/src/cmd/yacc/units.y
+++ b/src/cmd/yacc/units.y
@@ -78,7 +78,7 @@ var vflag bool
 
 %token	<vval>	VAL
 %token	<vvar>	VAR
-%token	<numb>	SUP
+%token	<numb>	_SUP // tests leading underscore in token name
 %%
 prog:
 	':' VAR expr
@@ -159,7 +159,7 @@ expr3:
 
 expr2:
 	expr1
-|	expr2 SUP
+|	expr2 _SUP
 	{
 		xpn(&$$, &$1, $2)
 	}
@@ -236,13 +236,13 @@ loop:
 		return '/'
 	case '¹', 'ⁱ':
 		yylval.numb = 1
-		return SUP
+		return _SUP
 	case '²', '⁲':
 		yylval.numb = 2
-		return SUP
+		return _SUP
 	case '³', '⁳':
 		yylval.numb = 3
-		return SUP
+		return _SUP
 	}
 	return int(c)

src/cmd/yacc/yacc.go

--- a/src/cmd/yacc/yacc.go
+++ b/src/cmd/yacc/yacc.go
@@ -988,7 +988,7 @@ func gettok() int {
 
 func getword(c rune) {
 	tokname = ""
-	for isword(c) || isdigit(c) || c == '_' || c == '.' || c == '$' {
+	for isword(c) || isdigit(c) || c == '.' || c == '$' {
 		tokname += string(c)
 		c = getrune(finput)
 	}
@@ -1338,7 +1338,7 @@ loop:
 			if j >= max {
 				errorf("Illegal use of $%v", j)
 			}
-		} else if isword(c) || c == '_' || c == '.' {
+		} else if isword(c) || c == '.' {
 			// look for $name
 			ungetrune(finput, c)
 			if gettok() != IDENTIFIER {
@@ -3090,7 +3090,7 @@ var peekrune rune
 func isdigit(c rune) bool { return c >= '0' && c <= '9' }
 
 func isword(c rune) bool {
-	return c >= 0xa0 || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+	return c >= 0xa0 || c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
 }
 
 func mktemp(t string) string { return t }

コアとなるコードの解説

src/cmd/yacc/units.y

  • %token <numb> _SUP: SUPトークンが_SUPに変更されました。これは、先頭にアンダースコアを持つトークンがyaccによって正しく認識されることをテストするためのものです。
  • expr2: expr2 _SUP: 文法規則内でSUPが使用されていた箇所も_SUPに更新されました。
  • return _SUP: 字句解析部分(yylexに相当)で、特定の文字(¹、ⁱ、²、⁲、³、⁳)が読み込まれた際に_SUPトークンを返すように変更されました。これにより、_SUPトークンが実際に生成され、パーサーに渡されることが保証されます。

src/cmd/yacc/yacc.go

  • func getword(c rune)内の変更:

    • for isword(c) || isdigit(c) || c == '_' || c == '.' || c == '$' { から
    • for isword(c) || isdigit(c) || c == '.' || c == '$' { へ変更。
    • この変更により、getword関数がトークンを構成する文字を読み取る際に、アンダースコアの扱いをisword関数に完全に委ねるようになりました。以前はc == '_'が明示的に含まれていましたが、これが削除されたことで、isword関数がアンダースコアを「単語」文字として認識するかどうかが重要になります。
  • loop関数内の変更:

    • else if isword(c) || c == '_' || c == '.' { から
    • else if isword(c) || c == '.' { へ変更。
    • これもgetword関数と同様に、isword関数がアンダースコアを処理するようになったため、冗長なc == '_'のチェックが削除されました。
  • func isword(c rune) boolの変更:

    • return c >= 0xa0 || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') から
    • return c >= 0xa0 || c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') へ変更。
    • これがこのコミットの最も重要な変更点です。isword関数が、アルファベットや特定のUnicode文字に加えて、アンダースコア(_)も有効な「単語」文字として認識するようになりました。これにより、getword関数がトークン名を解析する際に、先頭にアンダースコアを持つ文字列を正しく識別できるようになります。

これらの変更により、cmd/yaccは、トークン名の先頭にアンダースコアを持つことを許可し、そのようなトークンを正しく字句解析および構文解析できるようになります。

関連リンク

参考にした情報源リンク