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

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

このコミットは、Go言語の初期開発段階において、コードの識別子(identifier)と宣言(declaration)を追跡するための準備作業を導入するものです。最終的な目的は、Goコードを整形して表示する「プリティプリンター(pretty printer)」の出力において、HTMLリンクを生成できるようにすることにあります。これにより、生成されたHTMLドキュメント内で、コード内の要素(変数、関数、型など)がその定義元にリンクされるようになり、コードのナビゲーションと理解が大幅に向上することが期待されます。

コミット

Author: Robert Griesemer gri@golang.org Date: Tue Dec 16 18:02:22 2008 -0800

コミットメッセージ:

Snapshot.

Preparations to track identifiers and declarations so that we can
generate good html links as pretty printer output:
- brought over old code and adjusted it
- initial hookups, nothing really running yet

R=r
OCL=21383
CL=21383

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

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

元コミット内容

commit b86359073e8268093dbff1c5d5a8ed600218c816
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Dec 16 18:02:22 2008 -0800

    Snapshot.
    
    Preparations to track identifiers and declarations so that we can
    generate good html links as pretty printer output:
    - brought over old code and adjusted it
    - initial hookups, nothing really running yet
    
    R=r
    OCL=21383
    CL=21383
---
 usr/gri/pretty/Makefile       |  12 ++-
 usr/gri/pretty/compilation.go |   6 ++
 usr/gri/pretty/globals.go     | 240 ++++++++++++++++++++++++++++++++++++++++++\n usr/gri/pretty/object.go      |  36 +++++++
 usr/gri/pretty/type.go        | 207 ++++++++++++++++++++++++++++++++++++\n usr/gri/pretty/universe.go    | 125 ++++++++++++++++++++++\n 6 files changed, 624 insertions(+), 2 deletions(-)

diff --git a/usr/gri/pretty/Makefile b/usr/gri/pretty/Makefile
index 50585fe102..cfc2bb132c 100644
--- a/usr/gri/pretty/Makefile
+++ b/usr/gri/pretty/Makefile
@@ -23,11 +23,11 @@ install: pretty
 \tcp pretty $(HOME)/bin/pretty
 
 clean:
-\trm -f pretty *.6  *~\n+\trm -f pretty *.6 *.a *~\n 
 pretty.6:	 platform.6 printer.6 compilation.6
 
-compilation.6:	 platform.6 scanner.6 parser.6 ast.6\n+compilation.6:	 platform.6 scanner.6 parser.6 ast.6 typechecker.6\n 
 ast.6:	 scanner.6
 
 @@ -39,5 +39,13 @@ platform.6:	 utils.6
 
 printer.6:	 scanner.6 ast.6
 
+typechecker.6:	ast.6 universe.6 globals.6 type.6
+\n+universe.6:	globals.6 object.6 type.6
+\n+object.6:	globals.6
+\n+type.6:	globals.6 object.6
+\n %.6:	%.go
 \t$(G) $(F) $<\ndiff --git a/usr/gri/pretty/compilation.go b/usr/gri/pretty/compilation.go
 index 9df221436a..82b6618da3 100644
 --- a/usr/gri/pretty/compilation.go
 +++ b/usr/gri/pretty/compilation.go
 @@ -10,6 +10,7 @@ import Platform \"platform\"\n import Scanner \"scanner\"\n import Parser \"parser\"\n import AST \"ast\"\n+import TypeChecker \"typechecker\"\n \n \n func assert(b bool) {\
 @@ -133,6 +134,11 @@ export func Compile(src_file string, flags *Flags) (*AST.Program, int) {\
  \tparser.Open(flags.verbose, flags.sixg, flags.deps, &scanner, tstream);\
  \n \tprog := parser.ParseProgram();\
 +\t\n+\tif err.nerrors == 0 {\
 +\t\tTypeChecker.CheckProgram(prog);\
 +\t}\n+\t\n  \treturn prog, err.nerrors;\
  }\
  \ndiff --git a/usr/gri/pretty/globals.go b/usr/gri/pretty/globals.go
 new file mode 100644
 index 0000000000..d1dc47cb0b
 --- /dev/null
 +++ b/usr/gri/pretty/globals.go
 @@ -0,0 +1,240 @@
 +// Copyright 2009 The Go Authors.  All rights reserved.\
 +// Use of this source code is governed by a BSD-style\
 +// license that can be found in the LICENSE file.\
 +\n+package Globals\
 +\n+\n+// The following types should really be in their respective files\
 +// (object.go, type.go, scope.go, package.go, compilation.go, etc.) but\n +// they refer to each other and we don\'t know how to handle forward\n +// declared pointers across packages yet.\n +\n+\n+// ----------------------------------------------------------------------------\n +\n+type Type struct\
 +type Scope struct\
 +type Elem struct\
 +type OldCompilation struct\
 +\n+// Object represents a language object, such as a constant, variable, type,\n +// etc. (kind). An objects is (pre-)declared at a particular position in the\n +// source code (pos), has a name (ident), a type (typ), and a package number\n +// or nesting level (pnolev).\n +\n+export type Object struct {\
 +\texported bool;\
 +\tpos int;  // source position (< 0 if unknown position)\
 +\tkind int;\
 +\tident string;\
 +\ttyp *Type;  // nil for packages\
 +\tpnolev int;  // >= 0: package no., <= 0: function nesting level, 0: global level\
 +}\
 +\n+\n+export type Type struct {\
 +\tref int;  // for exporting only: >= 0 means already exported\
 +\tform int;\
 +\tsize int;  // in bytes\
 +\tlen int;  // array length, no. of function/method parameters (w/o recv)\
 +\taux int;  // channel info\
 +\tobj *Object;  // primary type object or NULL\
 +\tkey *Type;  // alias base type or map key\n +\telt *Type;  // aliased type, array, map, channel or pointer element type, function result type, tuple function type\
 +\tscope *Scope;  // forwards, structs, interfaces, functions\
 +}\
 +\n+\n+export type Package struct {\
 +\tref int;  // for exporting only: >= 0 means already exported\
 +\tfile_name string;\
 +\tkey string;\
 +\tobj *Object;\
 +\tscope *Scope;  // holds the (global) objects in this package\
 +}\
 +\n+\n+export type Scope struct {\
 +\tparent *Scope;\
 +\tentries *map[string] *Object;\
 +}\
 +\n+\n+export type Environment struct {\
 +\tError *(comp *OldCompilation, pos int, msg string);\
 +\tImport *(comp *OldCompilation, pkg_file string) *Package;\
 +\tExport *(comp *OldCompilation, pkg_file string);\
 +\tCompile *(comp *OldCompilation, src_file string);\
 +}\
 +\n+\n+export type OldCompilation struct {\
 +\t// environment\
 +\tenv *Environment;\
 +\t\n+\t// TODO rethink the need for this here\
 +\tsrc_file string;\
 +\tsrc string;\
 +\t\t\n+\t// Error handling\
 +\tnerrors int;  // number of errors reported\
 +\terrpos int;  // last error position\
 +\t\n+\t// TODO use open arrays eventually\
 +\tpkg_list [256] *Package;  // pkg_list[0] is the current package\
 +\tpkg_ref int;\
 +}\
 +\n+\n+export type Expr interface {\
 +\top() int;  // node operation\
 +\tpos() int;  // source position\
 +\ttyp() *Type;\
 +\t// ... more to come here\
 +}\
 +\n+\n+export type Stat interface {\
 +\t// ... more to come here\
 +}\
 +\n+\n+// TODO This is hideous! We need to have a decent way to do lists.\
 +// Ideally open arrays that allow \'+\'.\n +\n+export type Elem struct {\
 +\tnext *Elem;\
 +\tval int;\
 +\tstr string;\
 +\tobj *Object;\
 +\ttyp *Type;\
 +\texpr Expr\
 +}\
 +\n+\n+// ----------------------------------------------------------------------------\n +// Creation\n +\n+export var Universe_void_typ *Type  // initialized by Universe to Universe.void_typ\
 +\n+export func NewObject(pos, kind int, ident string) *Object {\
 +\tobj := new(Object);\
 +\tobj.exported = false;\
 +\tobj.pos = pos;\
 +\tobj.kind = kind;\
 +\tobj.ident = ident;\
 +\tobj.typ = Universe_void_typ;\
 +\tobj.pnolev = 0;\
 +\treturn obj;\
 +}\
 +\n+\n+export func NewType(form int) *Type {\
 +\ttyp := new(Type);\
 +\ttyp.ref = -1;  // not yet exported\
 +\ttyp.form = form;\
 +\treturn typ;\
 +}\
 +\n+\n+export func NewPackage(file_name string, obj *Object, scope *Scope) *Package {\
 +\tpkg := new(Package);\
 +\tpkg.ref = -1;  // not yet exported\n +\tpkg.file_name = file_name;\
 +\tpkg.key = \"<the package key>\";  // empty key means package forward declaration\
 +\tpkg.obj = obj;\
 +\tpkg.scope = scope;\
 +\treturn pkg;\
 +}\
 +\n+\n+export func NewScope(parent *Scope) *Scope {\
 +\tscope := new(Scope);\
 +\tscope.parent = parent;\
 +\tscope.entries = new(map[string]*Object, 8);\
 +\treturn scope;\
 +}\
 +\n+\n+// ----------------------------------------------------------------------------\n +// Object methods\n +\n+func (obj *Object) Copy() *Object {\
 +\tcopy := new(Object);\
 +\tcopy.exported = obj.exported;\
 +\tcopy.pos = obj.pos;\
 +\tcopy.kind = obj.kind;\
 +\tcopy.ident = obj.ident;\
 +\tcopy.typ = obj.typ;\
 +\tcopy.pnolev = obj.pnolev;\
 +\treturn copy;\
 +}\
 +\n+\n+// ----------------------------------------------------------------------------\n +// Scope methods\n +\n+func (scope *Scope) Lookup(ident string) *Object {\
 +\tobj, found := scope.entries[ident];\
 +\tif found {\
 +\t\treturn obj;\
 +\t}\
 +\treturn nil;\
 +}\
 +\n+\n+func (scope *Scope) Add(obj* Object) {\
 +\tscope.entries[obj.ident] = obj;\
 +}\
 +\n+\n+func (scope *Scope) Insert(obj *Object) {\
 +\tif scope.Lookup(obj.ident) != nil {\
 +\t\tpanic(\"obj already inserted\");\n +\t}\
 +\tscope.Add(obj);\
 +}\
 +\n+\n+func (scope *Scope) InsertImport(obj *Object) *Object {\
 +\t p := scope.Lookup(obj.ident);\
 +\t if p == nil {\
 +\t\tscope.Add(obj);\
 +\t\tp = obj;\
 +\t }\
 +\t return p;\
 +}\
 +\n+\n+func (scope *Scope) Print() {\
 +\tprint(\"scope {\");\n +\tfor key := range scope.entries {\
 +\t\tprint(\"\\n  \", key);\n +\t}\n +\tprint(\"\\n}\\n\");\n +}\
 +\n+\n+// ----------------------------------------------------------------------------\n +// Compilation methods\n +\n+func (C *OldCompilation) Lookup(file_name string) *Package {\
 +\tfor i := 0; i < C.pkg_ref; i++ {\
 +\t\tpkg := C.pkg_list[i];\
 +\t\tif pkg.file_name == file_name {\
 +\t\t\treturn pkg;\
 +\t\t}\n +\t}\n +\treturn nil;\
 +}\
 +\n+\n+func (C *OldCompilation) Insert(pkg *Package) {\
 +\tif C.Lookup(pkg.file_name) != nil {\
 +\t\tpanic(\"package already inserted\");\n +\t}\n +\tpkg.obj.pnolev = C.pkg_ref;\
 +\tC.pkg_list[C.pkg_ref] = pkg;\
 +\tC.pkg_ref++;\
 +}\ndiff --git a/usr/gri/pretty/object.go b/usr/gri/pretty/object.go
 new file mode 100755
 index 0000000000..220f4c8d8b
 --- /dev/null
 +++ b/usr/gri/pretty/object.go
 @@ -0,0 +1,36 @@
 +// Copyright 2009 The Go Authors.  All rights reserved.\
 +// Use of this source code is governed by a BSD-style\
 +// license that can be found in the LICENSE file.\
 +\n+package Object\
 +\n+import Globals \"globals\"\
 +\n+\n+export const /* kind */ (\
 +\tBAD = iota;  // error handling\
 +\tCONST; TYPE; VAR; FIELD; FUNC; BUILTIN; PACKAGE; LABEL;\
 +\tEND;  // end of scope (import/export only)\
 +)\
 +\n+\n+// The \'Object\' declaration should be here as well, but 6g cannot handle\
 +// this due to cross-package circular references. For now it\'s all in\
 +// globals.go.\n +\n+\n+export func KindStr(kind int) string {\
 +\tswitch kind {\
 +\tcase BAD: return \"BAD\";\
 +\tcase CONST: return \"CONST\";\
 +\tcase TYPE: return \"TYPE\";\
 +\tcase VAR: return \"VAR\";\
 +\tcase FIELD: return \"FIELD\";\
 +\tcase FUNC: return \"FUNC\";\
 +\tcase BUILTIN: return \"BUILTIN\";\
 +\tcase PACKAGE: return \"PACKAGE\";\
 +\tcase LABEL: return \"LABEL\";\
 +\tcase END: return \"END\";\
 +\t}\
 +\treturn \"<unknown Object kind>\";\
 +}\ndiff --git a/usr/gri/pretty/type.go b/usr/gri/pretty/type.go
 new file mode 100644
 index 0000000000..507357e65d
 --- /dev/null
 +++ b/usr/gri/pretty/type.go
 @@ -0,0 +1,207 @@
 +// Copyright 2009 The Go Authors.  All rights reserved.\
 +// Use of this source code is governed by a BSD-style\
 +// license that can be found in the LICENSE file.\
 +\n+package Type\
 +\n+import Globals \"globals\"\
 +import Object \"object\"\
 +\n+\n+export const /* form */ (\
 +\t// internal types\
 +\t// We should never see one of these.\
 +\tUNDEF = iota;\
 +\t\n+\t// VOID types are used when we don\'t have a type. Never exported.\
 +\t// (exported type forms must be > 0)\
 +\tVOID;\
 +\t\n+\t// BAD types are compatible with any type and don\'t cause further errors.\
 +\t// They are introduced only as a result of an error in the source code. A\n +\t// correct program cannot have BAD types.\n +\tBAD;\
 +\t\n+\t// FORWARD types are forward-declared (incomplete) types. They can only\n +\t// be used as element types of pointer types and must be resolved before\n +\t// their internals are accessible.\n +\tFORWARD;\
 +\n+\t// TUPLE types represent multi-valued result types of functions and\n +\t// methods.\n +\tTUPLE;\
 +\t\n+\t// The type of nil.\n +\tNIL;\
 +\n+\t// basic types\n +\tBOOL; UINT; INT; FLOAT; STRING; INTEGER;\
 +\t\n+\t// \'any\' type  // TODO this should go away eventually\n +\tANY;\
 +\t\n+\t// composite types\n +\tALIAS; ARRAY; STRUCT; INTERFACE; MAP; CHANNEL; FUNCTION; METHOD; POINTER;\
 +)\
 +\n+\n+export const /* Type.aux */ (\
 +\tSEND = 1;  // chan>\
 +\tRECV = 2;  // chan<\
 +)\
 +\n+\n+// The \'Type\' declaration should be here as well, but 6g cannot handle\
 +// this due to cross-package circular references. For now it\'s all in\
 +// globals.go.\n +\n+\n+export func FormStr(form int) string {\
 +\tswitch form {\
 +\tcase VOID: return \"VOID\";\
 +\tcase BAD: return \"BAD\";\
 +\tcase FORWARD: return \"FORWARD\";\
 +\tcase TUPLE: return \"TUPLE\";\
 +\tcase NIL: return \"NIL\";\
 +\tcase BOOL: return \"BOOL\";\
 +\tcase UINT: return \"UINT\";\
 +\tcase INT: return \"INT\";\
 +\tcase FLOAT: return \"FLOAT\";\
 +\tcase STRING: return \"STRING\";\
 +\tcase ANY: return \"ANY\";\
 +\tcase ALIAS: return \"ALIAS\";\
 +\tcase ARRAY: return \"ARRAY\";\
 +\tcase STRUCT: return \"STRUCT\";\
 +\tcase INTERFACE: return \"INTERFACE\";\
 +\tcase MAP: return \"MAP\";
 +\tcase CHANNEL: return \"CHANNEL\";
 +\tcase FUNCTION: return \"FUNCTION\";
 +\tcase METHOD: return \"METHOD\";
 +\tcase POINTER: return \"POINTER\";
 +\t}\
 +\treturn \"<unknown Type form>\";\
 +}\
 +\n+\n+export func Equal(x, y *Globals.Type) bool;\
 +\n+func Equal0(x, y *Globals.Type) bool {\
 +\tif x == y {\
 +\t\treturn true;  // identical types are equal\
 +\t}\
 +\n+\tif x.form == BAD || y.form == BAD {\
 +\t\treturn true;  // bad types are always equal (avoid excess error messages)\
 +\t}\
 +\n+\t// TODO where to check for *T == nil ?  \n +\tif x.form != y.form {\
 +\t\treturn false;  // types of different forms are not equal\
 +\t}\n +\n+\tswitch x.form {\
 +\tcase FORWARD, BAD:\
 +\t\tbreak;\
 +\n+\tcase NIL, BOOL, STRING, ANY:\
 +\t\treturn true;\
 +\n+\tcase UINT, INT, FLOAT:\
 +\t\treturn x.size == y.size;\
 +\n+\tcase ARRAY:\
 +\t\treturn\
 +\t\t\tx.len == y.len &&\
 +\t\t\tEqual(x.elt, y.elt);\
 +\n+\tcase MAP:\
 +\t\treturn\
 +\t\t\tEqual(x.key, y.key) &&\
 +\t\t\tEqual(x.elt, y.elt);\
 +\n+\tcase CHANNEL:\
 +\t\treturn\
 +\t\t\tx.aux == y.aux &&\
 +\t\t\tEqual(x.elt, y.elt);\
 +\n+\tcase FUNCTION, METHOD:\
 +\t\t{\tpanic();\
 +\t\t\t/*\n+\t\t\txp := x.scope.entries;\
 +\t\t\typ := x.scope.entries;\
 +\t\t\tif\tx.len != y.len &&  // number of parameters\
 +\t\t\t\txp.len != yp.len  // recv + parameters + results\n+\t\t\t{\n+\t\t\t\treturn false;\n+\t\t\t}\n+\t\t\tfor p, q := xp.first, yp.first; p != nil; p, q = p.next, q.next {\
 +\t\t\t\txf := p.obj;\
 +\t\t\t\tyf := q.obj;\
 +\t\t\t\tif xf.kind != Object.VAR || yf.kind != Object.VAR {\
 +\t\t\t\t\tpanic(\"parameters must be vars\");\
 +\t\t\t\t}\n+\t\t\t\tif !Equal(xf.typ, yf.typ) {\
 +\t\t\t\t\treturn false;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t*/\n+\t\t}\n+\t\treturn true;\n+\n+\tcase STRUCT:\
 +\t\t/*\n+\t\t{\tObjList* xl = &x.scope.list;\
 +\t\t\tObjList* yl = &y.scope.list;\
 +\t\t\tif xl.len() != yl.len() {\
 +\t\t\t\treturn false;  // scopes of different sizes are not equal\n+\t\t\t}\n+\t\t\tfor int i = xl.len(); i-- > 0; {\
 +\t\t\t\tObject* xf = (*xl)[i];\
 +\t\t\t\tObject* yf = (*yl)[i];\n+\t\t\t\tASSERT(xf.kind == Object.VAR && yf.kind == Object.VAR);\n+\t\t\t\tif xf.name != yf.name) || ! EqualTypes(xf.type(), yf.type() {\
 +\t\t\t\t\treturn false;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\treturn true;\n+\t\t*/\n+\t\t// Scopes must be identical for them to be equal.\n+\t\t// If we reach here, they weren\'t.\n+\t\treturn false;\n+\n+\tcase INTERFACE:\
 +\t\tpanic(\"UNIMPLEMENTED\");\n +\t\treturn false;\n +\n+\tcase POINTER:\
 +\t\treturn Equal(x.elt, y.elt);\n +\t\t\n+\tcase TUPLE:\
 +\t\tpanic(\"UNIMPLEMENTED\");\n +\t\treturn false;\n +\t}\n +\n+\tpanic(\"UNREACHABLE\");\n +\treturn false;\n +}\n +\n+\n+export func Equal(x, y *Globals.Type) bool {\
 +\tres := Equal0(x, y);\n +\t// TODO should do the check below only in debug mode\n +\tif Equal0(y, x) != res {\
 +\t\tpanic(\"type equality must be symmetric\");\n +\t}\n +\treturn res;\
 +}\
 +\n+\n+export func Assigneable(from, to *Globals.Type) bool {\
 +\tif Equal(from, to) {\
 +\t\treturn true;\
 +\t}\n +\t\n+\tpanic(\"UNIMPLEMENTED\");\n +\treturn false;\n +}\ndiff --git a/usr/gri/pretty/universe.go b/usr/gri/pretty/universe.go
 new file mode 100755
 index 0000000000..fb199ec353
 --- /dev/null
 +++ b/usr/gri/pretty/universe.go
 @@ -0,0 +1,125 @@
 +// Copyright 2009 The Go Authors.  All rights reserved.\
 +// Use of this source code is governed by a BSD-style\
 +// license that can be found in the LICENSE file.\
 +\n+package Universe\
 +\n+import (\
 +\t\"array\"\
 +\tGlobals \"globals\";\
 +\tObject \"object\";\
 +\tType \"type\";\
 +)\n +\n+\n+export var (\
 +\tscope *Globals.Scope;\
 +\ttypes array.Array;\
 +\t\n+\t// internal types\
 +\tvoid_typ,\
 +\tbad_typ,\
 +\tnil_typ,\
 +\t\n+\t// basic types\n +\tbool_typ,\
 +\tuint8_typ,\n +\tuint16_typ,\n +\tuint32_typ,\n +\tuint64_typ,\n +\tint8_typ,\n +\tint16_typ,\n +\tint32_typ,\n +\tint64_typ,\n +\tfloat32_typ,\n +\tfloat64_typ,\n +\tfloat80_typ,\
 +\tstring_typ,\
 +\tinteger_typ,\
 +\t\n+\t// convenience types\n +\tbyte_typ,\
 +\tuint_typ,\
 +\tint_typ,\
 +\tfloat_typ,\
 +\tuintptr_typ *Globals.Type;\
 +\t\n+\ttrue_obj,\
 +\tfalse_obj,\
 +\tiota_obj,\
 +\tnil_obj *Globals.Object;\
 +)\n +\n+\n+func DeclObj(kind int, ident string, typ *Globals.Type) *Globals.Object {\
 +\tobj := Globals.NewObject(-1 /* no source pos */, kind, ident);\
 +\tobj.typ = typ;\
 +\tif kind == Object.TYPE && typ.obj == nil {\
 +\t\ttyp.obj = obj;  // set primary type object\
 +\t}\n +\tscope.Insert(obj);\
 +\treturn obj\
 +}\
 +\n+\n+func DeclType(form int, ident string, size int) *Globals.Type {\
 +  typ := Globals.NewType(form);\
 +  typ.size = size;\
 +  return DeclObj(Object.TYPE, ident, typ).typ;\
 +}\
 +\n+\n+func Register(typ *Globals.Type) *Globals.Type {\
 +\ttyp.ref = types.Len();\
 +\ttypes.Push(typ);\
 +\treturn typ;\
 +}\
 +\n+\n+func init() {\
 +\tscope = Globals.NewScope(nil);  // universe has no parent\
 +\ttypes.Init(32);\
 +\t\n+\t// Interal types\n +\tvoid_typ = Globals.NewType(Type.VOID);\
 +\tGlobals.Universe_void_typ = void_typ;\
 +\tbad_typ = Globals.NewType(Type.BAD);\
 +\tnil_typ = Globals.NewType(Type.NIL);\
 +\t\n+\t// Basic types\n +\tbool_typ = Register(DeclType(Type.BOOL, \"bool\", 1));\
 +\tuint8_typ = Register(DeclType(Type.UINT, \"uint8\", 1));\
 +\tuint16_typ = Register(DeclType(Type.UINT, \"uint16\", 2));\
 +\tuint32_typ = Register(DeclType(Type.UINT, \"uint32\", 4));\
 +\tuint64_typ = Register(DeclType(Type.UINT, \"uint64\", 8));\
 +\tint8_typ = Register(DeclType(Type.INT, \"int8\", 1));\
 +\tint16_typ = Register(DeclType(Type.INT, \"int16\", 2));\
 +\tint32_typ = Register(DeclType(Type.INT, \"int32\", 4));\
 +\tint64_typ = Register(DeclType(Type.INT, \"int64\", 8));\
 +\tfloat32_typ = Register(DeclType(Type.FLOAT, \"float32\", 4));\
 +\tfloat64_typ = Register(DeclType(Type.FLOAT, \"float64\", 8));\
 +\tfloat80_typ = Register(DeclType(Type.FLOAT, \"float80\", 10));\
 +\tstring_typ = Register(DeclType(Type.STRING, \"string\", 8));\
 +\tinteger_typ = Register(DeclType(Type.INTEGER, \"integer\", 8));
 +\n+\t// All but \'byte\' should be platform-dependent, eventually.\
 +\tbyte_typ = Register(DeclType(Type.UINT, \"byte\", 1));\
 +\tuint_typ = Register(DeclType(Type.UINT, \"uint\", 4));\
 +\tint_typ = Register(DeclType(Type.INT, \"int\", 4));\
 +\tfloat_typ = Register(DeclType(Type.FLOAT, \"float\", 4));\
 +\tuintptr_typ = Register(DeclType(Type.UINT, \"uintptr\", 8));
 +\n+\t// Predeclared constants\
 +\ttrue_obj = DeclObj(Object.CONST, \"true\", bool_typ);\
 +\tfalse_obj = DeclObj(Object.CONST, \"false\", bool_typ);\
 +\tiota_obj = DeclObj(Object.CONST, \"iota\", int_typ);\
 +\tnil_obj = DeclObj(Object.CONST, \"nil\", nil_typ);\
 +\n+\t// Builtin functions\
 +\tDeclObj(Object.BUILTIN, \"len\", void_typ);\
 +\tDeclObj(Object.BUILTIN, \"new\", void_typ);\
 +\tDeclObj(Object.BUILTIN, \"panic\", void_typ);\
 +\tDeclObj(Object.BUILTIN, \"print\", void_typ);\
 +\t\n+\t// scope.Print();\
 +}\

変更の背景

このコミットは、Go言語のコンパイラおよびツールチェインの初期段階における重要な機能拡張の一環です。コミットメッセージにある「プリティプリンター出力として良いHTMLリンクを生成できるように、識別子と宣言を追跡するための準備」という記述が、この変更の核心を突いています。

当時のGo言語のツールチェインには、ソースコードを解析し、その構造を理解するための基本的なコンポーネント(スキャナー、パーサー、ASTビルダー)は存在していましたが、それらの要素間のセマンティックな関係(例えば、ある変数がどこで宣言され、どの型を持つか)を詳細に追跡する仕組みはまだ不十分でした。

この機能は、Goの公式ドキュメントやコードブラウザのようなツールを構築する上で不可欠です。例えば、Goの標準ライブラリのドキュメントでは、関数名や型名をクリックするとその定義にジャンプできます。このような機能を実現するためには、コンパイラがソースコード内の各識別子がどの宣言に対応しているかを正確に把握し、その位置情報を保持する必要があります。

このコミットは、そのための基盤となるデータ構造(ObjectTypeScopeなど)と、それらを管理するロジックを導入しています。特に、型チェック(TypeChecker)の概念が導入され、コンパイルプロセスに組み込まれることで、識別子と宣言のセマンティックな解析が可能になります。

また、globals.goのコメントにある「The following types should really be in their respective files... but they refer to each other and we don't know how to handle forward declared pointers across packages yet.」という記述は、当時のGoコンパイラ(6g)の制限、特にパッケージ間の循環参照の扱いの難しさを示唆しています。このため、本来は分離されるべき型定義がglobals.goに集約されているという背景があります。

前提知識の解説

このコミットを理解するためには、以下の概念についての基本的な知識が必要です。

  • コンパイラのフロントエンド:
    • スキャナー(Lexer): ソースコードをトークン(最小単位の語彙要素)に分解します。
    • パーサー(Parser): トークンのストリームを受け取り、言語の文法規則に従って抽象構文木(AST: Abstract Syntax Tree)を構築します。
    • 抽象構文木(AST): ソースコードの構造を木構造で表現したものです。
    • セマンティックアナライザー(意味解析器): ASTを走査し、型チェック、名前解決、スコープ解決など、プログラムの意味的な正当性を検証します。このコミットで導入される「識別子と宣言の追跡」や「型チェック」は、このセマンティック解析の一部です。
  • プリティプリンター(Pretty Printer): ソースコードを整形して、読みやすい形式で出力するツールです。このコミットの目的は、この出力にHTMLリンクを追加することです。
  • 識別子(Identifier): プログラム内で名前を付けるために使用される文字列(変数名、関数名、型名など)。
  • 宣言(Declaration): 識別子を導入し、その属性(型、スコープなど)を定義するコードの部分。
  • 型システム(Type System): プログラム内の値が持つ型を定義し、それらの型がどのように相互作用するかを規定する規則の集合。型チェックは、これらの規則に従ってプログラムが正しく型付けされているかを検証します。
  • スコープ(Scope): プログラム内で識別子が有効である範囲。スコープは通常、ネストされた構造を持ち、内側のスコープは外側のスコープの識別子にアクセスできますが、その逆はできません。
  • シンボルテーブル(Symbol Table): コンパイラが識別子とその宣言情報を格納するために使用するデータ構造。このコミットで導入されるObjectScopeは、シンボルテーブルの概念を具現化したものです。
  • Goの初期コンパイラ 6g: Go言語の初期に開発されたコンパイラツールチェインの一部。6gはGoのソースコードをコンパイルして実行可能なバイナリを生成しました。このコミットのコードコメントには、6gの特定の制限(循環参照の扱い)が言及されています。

技術的詳細

このコミットは、Go言語のコンパイラにおけるセマンティック解析の基盤を構築するために、複数の新しいファイルとデータ構造を導入しています。

新規ファイルと役割

  1. usr/gri/pretty/globals.go:

    • このファイルは、Go言語のオブジェクト(Object)、型(Type)、パッケージ(Package)、スコープ(Scope)、コンパイル環境(Environment)、コンパイル状態(OldCompilation)、式(Expr)、文(Stat)、リスト要素(Elem)といった、コンパイラのセマンティック解析で中心となるデータ構造の定義を集中させています。
    • コメントにあるように、本来はそれぞれの概念に対応するファイルに分割されるべきですが、当時の6gコンパイラの制限(パッケージ間の循環参照の扱いの難しさ)により、一時的にここに集約されています。
    • NewObject, NewType, NewPackage, NewScopeといったファクトリ関数も提供され、これらのオブジェクトの生成をカプセル化しています。
    • ObjectScopeOldCompilationのメソッド(Copy, Lookup, Add, Insert, InsertImportなど)も定義されており、これらのデータ構造の操作ロジックを提供します。
  2. usr/gri/pretty/object.go:

    • Go言語の「オブジェクト」の種類(CONST, TYPE, VAR, FIELD, FUNC, BUILTIN, PACKAGE, LABELなど)を定義する定数(kind)が含まれています。
    • KindStr関数は、これらのkindの整数値を対応する文字列に変換するユーティリティを提供します。
    • ここでもObjectの宣言がglobals.goにあることについて、6gの循環参照の制限がコメントで言及されています。
  3. usr/gri/pretty/type.go:

    • Go言語の「型」の形式(form)を定義する定数(VOID, BAD, FORWARD, TUPLE, NIL, BOOL, UINT, INT, FLOAT, STRING, INTEGER, ANY, ALIAS, ARRAY, STRUCT, INTERFACE, MAP, CHANNEL, FUNCTION, METHOD, POINTERなど)が含まれています。
    • Type.auxフィールドで使用されるチャネルの送受信方向を示す定数(SEND, RECV)も定義されています。
    • FormStr関数は、これらのformの整数値を対応する文字列に変換するユーティリティを提供します。
    • Equal関数は、2つの型が等しいかどうかを比較する重要なロジックを提供します。これは型チェックの根幹をなす機能です。
    • Assigneable関数は未実装ですが、型の代入互換性をチェックするためのプレースホルダーとして存在します。
    • ここでもTypeの宣言がglobals.goにあることについて、6gの循環参照の制限がコメントで言及されています。
  4. usr/gri/pretty/universe.go:

    • Go言語の「ユニバーススコープ(Universe Scope)」、つまりGoプログラム全体で常に利用可能な組み込みの識別子(bool, int, stringなどの基本型、true, false, iota, nilなどの定数、len, new, panic, printなどの組み込み関数)を初期化する役割を担います。
    • init関数内で、これらの組み込み型や定数、関数がUniverseスコープに登録されます。
    • DeclObjDeclType関数は、オブジェクトと型を宣言し、ユニバーススコープに挿入するためのヘルパー関数です。
    • Register関数は、型を登録し、エクスポートのための参照番号を割り当てます。

既存ファイルの変更点

  1. usr/gri/pretty/Makefile:

    • cleanターゲットに.aファイル(アーカイブファイル、おそらくコンパイル済みライブラリ)の削除が追加されました。
    • 最も重要な変更は、新しい依存関係の追加です。compilation.6typechecker.6に依存するようになり、typechecker.6ast.6, universe.6, globals.6, type.6に依存するようになりました。また、universe.6globals.6, object.6, type.6に、object.6globals.6に、type.6globals.6, object.6に依存するようになりました。これは、型チェックがAST、ユニバーススコープ、グローバルな型・オブジェクト定義に依存することを示しています。
  2. usr/gri/pretty/compilation.go:

    • TypeCheckerパッケージがインポートされました。
    • Compile関数内で、パーサーがプログラムを解析した後、エラーがなければTypeChecker.CheckProgram(prog)が呼び出されるようになりました。これは、コンパイルプロセスに型チェックのステップが正式に組み込まれたことを意味します。

これらの変更により、Goコンパイラはソースコードの構文解析だけでなく、意味解析(型チェック、名前解決、スコープ解決)を行うための基本的なフレームワークを手に入れました。これにより、識別子と宣言の正確な追跡が可能となり、最終的にHTMLリンク付きのプリティプリンター出力の実現に繋がります。

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

このコミットのコアとなる変更は、主に以下の新しいファイルと、既存のcompilation.goへの型チェックの組み込みです。

  • usr/gri/pretty/globals.go:
    • Object構造体の定義:
      export type Object struct {
      	exported bool;
      	pos int;  // source position (< 0 if unknown position)
      	kind int;
      	ident string;
      	typ *Type;  // nil for packages
      	pnolev int;  // >= 0: package no., <= 0: function nesting level, 0: global level
      }
      
    • Type構造体の定義:
      export type Type struct {
      	ref int;  // for exporting only: >= 0 means already exported
      	form int;
      	size int;  // in bytes
      	len int;  // array length, no. of function/method parameters (w/o recv)
      	aux int;  // channel info
      	obj *Object;  // primary type object or NULL
      	key *Type;  // alias base type or map key
      	elt *Type;  // aliased type, array, map, channel or pointer element type, function result type, tuple function type
      	scope *Scope;  // forwards, structs, interfaces, functions
      }
      
    • Scope構造体の定義:
      export type Scope struct {
      	parent *Scope;
      	entries *map[string] *Object;
      }
      
  • usr/gri/pretty/type.go:
    • Equal関数の定義(部分的に抜粋):
      export func Equal(x, y *Globals.Type) bool;
      
      func Equal0(x, y *Globals.Type) bool {
      	if x == y {
      		return true;  // identical types are equal
      	}
      
      	if x.form == BAD || y.form == BAD {
      		return true;  // bad types are always equal (avoid excess error messages)
      	}
      
      	// ... (各種型の比較ロジック) ...
      }
      
  • usr/gri/pretty/universe.go:
    • init関数での組み込み型の初期化:
      func init() {
      	scope = Globals.NewScope(nil);  // universe has no parent
      	types.Init(32);
      
      	// Interal types
      	void_typ = Globals.NewType(Type.VOID);
      	Globals.Universe_void_typ = void_typ;
      	bad_typ = Globals.NewType(Type.BAD);
      	nil_typ = Globals.NewType(Type.NIL);
      
      	// Basic types
      	bool_typ = Register(DeclType(Type.BOOL, "bool", 1));
      	// ... (他の基本型、定数、組み込み関数の初期化) ...
      }
      
  • usr/gri/pretty/compilation.go:
    • Compile関数内でのTypeChecker.CheckProgramの呼び出し:
      	prog := parser.ParseProgram();
      
      	if err.nerrors == 0 {
      		TypeChecker.CheckProgram(prog);
      	}
      	return prog, err.nerrors;
      }
      

コアとなるコードの解説

globals.goにおける主要なデータ構造

  • Object: Go言語のプログラム要素(定数、変数、型、関数など)を抽象的に表現する構造体です。

    • pos: ソースコード上の位置。HTMLリンク生成の際に重要になります。
    • kind: オブジェクトの種類(CONST, TYPE, VARなど)。
    • ident: 識別子の名前(例: myVariable, main)。
    • typ: オブジェクトの型へのポインタ。
    • pnolev: パッケージ番号または関数ネストレベル。スコープ解決に利用されます。 このObject構造体は、コンパイラがソースコード内の各識別子をシンボルテーブルに登録し、その属性を管理するための中心的なエンティティとなります。
  • Type: Go言語の型システムにおける型を表現する構造体です。

    • form: 型の形式(INT, STRING, ARRAY, STRUCT, FUNCTIONなど)。
    • size: 型のサイズ(バイト単位)。
    • elt: 配列の要素型、ポインタの指す型、チャネルの要素型など、複合型における内部の型へのポインタ。
    • key: マップのキー型。
    • scope: 構造体、インターフェース、関数などのスコープを持つ型に関連付けられたスコープ。 このType構造体は、型チェックの際に型の互換性を判断したり、型の詳細情報を取得したりするために使用されます。
  • Scope: 識別子の有効範囲を管理する構造体です。

    • parent: 親スコープへのポインタ。これにより、スコープの階層構造が形成されます。
    • entries: 識別子名(string)からObjectへのマップ。これにより、特定のスコープ内で識別子を検索できます。 Scopeは、名前解決(ある識別子がどの宣言に対応するかを見つけるプロセス)において不可欠な役割を果たします。LookupAddInsertInsertImportといったメソッドは、スコープ内でのオブジェクトの検索と追加を可能にします。

type.goにおける型比較ロジック

  • Equal(x, y *Globals.Type) bool: この関数は、2つの型xyが等しいかどうかを再帰的に判断します。
    • ポインタの同一性チェック (x == y) から始まり、BAD型(エラーによって導入された型)は常に他の型と等しいとみなされます(これにより、エラーメッセージの過剰な発生を防ぎます)。
    • 異なるform(形式)を持つ型は等しくないと判断されます。
    • 基本型(NIL, BOOL, STRING, ANY)は、形式が同じであれば等しいとされます。
    • 数値型(UINT, INT, FLOAT)は、形式とsizeが同じであれば等しいとされます。
    • 複合型(ARRAY, MAP, CHANNEL)については、その構成要素(len, elt, key, auxなど)も再帰的にEqual関数を用いて比較されます。
    • FUNCTION, METHOD, STRUCT, INTERFACE, TUPLEの比較ロジックは、このコミット時点ではまだ完全に実装されていないか、コメントアウトされていますが、その存在は将来的な型チェックの方向性を示しています。 このEqual関数は、Goの型システムにおける型の等価性を定義し、型チェックの正確性を保証するための基盤となります。

universe.goにおけるユニバーススコープの初期化

  • init()関数: Go言語のパッケージ初期化メカニズムを利用して、プログラム起動時にユニバーススコープを構築します。
    • Globals.NewScope(nil)を呼び出して、親を持たないルートスコープとしてユニバーススコープを作成します。
    • void_typ, bad_typ, nil_typといった内部的な型を定義します。
    • bool, uint8, int, float, stringなどのGoの組み込み基本型をDeclTypeRegister関数を使ってユニバーススコープに登録します。
    • true, false, iota, nilといった組み込み定数をDeclObjを使って登録します。
    • len, new, panic, printといった組み込み関数をDeclObjを使って登録します。 このinit関数は、Goプログラムが利用できる最も基本的な型、定数、関数を定義し、コンパイラがこれらを認識できるようにするための重要なステップです。

compilation.goにおける型チェックの組み込み

  • Compile関数内で、パーサーがASTを生成した後、TypeChecker.CheckProgram(prog)が呼び出されるようになりました。これは、コンパイルのフェーズにおいて、構文解析の次に意味解析(型チェック)が実行されることを明確に示しています。これにより、プログラムのセマンティックな正当性が検証され、識別子と宣言に関する情報が収集・整理されることになります。

これらのコード変更は、Goコンパイラが単にコードを解析するだけでなく、その意味を理解し、プログラム要素間の関係性を把握するための重要な一歩を示しています。これは、より高度なツール(例えば、IDEのコード補完やリファクタリング機能、そしてこのコミットの目的であるHTMLリンク付きのコード表示)を構築するための基盤となります。

関連リンク

  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Go言語のコンパイラ設計に関する初期の議論やドキュメント(もし公開されていれば、go.dev/s/golang.org/doc/以下で検索すると見つかる可能性がありますが、2008年当時のものは見つけにくいかもしれません。)
  • コンパイラ設計に関する一般的な情報源:
    • "Compilers: Principles, Techniques, and Tools" (通称 "Dragon Book")
    • "Engineering a Compiler"

参考にした情報源リンク

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

このコミットは、Go言語の初期開発段階において、コードの識別子(identifier)と宣言(declaration)を追跡するための準備作業を導入するものです。最終的な目的は、Goコードを整形して表示する「プリティプリンター(pretty printer)」の出力において、HTMLリンクを生成できるようにすることにあります。これにより、生成されたHTMLドキュメント内で、コード内の要素(変数、関数、型など)がその定義元にリンクされるようになり、コードのナビゲーションと理解が大幅に向上することが期待されます。

コミット

Author: Robert Griesemer gri@golang.org Date: Tue Dec 16 18:02:22 2008 -0800

コミットメッセージ:

Snapshot.

Preparations to track identifiers and declarations so that we can
generate good html links as pretty printer output:
- brought over old code and adjusted it
- initial hookups, nothing really running yet

R=r
OCL=21383
CL=21383

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

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

元コミット内容

commit b86359073e8268093dbff1c5d5a8ed600218c816
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Dec 16 18:02:22 2008 -0800

    Snapshot.
    
    Preparations to track identifiers and declarations so that we can
    generate good html links as pretty printer output:
    - brought over old code and adjusted it
    - initial hookups, nothing really running yet
    
    R=r
    OCL=21383
    CL=21383
---
 usr/gri/pretty/Makefile       |  12 ++-
 usr/gri/pretty/compilation.go |   6 ++
 usr/gri/pretty/globals.go     | 240 ++++++++++++++++++++++++++++++++++++++++++\n usr/gri/pretty/object.go      |  36 +++++++
 usr/gri/pretty/type.go        | 207 ++++++++++++++++++++++++++++++++++++\n usr/gri/pretty/universe.go    | 125 ++++++++++++++++++++++\n 6 files changed, 624 insertions(+), 2 deletions(-)

diff --git a/usr/gri/pretty/Makefile b/usr/gri/pretty/Makefile
index 50585fe102..cfc2bb132c 100644
--- a/usr/gri/pretty/Makefile
+++ b/usr/gri/pretty/Makefile
@@ -23,11 +23,11 @@ install: pretty
 \tcp pretty $(HOME)/bin/pretty
 
 clean:
-\trm -f pretty *.6  *~\n+\trm -f pretty *.6 *.a *~\n 
 pretty.6:	 platform.6 printer.6 compilation.6
 
-compilation.6:	 platform.6 scanner.6 parser.6 ast.6\n+compilation.6:	 platform.6 scanner.6 parser.6 ast.6 typechecker.6\n 
 ast.6:	 scanner.6
 
 @@ -39,5 +39,13 @@ platform.6:	 utils.6
 
 printer.6:	 scanner.6 ast.6
 
+typechecker.6:	ast.6 universe.6 globals.6 type.6
+\n+universe.6:	globals.6 object.6 type.6
+\n+object.6:	globals.6
+\n+type.6:	globals.6 object.6
+\n %.6:	%.go
 \t$(G) $(F) $<\ndiff --git a/usr/gri/pretty/compilation.go b/usr/gri/pretty/compilation.go
 index 9df221436a..82b6618da3 100644
 --- a/usr/gri/pretty/compilation.go
 +++ b/usr/gri/pretty/compilation.go
 @@ -10,6 +10,7 @@ import Platform \"platform\"\n import Scanner \"scanner\"\n import Parser \"parser\"\n import AST \"ast\"\n+import TypeChecker \"typechecker\"\n \n \n func assert(b bool) {\
 @@ -133,6 +134,11 @@ export func Compile(src_file string, flags *Flags) (*AST.Program, int) {\
  \tparser.Open(flags.verbose, flags.sixg, flags.deps, &scanner, tstream);\
  \n \tprog := parser.ParseProgram();\
 +\t\n+\tif err.nerrors == 0 {\
 +\t\tTypeChecker.CheckProgram(prog);\
 +\t}\n+\t\n  \treturn prog, err.nerrors;\
  }\
  \ndiff --git a/usr/gri/pretty/globals.go b/usr/gri/pretty/globals.go
 new file mode 100644
 index 0000000000..d1dc47cb0b
 --- /dev/null
 +++ b/usr/gri/pretty/globals.go
 @@ -0,0 +1,240 @@
 +// Copyright 2009 The Go Authors.  All rights reserved.\
 +// Use of this source code is governed by a BSD-style\
 +// license that can be found in the LICENSE file.\
 +\n+package Globals\
 +\n+\n+// The following types should really be in their respective files\
 +// (object.go, type.go, scope.go, package.go, compilation.go, etc.) but\n +// they refer to each other and we don\'t know how to handle forward\n +// declared pointers across packages yet.\n +\n+\n+// ----------------------------------------------------------------------------\n +\n+type Type struct\
 +type Scope struct\
 +type Elem struct\
 +type OldCompilation struct\
 +\n+// Object represents a language object, such as a constant, variable, type,\n +// etc. (kind). An objects is (pre-)declared at a particular position in the\n +// source code (pos), has a name (ident), a type (typ), and a package number\n +// or nesting level (pnolev).\n +\n+export type Object struct {\
 +\texported bool;\
 +\tpos int;  // source position (< 0 if unknown position)\
 +\tkind int;\
 +\tident string;\
 +\ttyp *Type;  // nil for packages
 +\tpnolev int;  // >= 0: package no., <= 0: function nesting level, 0: global level
 +}\
 +\n+\n+export type Type struct {\
 +\tref int;  // for exporting only: >= 0 means already exported
 +\tform int;\
 +\tsize int;  // in bytes
 +\tlen int;  // array length, no. of function/method parameters (w/o recv)
 +\taux int;  // channel info
 +\tobj *Object;  // primary type object or NULL
 +\tkey *Type;  // alias base type or map key
 +\telt *Type;  // aliased type, array, map, channel or pointer element type, function result type, tuple function type
 +\tscope *Scope;  // forwards, structs, interfaces, functions
 +}\
 +\n+\n+export type Package struct {\
 +\tref int;  // for exporting only: >= 0 means already exported
 +\tfile_name string;\
 +\tkey string;\
 +\tobj *Object;\
 +\tscope *Scope;  // holds the (global) objects in this package
 +}\
 +\n+\n+export type Scope struct {\
 +\tparent *Scope;\
 +\tentries *map[string] *Object;\
 +}\
 +\n+\n+export type Environment struct {\
 +\tError *(comp *OldCompilation, pos int, msg string);\
 +\tImport *(comp *OldCompilation, pkg_file string) *Package;\
 +\tExport *(comp *OldCompilation, pkg_file string);\
 +\tCompile *(comp *OldCompilation, src_file string);\
 +}\
 +\n+\n+export type OldCompilation struct {\
 +\t// environment
 +\tenv *Environment;\
 +\t\n+\t// TODO rethink the need for this here
 +\tsrc_file string;\
 +\tsrc string;\
 +\t\t\n+\t// Error handling
 +\tnerrors int;  // number of errors reported
 +\terrpos int;  // last error position
 +\t\n+\t// TODO use open arrays eventually
 +\tpkg_list [256] *Package;  // pkg_list[0] is the current package
 +\tpkg_ref int;\
 +}\
 +\n+\n+export type Expr interface {\
 +\top() int;  // node operation
 +\tpos() int;  // source position
 +\ttyp() *Type;\
 +\t// ... more to come here
 +}\
 +\n+\n+export type Stat interface {\
 +\t// ... more to come here
 +}\
 +\n+\n+// TODO This is hideous! We need to have a decent way to do lists.\
 +// Ideally open arrays that allow \'+\'.\n +\n+export type Elem struct {\
 +\tnext *Elem;\
 +\tval int;\
 +\tstr string;\
 +\tobj *Object;\
 +\ttyp *Type;\
 +\texpr Expr
 +}\
 +\n+\n+// ----------------------------------------------------------------------------\n +// Creation
 +\n+export var Universe_void_typ *Type  // initialized by Universe to Universe.void_typ
 +\n+export func NewObject(pos, kind int, ident string) *Object {\
 +\tobj := new(Object);\
 +\tobj.exported = false;\
 +\tobj.pos = pos;\
 +\tobj.kind = kind;\
 +\tobj.ident = ident;\
 +\tobj.typ = Universe_void_typ;\
 +\tobj.pnolev = 0;\
 +\treturn obj;\
 +}\
 +\n+\n+export func NewType(form int) *Type {\
 +\ttyp := new(Type);\
 +\ttyp.ref = -1;  // not yet exported
 +\ttyp.form = form;\
 +\treturn typ;\
 +}\
 +\n+\n+export func NewPackage(file_name string, obj *Object, scope *Scope) *Package {\
 +\tpkg := new(Package);\
 +\tpkg.ref = -1;  // not yet exported
 +\tpkg.file_name = file_name;\
 +\tpkg.key = \"<the package key>\";  // empty key means package forward declaration
 +\tpkg.obj = obj;\
 +\tpkg.scope = scope;\
 +\treturn pkg;\
 +}\
 +\n+\n+export func NewScope(parent *Scope) *Scope {\
 +\tscope := new(Scope);\
 +\tscope.parent = parent;\
 +\tscope.entries = new(map[string]*Object, 8);\
 +\treturn scope;\
 +}\
 +\n+\n+// ----------------------------------------------------------------------------\n +// Object methods
 +\n+func (obj *Object) Copy() *Object {\
 +\tcopy := new(Object);\
 +\tcopy.exported = obj.exported;\
 +\tcopy.pos = obj.pos;\
 +\tcopy.kind = obj.kind;\
 +\tcopy.ident = obj.ident;\
 +\tcopy.typ = obj.typ;\
 +\tcopy.pnolev = obj.pnolev;\
 +\treturn copy;\
 +}\
 +\n+\n+// ----------------------------------------------------------------------------\n +// Scope methods
 +\n+func (scope *Scope) Lookup(ident string) *Object {\
 +\tobj, found := scope.entries[ident];\
 +\tif found {\
 +\t\treturn obj;\
 +\t}\
 +\treturn nil;\
 +}\
 +\n+\n+func (scope *Scope) Add(obj* Object) {\
 +\tscope.entries[obj.ident] = obj;\
 +}\
 +\n+\n+func (scope *Scope) Insert(obj *Object) {\
 +\tif scope.Lookup(obj.ident) != nil {\
 +\t\tpanic(\"obj already inserted\");\n +\t}\
 +\tscope.Add(obj);\
 +}\
 +\n+\n+func (scope *Scope) InsertImport(obj *Object) *Object {\
 +\t p := scope.Lookup(obj.ident);\
 +\t if p == nil {\
 +\t\tscope.Add(obj);\
 +\t\tp = obj;\
 +\t }\
 +\t return p;\
 +}\
 +\n+\n+func (scope *Scope) Print() {\
 +\tprint(\"scope {\");\n +\tfor key := range scope.entries {\
 +\t\tprint(\"\\n  \", key);\
 +\t}\n +\tprint(\"\\n}\\n\");\n +}\
 +\n+\n+// ----------------------------------------------------------------------------\n +// Compilation methods
 +\n+func (C *OldCompilation) Lookup(file_name string) *Package {\
 +\tfor i := 0; i < C.pkg_ref; i++ {\
 +\t\tpkg := C.pkg_list[i];\
 +\t\tif pkg.file_name == file_name {\
 +\t\t\treturn pkg;\
 +\t\t}\n +\t}\n +\treturn nil;\
 +}\
 +\n+\n+func (C *OldCompilation) Insert(pkg *Package) {\
 +\tif C.Lookup(pkg.file_name) != nil {\
 +\t\tpanic(\"package already inserted\");\n +\t}\n +\tpkg.obj.pnolev = C.pkg_ref;\
 +\tC.pkg_list[C.pkg_ref] = pkg;\
 +\tC.pkg_ref++;\
 +}\ndiff --git a/usr/gri/pretty/object.go b/usr/gri/pretty/object.go
 new file mode 100755
 index 0000000000..220f4c8d8b
 --- /dev/null
 +++ b/usr/gri/pretty/object.go
 @@ -0,0 +1,36 @@
 +// Copyright 2009 The Go Authors.  All rights reserved.\
 +// Use of this source code is governed by a BSD-style\
 +// license that can be found in the LICENSE file.\
 +\n+package Object\
 +\n+import Globals \"globals\"\
 +\n+\n+export const /* kind */ (\
 +\tBAD = iota;  // error handling
 +\tCONST; TYPE; VAR; FIELD; FUNC; BUILTIN; PACKAGE; LABEL;
 +\tEND;  // end of scope (import/export only)
 +)\
 +\n+\n+// The \'Object\' declaration should be here as well, but 6g cannot handle\
 +// this due to cross-package circular references. For now it\'s all in
 +// globals.go.\n +\n+\n+export func KindStr(kind int) string {\
 +\tswitch kind {\
 +\tcase BAD: return \"BAD\";
 +\tcase CONST: return \"CONST\";
 +\tcase TYPE: return \"TYPE\";
 +\tcase VAR: return \"VAR\";
 +\tcase FIELD: return \"FIELD\";
 +\tcase FUNC: return \"FUNC\";
 +\tcase BUILTIN: return \"BUILTIN\";
 +\tcase PACKAGE: return \"PACKAGE\";
 +\tcase LABEL: return \"LABEL\";
 +\tcase END: return \"END\";
 +\t}\
 +\treturn \"<unknown Object kind>\";
 +}\ndiff --git a/usr/gri/pretty/type.go b/usr/gri/pretty/type.go
 new file mode 100644
 index 0000000000..507357e65d
 --- /dev/null
 +++ b/usr/gri/pretty/type.go
 @@ -0,0 +1,207 @@
 +// Copyright 2009 The Go Authors.  All rights reserved.\
 +// Use of this source code is governed by a BSD-style\
 +// license that can be found in the LICENSE file.\
 +\n+package Type\
 +\n+import Globals \"globals\"\
 +import Object \"object\"\
 +\n+\n+export const /* form */ (\
 +\t// internal types
 +\t// We should never see one of these.\
 +\tUNDEF = iota;\
 +\t\n+\t// VOID types are used when we don\'t have a type. Never exported.\
 +\t// (exported type forms must be > 0)\
 +\tVOID;\
 +\t\n+\t// BAD types are compatible with any type and don\'t cause further errors.\
 +\t// They are introduced only as a result of an error in the source code. A\n +\t// correct program cannot have BAD types.\n +\tBAD;\
 +\t\n+\t// FORWARD types are forward-declared (incomplete) types. They can only\n +\t// be used as element types of pointer types and must be resolved before\n +\t// their internals are accessible.\n +\tFORWARD;\
 +\n+\t// TUPLE types represent multi-valued result types of functions and\n +\t// methods.\n +\tTUPLE;\
 +\t\n+\t// The type of nil.\n +\tNIL;\
 +\n+\t// basic types
 +\tBOOL; UINT; INT; FLOAT; STRING; INTEGER;\
 +\t\n+\t// \'any\' type  // TODO this should go away eventually
 +\tANY;\
 +\t\n+\t// composite types
 +\tALIAS; ARRAY; STRUCT; INTERFACE; MAP; CHANNEL; FUNCTION; METHOD; POINTER;\
 +)\
 +\n+\n+export const /* Type.aux */ (\
 +\tSEND = 1;  // chan>
 +\tRECV = 2;  // chan<
 +)\
 +\n+\n+// The \'Type\' declaration should be here as well, but 6g cannot handle\
 +// this due to cross-package circular references. For now it\'s all in
 +// globals.go.\n +\n+\n+export func FormStr(form int) string {\
 +\tswitch form {\
 +\tcase VOID: return \"VOID\";
 +\tcase BAD: return \"BAD\";
 +\tcase FORWARD: return \"FORWARD\";
 +\tcase TUPLE: return \"TUPLE\";
 +\tcase NIL: return \"NIL\";
 +\tcase BOOL: return \"BOOL\";
 +\tcase UINT: return \"UINT\";
 +\tcase INT: return \"INT\";
 +\tcase FLOAT: return \"FLOAT\";
 +\tcase STRING: return \"STRING\";
 +\tcase ANY: return \"ANY\";
 +\tcase ALIAS: return \"ALIAS\";
 +\tcase ARRAY: return \"ARRAY\";
 +\tcase STRUCT: return \"STRUCT\";
 +\tcase INTERFACE: return \"INTERFACE\";
 +\tcase MAP: return \"MAP\";
 +\tcase CHANNEL: return \"CHANNEL\";
 +\tcase FUNCTION: return \"FUNCTION\";
 +\tcase METHOD: return \"METHOD\";
 +\tcase POINTER: return \"POINTER\";
 +\t}\
 +\treturn \"<unknown Type form>\";
 +}\
 +\n+\n+export func Equal(x, y *Globals.Type) bool;\
 +\n+func Equal0(x, y *Globals.Type) bool {\
 +\tif x == y {\
 +\t\treturn true;  // identical types are equal
 +\t}\
 +\n+\tif x.form == BAD || y.form == BAD {\
 +\t\treturn true;  // bad types are always equal (avoid excess error messages)
 +\t}\
 +\n+\t// TODO where to check for *T == nil ?  
 +\tif x.form != y.form {\
 +\t\treturn false;  // types of different forms are not equal
 +\t}\n +\n+\tswitch x.form {\
 +\tcase FORWARD, BAD:\
 +\t\tbreak;\
 +\n+\tcase NIL, BOOL, STRING, ANY:\
 +\t\treturn true;\
 +\n+\tcase UINT, INT, FLOAT:\
 +\t\treturn x.size == y.size;\
 +\n+\tcase ARRAY:\
 +\t\treturn\
 +\t\t\tx.len == y.len &&\
 +\t\t\tEqual(x.elt, y.elt);\
 +\n+\tcase MAP:\
 +\t\treturn\
 +\t\t\tEqual(x.key, y.key) &&\
 +\t\t\tEqual(x.elt, y.elt);\
 +\n+\tcase CHANNEL:\
 +\t\treturn\
 +\t\t\tx.aux == y.aux &&\
 +\t\t\tEqual(x.elt, y.elt);\
 +\n+\tcase FUNCTION, METHOD:\
 +\t\t{\tpanic();\
 +\t\t\t/*
 +\t\t\txp := x.scope.entries;\
 +\t\t\typ := x.scope.entries;\
 +\t\t\tif\tx.len != y.len &&  // number of parameters
 +\t\t\t\txp.len != yp.len  // recv + parameters + results
 +\t\t\t{\n+\t\t\t\treturn false;\n+\t\t\t}\n+\t\t\tfor p, q := xp.first, yp.first; p != nil; p, q = p.next, q.next {\
 +\t\t\t\txf := p.obj;\
 +\t\t\t\tyf := q.obj;\
 +\t\t\t\tif xf.kind != Object.VAR || yf.kind != Object.VAR {\
 +\t\t\t\t\tpanic(\"parameters must be vars\");
 +\t\t\t\t}\n+\t\t\t\tif !Equal(xf.typ, yf.typ) {\
 +\t\t\t\t\treturn false;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\t*/
 +\t\t}\n+\t\treturn true;\n+\n+\tcase STRUCT:\
 +\t\t/*
 +\t\t{\tObjList* xl = &x.scope.list;\
 +\t\t\tObjList* yl = &y.scope.list;\
 +\t\t\tif xl.len() != yl.len() {\
 +\t\t\t\treturn false;  // scopes of different sizes are not equal
 +\t\t\t}\n+\t\t\tfor int i = xl.len(); i-- > 0; {\
 +\t\t\t\tObject* xf = (*xl)[i];
 +\t\t\t\tObject* yf = (*yl)[i];
 +\t\t\t\tASSERT(xf.kind == Object.VAR && yf.kind == Object.VAR);\
 +\t\t\t\tif xf.name != yf.name) || ! EqualTypes(xf.type(), yf.type() {\
 +\t\t\t\t\treturn false;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\treturn true;\n+\t\t*/
 +\t\t// Scopes must be identical for them to be equal.\
 +\t\t// If we reach here, they weren\'t.\
 +\t\treturn false;\
 +\n+\tcase INTERFACE:\
 +\t\tpanic(\"UNIMPLEMENTED\");
 +\t\treturn false;\
 +\n+\tcase POINTER:\
 +\t\treturn Equal(x.elt, y.elt);\
 +\t\t\n+\tcase TUPLE:\
 +\t\tpanic(\"UNIMPLEMENTED\");
 +\t\treturn false;\
 +\t}\n +\n+\tpanic(\"UNREACHABLE\");
 +\treturn false;\
 +}\
 +\n+\n+export func Equal(x, y *Globals.Type) bool {\
 +\tres := Equal0(x, y);\
 +\t// TODO should do the check below only in debug mode
 +\tif Equal0(y, x) != res {\
 +\t\tpanic(\"type equality must be symmetric\");
 +\t}\n +\treturn res;\
 +}\
 +\n+\n+export func Assigneable(from, to *Globals.Type) bool {\
 +\tif Equal(from, to) {\
 +\t\treturn true;\
 +\t}\n +\t\n+\tpanic(\"UNIMPLEMENTED\");
 +\treturn false;\
 +}\ndiff --git a/usr/gri/pretty/universe.go b/usr/gri/pretty/universe.go
 new file mode 100755
 index 0000000000..fb199ec353
 --- /dev/null
 +++ b/usr/gri/pretty/universe.go
 @@ -0,0 +1,125 @@
 +// Copyright 2009 The Go Authors.  All rights reserved.\
 +// Use of this source code is governed by a BSD-style\
 +// license that can be found in the LICENSE file.\
 +\n+package Universe\
 +\n+import (\
 +\t\"array\"\
 +\tGlobals \"globals\";
 +\tObject \"object\";
 +\tType \"type\";
 +)\n +\n+\n+export var (\
 +\tscope *Globals.Scope;\
 +\ttypes array.Array;\
 +\t\n+\t// internal types
 +\tvoid_typ,\
 +\tbad_typ,\
 +\tnil_typ,\
 +\t\n+\t// basic types
 +\tbool_typ,\
 +\tuint8_typ,\
 +\tuint16_typ,\
 +\tuint32_typ,\
 +\tuint64_typ,\
 +\tint8_typ,\
 +\tint16_typ,\
 +\tint32_typ,\
 +\tint64_typ,\
 +\tfloat32_typ,\
 +\tfloat64_typ,\
 +\tfloat80_typ,\
 +\tstring_typ,\
 +\tinteger_typ,\
 +\t\n+\t// convenience types
 +\tbyte_typ,\
 +\tuint_typ,\
 +\tint_typ,\
 +\tfloat_typ,\
 +\tuintptr_typ *Globals.Type;\
 +\t\n+\ttrue_obj,\
 +\tfalse_obj,\
 +\tiota_obj,\
 +\tnil_obj *Globals.Object;\
 +)\n +\n+\n+func DeclObj(kind int, ident string, typ *Globals.Type) *Globals.Object {\
 +\tobj := Globals.NewObject(-1 /* no source pos */, kind, ident);\
 +\tobj.typ = typ;\
 +\tif kind == Object.TYPE && typ.obj == nil {\
 +\t\ttyp.obj = obj;  // set primary type object
 +\t}\n +\tscope.Insert(obj);\
 +\treturn obj
 +}\
 +\n+\n+func DeclType(form int, ident string, size int) *Globals.Type {\
 +  typ := Globals.NewType(form);\
 +  typ.size = size;\
 +  return DeclObj(Object.TYPE, ident, typ).typ;\
 +}\
 +\n+\n+func Register(typ *Globals.Type) *Globals.Type {\
 +\ttyp.ref = types.Len();\
 +\ttypes.Push(typ);\
 +\treturn typ;\
 +}\
 +\n+\n+func init() {\
 +\tscope = Globals.NewScope(nil);  // universe has no parent
 +\ttypes.Init(32);\
 +\t\n+\t// Interal types
 +\tvoid_typ = Globals.NewType(Type.VOID);\
 +\tGlobals.Universe_void_typ = void_typ;\
 +\tbad_typ = Globals.NewType(Type.BAD);\
 +\tnil_typ = Globals.NewType(Type.NIL);\
 +\t\n+\t// Basic types
 +\tbool_typ = Register(DeclType(Type.BOOL, \"bool\", 1));
 +\tuint8_typ = Register(DeclType(Type.UINT, \"uint8\", 1));
 +\tuint16_typ = Register(DeclType(Type.UINT, \"uint16\", 2));
 +\tuint32_typ = Register(DeclType(Type.UINT, \"uint32\", 4));
 +\tuint64_typ = Register(DeclType(Type.UINT, \"uint64\", 8));
 +\tint8_typ = Register(DeclType(Type.INT, \"int8\", 1));
 +\tint16_typ = Register(DeclType(Type.INT, \"int16\", 2));
 +\tint32_typ = Register(DeclType(Type.INT, \"int32\", 4));
 +\tint64_typ = Register(DeclType(Type.INT, \"int64\", 8));
 +\tfloat32_typ = Register(DeclType(Type.FLOAT, \"float32\", 4));
 +\tfloat64_typ = Register(DeclType(Type.FLOAT, \"float64\", 8));
 +\tfloat80_typ = Register(DeclType(Type.FLOAT, \"float80\", 10));
 +\tstring_typ = Register(DeclType(Type.STRING, \"string\", 8));
 +\tinteger_typ = Register(DeclType(Type.INTEGER, \"integer\", 8));
 +\n+\t// All but \'byte\' should be platform-dependent, eventually.\
 +\tbyte_typ = Register(DeclType(Type.UINT, \"byte\", 1));
 +\tuint_typ = Register(DeclType(Type.UINT, \"uint\", 4));
 +\tint_typ = Register(DeclType(Type.INT, \"int\", 4));
 +\tfloat_typ = Register(DeclType(Type.FLOAT, \"float\", 4));
 +\tuintptr_typ = Register(DeclType(Type.UINT, \"uintptr\", 8));
 +\n+\t// Predeclared constants
 +\ttrue_obj = DeclObj(Object.CONST, \"true\", bool_typ);\
 +\tfalse_obj = DeclObj(Object.CONST, \"false\", bool_typ);\
 +\tiota_obj = DeclObj(Object.CONST, \"iota\", int_typ);\
 +\tnil_obj = DeclObj(Object.CONST, \"nil\", nil_typ);\
 +\n+\t// Builtin functions
 +\tDeclObj(Object.BUILTIN, \"len\", void_typ);\
 +\tDeclObj(Object.BUILTIN, \"new\", void_typ);\
 +\tDeclObj(Object.BUILTIN, \"panic\", void_typ);\
 +\tDeclObj(Object.BUILTIN, \"print\", void_typ);\
 +\t\n+\t// scope.Print();
 +}\

変更の背景

このコミットは、Go言語のコンパイラおよびツールチェインの初期段階における重要な機能拡張の一環です。コミットメッセージにある「プリティプリンター出力として良いHTMLリンクを生成できるように、識別子と宣言を追跡するための準備」という記述が、この変更の核心を突いています。

当時のGo言語のツールチェインには、ソースコードを解析し、その構造を理解するための基本的なコンポーネント(スキャナー、パーサー、ASTビルダー)は存在していましたが、それらの要素間のセマンティックな関係(例えば、ある変数がどこで宣言され、どの型を持つか)を詳細に追跡する仕組みはまだ不十分でした。

この機能は、Goの公式ドキュメントやコードブラウザのようなツールを構築する上で不可欠です。例えば、Goの標準ライブラリのドキュメントでは、関数名や型名をクリックするとその定義にジャンプできます。このような機能を実現するためには、コンパイラがソースコード内の各識別子がどの宣言に対応しているかを正確に把握し、その位置情報を保持する必要があります。

このコミットは、そのための基盤となるデータ構造(ObjectTypeScopeなど)と、それらを管理するロジックを導入しています。特に、型チェック(TypeChecker)の概念が導入され、コンパイルプロセスに組み込まれることで、識別子と宣言のセマンティックな解析が可能になります。

また、globals.goのコメントにある「The following types should really be in their respective files... but they refer to each other and we don't know how to handle forward declared pointers across packages yet.」という記述は、当時のGoコンパイラ(6g)の制限、特にパッケージ間の循環参照の扱いの難しさを示唆しています。Go言語はパッケージ間の循環インポートを許可していませんが、データ構造間の循環参照はガベージコレクションによって問題なく処理されます。しかし、このコメントは、コンパイラの実装上の制約として、型定義を複数のファイルに分割する際の課題があったことを示しています。このため、本来は分離されるべき型定義がglobals.goに集約されているという背景があります。

前提知識の解説

このコミットを理解するためには、以下の概念についての基本的な知識が必要です。

  • コンパイラのフロントエンド:
    • スキャナー(Lexer): ソースコードをトークン(最小単位の語彙要素)に分解します。
    • パーサー(Parser): トークンのストリームを受け取り、言語の文法規則に従って抽象構文木(AST: Abstract Syntax Tree)を構築します。
    • 抽象構文木(AST): ソースコードの構造を木構造で表現したものです。
    • セマンティックアナライザー(意味解析器): ASTを走査し、型チェック、名前解決、スコープ解決など、プログラムの意味的な正当性を検証します。このコミットで導入される「識別子と宣言の追跡」や「型チェック」は、このセマンティック解析の一部です。
  • プリティプリンター(Pretty Printer): ソースコードを整形して、読みやすい形式で出力するツールです。このコミットの目的は、この出力にHTMLリンクを追加することです。Go言語でHTMLを整形するライブラリはいくつか存在しますが、このコミットの目的は、Goコードの構造を解析し、その要素にリンクを付与することにあります。
  • 識別子(Identifier): プログラム内で名前を付けるために使用される文字列(変数名、関数名、型名など)。
  • 宣言(Declaration): 識別子を導入し、その属性(型、スコープなど)を定義するコードの部分。
  • 型システム(Type System): プログラム内の値が持つ型を定義し、それらの型がどのように相互作用するかを規定する規則の集合。型チェックは、これらの規則に従ってプログラムが正しく型付けされているかを検証します。
  • スコープ(Scope): プログラム内で識別子が有効である範囲。スコープは通常、ネストされた構造を持ち、内側のスコープは外側のスコープの識別子にアクセスできますが、その逆はできません。Go言語は字句スコープ(lexical scoping)を採用しており、識別子の可視性はソースコード上の位置によってコンパイル時に決定されます。
  • シンボルテーブル(Symbol Table): コンパイラが識別子とその宣言情報を格納するために使用するデータ構造。このコミットで導入されるObjectScopeは、シンボルテーブルの概念を具現化したものです。
  • Goの初期コンパイラ 6g: Go言語の初期に開発されたコンパイラツールチェインの一部。6gはGoのソースコードをコンパイルして実行可能なバイナリを生成しました。このコミットのコードコメントには、6gの特定の制限(循環参照の扱い)が言及されています。

技術的詳細

このコミットは、Go言語のコンパイラにおけるセマンティック解析の基盤を構築するために、複数の新しいファイルとデータ構造を導入しています。

新規ファイルと役割

  1. usr/gri/pretty/globals.go:

    • このファイルは、Go言語のオブジェクト(Object)、型(Type)、パッケージ(Package)、スコープ(Scope)、コンパイル環境(Environment)、コンパイル状態(OldCompilation)、式(Expr)、文(Stat)、リスト要素(Elem)といった、コンパイラのセマンティック解析で中心となるデータ構造の定義を集中させています。
    • コメントにあるように、本来はそれぞれの概念に対応するファイルに分割されるべきですが、当時の6gコンパイラの制限(パッケージ間の循環参照の扱いの難しさ)により、一時的にここに集約されています。
    • NewObject, NewType, NewPackage, NewScopeといったファクトリ関数も提供され、これらのオブジェクトの生成をカプセル化しています。
    • ObjectScopeOldCompilationのメソッド(Copy, Lookup, Add, Insert, InsertImportなど)も定義されており、これらのデータ構造の操作ロジックを提供します。
  2. usr/gri/pretty/object.go:

    • Go言語の「オブジェクト」の種類(CONST, TYPE, VAR, FIELD, FUNC, BUILTIN, PACKAGE, LABELなど)を定義する定数(kind)が含まれています。これらの定数はiotaを用いて定義されており、Goにおける列挙型の一般的なパターンを示しています。
    • KindStr関数は、これらのkindの整数値を対応する文字列に変換するユーティリティを提供します。
    • ここでもObjectの宣言がglobals.goにあることについて、6gの循環参照の制限がコメントで言及されています。
  3. usr/gri/pretty/type.go:

    • Go言語の「型」の形式(form)を定義する定数(VOID, BAD, FORWARD, TUPLE, NIL, BOOL, UINT, INT, FLOAT, STRING, INTEGER, ANY, ALIAS, ARRAY, STRUCT, INTERFACE, MAP, CHANNEL, FUNCTION, METHOD, POINTERなど)が含まれています。
    • Type.auxフィールドで使用されるチャネルの送受信方向を示す定数(SEND, RECV)も定義されています。
    • FormStr関数は、これらのformの整数値を対応する文字列に変換するユーティリティを提供します。
    • Equal関数は、2つの型が等しいかどうかを比較する重要なロジックを提供します。これは型チェックの根幹をなす機能です。この関数は、型の形式に基づいて再帰的に比較を行い、配列、マップ、チャネルなどの複合型についてもその要素型を比較します。
    • Assigneable関数は未実装ですが、型の代入互換性をチェックするためのプレースホルダーとして存在します。
    • ここでもTypeの宣言がglobals.goにあることについて、6gの循環参照の制限がコメントで言及されています。
  4. usr/gri/pretty/universe.go:

    • Go言語の「ユニバーススコープ(Universe Block)」、つまりGoプログラム全体で常に利用可能な組み込みの識別子(bool, int, stringなどの基本型、true, false, iota, nilなどの定数、len, new, panic, printなどの組み込み関数)を初期化する役割を担います。
    • init関数内で、これらの組み込み型や定数、関数がUniverseスコープに登録されます。Goのinit関数は、パッケージがインポートされた際に自動的に実行される特別な関数です。
    • DeclObjDeclType関数は、オブジェクトと型を宣言し、ユニバーススコープに挿入するためのヘルパー関数です。
    • Register関数は、型を登録し、エクスポートのための参照番号を割り当てます。

既存ファイルの変更点

  1. usr/gri/pretty/Makefile:

    • cleanターゲットに.aファイル(アーカイブファイル、おそらくコンパイル済みライブラリ)の削除が追加されました。
    • 最も重要な変更は、新しい依存関係の追加です。compilation.6typechecker.6に依存するようになり、typechecker.6ast.6, universe.6, globals.6, type.6に依存するようになりました。また、universe.6globals.6, object.6, type.6に、object.6globals.6に、type.6globals.6, object.6に依存するようになりました。これは、型チェックがAST、ユニバーススコープ、グローバルな型・オブジェクト定義に依存することを示しています。この依存関係の追加は、コンパイラのビルドプロセスにおける重要な変更です。
  2. usr/gri/pretty/compilation.go:

    • TypeCheckerパッケージがインポートされました。
    • Compile関数内で、パーサーがプログラムを解析した後、エラーがなければTypeChecker.CheckProgram(prog)が呼び出されるようになりました。これは、コンパイルプロセスに型チェックのステップが正式に組み込まれたことを意味します。これにより、構文解析によって構築されたASTに対して、意味的な検証が行われるようになります。

これらの変更により、Goコンパイラはソースコードの構文解析だけでなく、意味解析(型チェック、名前解決、スコープ解決)を行うための基本的なフレームワークを手に入れました。これにより、識別子と宣言の正確な追跡が可能となり、最終的にHTMLリンク付きのプリティプリンター出力の実現に繋がります。

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

このコミットのコアとなる変更は、主に以下の新しいファイルと、既存のcompilation.goへの型チェックの組み込みです。

  • usr/gri/pretty/globals.go:
    • Object構造体の定義:
      export type Object struct {
      	exported bool;
      	pos int;  // source position (< 0 if unknown position)
      	kind int;
      	ident string;
      	typ *Type;  // nil for packages
      	pnolev int;  // >= 0: package no., <= 0: function nesting level, 0: global level
      }
      
    • Type構造体の定義:
      export type Type struct {
      	ref int;  // for exporting only: >= 0 means already exported
      	form int;
      	size int;  // in bytes
      	len int;  // array length, no. of function/method parameters (w/o recv)
      	aux int;  // channel info
      	obj *Object;  // primary type object or NULL
      	key *Type;  // alias base type or map key
      	elt *Type;  // aliased type, array, map, channel or pointer element type, function result type, tuple function type
      	scope *Scope;  // forwards, structs, interfaces, functions
      }
      
    • Scope構造体の定義:
      export type Scope struct {
      	parent *Scope;
      	entries *map[string] *Object;
      }
      
  • usr/gri/pretty/type.go:
    • Equal関数の定義(部分的に抜粋):
      export func Equal(x, y *Globals.Type) bool;
      
      func Equal0(x, y *Globals.Type) bool {
      	if x == y {
      		return true;  // identical types are equal
      	}
      
      	if x.form == BAD || y.form == BAD {
      		return true;  // bad types are always equal (avoid excess error messages)
      	}
      
      	// ... (各種型の比較ロジック) ...
      }
      
  • usr/gri/pretty/universe.go:
    • init関数での組み込み型の初期化:
      func init() {
      	scope = Globals.NewScope(nil);  // universe has no parent
      	types.Init(32);
      
      	// Interal types
      	void_typ = Globals.NewType(Type.VOID);
      	Globals.Universe_void_typ = void_typ;
      	bad_typ = Globals.NewType(Type.BAD);
      	nil_typ = Globals.NewType(Type.NIL);
      
      	// Basic types
      	bool_typ = Register(DeclType(Type.BOOL, "bool", 1));
      	// ... (他の基本型、定数、組み込み関数の初期化) ...
      }
      
  • usr/gri/pretty/compilation.go:
    • Compile関数内でのTypeChecker.CheckProgramの呼び出し:
      	prog := parser.ParseProgram();
      
      	if err.nerrors == 0 {
      		TypeChecker.CheckProgram(prog);
      	}
      	return prog, err.nerrors;
      }
      

コアとなるコードの解説

globals.goにおける主要なデータ構造

  • Object: Go言語のプログラム要素(定数、変数、型、関数など)を抽象的に表現する構造体です。

    • pos: ソースコード上の位置。HTMLリンク生成の際に重要になります。
    • kind: オブジェクトの種類(CONST, TYPE, VARなど)。object.goで定義される定数に対応します。
    • ident: 識別子の名前(例: myVariable, main)。
    • typ: オブジェクトの型へのポインタ。nilの場合、パッケージオブジェクトなど型を持たないものを示します。
    • pnolev: パッケージ番号または関数ネストレベル。スコープ解決に利用されます。0はグローバルレベルを示します。 このObject構造体は、コンパイラがソースコード内の各識別子をシンボルテーブルに登録し、その属性を管理するための中心的なエンティティとなります。
  • Type: Go言語の型システムにおける型を表現する構造体です。

    • ref: エクスポート用。エクスポート済みであれば0以上。
    • form: 型の形式(INT, STRING, ARRAY, STRUCT, FUNCTIONなど)。type.goで定義される定数に対応します。
    • size: 型のサイズ(バイト単位)。
    • len: 配列の長さ、関数/メソッドのパラメータ数(レシーバを除く)。
    • aux: チャネルの送受信情報(SEND, RECV)。
    • obj: プライマリ型オブジェクトへのポインタ。
    • key: マップのキー型、またはエイリアスの基底型。
    • elt: エイリアスされた型、配列、マップ、チャネル、ポインタの要素型、関数の結果型、タプル関数の型。
    • scope: 構造体、インターフェース、関数など、スコープを持つ型に関連付けられたスコープ。 このType構造体は、型チェックの際に型の互換性を判断したり、型の詳細情報を取得したりするために使用されます。
  • Package: Go言語のパッケージを表現する構造体です。

    • ref: エクスポート用。
    • file_name: パッケージのファイル名。
    • key: パッケージのキー(空の場合は前方宣言)。
    • obj: パッケージオブジェクトへのポインタ。
    • scope: このパッケージ内の(グローバルな)オブジェクトを保持するスコープ。
  • Scope: 識別子の有効範囲を管理する構造体です。

    • parent: 親スコープへのポインタ。これにより、スコープの階層構造が形成されます。
    • entries: 識別子名(string)からObjectへのマップ。これにより、特定のスコープ内で識別子を検索できます。 Scopeは、名前解決(ある識別子がどの宣言に対応するかを見つけるプロセス)において不可欠な役割を果たします。LookupAddInsertInsertImportといったメソッドは、スコープ内でのオブジェクトの検索と追加を可能にします。
  • OldCompilation: コンパイルの状態を保持する構造体です。

    • env: 環境情報。
    • src_file, src: ソースファイル名とソースコード。
    • nerrors, errpos: エラー処理関連。
    • pkg_list, pkg_ref: パッケージリストと参照。

type.goにおける型比較ロジック

  • Equal(x, y *Globals.Type) bool: この関数は、2つの型xyが等しいかどうかを再帰的に判断します。
    • ポインタの同一性チェック (x == y) から始まり、BAD型(エラーによって導入された型)は常に他の型と等しいとみなされます(これにより、エラーメッセージの過剰な発生を防ぎます)。
    • 異なるform(形式)を持つ型は等しくないと判断されます。
    • 基本型(NIL, BOOL, STRING, ANY)は、形式が同じであれば等しいとされます。
    • 数値型(UINT, INT, FLOAT)は、形式とsizeが同じであれば等しいとされます。
    • 複合型(ARRAY, MAP, CHANNEL)については、その構成要素(len, elt, key, auxなど)も再帰的にEqual関数を用いて比較されます。
    • FUNCTION, METHOD, STRUCT, INTERFACE, TUPLEの比較ロジックは、このコミット時点ではまだ完全に実装されていないか、コメントアウトされていますが、その存在は将来的な型チェックの方向性を示しています。 このEqual関数は、Goの型システムにおける型の等価性を定義し、型チェックの正確性を保証するための基盤となります。

universe.goにおけるユニバーススコープの初期化

  • init()関数: Go言語のパッケージ初期化メカニズムを利用して、プログラム起動時にユニバーススコープを構築します。
    • Globals.NewScope(nil)を呼び出して、親を持たないルートスコープとしてユニバーススコープを作成します。
    • void_typ, bad_typ, nil_typといった内部的な型を定義します。
    • bool, uint8, int, float, stringなどのGoの組み込み基本型をDeclTypeRegister関数を使ってユニバーススコープに登録します。
    • true, false, iota, nilといった組み込み定数をDeclObjを使って登録します。iotaはGoの定数宣言で連番を生成するために使用される特別な識別子です。
    • len, new, panic, printといった組み込み関数をDeclObjを使って登録します。 このinit関数は、Goプログラムが利用できる最も基本的な型、定数、関数を定義し、コンパイラがこれらを認識できるようにするための重要なステップです。

compilation.goにおける型チェックの組み込み

  • Compile関数内で、パーサーがASTを生成した後、TypeChecker.CheckProgram(prog)が呼び出されるようになりました。これは、コンパイルのフェーズにおいて、構文解析の次に意味解析(型チェック)が実行されることを明確に示しています。これにより、プログラムのセマンティックな正当性が検証され、識別子と宣言に関する情報が収集・整理されることになります。

これらのコード変更は、Goコンパイラが単にコードを解析するだけでなく、その意味を理解し、プログラム要素間の関係性を把握するための重要な一歩を示しています。これは、より高度なツール(例えば、IDEのコード補完やリファクタリング機能、そしてこのコミットの目的であるHTMLリンク付きのコード表示)を構築するための基盤となります。

関連リンク

  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Go言語のコンパイラ設計に関する初期の議論やドキュメント(もし公開されていれば、go.dev/s/golang.org/doc/以下で検索すると見つかる可能性がありますが、2008年当時のものは見つけにくいかもしれません。)
  • コンパイラ設計に関する一般的な情報源:
    • "Compilers: Principles, Techniques, and Tools" (通称 "Dragon Book")
    • "Engineering a Compiler"

参考にした情報源リンク