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

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

このコミットは、Go言語の初期開発段階における抽象構文木(AST: Abstract Syntax Tree)の表現方法と、コードのトレーシング(追跡)メカニズムに関する重要な変更を導入しています。特に、ASTの表現をよりクリーンで柔軟なインターフェースベースの設計に移行し、同時にトレーシングコードにdeferステートメントを適用することで、コードの可読性と保守性を向上させています。

コミット

commit c048ee21ada70be4c0085edfce01da27ac41c272
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Feb 3 17:44:01 2009 -0800

    - converted expr representation of ast into a new representation
    using interfaces properly => much cleaner code
    - converted tracing code to use 'defer' statement
    - next steps: convert rest of ast as well
    
    R=r
    OCL=24277
    CL=24277

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

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

元コミット内容

このコミットは、以下の主要な変更を含んでいます。

  1. ASTの式(expr)表現の変更: 従来のASTにおける式の表現を、インターフェースを適切に利用した新しい表現に変換しました。これにより、コードが大幅にクリーンになりました。
  2. トレーシングコードへのdeferステートメントの適用: コードの実行フローを追跡するためのトレーシングコードにおいて、deferステートメントを使用するように変更しました。
  3. 今後のステップ: ASTの残りの部分も同様に変換していくことが示唆されています。

変更の背景

Go言語のコンパイラやツールチェインにおいて、ソースコードを解析して内部的に表現する抽象構文木(AST)は非常に重要な役割を担っています。初期のAST設計では、式の表現が特定の構造体に密結合しており、柔軟性や拡張性に課題があったと考えられます。

このコミットの背景には、以下の目的があったと推測されます。

  • コードのクリーンアップと保守性の向上: 従来のAST表現が複雑であったり、特定のパターンに依存していたりしたため、それをよりシンプルで理解しやすいインターフェースベースの設計にすることで、コード全体の品質を向上させる狙いがありました。
  • 柔軟性と拡張性の確保: インターフェースを導入することで、将来的に新しい種類の式やASTノードを追加する際に、既存のコードへの影響を最小限に抑え、より容易に拡張できる基盤を構築しようとしていました。
  • deferステートメントの活用: deferはGo言語の重要な機能の一つであり、関数の終了時に必ず実行される処理を記述するのに適しています。トレーシングのような、関数の入り口と出口で特定の処理を行う必要がある場面でdeferを使用することで、コードの重複を減らし、エラーハンドリングを簡素化し、ロジックをより明確に表現できるという利点があります。このコミットは、deferの有効性を認識し、それを実際のコードベースに適用する初期の試みであったと考えられます。

前提知識の解説

抽象構文木(AST: Abstract Syntax Tree)

ASTは、プログラミング言語のソースコードを抽象的な構文構造で表現したツリー構造のデータです。コンパイラやインタプリタは、ソースコードを字句解析(トークン化)し、構文解析(パース)してASTを生成します。ASTは、コードの意味解析、最適化、コード生成など、後続の処理の基盤となります。

例えば、a + b * cという式は、以下のようなASTで表現されることがあります。

      +
     / \
    a   *
       / \
      b   c

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

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。インターフェースを実装する型は、そのインターフェースで定義されたすべてのメソッドを持つ必要があります。Goのインターフェースは「暗黙的」であり、型がインターフェースのすべてのメソッドを実装していれば、明示的に「このインターフェースを実装する」と宣言する必要はありません。

インターフェースの主な利点は以下の通りです。

  • ポリモーフィズム: 異なる具象型を同じインターフェース型として扱うことができます。これにより、柔軟で再利用可能なコードを書くことができます。
  • 疎結合: コードが特定の具象型に依存するのではなく、インターフェースに依存するようになるため、コンポーネント間の結合度が低減します。
  • テスト容易性: モックやスタブを簡単に作成し、テスト対象のコンポーネメントが依存する外部サービスやコンポーネントをシミュレートできます。

deferステートメント

deferステートメントは、Go言語のユニークな機能の一つで、そのステートメントが属する関数がリターンする直前に、指定された関数呼び出しを遅延実行させます。deferされた関数は、関数の実行が正常に終了した場合でも、パニック(ランタイムエラー)が発生した場合でも、必ず実行されます。

deferの主な用途は以下の通りです。

  • リソースの解放: ファイルのクローズ、ロックの解除、データベース接続の終了など、確保したリソースを確実に解放するために使用されます。
  • トレーシング/ロギング: 関数の入り口と出口でログを出力する際に、deferを使って出口でのログ出力を簡潔に記述できます。
  • エラーハンドリング: recoverと組み合わせて、パニックからの回復処理を記述できます。

deferされた関数はLIFO(Last-In, First-Out)の順序で実行されます。つまり、最後にdeferされた関数が最初に実行されます。

技術的詳細

このコミットの技術的詳細は、主にusr/gri/pretty/ast.gousr/gri/pretty/parser.goの変更に集約されています。

ASTの式表現のインターフェース化

従来のExpr構造体は、X, Y(二項演算子や単項演算子のオペランド)、Obj(識別子やリテラル)、Typ(型)といったフィールドを持っていました。これは、すべての種類の式を単一の具象型で表現しようとするアプローチでした。しかし、式の種類(例えば、二項演算、単項演算、リテラル、関数呼び出しなど)が増えるにつれて、この単一のExpr構造体では表現が複雑になり、フィールドの利用が特定の式タイプに限定されるなど、非効率的になる可能性があります。

このコミットでは、Exprをインターフェースとして定義し、様々な種類の式をそれぞれ異なる具象型(構造体)で表現するように変更しています。

usr/gri/pretty/ast.goの変更点:

  • type Lit struct;type Expr struct; が削除され、代わりに Expr interface; が導入されました。
  • 新しいExprインターフェースを実装する複数の具象型が定義されました。
    • BadExpr: 不正な式
    • Ident: 識別子
    • BinaryExpr: 二項演算式(例: a + b
    • UnaryExpr: 単項演算式(例: -a
    • BasicLit: 基本リテラル(整数、浮動小数点数、文字列など)
    • FunctionLit: 関数リテラル
    • CompositeLit: 複合リテラル(構造体、配列、マップの初期化など)
    • TypeLit: 型リテラル(型そのものを表す式)
    • Selector: セレクタ式(例: obj.field
    • TypeGuard: 型ガード(型アサーションなど)
    • Index: インデックス式(例: arr[i]
    • Call: 関数呼び出し式
  • これらの新しい具象型は、それぞれが持つべき最小限のフィールド(例: BinaryExprX, YBasicLitVal)を持つように設計されています。
  • ExprインターフェースはPos() intVisit(v Visitor)メソッドを定義しており、すべての具象型がこれらを実装しています。Visitメソッドは、Visitorパターンを実装するためのもので、ASTの走査を容易にします。
  • ExprLenExprAt関数が導入され、カンマ区切りの式リストの長さを取得したり、特定のインデックスの式を取得したりする際に、新しいインターフェースベースのExpr型を扱うように変更されました。

トレーシングコードへのdeferステートメントの適用

Go言語のパーサー(usr/gri/pretty/parser.go)には、デバッグや開発のためにコードの実行フローを追跡するトレーシング機能がありました。従来のトレーシングでは、関数の入り口でP.Trace()を呼び出し、関数の出口でP.Ecart()を呼び出すことで、インデントされたログ出力を実現していました。

usr/gri/pretty/parser.goの変更点:

  • P.Trace(msg string)関数内で、P.indent++の前にdefer P.Ecart()が追加されました。
  • これにより、P.Trace()が呼び出された関数がリターンする際に、自動的にP.Ecart()が実行されるようになります。
  • Parse...関数(例: ParseIdent, ParseIdentList, ParseTypeなど)の冒頭にif P.verbose { P.Trace("...") }defer P.Ecart()のペアが追加されました。これにより、明示的にP.Ecart()を呼び出す必要がなくなり、コードが簡潔になりました。

この変更は、deferステートメントが導入された初期のGo言語において、その有用性を示す良い例です。リソースの解放やトレーシングのような、関数のスコープを抜ける際に必ず実行されるべき処理にdeferを適用することで、コードの重複を排除し、ロジックをより堅牢にすることができます。

その他の変更点

  • usr/gri/pretty/compilation.go: addDeps関数内でpanic()が追加され、既存のコードがコメントアウトされています。これは一時的な変更か、あるいは依存関係の追加ロジックがまだ完全に実装されていないことを示唆している可能性があります。
  • usr/gri/pretty/printer.go: fmt.Printfの使用が増え、printの直接使用が減っています。これは、より標準的なGoのI/Oライブラリへの移行を示唆しています。

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

usr/gri/pretty/ast.go

--- a/usr/gri/pretty/ast.go
+++ b/usr/gri/pretty/ast.go
@@ -17,8 +17,7 @@ type (
 	Type struct;
 
 	Block struct;
-	Lit struct;
-	Expr struct;
+	Expr interface;
 	Stat struct;
 	Decl struct;
 )
@@ -128,26 +127,6 @@ type Node struct {
 }
 
 
-// ----------------------------------------------------------------------------
-// Literals
-
-type Lit struct {
-	Node;
-	
-	// Identifiers
-	Obj *Object;
-	
-	// Constant literals
-	
-	// Type literals
-	Len *Expr;  // array length
-	Dir int;  // channel direction
-	Key *Type;  // receiver or map key type
-	Elt *Type;  // array, map, channel, pointer element, or function result type
-	List *array.Array; End int;  // struct fields, interface methods, function parameters
-}
-
-
 // ----------------------------------------------------------------------------
 // Scopes
 
@@ -218,87 +197,6 @@ func (scope *Scope) Print() {
 }
 
 
-// ----------------------------------------------------------------------------
-// Blocks
-//
-// Syntactic constructs of the form:
-//
-//   "{" StatementList "}"
-//   ":" StatementList
-
-type Block struct {
-	Node;
-	List *array.Array;
-	End int;  // position of closing "}" if present
-}
-
-
-func NewBlock(pos, tok int) *Block {
-	assert(tok == Scanner.LBRACE || tok == Scanner.COLON);
-	b := new(Block);
-	b.Pos, b.Tok, b.List = pos, tok, array.New(0);
-	return b;
-}
-
-
-// ----------------------------------------------------------------------------
-// Expressions
-
-type Expr struct {
-	Node;
-	X, Y *Expr;  // binary (X, Y) and unary (Y) expressions
-	Obj *Object;  // identifiers, literals
-	Typ *Type;
-}
-
-
-// Length of a comma-separated expression list.
-func (x *Expr) Len() int {
-	if x == nil {
-		return 0;
-	}
-	n := 1;
-	for ; x.Tok == Scanner.COMMA; x = x.Y {
-		n++;
-	}
-	return n;
-}
-
-
-// The i'th expression in a comma-separated expression list.
-func (x *Expr) At(i int) *Expr {
-	for j := 0; j < i; j++ {
-		assert(x.Tok == Scanner.COMMA);
-		x = x.Y;
-	}
-	if x.Tok == Scanner.COMMA {
-		x = x.X;
-	}
-	return x;
-}
-
-
-func NewExpr(pos, tok int, x, y *Expr) *Expr {
-	if x != nil && x.Tok == Scanner.TYPE || y != nil && y.Tok == Scanner.TYPE {
-		panic("no type expression allowed");
-	}
-	e := new(Expr);
-	e.Pos, e.Tok, e.X, e.Y = pos, tok, x, y;
-	return e;
-}
-
-
-// TODO probably don't need the tok parameter eventually
-func NewLit(tok int, obj *Object) *Expr {
-	e := new(Expr);
-	e.Pos, e.Tok, e.Obj, e.Typ = obj.Pos, tok, obj, obj.Typ;
-	return e;
-}
-
-
-var BadExpr = NewExpr(0, Scanner.ILLEGAL, nil, nil);
-
-
 // ----------------------------------------------------------------------------
 // Types
 
@@ -388,7 +286,7 @@ type Type struct {
 
 	// syntactic components
 	Pos int;  // source position (< 0 if unknown position)
-	Expr *Expr;  // type name, array length
+	Expr Expr;  // type name, array length
 	Mode int;  // channel mode
 	Key *Type;  // receiver type or map key
 	Elt *Type;  // type name type, array, map, channel or pointer element type, function result type
@@ -411,25 +309,6 @@ func NewType(pos, form int) *Type {
 }
 
 
-func (t *Type) Nfields() int {
-	if t.List == nil {
-		return 0;
-	}
-	nx, nt := 0, 0;
-	for i, n := 0, t.List.Len(); i < n; i++ {
-		if t.List.At(i).(*Expr).Tok == Scanner.TYPE {
-			nt++;
-		} else {
-			nx++;
-		}
-	}
-	if nx == 0 {
-		return nt;
-	}
-	return nx;
-}
-
-
 func (typ* Type) String() string {
 	if typ != nil {
 		return
@@ -441,31 +320,199 @@ func (typ* Type) String() string {
 }
 
 
-// requires complete Type.Pos access
-func NewTypeExpr(typ *Type) *Expr {
-	e := new(Expr);
-	e.Pos, e.Tok, e.Typ = typ.Pos, Scanner.TYPE, typ;
-	return e;
+var BadType = NewType(0, Scanner.ILLEGAL);
+
+
+// ----------------------------------------------------------------------------
+// Blocks
+//
+// Syntactic constructs of the form:
+//
+//   "{" StatementList "}"
+//   ":" StatementList
+
+type Block struct {
+	Node;
+	List *array.Array;
+	End int;  // position of closing "}" if present
 }
 
 
-// requires complete Type.String access
-func (x *Expr) String() string {
-	if x != nil {
-		return
-			"Expr(" +
-			Scanner.TokenString(x.Tok) + ", " +
-			x.X.String() + ", " +
-			x.Y.String() + ", " +
-			x.Obj.String() + ", " +
-			x.Typ.String() +
-			")";
+func NewBlock(pos, tok int) *Block {
+	assert(tok == Scanner.LBRACE || tok == Scanner.COLON);
+	b := new(Block);
+	b.Pos, b.Tok, b.List = pos, tok, array.New(0);
+	return b;
+}
+
+
+// ----------------------------------------------------------------------------
+// Expressions
+
+type (
+	Visitor interface;
+
+	Expr interface {
+		Pos() int;
+		Visit(v Visitor);
+	};
+
+	BadExpr struct {
+		Pos_ int;
+	};
+
+	Ident struct {
+		Pos_ int;
+		Obj *Object;
+	};
+
+	BinaryExpr struct {
+		Pos_, Tok int;
+		X, Y Expr;
+	};
+
+	UnaryExpr struct {
+		Pos_, Tok int;
+		X Expr;
+	};
+
+	BasicLit struct {
+		Pos_, Tok int;
+		Val string
+	};
+
+	FunctionLit struct {
+		Pos_ int;  // position of "func"
+		Typ *Type;
+		Body *Block;
+	};
+	
+	CompositeLit struct {
+		Pos_ int;  // position of "{"
+		Typ *Type;
+		Elts Expr;
+	};
+
+	TypeLit struct {
+		Typ *Type;
+	};
+
+	Selector struct {
+		Pos_ int;  // position of "."
+		X Expr;
+		Sel *Ident;
+	};
+
+	TypeGuard struct {
+		Pos_ int;  // position of "."
+		X Expr;
+		Typ *Type;
+	};
+
+	Index struct {
+		Pos_ int;  // position of "["
+		X, I Expr;
+	};
+	
+	Call struct {
+		Pos_ int;  // position of "("
+		F, Args Expr
+	};
+)
+
+
+type Visitor interface {
+	DoBadExpr(x *BadExpr);
+	DoIdent(x *Ident);
+	DoBinaryExpr(x *BinaryExpr);
+	DoUnaryExpr(x *UnaryExpr);
+	DoBasicLit(x *BasicLit);
+	DoFunctionLit(x *FunctionLit);
+	DoCompositeLit(x *CompositeLit);
+	DoTypeLit(x *TypeLit);
+	DoSelector(x *Selector);
+	DoTypeGuard(x *TypeGuard);
+	DoIndex(x *Index);
+	DoCall(x *Call);
+}
+
+
+func (x *BadExpr) Pos() int { return x.Pos_; }
+func (x *Ident) Pos() int { return x.Pos_; }
+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_; }
+func (x *Index) Pos() int { return x.Pos_; }
+func (x *Call) Pos() int { return x.Pos_; }
+
+
+func (x *BadExpr) Visit(v Visitor) { v.DoBadExpr(x); }
+func (x *Ident) Visit(v Visitor) { v.DoIdent(x); }
+func (x *BinaryExpr) Visit(v Visitor) { v.DoBinaryExpr(x); }
+func (x *UnaryExpr) Visit(v Visitor) { v.DoUnaryExpr(x); }
+func (x *BasicLit) Visit(v Visitor) { v.DoBasicLit(x); }
+func (x *FunctionLit) Visit(v Visitor) { v.DoFunctionLit(x); }
+func (x *CompositeLit) Visit(v Visitor) { v.DoCompositeLit(x); }
+func (x *TypeLit) Visit(v Visitor) { v.DoTypeLit(x); }
+func (x *Selector) Visit(v Visitor) { v.DoSelector(x); }
+func (x *TypeGuard) Visit(v Visitor) { v.DoTypeGuard(x); }
+func (x *Index) Visit(v Visitor) { v.DoIndex(x); }
+func (x *Call) Visit(v Visitor) { v.DoCall(x); }
+
+
+
+// Length of a comma-separated expression list.
+func ExprLen(x Expr) int {
+	if x == nil {
+		return 0;
 	}
-	return "nil";
+	n := 1;
+	for {
+		if p, ok := x.(*BinaryExpr); ok && p.Tok == Scanner.COMMA {
+			n++;
+			x = p.Y;
+		} else {
+			break;
+		}
+	}
+	return n;
 }
 
 
-var BadType = NewType(0, Scanner.ILLEGAL);
+func ExprAt(x Expr, i int) Expr {
+	for j := 0; j < i; j++ {
+		assert(x.(*BinaryExpr).Tok == Scanner.COMMA);
+		x = x.(*BinaryExpr).Y;
+	}
+	if t, is_binary := x.(*BinaryExpr); is_binary && t.Tok == Scanner.COMMA {
+		x = t.X;
+	}
+	return x;
+}
+
+
+func (t *Type) Nfields() int {
+	if t.List == nil {
+		return 0;
+	}
+	nx, nt := 0, 0;
+	for i, n := 0, t.List.Len(); i < n; i++ {
+		if dummy, ok := t.List.At(i).(*TypeLit); ok {
+			nt++;
+		} else {
+			nx++;
+		}
+	}
+	if nx == 0 {
+		return nt;
+	}
+	return nx;
+}
 
 
 // ----------------------------------------------------------------------------
@@ -474,7 +521,7 @@ var BadType = NewType(0, Scanner.ILLEGAL);\n type Stat struct {\n 	Node;\n 	Init, Post *Stat;\n-\tExpr *Expr;\n+\tExpr Expr;\
 	Body *Block;  // composite statement body\n 	Decl *Decl;  // declaration statement\n }\n@@ -495,9 +542,10 @@ var BadStat = NewStat(0, Scanner.ILLEGAL);\n \n type Decl struct {\n 	Node;\n-\tIdent *Expr;  // nil for ()-style declarations\n+\tIdent Expr;  // nil for ()-style declarations\n \tTyp *Type;\n-\tVal *Expr;\n+\tVal Expr;\
+\tBody *Block;\
 	// list of *Decl for ()-style declarations\n 	List *array.Array; End int;\n }\n@@ -531,7 +579,7 @@ func NewComment(pos int, text string) *Comment {\n \n type Program struct {\n 	Pos int;  // tok is Scanner.PACKAGE\n-\tIdent *Expr;\n+\tIdent Expr;\
 	Decls *array.Array;\n 	Comments *array.Array;\n }\

usr/gri/pretty/parser.go

--- a/usr/gri/pretty/parser.go
+++ b/usr/gri/pretty/parser.go
@@ -64,26 +64,22 @@ func assert(pred bool) {
 
 func (P *Parser) PrintIndent() {
 	for i := P.indent; i > 0; i-- {
-\t\tprint(". ");
+\t\tfmt.Printf(". ");
 	}
 }
 
 
 func (P *Parser) Trace(msg string) {
-\tif P.verbose {\n-\t\tP.PrintIndent();\n-\t\tprint(msg, " {\n");
-\t}\n-\tP.indent++;  // always check proper identation
+\tP.PrintIndent();
+\tfmt.Printf("%s {\n", msg);
+\tP.indent++;
 }
 
 
 func (P *Parser) Ecart() {
-\tP.indent--;  // always check proper identation
-\tif P.verbose {\n-\t\tP.PrintIndent();\n-\t\tprint("}\n");
-\t}\
+\tP.indent--;
+\tP.PrintIndent();
+\tfmt.Printf("}\n");
 }
 
 
@@ -91,13 +87,12 @@ func (P *Parser) Next0() {
 	case "(": s = "LPAREN";
 	case ")": s = "RPAREN";
 	case "{": s = "LBRACE";
 	case "}": s = "RBRACE";
 	}
-\t\tprint("[", P.pos, "] ", s, "\n");
+\t\tfmt.Printf("[%d] %s\n", P.pos, s);
 	}
 }
 
@@ -171,13 +167,12 @@ func (P *Parser) CloseScope() {
 }
 
 
-func (P *Parser) DeclareInScope(scope *AST.Scope, x *AST.Expr, kind int, typ *AST.Type) {
+func (P *Parser) DeclareInScope(scope *AST.Scope, x AST.Expr, kind int, typ *AST.Type) {
 	if P.scope_lev < 0 {
 		panic("cannot declare objects in other packages");
 	}
-\tif x.Tok != Scanner.ILLEGAL {  // ignore bad exprs
-\t\tassert(x.Tok == Scanner.IDENT);\
-\t\tobj := x.Obj;
+\tif ident, ok := x.(*AST.Ident); ok {  // ignore bad exprs
+\t\tobj := ident.Obj;
 		obj.Kind = kind;
 		obj.Typ = typ;
 		obj.Pnolev = P.scope_lev;
@@ -196,47 +191,54 @@ func (P *Parser) DeclareInScope(scope *AST.Scope, x *AST.Expr, kind int, typ *AS
 
 
 // Declare a comma-separated list of idents or a single ident.
-func (P *Parser) Declare(p *AST.Expr, kind int, typ *AST.Type) {
-\tfor p.Tok == Scanner.COMMA {
-\t\tP.DeclareInScope(P.top_scope, p.X, kind, typ);
-\t\tp = p.Y;
+func (P *Parser) Declare(x AST.Expr, kind int, typ *AST.Type) {
+\tfor {
+\t\tp, ok := x.(*AST.BinaryExpr);
+\t\tif ok && p.Tok == Scanner.COMMA {
+\t\t\tP.DeclareInScope(P.top_scope, p.X, kind, typ);
+\t\t\tx = p.Y;
+\t\t} else {
+\t\t\tbreak;
+\t\t}
 	}
-\tP.DeclareInScope(P.top_scope, p, kind, typ);
+\tP.DeclareInScope(P.top_scope, x, kind, typ);
 }
 
 
 // ----------------------------------------------------------------------------
 // AST support
 
-func exprType(x *AST.Expr) *AST.Type {
-\tvar t *AST.Type;
-\tif x.Tok == Scanner.TYPE {
-\t\tt = x.Typ;
-\t} else if x.Tok == Scanner.IDENT {
+func exprType(x AST.Expr) *AST.Type {
+\tvar typ *AST.Type;
+\tif t, is_type := x.(*AST.TypeLit); is_type {
+\t\ttyp = t.Typ
+\t} else if t, is_ident := x.(*AST.Ident); is_ident {
 		// assume a type name
-\t\tt = AST.NewType(x.Pos, AST.TYPENAME);
-\t\tt.Expr = x;
-\t} else if x.Tok == Scanner.PERIOD && x.Y != nil && exprType(x.X) != nil {
+\t\ttyp = AST.NewType(t.Pos(), AST.TYPENAME);
+\t\ttyp.Expr = x;
+\t} else if t, is_selector := x.(*AST.Selector); is_selector && exprType(t.Sel) != nil {
 		// possibly a qualified (type) identifier
-\t\tt = AST.NewType(x.Pos, AST.TYPENAME);
-\t\tt.Expr = x;
+\t\ttyp = AST.NewType(t.Pos(), AST.TYPENAME);
+\t\ttyp.Expr = x;
 	}
-\treturn t;
+\treturn typ;
 }
 
 
-func (P *Parser) NoType(x *AST.Expr) *AST.Expr {
-\tif x != nil && x.Tok == Scanner.TYPE {
-\t\tP.Error(x.Pos, "expected expression, found type");
-\t\tval := AST.NewObject(x.Pos, AST.NONE, "0");
-\t\tx = AST.NewLit(Scanner.INT, val);
+\tfunc (P *Parser) NoType(x AST.Expr) AST.Expr {
+\tif x != nil {
+\t\tlit, ok := x.(*AST.TypeLit);
+\t\tif ok {
+\t\t\tP.Error(lit.Typ.Pos, "expected expression, found type");
+\t\t\tx = &AST.BasicLit{lit.Typ.Pos, Scanner.STRING, ""};
+\t\t}
 	}
 	return x;
 }
 
 
-func (P *Parser) NewExpr(pos, tok int, x, y *AST.Expr) *AST.Expr {
-\treturn AST.NewExpr(pos, tok, P.NoType(x), P.NoType(y));
+func (P *Parser) NewBinary(pos, tok int, x, y AST.Expr) *AST.BinaryExpr {
+\treturn &AST.BinaryExpr{pos, tok, P.NoType(x), P.NoType(y)};
 }
 
 
@@ -244,16 +246,18 @@ func (P *Parser) NewExpr(pos, tok int, x, y *AST.Expr) *AST.Expr {
 // Common productions
 
 func (P *Parser) TryType() *AST.Type;
-func (P *Parser) ParseExpression(prec int) *AST.Expr;
+func (P *Parser) ParseExpression(prec int) AST.Expr;
 func (P *Parser) ParseStatement() *AST.Stat;
 func (P *Parser) ParseDeclaration() *AST.Decl;
 
 
 // If scope != nil, lookup identifier in scope. Otherwise create one.
-func (P *Parser) ParseIdent(scope *AST.Scope) *AST.Expr {
-\tP.Trace("Ident");
+func (P *Parser) ParseIdent(scope *AST.Scope) *AST.Ident {
+\tif P.verbose {
+\t\tP.Trace("Ident");
+\t\tdefer P.Ecart();
+\t}
 
-\tx := AST.BadExpr;
 	if P.tok == Scanner.IDENT {
 		var obj *AST.Object;
 		if scope != nil {
@@ -264,41 +268,41 @@ func (P *Parser) ParseIdent(scope *AST.Scope) *AST.Expr {
 		} else {
 			assert(obj.Kind != AST.NONE);
 		}
-\t\tx = AST.NewLit(Scanner.IDENT, obj);
-\t\tx.Pos = P.pos;  // override obj.pos (incorrect if object was looked up!)
+\t\tx := &AST.Ident{P.pos, obj};
 		if P.verbose {
 			P.PrintIndent();
-\t\t\tprint("Ident = \"", P.val, "\"\n");
+\t\t\tfmt.Printf("ident = \"%s\"\n", P.val);
 		}
 		P.Next();
-\t} else {
-\t\tP.Expect(Scanner.IDENT);  // use Expect() error handling
+\t\treturn x;
 	}
-\n-\tP.Ecart();
-\treturn x;
+\t
+\tP.Expect(Scanner.IDENT);  // use Expect() error handling
+\treturn &AST.Ident{P.pos, nil};
 }
 
 
-func (P *Parser) ParseIdentList() *AST.Expr {
-\tP.Trace("IdentList");
+func (P *Parser) ParseIdentList() AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("IdentList");
+\t\tdefer P.Ecart();
+\t}
 
-\tvar last *AST.Expr;
-\tx := P.ParseIdent(nil);
+\tvar last *AST.BinaryExpr;
+\tvar x AST.Expr = P.ParseIdent(nil);
 	for P.tok == Scanner.COMMA {
 		pos := P.pos;
 		P.Next();
 		y := P.ParseIdent(nil);
 		if last == nil {
-\t\t\tx = P.NewExpr(pos, Scanner.COMMA, x, y);
-\t\t\tlast = x;
+\t\t\tlast = P.NewBinary(pos, Scanner.COMMA, x, y);
+\t\t\tx = last;
 		} else {
-\t\t\tlast.Y = P.NewExpr(pos, Scanner.COMMA, last.Y, y);
+\t\t\tlast.Y = P.NewBinary(pos, Scanner.COMMA, last.Y, y);
 			last = last.Y;
 		}
 	}
 
-\tP.Ecart();
 	return x;
 }
 
@@ -307,7 +311,10 @@ func (P *Parser) ParseIdentList() *AST.Expr {
 // Types
 
 func (P *Parser) ParseType() *AST.Type {
-\tP.Trace("Type");
+\tif P.verbose {
+\t\tP.Trace("Type");
+\t\tdefer P.Ecart();
+\t}
 
 	t := P.TryType();
 	if t == nil {
@@ -315,56 +322,61 @@ func (P *Parser) ParseType() *AST.Type {
 		t = AST.BadType;
 	}
 
-\tP.Ecart();
 	return t;
 }
 
 
 func (P *Parser) ParseVarType() *AST.Type {
-\tP.Trace("VarType");
-\n-\ttyp := P.ParseType();
+\tif P.verbose {
+\t\tP.Trace("VarType");
+\t\tdefer P.Ecart();
+\t}
 
-\tP.Ecart();
-\treturn typ;
+\treturn P.ParseType();
 }
 
 
-func (P *Parser) ParseQualifiedIdent() *AST.Expr {
-\tP.Trace("QualifiedIdent");
+\tfunc (P *Parser) ParseQualifiedIdent() AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("QualifiedIdent");
+\t\tdefer P.Ecart();
+\t}
 
-\tx := P.ParseIdent(P.top_scope);
+\tvar x AST.Expr = P.ParseIdent(P.top_scope);
 	for P.tok == Scanner.PERIOD {
 		pos := P.pos;
 		P.Next();
 		y := P.ParseIdent(nil);
-\t\tx = P.NewExpr(pos, Scanner.PERIOD, x, y);
+\t\tx = &AST.Selector{pos, x, y};
 	}
 
-\tP.Ecart();
 	return x;
 }
 
 
 func (P *Parser) ParseTypeName() *AST.Type {
-\tP.Trace("TypeName");
+\tif P.verbose {
+\t\tP.Trace("TypeName");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.NewType(P.pos, AST.TYPENAME);
 	t.Expr = P.ParseQualifiedIdent();
-\tt.Elt = t.Expr.Typ;
 
-\tP.Ecart();
 	return t;
 }
 
 
 func (P *Parser) ParseArrayType() *AST.Type {
-\tP.Trace("ArrayType");
+\tif P.verbose {
+\t\tP.Trace("ArrayType");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.NewType(P.pos, AST.ARRAY);
 	P.Expect(Scanner.LBRACK);
 	if P.tok == Scanner.ELLIPSIS {
-\t\tt.Expr = P.NewExpr(P.pos, Scanner.ELLIPSIS, nil, nil);
+\t\tt.Expr = P.NewBinary(P.pos, Scanner.ELLIPSIS, nil, nil);
 		P.Next();
 	} else if P.tok != Scanner.RBRACK {
 		t.Expr = P.ParseExpression(1);
@@ -372,13 +384,15 @@ func (P *Parser) ParseArrayType() *AST.Type {
 	P.Expect(Scanner.RBRACK);
 	t.Elt = P.ParseType();
 
-\tP.Ecart();
 	return t;
 }
 
 
 func (P *Parser) ParseChannelType() *AST.Type {
-\tP.Trace("ChannelType");
+\tif P.verbose {
+\t\tP.Trace("ChannelType");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.NewType(P.pos, AST.CHANNEL);
 	t.Mode = AST.FULL;
@@ -395,7 +409,6 @@ func (P *Parser) ParseChannelType() *AST.Type {
 	}
 	t.Elt = P.ParseVarType();
 
-\tP.Ecart();
 	return t;
 }
 
@@ -404,7 +417,7 @@ func (P *Parser) ParseVar(expect_ident bool) *AST.Type {
 	t := AST.BadType;
 	if expect_ident {
 		x := P.ParseIdent(nil);
-\t\tt = AST.NewType(x.Pos, AST.TYPENAME);
+\t\tt = AST.NewType(x.Pos(), AST.TYPENAME);
 		t.Expr = x;
 	} else if P.tok == Scanner.ELLIPSIS {
 		t = AST.NewType(P.pos, AST.ELLIPSIS);
@@ -417,7 +430,10 @@ func (P *Parser) ParseVar(expect_ident bool) *AST.Type {
 
 
 func (P *Parser) ParseVarList(list *array.Array, ellipsis_ok bool) {
-\tP.Trace("VarList");
+\tif P.verbose {
+\t\tP.Trace("VarList");
+\t\tdefer P.Ecart();
+\t}
 
 	// assume a list of types
 	// (a list of identifiers looks like a list of type names)
@@ -450,31 +466,34 @@ func (P *Parser) ParseVarList(list *array.Array, ellipsis_ok bool) {
 		// convert the type entries into identifiers
 		for i, n := i0, list.Len(); i < n; i++ {
 			t := list.At(i).(*AST.Type);
-\t\t\tif t.Form == AST.TYPENAME && t.Expr.Tok == Scanner.IDENT {
-\t\t\t\tlist.Set(i, t.Expr);
-\t\t\t} else {
-\t\t\t\tlist.Set(i, AST.BadExpr);
-\t\t\t\tP.Error(t.Pos, "identifier expected");
+\t\t\tif t.Form == AST.TYPENAME {
+\t\t\t\tif ident, ok := t.Expr.(*AST.Ident); ok {
+\t\t\t\t\tlist.Set(i, ident);
+\t\t\t\t\tcontinue;
+\t\t\t\t}
 			}
+\t\t\tlist.Set(i, &AST.BadExpr{0});
+\t\t\tP.Error(t.Pos, "identifier expected");
 		}
 		// add type
-\t\tlist.Push(AST.NewTypeExpr(typ));
+\t\tlist.Push(&AST.TypeLit{typ});
 
 	} else {
 		// all list entries are types
 		// convert all type entries into type expressions
 		for i, n := i0, list.Len(); i < n; i++ {
 			t := list.At(i).(*AST.Type);
-\t\t\tlist.Set(i, AST.NewTypeExpr(t));
+\t\t\tlist.Set(i, &AST.TypeLit{t});
 		}
 	}
-\n-\tP.Ecart();
 }
 
 
 func (P *Parser) ParseParameterList(ellipsis_ok bool) *array.Array {
-\tP.Trace("ParameterList");
+\tif P.verbose {
+\t\tP.Trace("ParameterList");
+\t\tdefer P.Ecart();
+\t}
 
 	list := array.New(0);
 	P.ParseVarList(list, ellipsis_ok);
@@ -483,13 +502,15 @@ func (P *Parser) ParseParameterList(ellipsis_ok bool) *array.Array {
 		P.ParseVarList(list, ellipsis_ok);
 	}
 
-\tP.Ecart();
 	return list;
 }
 
 
 func (P *Parser) ParseParameters(ellipsis_ok bool) *AST.Type {
-\tP.Trace("Parameters");
+\tif P.verbose {
+\t\tP.Trace("Parameters");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.NewType(P.pos, AST.STRUCT);
 	P.Expect(Scanner.LPAREN);
@@ -499,13 +520,15 @@ func (P *Parser) ParseParameters(ellipsis_ok bool) *AST.Type {
 	t.End = P.pos;
 	P.Expect(Scanner.RPAREN);
 
-\tP.Ecart();
 	return t;
 }
 
 
 func (P *Parser) ParseResultList() {
-\tP.Trace("ResultList");
+\tif P.verbose {
+\t\tP.Trace("ResultList");
+\t\tdefer P.Ecart();
+\t}
 
 	P.ParseType();
 	for P.tok == Scanner.COMMA {
@@ -515,13 +538,14 @@ func (P *Parser) ParseResultList() {
 	if P.tok != Scanner.RPAREN {
 		P.ParseType();
 	}
-\n-\tP.Ecart();
 }
 
 
 func (P *Parser) ParseResult(ftyp *AST.Type) *AST.Type {
-\tP.Trace("Result");
+\tif P.verbose {
+\t\tP.Trace("Result");
+\t\tdefer P.Ecart();
+\t}
 
 	var t *AST.Type;
 	if P.tok == Scanner.LPAREN {
@@ -531,12 +555,11 @@ func (P *Parser) ParseResult(ftyp *AST.Type) *AST.Type {
 		if typ != nil {
 			t = AST.NewType(P.pos, AST.STRUCT);
 			t.List = array.New(0);
-\t\t\tt.List.Push(AST.NewTypeExpr(typ));
+\t\t\tt.List.Push(&AST.TypeLit{typ});
 			t.End = P.pos;
 		}
 	}
 
-\tP.Ecart();
 	return t;
 }
 
@@ -548,7 +571,10 @@ func (P *Parser) ParseResult(ftyp *AST.Type) *AST.Type {
 // (params) (results)
 
 func (P *Parser) ParseSignature() *AST.Type {
-\tP.Trace("Signature");
+\tif P.verbose {
+\t\tP.Trace("Signature");
+\t\tdefer P.Ecart();
+\t}
 
 	P.OpenScope();
 	P.scope_lev++;
@@ -562,35 +588,38 @@ func (P *Parser) ParseSignature() *AST.Type {
 	P.scope_lev--;
 	P.CloseScope();
 
-\tP.Ecart();
 	return t;
 }
 
 
 func (P *Parser) ParseFunctionType() *AST.Type {
-\tP.Trace("FunctionType");
+\tif P.verbose {
+\t\tP.Trace("FunctionType");
+\t\tdefer P.Ecart();
+\t}
 
 	P.Expect(Scanner.FUNC);
-\tt := P.ParseSignature();
-\n-\tP.Ecart();
-\treturn t;
+\treturn P.ParseSignature();
 }
 
 
 func (P *Parser) ParseMethodSpec(list *array.Array) {
-\tP.Trace("MethodDecl");
+\tif P.verbose {
+\t\tP.Trace("MethodDecl");
+\t\tdefer P.Ecart();
+\t}
 
 	list.Push(P.ParseIdentList());
 	t := P.ParseSignature();
-\tlist.Push(AST.NewTypeExpr(t));
-\n-\tP.Ecart();
+\tlist.Push(&AST.TypeLit{t});
 }
 
 
 func (P *Parser) ParseInterfaceType() *AST.Type {
-\tP.Trace("InterfaceType");
+\tif P.verbose {
+\t\tP.Trace("InterfaceType");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.NewType(P.pos, AST.INTERFACE);
 	P.Expect(Scanner.INTERFACE);
@@ -613,13 +642,15 @@ func (P *Parser) ParseInterfaceType() *AST.Type {
 		P.Expect(Scanner.RBRACE);
 	}
 
-\tP.Ecart();
 	return t;
 }
 
 
 func (P *Parser) ParseMapType() *AST.Type {
-\tP.Trace("MapType");
+\tif P.verbose {
+\t\tP.Trace("MapType");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.NewType(P.pos, AST.MAP);
 	P.Expect(Scanner.MAP);
@@ -628,15 +659,17 @@ func (P *Parser) ParseMapType() *AST.Type {
 	P.Expect(Scanner.RBRACK);
 	t.Elt = P.ParseVarType();
 
-\tP.Ecart();
 	return t;
 }
 
 
-func (P *Parser) ParseOperand() *AST.Expr
+func (P *Parser) ParseOperand() AST.Expr
 
 func (P *Parser) ParseStructType() *AST.Type {
-\tP.Trace("StructType");
+\tif P.verbose {
+\t\tP.Trace("StructType");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.NewType(P.pos, AST.STRUCT);
 	P.Expect(Scanner.STRUCT);
@@ -664,32 +697,35 @@ func (P *Parser) ParseStructType() *AST.Type {
 		
 		// enter fields into struct scope
 		for i, n := 0, t.List.Len(); i < n; i++ {
-\t\t\tx := t.List.At(i).(*AST.Expr);
-\t\t\tif x.Tok == Scanner.IDENT {
+\t\t\tif x, ok := t.List.At(i).(*AST.Ident); ok {
 				P.DeclareInScope(t.Scope, x, AST.FIELD, nil);
 			}
 		}
 	}
 
-\tP.Ecart();
 	return t;
 }
 
 
 func (P *Parser) ParsePointerType() *AST.Type {
-\tP.Trace("PointerType");
+\tif P.verbose {
+\t\tP.Trace("PointerType");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.NewType(P.pos, AST.POINTER);
 	P.Expect(Scanner.MUL);
 	t.Elt = P.ParseType();
 
-\tP.Ecart();
 	return t;
 }
 
 
 func (P *Parser) TryType() *AST.Type {
-\tP.Trace("Type (try)");
+\tif P.verbose {
+\t\tP.Trace("Type (try)");
+\t\tdefer P.Ecart();
+\t}
 
 	t := AST.BadType;
 	switch P.tok {
@@ -703,8 +739,6 @@ func (P *Parser) TryType() *AST.Type {
 	case Scanner.MUL: t = P.ParsePointerType();
 	default: t = nil;  // no type found
 	}
-\n-\tP.Ecart();
 	return t;
 }
 
@@ -713,7 +747,10 @@ func (P *Parser) TryType() *AST.Type {
 // Blocks
 
 func (P *Parser) ParseStatementList(list *array.Array) {
-\tP.Trace("StatementList");
+\tif P.verbose {
+\t\tP.Trace("StatementList");
+\t\tdefer P.Ecart();
+\t}
 
 	for P.tok != Scanner.CASE && P.tok != Scanner.DEFAULT && P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {
 		s := P.ParseStatement();
@@ -734,13 +771,14 @@ func (P *Parser) ParseStatementList(list *array.Array) {
 	if P.tok != Scanner.CASE && P.tok != Scanner.DEFAULT && P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {
 		P.Error(P.pos, "expected end of statement list (semicolon missing?)");
 	}
-\n-\tP.Ecart();
 }
 
 
 func (P *Parser) ParseBlock(ftyp *AST.Type, tok int) *AST.Block {
-\tP.Trace("Block");
+\tif P.verbose {
+\t\tP.Trace("Block");
+\t\tdefer P.Ecart();
+\t}
 
 	b := AST.NewBlock(P.pos, tok);
 	P.Expect(tok);
@@ -753,8 +791,7 @@ func (P *Parser) ParseBlock(ftyp *AST.Type, tok int) *AST.Block {
 		}
 		if ftyp.List != nil {
 			for i, n := 0, ftyp.List.Len(); i < n; i++ {
-\t\t\t\tx := ftyp.List.At(i).(*AST.Expr);
-\t\t\t\tif x.Tok == Scanner.IDENT {
+\t\t\t\tif x, ok := ftyp.List.At(i).(*AST.Ident); ok {
 					P.DeclareInScope(P.top_scope, x, AST.VAR, nil);
 				}
 			}
@@ -770,7 +807,6 @@ func (P *Parser) ParseBlock(ftyp *AST.Type, tok int) *AST.Block {
 		P.opt_semi = true;
 	}
 
-\tP.Ecart();
 	return b;
 }
 
@@ -778,111 +814,112 @@ func (P *Parser) ParseBlock(ftyp *AST.Type, tok int) *AST.Block {
 // ----------------------------------------------------------------------------
 // Expressions
 
-func (P *Parser) ParseExpressionList() *AST.Expr {
-\tP.Trace("ExpressionList");
+\tfunc (P *Parser) ParseExpressionList() AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("ExpressionList");
+\t\tdefer P.Ecart();
+\t}
 
 \tx := P.ParseExpression(1);
 	for first := true; P.tok == Scanner.COMMA; {
 		pos := P.pos;
 		P.Next();
 		y := P.ParseExpression(1);
 		if first {
-\t\t\tx = P.NewExpr(pos, Scanner.COMMA, x, y);
+\t\t\tx = P.NewBinary(pos, Scanner.COMMA, x, y);
 			first = false;
 		} else {
-\t\t\tx.Y = P.NewExpr(pos, Scanner.COMMA, x.Y, y);
+\t\t\tx.(*AST.BinaryExpr).Y = P.NewBinary(pos, Scanner.COMMA, x.(*AST.BinaryExpr).Y, y);
 		}
 	}
 
-\tP.Ecart();
 	return x;
 }
 
 
-func (P *Parser) ParseFunctionLit() *AST.Expr {
-\tP.Trace("FunctionLit");
+\tfunc (P *Parser) ParseFunctionLit() AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("FunctionLit");
+\t\tdefer P.Ecart();
+\t}
 
-\tf := AST.NewObject(P.pos, AST.FUNC, "");
+\tpos := P.pos;
 	P.Expect(Scanner.FUNC);
-\tf.Typ = P.ParseSignature();
+\ttyp := P.ParseSignature();
 	P.expr_lev++;
 	P.scope_lev++;
-\tf.Body = P.ParseBlock(f.Typ, Scanner.LBRACE);
+\tbody := P.ParseBlock(typ, Scanner.LBRACE);
 	P.scope_lev--;
 	P.expr_lev--;
 
-\tP.Ecart();
-\treturn AST.NewLit(Scanner.FUNC, f);\
+\treturn &AST.FunctionLit{pos, typ, body};
 }
 
 
-func (P *Parser) ParseOperand() *AST.Expr {
-\tP.Trace("Operand");
+\tfunc (P *Parser) ParseOperand() AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("Operand");
+\t\tdefer P.Ecart();
+\t}
 
-\tx := AST.BadExpr;
 	switch P.tok {
 	case Scanner.IDENT:
-\t\tx = P.ParseIdent(P.top_scope);
+\t\treturn P.ParseIdent(P.top_scope);
 
 	case Scanner.LPAREN:
 		// TODO we could have a function type here as in: new(())
 		// (currently not working)
 		P.Next();
 		P.expr_lev++;
-\t\tx = P.ParseExpression(1);
+\t\tx := P.ParseExpression(1);
 		P.expr_lev--;
 		P.Expect(Scanner.RPAREN);
+\t\treturn x;
 
 	case Scanner.INT, Scanner.FLOAT, Scanner.STRING:
-\t\tval := AST.NewObject(P.pos, AST.NONE, P.val);
-\t\tx = AST.NewLit(P.tok, val);
+\t\tx := &AST.BasicLit{P.pos, P.tok, P.val};
 		P.Next();
 		if x.Tok == Scanner.STRING {
 			// TODO should remember the list instead of
 			//      concatenate the strings here
 			for ; P.tok == Scanner.STRING; P.Next() {
-\t\t\t\tx.Obj.Ident += P.val;
+\t\t\t\tx.Val += P.val;
 			}
 		}
+\t\treturn x;
 
 	case Scanner.FUNC:
-\t\tx = P.ParseFunctionLit();
+\t\treturn P.ParseFunctionLit();
 
 	default:
 		t := P.TryType();
 		if t != nil {
-\t\t\tx = AST.NewTypeExpr(t);
+\t\t\treturn &AST.TypeLit{t};
 		} else {
 			P.Error(P.pos, "operand expected");
 			P.Next();  // make progress
 		}
 	}
 
-\tP.Ecart();
-\treturn x;
+\treturn &AST.BadExpr{P.pos};
 }
 
 
-func (P *Parser) ParseSelectorOrTypeGuard(x *AST.Expr) *AST.Expr {
-\tP.Trace("SelectorOrTypeGuard");
+\tfunc (P *Parser) ParseSelectorOrTypeGuard(x AST.Expr) AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("SelectorOrTypeGuard");
+\t\tdefer P.Ecart();
+\t}
 
-\tx = P.NewExpr(P.pos, Scanner.PERIOD, x, nil);
+\tpos := P.pos;
 	P.Expect(Scanner.PERIOD);
 
 	if P.tok == Scanner.IDENT {
-\t\t// TODO should always guarantee x.Typ != nil
-\t\tvar scope *AST.Scope;\
-\t\tif x.X.Typ != nil {\
-\t\t\tscope = x.X.Typ.Scope;\
-\t\t}\
-\t\tx.Y = P.ParseIdent(scope);\
-\t\tx.Typ = x.Y.Obj.Typ;\
+\t\tx = &AST.Selector{pos, x, P.ParseIdent(nil)};
 
 	} else {
 		P.Expect(Scanner.LPAREN);
-\t\tx.Y = AST.NewTypeExpr(P.ParseType());
-\t\tx.Typ = x.Y.Typ;\
+\t\tx = &AST.TypeGuard{pos, x, P.ParseType()};
 		P.Expect(Scanner.RPAREN);
 	}
 
-\tP.Ecart();
 	return x;
 }
 
 
-func (P *Parser) ParseIndex(x *AST.Expr) *AST.Expr {
-\tP.Trace("IndexOrSlice");
+\tfunc (P *Parser) ParseIndex(x AST.Expr) AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("IndexOrSlice");
+\t\tdefer P.Ecart();
+\t}
 
 	pos := P.pos;
 	P.Expect(Scanner.LBRACK);
@@ -900,51 +940,51 @@ func (P *Parser) ParseIndex(x *AST.Expr) *AST.Expr {
 	P.expr_lev--;
 	P.Expect(Scanner.RBRACK);
 
-\tP.Ecart();
-\treturn P.NewExpr(pos, Scanner.LBRACK, x, i);\
+\treturn &AST.Index{pos, x, i};
 }
 
 
-func (P *Parser) ParseBinaryExpr(prec1 int) *AST.Expr
+func (P *Parser) ParseBinaryExpr(prec1 int) AST.Expr
 
-func (P *Parser) ParseCall(x0 *AST.Expr) *AST.Expr {
-\tP.Trace("Call");
+\tfunc (P *Parser) ParseCall(f AST.Expr) AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("Call");
+\t\tdefer P.Ecart();
+\t}
 
-\tx := P.NewExpr(P.pos, Scanner.LPAREN, x0, nil);
+\tcall := &AST.Call{P.pos, f, nil};
 	P.Expect(Scanner.LPAREN);
 	if P.tok != Scanner.RPAREN {
 		P.expr_lev++;
 		var t *AST.Type;
-\t\tif x0.Tok == Scanner.IDENT && (x0.Obj.Ident == "new" || x0.Obj.Ident == "make") {
+\t\tif 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
-\t\t\tx.Y = AST.NewTypeExpr(t);
+\t\t\targs := &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
-\t\t\t\tz := AST.NewExpr(pos, Scanner.COMMA, nil, y);
-\t\t\t\tz.X = x.Y;
-\t\t\t\tx.Y = z;
+\t\t\t\targs := &AST.BinaryExpr{pos, Scanner.COMMA, args, y};
 			}
+\t\t\tcall.Args = args;
 		} else {
 			// normal argument list
-\t\t\tx.Y = P.ParseExpressionList();
+\t\t\tcall.Args = P.ParseExpressionList();
 		}
 		P.expr_lev--;
 	}
 	P.Expect(Scanner.RPAREN);
 
-\tP.Ecart();
-\treturn x;
+\treturn call;
 }
 
 
-func (P *Parser) ParseCompositeElements() *AST.Expr {
+func (P *Parser) ParseCompositeElements() AST.Expr {
 \tx := P.ParseExpression(0);
 	if P.tok == Scanner.COMMA {
 		pos := P.pos;
@@ -952,29 +992,29 @@ func (P *Parser) ParseCompositeElements() *AST.Expr {
 
 		// first element determines mode
 		singles := true;
-\t\tif x.Tok == Scanner.COLON {
+\t\tif t, is_binary := x.(*AST.BinaryExpr); is_binary && t.Tok == Scanner.COLON {
 			singles = false;
 		}
 
-\t\tvar last *AST.Expr;
+\t\tvar last *AST.BinaryExpr;
 		for P.tok != Scanner.RBRACE && P.tok != Scanner.EOF {
 			y := P.ParseExpression(0);
 
 			if singles {
-\t\t\t\tif y.Tok == Scanner.COLON {
-\t\t\t\t\tP.Error(y.X.Pos, "single value expected; found pair");
+\t\t\t\tif t, is_binary := y.(*AST.BinaryExpr); is_binary && t.Tok == Scanner.COLON {
+\t\t\t\t\tP.Error(t.X.Pos(), "single value expected; found pair");
 				}
 			} else {
-\t\t\t\tif y.Tok != Scanner.COLON {
-\t\t\t\t\tP.Error(y.Pos, "key:value pair expected; found single value");
+\t\t\t\tif t, is_binary := y.(*AST.BinaryExpr); !is_binary || t.Tok != Scanner.COLON {
+\t\t\t\t\tP.Error(y.Pos(), "key:value pair expected; found single value");
 				}
 			}
 
 			if last == nil {
-\t\t\t\tx = P.NewExpr(pos, Scanner.COMMA, x, y);
-\t\t\t\tlast = x;
+\t\t\t\tlast = P.NewBinary(pos, Scanner.COMMA, x, y);
+\t\t\t\tx = last;
 			} else {
-\t\t\t\tlast.Y = P.NewExpr(pos, Scanner.COMMA, last.Y, y);
+\t\t\t\tlast.Y = P.NewBinary(pos, Scanner.COMMA, last.Y, y);
 				last = last.Y;
 			}
 
@@ -991,25 +1031,29 @@ func (P *Parser) ParseCompositeElements() *AST.Expr {
 }
 
 
-func (P *Parser) ParseCompositeLit(t *AST.Type) *AST.Expr {
-\tP.Trace("CompositeLit");
+\tfunc (P *Parser) ParseCompositeLit(t *AST.Type) AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("CompositeLit");
+\t\tdefer P.Ecart();
+\t}
 
-\tx := P.NewExpr(P.pos, Scanner.LBRACE, nil, nil);
-\tx.Obj = AST.NewObject(t.Pos, AST.TYPE, "");
-\tx.Obj.Typ = t;
+\tpos := P.pos;
 	P.Expect(Scanner.LBRACE);
+\tvar elts AST.Expr;
 	if P.tok != Scanner.RBRACE {
-\t\tx.Y = P.ParseCompositeElements();
+\t\telts = P.ParseCompositeElements();
 	}
 	P.Expect(Scanner.RBRACE);
 
-\tP.Ecart();
-\treturn x;
+\treturn &AST.CompositeLit{pos, t, elts};
 }
 
 
-func (P *Parser) ParsePrimaryExpr() *AST.Expr {
-\tP.Trace("PrimaryExpr");
+\tfunc (P *Parser) ParsePrimaryExpr() AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("PrimaryExpr");
+\t\tdefer P.Ecart();
+\t}
 
 \tx := P.ParseOperand();
 	for {
@@ -1028,47 +1072,48 @@ func (P *Parser) ParsePrimaryExpr() *AST.Expr {
 			if t != nil {
 				x = P.ParseCompositeLit(t);
 			} else {
-\t\t\t\tgoto exit;
+\t\t\t\treturn x;
 			}
-\t\tdefault: goto exit;
+\t\tdefault:
+\t\t\treturn x;
 		}
 	}
 
-exit:
-\tP.Ecart();
-\treturn x;
+\tunreachable();
+\treturn nil;
 }
 
 
-func (P *Parser) ParseUnaryExpr() *AST.Expr {
-\tP.Trace("UnaryExpr");
+\tfunc (P *Parser) ParseUnaryExpr() AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("UnaryExpr");
+\t\tdefer P.Ecart();
+\t}
 
-\tx := AST.BadExpr;
 	switch P.tok {
 	case Scanner.ADD, Scanner.SUB, Scanner.MUL, Scanner.NOT, Scanner.XOR, Scanner.ARROW, Scanner.AND:
 		pos, tok := P.pos, P.tok;
 		P.Next();
 		y := P.ParseUnaryExpr();
-\t\tif tok == Scanner.MUL && y.Tok == Scanner.TYPE {
+\t\tif lit, ok := y.(*AST.TypeLit); ok && tok == Scanner.MUL {
 			// pointer type
 			t := AST.NewType(pos, AST.POINTER);
-\t\t\tt.Elt = y.Obj.Typ;
-\t\t\tx = AST.NewTypeExpr(t);
+\t\t\tt.Elt = lit.Typ;
+\t\t\treturn &AST.TypeLit{t};
 		} else {
-\t\t\tx = P.NewExpr(pos, tok, nil, y);
+\t\t\treturn &AST.UnaryExpr{pos, tok, y};
 		}
-\n-\tdefault:\
-\t\tx = P.ParsePrimaryExpr();
 	}
 
-\tP.Ecart();
-\treturn x;
+\treturn P.ParsePrimaryExpr();
 }
 
 
-func (P *Parser) ParseBinaryExpr(prec1 int) *AST.Expr {
-\tP.Trace("BinaryExpr");
+\tfunc (P *Parser) ParseBinaryExpr(prec1 int) AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("BinaryExpr");
+\t\tdefer P.Ecart();
+\t}
 
 \tx := P.ParseUnaryExpr();
 	for prec := Scanner.Precedence(P.tok); prec >= prec1; prec-- {
@@ -1076,28 +1121,29 @@ func (P *Parser) ParseBinaryExpr(prec1 int) *AST.Expr {
 			pos, tok := P.pos, P.tok;
 			P.Next();
 			y := P.ParseBinaryExpr(prec + 1);
-\t\t\tx = P.NewExpr(pos, tok, x, y);
+\t\t\tx = P.NewBinary(pos, tok, x, y);
 		}
 	}
 
-\tP.Ecart();
 	return x;
 }
 
 
-func (P *Parser) ParseExpression(prec int) *AST.Expr {
-\tP.Trace("Expression");
-\tindent := P.indent;\
+\tfunc (P *Parser) ParseExpression(prec int) AST.Expr {
+\tif P.verbose {
+\t\tP.Trace("Expression");
+\t\tdefer P.Ecart();
+\t}
 
+\tindent := P.indent;
 	if prec < 0 {
 		panic("precedence must be >= 0");
 	}
 \tx := P.NoType(P.ParseBinaryExpr(prec));
-\n \tif indent != P.indent {\
+\tif indent != P.indent {
 		panic("imbalanced tracing code (Expression)");
 	}
-\tP.Ecart();
+\n \treturn x;
 }
 
 @@ -1106,35 +1152,25 @@ func (P *Parser) ParseExpression(prec int) *AST.Expr {
 // Statements
 
 func (P *Parser) ParseSimpleStat(range_ok bool) *AST.Stat {
-\tP.Trace("SimpleStat");
+\tif P.verbose {
+\t\tP.Trace("SimpleStat");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := AST.BadStat;
 \tx := P.ParseExpressionList();
 
-\tis_range := false;\
-\tif range_ok && P.tok == Scanner.COLON {\
-\t\tpos := P.pos;\
-\t\tP.Next();\
-\t\ty := P.ParseExpression(1);\
-\t\tif x.Len() == 1 {\
-\t\t\tx = P.NewExpr(pos, Scanner.COLON, x, y);\
-\t\t\tis_range = true;\
-\t\t} else {\
-\t\t\tP.Error(pos, "expected initialization, found ':'");
-\t\t}\
-\t}\
-\n \tswitch P.tok {\
 \tcase Scanner.COLON:\
 \t\t// label declaration
 \t\ts = AST.NewStat(P.pos, Scanner.COLON);\
 \t\ts.Expr = x;\
-\t\tif x.Len() != 1 {\
-\t\t\tP.Error(x.Pos, "illegal label declaration");
+\t\tif AST.ExprLen(x) != 1 {\
+\t\t\tP.Error(x.Pos(), "illegal label declaration");
 		}
 \t\tP.Next();  // consume ":"
 \t\tP.opt_semi = true;\
-\n+\t\t\
 \tcase\
 \t\tScanner.DEFINE, Scanner.ASSIGN, Scanner.ADD_ASSIGN,\
 \t\tScanner.SUB_ASSIGN, Scanner.MUL_ASSIGN, Scanner.QUO_ASSIGN,\
@@ -1143,34 +1179,31 @@ func (P *Parser) ParseSimpleStat(range_ok bool) *AST.Stat {
 \t\t// declaration/assignment
 \t\tpos, tok := P.pos, P.tok;\
 \t\tP.Next();\
-\t\ty := AST.BadExpr;\
+\t\tvar y AST.Expr = &AST.BadExpr{pos};
 \t\tif P.tok == Scanner.RANGE {\
 \t\t\trange_pos := P.pos;\
 \t\t\tP.Next();\
 \t\t\ty = P.ParseExpression(1);\
-\t\t\ty = P.NewExpr(range_pos, Scanner.RANGE, nil, y);\
+\t\t\ty = P.NewBinary(range_pos, Scanner.RANGE, nil, y);
 \t\t\tif tok != Scanner.DEFINE && tok != Scanner.ASSIGN {\
 \t\t\t\tP.Error(pos, "expected '=' or ':=', found '" + Scanner.TokenString(tok) + "'");
 \t\t\t}\
 \t\t} else {\
 \t\t\ty = P.ParseExpressionList();
-\t\t\tif is_range {\
-\t\t\t\tP.Error(y.Pos, "expected 'range', found expression");
-\t\t\t}\
-\t\t\tif xl, yl := x.Len(), y.Len(); xl > 1 && yl > 1 && xl != yl {\
-\t\t\t\tP.Error(x.Pos, "arity of lhs doesn't match rhs");
+\t\t\tif xl, yl := AST.ExprLen(x), AST.ExprLen(y); xl > 1 && yl > 1 && xl != yl {\
+\t\t\t\tP.Error(x.Pos(), "arity of lhs doesn't match rhs");
 			}
 		}
-\t\ts = AST.NewStat(x.Pos, Scanner.EXPRSTAT);\
-\t\ts.Expr = AST.NewExpr(pos, tok, x, y);\
-\n+\t\ts = AST.NewStat(x.Pos(), Scanner.EXPRSTAT);\
+\t\ts.Expr = P.NewBinary(pos, tok, x, y);
+\t\t\
 \tcase Scanner.RANGE:\
 \t\tpos := P.pos;\
 \t\tP.Next();\
 \t\ty := P.ParseExpression(1);\
-\t\ty = P.NewExpr(pos, Scanner.RANGE, nil, y);\
-\t\ts = AST.NewStat(x.Pos, Scanner.EXPRSTAT);\
-\t\ts.Expr = AST.NewExpr(pos, Scanner.DEFINE, x, y);\
+\t\ty = &AST.UnaryExpr{pos, Scanner.RANGE, y};
+\t\ts = AST.NewStat(x.Pos(), Scanner.EXPRSTAT);\
+\t\ts.Expr = P.NewBinary(pos, Scanner.DEFINE, x, y);
 
 \tdefault:\
 \t\tvar pos, tok int;\
@@ -1178,34 +1211,39 @@ func (P *Parser) ParseSimpleStat(range_ok bool) *AST.Stat {
 \t\t\tpos, tok = P.pos, P.tok;\
 \t\t\tP.Next();\
 \t\t} else {\
-\t\t\tpos, tok = x.Pos, Scanner.EXPRSTAT;\
+\t\t\tpos, tok = x.Pos(), Scanner.EXPRSTAT;
 		}
 \t\ts = AST.NewStat(pos, tok);\
 \t\ts.Expr = x;\
-\t\tif x.Len() != 1 {\
-\t\t\tP.Error(x.Pos, "only one expression allowed");
+\t\tif AST.ExprLen(x) != 1 {\
+\t\t\tP.Error(pos, "only one expression allowed");
+\t\t\tpanic();  // fix position
 		}
 	}\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseInvocationStat(keyword int) *AST.Stat {
-\tP.Trace("InvocationStat");
+\tif P.verbose {
+\t\tP.Trace("InvocationStat");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := AST.NewStat(P.pos, keyword);\
 \tP.Expect(keyword);\
 \ts.Expr = P.ParseExpression(1);\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseReturnStat() *AST.Stat {
-\tP.Trace("ReturnStat");
+\tif P.verbose {
+\t\tP.Trace("ReturnStat");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := AST.NewStat(P.pos, Scanner.RETURN);\
 \tP.Expect(Scanner.RETURN);\
@@ -1213,13 +1254,15 @@ func (P *Parser) ParseReturnStat() *AST.Stat {\
 \t\ts.Expr = P.ParseExpressionList();
 \t}\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseControlFlowStat(tok int) *AST.Stat {
-\tP.Trace("ControlFlowStat");
+\tif P.verbose {
+\t\tP.Trace("ControlFlowStat");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := AST.NewStat(P.pos, tok);\
 \tP.Expect(tok);\
@@ -1227,13 +1270,15 @@ func (P *Parser) ParseControlFlowStat(tok int) *AST.Stat {\
 \t\ts.Expr = P.ParseIdent(P.top_scope);\
 \t}\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseControlClause(keyword int) *AST.Stat {
-\tP.Trace("ControlClause");
+\tif P.verbose {
+\t\tP.Trace("ControlClause");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := AST.NewStat(P.pos, keyword);\
 \tP.Expect(keyword);\
@@ -1263,13 +1308,15 @@ func (P *Parser) ParseControlClause(keyword int) *AST.Stat {\
 \t\tP.expr_lev = prev_lev;\
 \t}\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseIfStat() *AST.Stat {
-\tP.Trace("IfStat");
+\tif P.verbose {
+\t\tP.Trace("IfStat");
+\t\tdefer P.Ecart();
+\t}
 
 \tP.OpenScope();
 \ts := P.ParseControlClause(Scanner.IF);\
@@ -1297,26 +1344,30 @@ func (P *Parser) ParseIfStat() *AST.Stat {\
 \t}\
 \tP.CloseScope();
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseForStat() *AST.Stat {
-\tP.Trace("ForStat");
+\tif P.verbose {
+\t\tP.Trace("ForStat");
+\t\tdefer P.Ecart();
+\t}
 
 \tP.OpenScope();
 \ts := P.ParseControlClause(Scanner.FOR);\
 \ts.Body = P.ParseBlock(nil, Scanner.LBRACE);\
 \tP.CloseScope();
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseSwitchCase() *AST.Stat {
-\tP.Trace("SwitchCase");
+\tif P.verbose {
+\t\tP.Trace("SwitchCase");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := AST.NewStat(P.pos, P.tok);\
 \tif P.tok == Scanner.CASE {\
@@ -1326,24 +1377,28 @@ func (P *Parser) ParseSwitchCase() *AST.Stat {\
 \t\tP.Expect(Scanner.DEFAULT);\
 \t}\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseCaseClause() *AST.Stat {
-\tP.Trace("CaseClause");
+\tif P.verbose {
+\t\tP.Trace("CaseClause");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := P.ParseSwitchCase();
 \ts.Body = P.ParseBlock(nil, Scanner.COLON);\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseSwitchStat() *AST.Stat {
-\tP.Trace("SwitchStat");
+\tif P.verbose {
+\t\tP.Trace("SwitchStat");
+\t\tdefer P.Ecart();
+\t}
 
 \tP.OpenScope();
 \ts := P.ParseControlClause(Scanner.SWITCH);\
@@ -1358,13 +1413,15 @@ func (P *Parser) ParseSwitchStat() *AST.Stat {\
 \tP.CloseScope();
 \ts.Body = b;\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseCommCase() *AST.Stat {
-\tP.Trace("CommCase");
+\tif P.verbose {
+\t\tP.Trace("CommCase");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := AST.NewStat(P.pos, P.tok);\
 \tif P.tok == Scanner.CASE {\
@@ -1375,7 +1432,7 @@ func (P *Parser) ParseCommCase() *AST.Stat {\
 \t\t\tP.Next();\
 \t\t\tif P.tok == Scanner.ARROW {\
 \t\t\t\ty := P.ParseExpression(1);\
-\t\t\t\tx = AST.NewExpr(pos, tok, x, y);\
+\t\t\t\tx = P.NewBinary(pos, tok, x, y);
 \t\t\t} else {\
 \t\t\t\tP.Expect(Scanner.ARROW);  // use Expect() error handling
 \t\t\t}\
@@ -1385,24 +1442,28 @@ func (P *Parser) ParseCommCase() *AST.Stat {\
 \t\tP.Expect(Scanner.DEFAULT);\
 \t}\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseCommClause() *AST.Stat {
-\tP.Trace("CommClause");
+\tif P.verbose {
+\t\tP.Trace("CommClause");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := P.ParseCommCase();
 \ts.Body = P.ParseBlock(nil, Scanner.COLON);\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseSelectStat() *AST.Stat {
-\tP.Trace("SelectStat");
+\tif P.verbose {
+\t\tP.Trace("SelectStat");
+\t\tdefer P.Ecart();
+\t}
 
 \ts := AST.NewStat(P.pos, Scanner.SELECT);\
 \tP.Expect(Scanner.SELECT);\
@@ -1416,13 +1477,15 @@ func (P *Parser) ParseSelectStat() *AST.Stat {\
 \tP.opt_semi = true;\
 \ts.Body = b;\
 
-\tP.Ecart();
 	return s;
 }
 
 
 func (P *Parser) ParseStatement() *AST.Stat {
-\tP.Trace("Statement");
+\tif P.verbose {
+\t\tP.Trace("Statement");
+\t\tdefer P.Ecart();
+\t}
 \tindent := P.indent;\
 
 \ts := AST.BadStat;\
@@ -1465,7 +1528,6 @@ func (P *Parser) ParseStatement() *AST.Stat {\
 \tif indent != P.indent {\
 \t\tpanic("imbalanced tracing code (Statement)");
 \t}\
-\tP.Ecart();
 	return s;
 }
 
@@ -1474,7 +1536,10 @@ func (P *Parser) ParseStatement() *AST.Stat {\
 // Declarations
 
 func (P *Parser) ParseImportSpec(d *AST.Decl) {
-\tP.Trace("ImportSpec");
+\tif P.verbose {
+\t\tP.Trace("ImportSpec");
+\t\tdefer P.Ecart();
+\t}
 
 \tif P.tok == Scanner.PERIOD {\
 \t\tP.Error(P.pos, `"import ." not yet handled properly`);
@@ -1485,19 +1550,19 @@ func (P *Parser) ParseImportSpec(d *AST.Decl) {\
 
 \tif P.tok == Scanner.STRING {\
 \t\t// TODO eventually the scanner should strip the quotes
-\t\tval := AST.NewObject(P.pos, AST.NONE, P.val);\
-\t\td.Val = AST.NewLit(Scanner.STRING, val);\
+\t\td.Val = &AST.BasicLit{P.pos, Scanner.STRING, P.val};
 \t\tP.Next();
 \t} else {\
 \t\tP.Expect(Scanner.STRING);  // use Expect() error handling
 \t}\
-\n-\tP.Ecart();
 }
 
 
 func (P *Parser) ParseConstSpec(d *AST.Decl) {
-\tP.Trace("ConstSpec");
+\tif P.verbose {
+\t\tP.Trace("ConstSpec");
+\t\tdefer P.Ecart();
+\t}
 
 \td.Ident = P.ParseIdentList();
 \td.Typ = P.TryType();
@@ -1505,24 +1570,26 @@ func (P *Parser) ParseConstSpec(d *AST.Decl) {\
 \t\tP.Next();
 \t\td.Val = P.ParseExpressionList();
 \t}\
-\n-\tP.Ecart();
 }
 
 
 func (P *Parser) ParseTypeSpec(d *AST.Decl) {
-\tP.Trace("TypeSpec");
+\tif P.verbose {
+\t\tP.Trace("TypeSpec");
+\t\tdefer P.Ecart();
+\t}
 
 \td.Ident = P.ParseIdent(nil);\
 \td.Typ = P.ParseType();
 \tP.opt_semi = true;\
-\n-\tP.Ecart();
 }
 
 
 func (P *Parser) ParseVarSpec(d *AST.Decl) {
-\tP.Trace("VarSpec");
+\tif P.verbose {
+\t\tP.Trace("VarSpec");
+\t\tdefer P.Ecart();
+\t}
 
 \td.Ident = P.ParseIdentList();
 \tif P.tok == Scanner.ASSIGN {\
@@ -1535,8 +1602,6 @@ func (P *Parser) ParseVarSpec(d *AST.Decl) {\
 \t\t\td.Val = P.ParseExpressionList();
 \t\t}\
 \t}\
-\n-\tP.Ecart();
 }
 
 
@@ -1560,17 +1625,17 @@ func (P *Parser) ParseSpec(d *AST.Decl) {\
 \t\tP.Declare(d.Ident, kind, d.Typ);\
 \t\tif d.Val != nil {\
 \t\t\t// initialization/assignment
-\t\t\tllen := d.Ident.Len();
-\t\t\trlen := d.Val.Len();
+\t\t\tllen := AST.ExprLen(d.Ident);
+\t\t\trlen := AST.ExprLen(d.Val);
 \t\t\tif llen == rlen {\
 \t\t\t\t// TODO
 \t\t\t} else if rlen == 1 {\
 \t\t\t\t// TODO
 \t\t\t} else {\
 \t\t\t\tif llen < rlen {\
-\t\t\t\t\tP.Error(d.Val.At(llen).Pos, "more expressions than variables");
+\t\t\t\t\tP.Error(AST.ExprAt(d.Val, llen).Pos(), "more expressions than variables");
 \t\t\t\t} else {\
-\t\t\t\t\tP.Error(d.Ident.At(rlen).Pos, "more variables than expressions");
+\t\t\t\t\tP.Error(AST.ExprAt(d.Ident, rlen).Pos(), "more variables than expressions");
 			}
 		} else {\
 
@@ -1581,7 +1646,10 @@ func (P *Parser) ParseSpec(d *AST.Decl) {\
 
 
 func (P *Parser) ParseDecl(keyword int) *AST.Decl {
-\tP.Trace("Decl");
+\tif P.verbose {
+\t\tP.Trace("Decl");
+\t\tdefer P.Ecart();
+\t}
 
 \td := AST.NewDecl(P.pos, keyword);\
 \tP.Expect(keyword);\
@@ -1606,7 +1674,6 @@ func (P *Parser) ParseDecl(keyword int) *AST.Decl {\
 \t\tP.ParseSpec(d);\
 \t}

コアとなるコードの解説

ASTの式表現のインターフェース化 (usr/gri/pretty/ast.go)

この変更の核心は、Go言語のASTにおける式の表現を、具象型からインターフェースベースの設計へと根本的に変更した点にあります。

  • Expr interfaceの導入: 従来のExpr structLit structが削除され、Exprがインターフェースとして再定義されました。これにより、すべての式ノードはExprインターフェースを実装することになります。
  • 多様な具象型: BadExpr, Ident, BinaryExpr, UnaryExpr, BasicLit, FunctionLit, CompositeLit, TypeLit, Selector, TypeGuard, Index, Callといった、具体的な式の種類に対応する新しい構造体が導入されました。
    • 例えば、BinaryExprXYという2つのExpr型のフィールドを持ち、二項演算子(+, -, *など)を表現します。
    • BasicLitVal stringフィールドを持ち、数値や文字列などのリテラル値を表現します。
    • これにより、各式の種類がその特性に合わせた専用のデータ構造を持つことができ、メモリ効率と型安全性が向上します。
  • Pos()Visit(v Visitor)メソッド: Exprインターフェースは、ソースコード上の位置を返すPos()メソッドと、VisitorパターンをサポートするためのVisit()メソッドを定義しています。これにより、ASTの走査や処理が統一された方法で行えるようになります。
  • ExprLenExprAtの変更: カンマ区切りの式リストを扱うためのヘルパー関数ExprLenExprAtも、新しいインターフェースベースのExpr型を引数に取るように変更されました。これにより、ポリモーフィズムが活用され、より汎用的なコードが実現されています。

この設計変更は、Go言語のASTが、より複雑な構文構造や意味解析に対応するための基盤を築く上で不可欠なステップでした。インターフェースの活用により、ASTの各ノードがその役割に特化した情報を持つことができ、コードのモジュール性と拡張性が大幅に向上しています。

トレーシングコードへのdeferステートメントの適用 (usr/gri/pretty/parser.go)

この変更は、Go言語のdeferステートメントの強力なユースケースを示しています。

  • P.TraceP.Ecartの連携: 従来のトレーシングでは、関数の開始時にP.Trace()を呼び出し、終了時にP.Ecart()を呼び出す必要がありました。これは、特にネストされた関数呼び出しが多い場合、P.Ecart()の呼び出し忘れや、複数のリターンパスがある場合の記述の複雑さにつながる可能性がありました。
  • defer P.Ecart()の導入: P.Trace関数内でdefer P.Ecart()を呼び出すように変更されたことで、P.Traceが呼び出された関数が終了する際に、Goランタイムが自動的にP.Ecart()を実行するようになります。
    • これにより、パーサーの各Parse...関数内で、明示的にP.Ecart()を呼び出すコードを削除することができました。
    • 例えば、ParseIdent関数では、P.Trace("Ident")の直後にdefer P.Ecart()が追加され、関数の最後にあるP.Ecart()の呼び出しが削除されています。
  • コードの簡潔化と堅牢化: この変更により、トレーシングコードの記述が大幅に簡潔になり、関数の出口処理が常に保証されるため、コードの堅牢性が向上しました。これは、リソース管理やデバッグログなど、関数のスコープを抜ける際に特定のクリーンアップ処理が必要な場面でdeferが非常に有効であることを示しています。

関連リンク

参考にした情報源リンク

  • Go言語のAST表現とdeferステートメントに関するWeb検索結果 (Google Web Search)
    • GoのASTにおけるdeferステートメントの表現 (ast.DeferStmt) や、ASTの走査方法 (ast.Inspect, ast.Walk)、インターフェース設計の考慮事項など、このコミットの理解を深めるための情報源として活用しました。
    • 特に、GoのASTがast.Nodeインターフェースを基盤としていることや、VisitorパターンがAST走査に有効であるという情報は、このコミットで導入されたExprインターフェースとVisitメソッドの設計思想を理解する上で役立ちました。
  • Go言語の初期のコミット履歴 (GitHub): コミットメッセージと差分から直接的な変更内容を把握しました。
  • Go言語のインターフェースとdeferに関する一般的な知識。