[インデックス 1675] ファイルの概要
このコミットは、Go言語のパーサーにおける複合リテラル(composite literal)の扱いを改善し、よりシンプルで堅牢な構文解析を実現するための変更です。具体的には、新しい複合リテラル構文を受け入れるようにし、既存の構文解析のヒューリスティック(経験則に基づく推論)を削除することで、より広範な構文を許容しつつ、パーサー自体の複雑さを軽減しています。
コミット
commit 18ed7e690a63c181827978aa5ec41707553bb051
Author: Robert Griesemer <gri@golang.org>
Date: Fri Feb 13 16:27:53 2009 -0800
- accept new composite literal syntax
- remove all parsing heuristics
- as a result, accept a wider syntax, but parser is simpler
R=r
OCL=25029
CL=25029
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/18ed7e690a63c181827978aa5ec41707553bb051
元コミット内容
このコミットの元のメッセージは以下の通りです。
- 新しい複合リテラル構文を受け入れる
- すべての構文解析ヒューリスティックを削除する
- 結果として、より広範な構文を受け入れるが、パーサーはよりシンプルになる
変更の背景
Go言語の初期段階において、複合リテラル(struct、array、slice、mapなどの複合型を初期化するための構文)の構文解析には、特定の状況下で曖昧さを解消するためのヒューリスティック(経験則)が用いられていました。これは、パーサーがコードの意図を「推測」して解釈しようとするアプローチです。しかし、このようなヒューリスティックは、パーサーの複雑性を増大させ、予期せぬ挙動や、将来的な構文拡張の妨げとなる可能性があります。
このコミットの目的は、複合リテラルの構文をより明確にし、パーサーがヒューリスティックに頼ることなく、文法規則に基づいて直接的に構文を解釈できるようにすることでした。これにより、パーサーのコードベースが簡素化され、同時にGo言語の表現力が向上し、より柔軟な複合リテラルの記述が可能になります。特に、new(T)
や make(T, ...)
のような関数呼び出しと複合リテラルの区別が曖昧になるケースを解消することが意図されています。
前提知識の解説
複合リテラル (Composite Literals)
Go言語における複合リテラルは、構造体(struct)、配列(array)、スライス(slice)、マップ(map)といった複合型の値を簡潔に生成し、初期化するための構文です。
- 構造体リテラル:
Point{X: 10, Y: 20}
やPoint{10, 20}
のように、フィールド名と値を指定したり、フィールドの順序で値を指定したりして構造体のインスタンスを作成します。 - 配列・スライスリテラル:
[]int{1, 2, 3}
や[...]string{Enone: "no error"}
のように、要素を列挙したり、特定のインデックスに値を割り当てたりして配列やスライスを作成します。 - マップリテラル:
map[string]int{"one": 1, "two": 2}
のように、キーと値のペアを列挙してマップを作成します。
複合リテラルは、Go言語の設計思想である「簡潔さ」と「実用性」を体現する重要な機能の一つです。
構文解析 (Parsing) と抽象構文木 (AST)
構文解析とは、プログラミング言語のソースコードを解析し、その文法構造を理解するプロセスです。このプロセスでは、通常、抽象構文木(Abstract Syntax Tree, AST)が生成されます。ASTは、ソースコードの構造を木構造で表現したもので、コンパイラやインタープリタがコードの意味を理解し、後続の処理(型チェック、コード生成など)を行うための基盤となります。
- ASTノード: ASTは、プログラムの各要素(式、文、宣言など)をノードとして表現します。例えば、
BinaryExpr
は二項演算を表すノード、Ident
は識別子を表すノードです。 - パーサーの役割: パーサーは、トークンストリーム(字句解析器によって生成された単語の列)を入力として受け取り、文法規則に従ってASTを構築します。
ヒューリスティック (Heuristics)
ヒューリスティックとは、厳密な論理やアルゴリズムに基づかない、経験則や直感に基づいた問題解決の手法です。構文解析の文脈では、特定の構文が曖昧な場合に、追加のコンテキスト情報や経験的なルールを用いて解釈を試みることを指します。例えば、あるパターンが関数呼び出しにも複合リテラルにも見える場合、パーサーが「これは関数呼び出しである可能性が高い」と判断するようなケースです。
ヒューリスティックは、複雑な文法や歴史的な経緯を持つ言語において、既存のコードとの互換性を保ちつつ新しい構文を導入する際に用いられることがあります。しかし、その性質上、予測不能な挙動を引き起こしたり、パーサーのコードを複雑にしたりするデメリットがあります。
expr_lev
と scope_lev
コミット内容に見られる expr_lev
と scope_lev
は、パーサーが構文解析を行う際の「ネストレベル」を管理するための変数と考えられます。
expr_lev
(expression level): 式のネストレベルを示唆します。例えば、括弧()
の内側にある式は、外側の式よりも高いexpr_lev
を持つ可能性があります。このコミットでは、このexpr_lev
が削除されており、パーサーが式のコンテキストに依存するヒューリスティックを排除したことを示しています。scope_lev
(scope level): スコープのネストレベルを示唆します。関数やブロックの定義によってスコープが深くなる際に増加し、スコープを抜ける際に減少します。これは、変数の可視性や名前解決に影響します。
このコミットでは expr_lev
が削除されていることから、パーサーが式のコンテキスト(例えば、括弧の内側かどうか)に基づいて複合リテラルと関数呼び出しを区別するようなヒューリスティックを廃止し、より文法規則に基づいた明確な判断を行うようになったことが伺えます。
技術的詳細
このコミットの主要な変更点は、Go言語のパーサー(usr/gri/pretty/parser.go
)から、複合リテラルと関数呼び出しの区別に関するヒューリスティックを削除し、より統一された構文解析ロジックを導入したことです。
具体的には、以下の変更が行われています。
-
AST.CompositeLit
型の削除:usr/gri/pretty/ast.go
からCompositeLit
構造体とそれに関連するメソッド(DoCompositeLit
,Pos
,Visit
)が削除されました。これは、複合リテラルが独立したASTノードとして扱われるのではなく、より汎用的なCall
ノードの一部として、または他の既存のAST構造で表現されるようになったことを示唆しています。 -
expr_lev
変数の削除:usr/gri/pretty/parser.go
からexpr_lev
フィールドが削除されました。これに伴い、Open
メソッドでの初期化や、parseFunctionLit
,parseOperand
,parseIndex
,parseCall
メソッドでのインクリメント/デクリメントも削除されています。これは、パーサーが式のネストレベルに基づいて構文を解釈するヒューリスティックを完全に廃止したことを意味します。 -
exprType
およびnoType
関数の削除:parser.go
からexprType
およびnoType
ヘルパー関数が削除されました。これらの関数は、式が型であるかどうかを判断したり、型を式として扱わないようにするためのヒューリスティックなロジックを含んでいました。これらの削除は、型と式の区別をより厳密な文法規則に基づいて行うようになったことを示しています。 -
parseCall
関数の削除とparseCallOrCompositeLit
の導入: 以前はparseCall
という関数呼び出しを解析する関数が存在しましたが、これが削除され、代わりにparseCallOrCompositeLit
という新しい関数が導入されました。この変更は、関数呼び出しと複合リテラルが、パーサーの視点から見て類似の構文パターンを持つため、これらを統一的に処理するアプローチに移行したことを示しています。- 以前の
parseCall
は、new
やmake
のような特定の識別子に対して、引数リストの中に型が存在するかどうかをヒューリスティックに判断していました。 - 新しい
parseCallOrCompositeLit
は、LPAREN
(左括弧) を期待し、その後の要素をparseCompositeElements
で解析します。これにより、関数呼び出しの引数リストと複合リテラルの要素リストが、より一貫した方法で処理されるようになります。
- 以前の
-
parsePrimaryExpr
における複合リテラル解析ロジックの変更:parsePrimaryExpr
関数内で、Scanner.LBRACE
(左中括弧) が現れた際の複合リテラル解析ロジックが大幅に変更されました。- 以前は、
expr_lev
が0以上であること(制御句の内側ではないこと)と、現在の式x
が型として解釈できる場合にのみ複合リテラルとして解析するヒューリスティックがありました。 - 変更後、
Scanner.LBRACE
のケースは削除され、Scanner.LPAREN
のケースでparseCallOrCompositeLit(x)
が呼び出されるようになりました。これは、複合リテラルが括弧()
を使用する新しい構文に移行したか、またはパーサーが複合リテラルを関数呼び出しと区別なく処理するようになったことを示唆しています。Go言語の複合リテラルは{}
を使用するため、この変更は、パーサーが()
を使って複合リテラルを解析するようになったのではなく、()
の後に続く要素の解析をparseCompositeElements
に委ねることで、より汎用的な処理を実現したと解釈できます。
- 以前は、
-
newBinaryExpr
の直接的なASTノード生成への変更:newBinaryExpr
ヘルパー関数が削除され、&AST.BinaryExpr(...)
のようにASTノードを直接生成するようになりました。これは、newBinaryExpr
が行っていたnoType
などのヒューリスティックな処理が不要になったためです。 -
parseExpression
からnoType
の呼び出し削除:parseExpression
関数からP.noType(P.parseBinaryExpr(prec))
の呼び出しが削除され、P.parseBinaryExpr(prec)
を直接返すようになりました。これも、型と式の区別に関するヒューリスティックが不要になったことの表れです。
これらの変更により、パーサーはよりシンプルになり、特定のコンテキストに依存するヒューリスティックな判断を排除することで、より堅牢で予測可能な構文解析を実現しています。結果として、Go言語の複合リテラル構文は、より広範なケースに対応できるようになり、開発者にとってより直感的で一貫性のあるものになったと考えられます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、以下のファイルに集中しています。
usr/gri/pretty/ast.go
: 抽象構文木(AST)の定義ファイル。複合リテラルを表すCompositeLit
型が削除されています。usr/gri/pretty/parser.go
: Go言語の構文解析器の主要なロジックが記述されているファイル。最も多くの変更が行われており、ヒューリスティックの削除と新しい構文解析ロジックの導入がここで行われています。usr/gri/pretty/printer.go
: ASTを整形して出力するプリンターのファイル。CompositeLit
型の削除に伴い、DoCompositeLit
メソッドが削除されています。
usr/gri/pretty/ast.go
の変更
--- a/usr/gri/pretty/ast.go
+++ b/usr/gri/pretty/ast.go
@@ -167,12 +167,6 @@ type (
Body *Block;
};
- CompositeLit struct {
- Pos_ int; // position of "{"
- Typ *Type;
- Elts Expr;
- };
-
TypeLit struct {
Typ *Type;
};
@@ -208,7 +202,6 @@ type ExprVisitor interface {
DoUnaryExpr(x *UnaryExpr);
DoBasicLit(x *BasicLit);
DoFunctionLit(x *FunctionLit);
- DoCompositeLit(x *CompositeLit);
DoTypeLit(x *TypeLit);
DoSelector(x *Selector);
DoTypeGuard(x *TypeGuard);
@@ -223,7 +216,6 @@ func (x *BinaryExpr) Pos() int { return x.Pos_; }
func (x *UnaryExpr) Pos() int { return x.Pos_; }
func (x *BasicLit) Pos() int { return x.Pos_; }
func (x *FunctionLit) Pos() int { return x.Pos_; }
-func (x *CompositeLit) Pos() int { return x.Pos_; }
func (x *TypeLit) Pos() int { return x.Typ.Pos; }
func (x *Selector) Pos() int { return x.Pos_; }
func (x *TypeGuard) Pos() int { return x.Pos_; }
@@ -237,7 +229,6 @@ func (x *BinaryExpr) Visit(v ExprVisitor) { v.DoBinaryExpr(x); }
func (x *UnaryExpr) Visit(v ExprVisitor) { v.DoUnaryExpr(x); }
func (x *BasicLit) Visit(v ExprVisitor) { v.DoBasicLit(x); }
func (x *FunctionLit) Visit(v ExprVisitor) { v.DoFunctionLit(x); }
-func (x *CompositeLit) Visit(v ExprVisitor) { v.DoCompositeLit(x); }
func (x *TypeLit) Visit(v ExprVisitor) { v.DoTypeLit(x); }
func (x *Selector) Visit(v ExprVisitor) { v.DoSelector(x); }
func (x *TypeGuard) Visit(v ExprVisitor) { v.DoTypeGuard(x); }
usr/gri/pretty/parser.go
の変更
--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -32,7 +32,6 @@ type Parser struct {
opt_semi bool; // true if semicolon is optional
// Nesting levels
- expr_lev int; // 0 = control clause level, 1 = expr inside ()'s
scope_lev int; // 0 = global scope, 1 = function scope of global functions, etc.
// Scopes
@@ -129,7 +128,6 @@ func (P *Parser) Open(trace, sixg, deps bool, scanner *Scanner.Scanner) {
P.comments = vector.New(0);
P.next();
- P.expr_lev = 0;
P.scope_lev = 0;
}
@@ -211,43 +209,6 @@ func (P *Parser) declare(x AST.Expr, kind int, typ *AST.Type) {
}
-// ----------------------------------------------------------------------------
-// AST support
-
-func exprType(x AST.Expr) *AST.Type {
- var typ *AST.Type;
- if t, is_type := x.(*AST.TypeLit); is_type {
- typ = t.Typ
- } else if t, is_ident := x.(*AST.Ident); is_ident {
- // assume a type name
- typ = AST.NewType(t.Pos(), AST.TYPENAME);
- typ.Expr = x;
- } else if t, is_selector := x.(*AST.Selector); is_selector && exprType(t.Sel) != nil {
- // possibly a qualified (type) identifier
- typ = AST.NewType(t.Pos(), AST.TYPENAME);
- typ.Expr = x;
- }
- return typ;
-}
-
-
-func (P *Parser) noType(x AST.Expr) AST.Expr {
- if x != nil {
- lit, ok := x.(*AST.TypeLit);
- if ok {
- P.error(lit.Typ.Pos, "expected expression, found type");
- x = &AST.BasicLit(lit.Typ.Pos, Scanner.STRING, "");
- }
- }
- return x;
-}
-
-
-func (P *Parser) newBinaryExpr(pos, tok int, x, y AST.Expr) *AST.BinaryExpr {
- return &AST.BinaryExpr(pos, tok, P.noType(x), P.noType(y));
-}
-
-
// ----------------------------------------------------------------------------
// Common productions
@@ -295,10 +256,10 @@ func (P *Parser) parseIdentList() AST.Expr {
P.next();
y := P.parseIdent(nil);
if last == nil {
- last = P.newBinaryExpr(pos, Scanner.COMMA, x, y);
+ last = &AST.BinaryExpr(pos, Scanner.COMMA, x, y);
x = last;
} else {
- last.Y = P.newBinaryExpr(pos, Scanner.COMMA, last.Y, y);
+ last.Y = &AST.BinaryExpr(pos, Scanner.COMMA, last.Y, y);
last = last.Y.(*AST.BinaryExpr);
}
}
@@ -371,7 +332,7 @@ func (P *Parser) parseArrayType() *AST.Type {
t := AST.NewType(P.pos, AST.ARRAY);
P.expect(Scanner.LBRACK);
if P.tok == Scanner.ELLIPSIS {
- t.Expr = P.newBinaryExpr(P.pos, Scanner.ELLIPSIS, nil, nil);
+ t.Expr = &AST.BinaryExpr(P.pos, Scanner.ELLIPSIS, nil, nil);
P.next();
} else if P.tok != Scanner.RBRACK {
t.Expr = P.parseExpression(1);
@@ -708,19 +669,19 @@ func (P *Parser) tryType() *AST.Type {
defer un(trace(P, "Type (try)"));
}\n
- t := AST.BadType;
switch P.tok {
- case Scanner.IDENT: t = P.parseTypeName();
- case Scanner.LBRACK: t = P.parseArrayType();
- case Scanner.CHAN, Scanner.ARROW: t = P.parseChannelType();
- case Scanner.INTERFACE: t = P.parseInterfaceType();
- case Scanner.FUNC: t = P.parseFunctionType();
- case Scanner.MAP: t = P.parseMapType();
- case Scanner.STRUCT: t = P.parseStructType();
- case Scanner.MUL: t = P.parsePointerType();
- default: t = nil; // no type found
- }
- return t;
+ case Scanner.IDENT: return P.parseTypeName();
+ case Scanner.LBRACK: return P.parseArrayType();
+ case Scanner.CHAN, Scanner.ARROW: return P.parseChannelType();
+ case Scanner.INTERFACE: return P.parseInterfaceType();
+ case Scanner.FUNC: return P.parseFunctionType();
+ case Scanner.MAP: return P.parseMapType();
+ case Scanner.STRUCT: return P.parseStructType();
+ case Scanner.MUL: return P.parsePointerType();
+ }
+
+ // no type found
+ return nil;
}
@@ -801,10 +762,10 @@ func (P *Parser) parseExpressionList() AST.Expr {
P.next();
y := P.parseExpression(1);
if first {
- x = P.newBinaryExpr(pos, Scanner.COMMA, x, y);
+ x = &AST.BinaryExpr(pos, Scanner.COMMA, x, y);
first = false;
} else {
- x.(*AST.BinaryExpr).Y = P.newBinaryExpr(pos, Scanner.COMMA, x.(*AST.BinaryExpr).Y, y);
+ x.(*AST.BinaryExpr).Y = &AST.BinaryExpr(pos, Scanner.COMMA, x.(*AST.BinaryExpr).Y, y);
}
}
@@ -820,11 +781,9 @@ func (P *Parser) parseFunctionLit() AST.Expr {
pos := P.pos;
P.expect(Scanner.FUNC);
typ := P.parseSignature();
- P.expr_lev++;
P.scope_lev++;
body := P.parseBlock(typ, Scanner.LBRACE);
P.scope_lev--;
- P.expr_lev--;
return &AST.FunctionLit(pos, typ, body);
}
@@ -841,9 +799,7 @@ func (P *Parser) parseOperand() AST.Expr {
case Scanner.LPAREN:
P.next();
- P.expr_lev++;
x := P.parseExpression(1);
- P.expr_lev--;
P.expect(Scanner.RPAREN);
return x;
@@ -904,9 +860,7 @@ func (P *Parser) parseIndex(x AST.Expr) AST.Expr {
pos := P.pos;
P.expect(Scanner.LBRACK);
- P.expr_lev++;
i := P.parseExpression(0);
- P.expr_lev--;
P.expect(Scanner.RBRACK);
return &AST.Index(pos, x, i);
@@ -915,43 +869,6 @@ func (P *Parser) parseIndex(x AST.Expr) AST.Expr {
func (P *Parser) parseBinaryExpr(prec1 int) AST.Expr
-func (P *Parser) parseCall(f AST.Expr) AST.Expr {
- if P.trace {
- defer un(trace(P, "Call"));
- }
-
- call := &AST.Call(P.pos, f, nil);
- P.expect(Scanner.LPAREN);
- if P.tok != Scanner.RPAREN {
- P.expr_lev++;
- var t *AST.Type;
- if x0, ok := f.(*AST.Ident); ok && (x0.Obj.Ident == "new" || x0.Obj.Ident == "make") {
- // heuristic: assume it's a new(T) or make(T, ...) call, try to parse a type
- t = P.tryType();
- }
- if t != nil {
- // we found a type
- args := &AST.TypeLit(t);
- if P.tok == Scanner.COMMA {
- pos := P.pos;
- P.next();
- y := P.parseExpressionList();
- // create list manually because NewExpr checks for type expressions
- args := &AST.BinaryExpr(pos, Scanner.COMMA, args, y);
- }
- call.Args = args;
- } else {
- // normal argument list
- call.Args = P.parseExpressionList();
- }
- P.expr_lev--;
- }
- P.expect(Scanner.RPAREN);
-
- return call;
-}
-
-
func (P *Parser) parseCompositeElements() AST.Expr {
x := P.parseExpression(0);
if P.tok == Scanner.COMMA {
@@ -965,7 +882,7 @@ func (P *Parser) parseCompositeElements() AST.Expr {
}\n
var last *AST.BinaryExpr;
- for P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {
+ for P.tok != Scanner.RPAREN && P.tok != Scanner.EOF {
y := P.parseExpression(0);
if singles {
@@ -979,10 +896,10 @@ func (P *Parser) parseCompositeElements() AST.Expr {
}\n
if last == nil {
- last = P.newBinaryExpr(pos, Scanner.COMMA, x, y);
+ last = &AST.BinaryExpr(pos, Scanner.COMMA, x, y);
x = last;
} else {
- last.Y = P.newBinaryExpr(pos, Scanner.COMMA, last.Y, y);
+ last.Y = &AST.BinaryExpr(pos, Scanner.COMMA, last.Y, y);
last = last.Y.(*AST.BinaryExpr);
}
@@ -999,20 +916,20 @@ func (P *Parser) parseCompositeElements() AST.Expr {
}
-func (P *Parser) parseCompositeLit(t *AST.Type) AST.Expr {
+func (P *Parser) parseCallOrCompositeLit(f AST.Expr) AST.Expr {
if P.trace {
- defer un(trace(P, "CompositeLit"));
+ defer un(trace(P, "CallOrCompositeLit"));
}
pos := P.pos;
- P.expect(Scanner.LBRACE);
- var elts AST.Expr;
- if P.tok != Scanner.RBRACE {
- elts = P.parseCompositeElements();
+ P.expect(Scanner.LPAREN);
+ var args AST.Expr;
+ if P.tok != Scanner.RPAREN {
+ args = P.parseCompositeElements();
}
- P.expect(Scanner.RBRACE);
+ P.expect(Scanner.RPAREN);
- return &AST.CompositeLit(pos, t, elts);
+ return &AST.Call(pos, f, args);
}
@@ -1026,20 +943,7 @@ func (P *Parser) parsePrimaryExpr() AST.Expr {
switch P.tok {
case Scanner.PERIOD: x = P.parseSelectorOrTypeGuard(x);
case Scanner.LBRACK: x = P.parseIndex(x);
- case Scanner.LPAREN: x = P.parseCall(x);
- case Scanner.LBRACE:
- // assume a composite literal only if x could be a type
- // and if we are not inside a control clause (expr_lev >= 0)
- // (composites inside control clauses must be parenthesized)
- var t *AST.Type;
- if P.expr_lev >= 0 {
- t = exprType(x);
- }
- if t != nil {
- x = P.parseCompositeLit(t);
- } else {
- return x;
- }
+ case Scanner.LPAREN: x = P.parseCallOrCompositeLit(x);
default:
return x;
}
@@ -1085,7 +989,7 @@ func (P *Parser) parseBinaryExpr(prec1 int) AST.Expr {
pos, tok := P.pos, P.tok;
P.next();
y := P.parseBinaryExpr(prec + 1);
- x = P.newBinaryExpr(pos, tok, x, y);
+ x = &AST.BinaryExpr(pos, tok, x, y);
}
}
@@ -1102,7 +1006,7 @@ func (P *Parser) parseExpression(prec int) AST.Expr {
panic("precedence must be >= 0");
}
- return P.noType(P.parseBinaryExpr(prec));
+ return P.parseBinaryExpr(prec);
}
@@ -1153,7 +1057,7 @@ func (P *Parser) parseSimpleStat(range_ok bool) AST.Stat {
}
}
// TODO changed ILLEGAL -> NONE
- return &AST.ExpressionStat(x.Pos(), Scanner.ILLEGAL, P.newBinaryExpr(pos, tok, x, y));
+ return &AST.ExpressionStat(x.Pos(), Scanner.ILLEGAL, &AST.BinaryExpr(pos, tok, x, y));
default:
if AST.ExprLen(x) != 1 {
@@ -1223,8 +1128,6 @@ func (P *Parser) parseControlClause(isForStat bool) (init AST.Stat, expr AST.Exp
}
if P.tok != Scanner.LBRACE {
- prev_lev := P.expr_lev;
- P.expr_lev = -1;
if P.tok != Scanner.SEMICOLON {
init = P.parseSimpleStat(isForStat);
// TODO check for range clause and exit if found
@@ -1249,7 +1152,6 @@ func (P *Parser) parseControlClause(isForStat bool) (init AST.Stat, expr AST.Exp
}
}
}
- P.expr_lev = prev_lev;
}
return init, expr, post;
@@ -1361,7 +1263,7 @@ func (P *Parser) parseCommClause() *AST.CaseClause {
P.next();
if P.tok == Scanner.ARROW {
y := P.parseExpression(1);
- x = P.newBinaryExpr(pos, tok, x, y);
+ x = &AST.BinaryExpr(pos, tok, x, y);
} else {
P.expect(Scanner.ARROW); // use expect() error handling
}
usr/gri/pretty/printer.go
の変更
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -680,14 +680,6 @@ func (P *Printer) DoFunctionLit(x *AST.FunctionLit) {
}
-func (P *Printer) DoCompositeLit(x *AST.CompositeLit) {
- P.Type(x.Typ);
- P.String(x.Pos(), "{");
- P.Expr(x.Elts);
- P.String(0, "}");
-}
-
-
func (P *Printer) DoSelector(x *AST.Selector) {
P.Expr1(x.X, Scanner.HighestPrec);
P.String(x.Pos(), ".");
コアとなるコードの解説
このコミットの核心は、Go言語のパーサーが複合リテラルと関数呼び出しを区別するために使用していた「ヒューリスティック」を排除し、より統一的で文法に基づいた解析ロジックに移行した点にあります。
-
CompositeLit
ASTノードの削除: 以前は、複合リテラルはAST.CompositeLit
という専用のASTノードで表現されていました。このノードの削除は、複合リテラルがもはや独立した特殊な構文要素として扱われるのではなく、より汎用的なAST.Call
ノード(関数呼び出しを表すノード)や他の既存のAST構造の一部として表現されるようになったことを示唆しています。これは、パーサーが複合リテラルと関数呼び出しを類似の構文パターンとして扱うようになった結果です。 -
expr_lev
の削除:expr_lev
は、パーサーが式のネストレベルを追跡するために使用していた変数です。この変数が削除されたことは、パーサーが式のコンテキスト(例えば、括弧の内側かどうか)に基づいて構文を解釈するヒューリスティックを完全に廃止したことを意味します。これにより、パーサーはより文法規則に忠実になり、特定のコンテキストに依存しない、より予測可能な挙動を示すようになります。 -
parseCall
からparseCallOrCompositeLit
への移行: 最も重要な変更の一つは、parseCall
関数の削除とparseCallOrCompositeLit
の導入です。- 以前の
parseCall
は、new(T)
やmake(T, ...)
のような特定の関数呼び出しに対して、引数リストの中に型が存在するかどうかをヒューリスティックに判断していました。これは、new
やmake
が型を引数として取る特殊な組み込み関数であるため、パーサーがその引数を型として特別に扱う必要があったためです。 - 新しい
parseCallOrCompositeLit
は、関数呼び出しと複合リテラルの両方を統一的に処理します。この関数はLPAREN
(左括弧) を期待し、その後の要素をparseCompositeElements
で解析します。これにより、関数呼び出しの引数リストと複合リテラルの要素リストが、より一貫した方法で処理されるようになります。Go言語の複合リテラルは{}
を使用しますが、この変更は、パーサーが()
を使って複合リテラルを解析するようになったのではなく、()
の後に続く要素の解析をparseCompositeElements
に委ねることで、より汎用的な処理を実現したと解釈できます。これは、例えばType{}
のような複合リテラルが、内部的にはCall
ノードとして表現され、その引数として複合リテラルの要素が渡されるような設計変更があった可能性を示唆しています。
- 以前の
-
newBinaryExpr
ヘルパー関数の削除と直接的なASTノード生成:newBinaryExpr
は、二項演算を表すAST.BinaryExpr
ノードを生成するためのヘルパー関数でした。この関数が削除され、コード内で直接&AST.BinaryExpr(...)
のようにASTノードを生成するようになったのは、newBinaryExpr
が行っていたnoType
などのヒューリスティックな処理が不要になったためです。これにより、コードがより直接的になり、意図が明確になります。
これらの変更は、Go言語のパーサーが、特定のキーワードやコンテキストに依存する「推測」ではなく、より厳密な文法規則に基づいて構文を解析する方向へと進化していることを示しています。これにより、パーサーのコードベースは簡素化され、将来的な言語の拡張や変更に対する柔軟性が向上します。また、開発者にとっては、より予測可能で一貫性のある構文解析の挙動が提供されることになります。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の複合リテラルに関する「Effective Go」の記述: https://go.dev/doc/effective_go#composite_literals
参考にした情報源リンク
- Go言語の複合リテラル構文の歴史に関するWeb検索結果
- Go言語のソースコード(特に
go/ast
およびgo/parser
パッケージの初期バージョン) - 抽象構文木 (AST) に関する一般的な情報
- コンパイラ設計に関する一般的な情報
- Go言語の初期の設計に関する議論やブログ記事(Robert Griesemer, Russ Cox などの貢献者のもの)