mirror of
https://github.com/golang/go.git
synced 2026-01-29 15:12:08 +03:00
Compare commits
17 Commits
dev.fuzz
...
release.r5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0584eb2e77 | ||
|
|
adfa87c5d7 | ||
|
|
af97a0b94c | ||
|
|
beca117ea3 | ||
|
|
ecfec2c61a | ||
|
|
fb10bce0c2 | ||
|
|
ff5182390a | ||
|
|
36d155b2b5 | ||
|
|
f86856b083 | ||
|
|
f12a1d38b2 | ||
|
|
3b32b3eb3d | ||
|
|
f70c7b2b63 | ||
|
|
01a1c91696 | ||
|
|
dbdc8698df | ||
|
|
47906598d8 | ||
|
|
0ea0d7b65c | ||
|
|
35f3007cf1 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,6 +14,86 @@ hg pull
|
||||
hg update release.r<i>NN</i>
|
||||
</pre>
|
||||
|
||||
<h2 id="r58">r58 (released 2011/06/29)</h2>
|
||||
|
||||
<p>
|
||||
The r58 release corresponds to
|
||||
<code><a href="weekly.html#2011-06-09">weekly.2011-06-09</a></code>
|
||||
with additional bug fixes.
|
||||
This section highlights the most significant changes in this release.
|
||||
For a more detailed summary, see the
|
||||
<a href="weekly.html#2011-06-09">weekly release notes</a>.
|
||||
For complete information, see the
|
||||
<a href="http://code.google.com/p/go/source/list?r=release-branch.r58">Mercurial change list</a>.
|
||||
</p>
|
||||
|
||||
<h3 id="r58.lang">Language</h3>
|
||||
|
||||
<p>
|
||||
This release fixes a <a href="http://code.google.com/p/go/source/detail?r=b720749486e1">use of uninitialized memory in programs that misuse <code>goto</code></a>.
|
||||
</p>
|
||||
|
||||
<h3 id="r58.pkg">Packages</h3>
|
||||
|
||||
<p>
|
||||
As usual, <a href="/cmd/gofix/">gofix</a> will handle the bulk of the rewrites
|
||||
necessary for these changes to package APIs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/pkg/http/">Package http</a> drops the <code>finalURL</code> return
|
||||
value from the <a href="/pkg/http/#Client.Get">Client.Get</a> method. The value
|
||||
is now available via the new <code>Request</code> field on <a
|
||||
href="/pkg/http/#Response">http.Response</a>.
|
||||
Most instances of the type map[string][]string in have been
|
||||
replaced with the new <a href="/pkg/http/#Values">Values</a> type.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/pkg/exec/">Package exec</a> has been redesigned with a more
|
||||
convenient and succinct API.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/pkg/strconv/">Package strconv</a>'s <a href="/pkg/strconv/#Quote">Quote</a>
|
||||
function now escapes only those Unicode code points not classified as printable
|
||||
by <a href="/pkg/unicode/#IsPrint">unicode.IsPrint</a>.
|
||||
Previously Quote would escape all non-ASCII characters.
|
||||
This also affects the <a href="/pkg/fmt/">fmt</a> package's <code>"%q"</code>
|
||||
formatting directive. The previous quoting behavior is still available via
|
||||
strconv's new <a href="/pkg/strconv/#QuoteToASCII">QuoteToASCII</a> function.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/pkg/os/signal/">Package os/signal</a>'s
|
||||
<a href="/pkg/os/#Signal">Signal</a> and
|
||||
<a href="/pkg/os/#UnixSignal">UnixSignal</a> types have been moved to the
|
||||
<a href="/pkg/os/">os</a> package.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="/pkg/image/draw/">Package image/draw</a> is the new name for
|
||||
<code>exp/draw</code>. The GUI-related code from <code>exp/draw</code> is now
|
||||
located in the <a href="/pkg/exp/gui/">exp/gui</a> package.
|
||||
</p>
|
||||
|
||||
<h3 id="r58.cmd">Tools</h3>
|
||||
|
||||
<p>
|
||||
<a href="/cmd/goinstall/">Goinstall</a> now observes the GOPATH environment
|
||||
variable to build and install your own code and external libraries outside of
|
||||
the Go tree (and avoid writing Makefiles).
|
||||
</p>
|
||||
|
||||
|
||||
<h3 id="r58.minor">Minor revisions</h3>
|
||||
|
||||
<p>r58.1 adds
|
||||
<a href="http://code.google.com/p/go/source/detail?r=293c25943586">build</a> and
|
||||
<a href="http://code.google.com/p/go/source/detail?r=bf17e96b6582">runtime</a>
|
||||
changes to make Go run on OS X 10.7 Lion.
|
||||
</p>
|
||||
|
||||
<h2 id="r57">r57 (released 2011/05/03)</h2>
|
||||
|
||||
<p>
|
||||
@@ -162,6 +242,7 @@ For other uses, see the <a href="/pkg/runtime/pprof/">runtime/pprof</a> document
|
||||
<h3 id="r57.minor">Minor revisions</h3>
|
||||
|
||||
<p>r57.1 fixes a <a href="http://code.google.com/p/go/source/detail?r=ff2bc62726e7145eb2ecc1e0f076998e4a8f86f0">nil pointer dereference in http.FormFile</a>.</p>
|
||||
<p>r57.2 fixes a <a href="http://code.google.com/p/go/source/detail?r=063b0ff67d8277df03c956208abc068076818dae">use of uninitialized memory in programs that misuse <code>goto</code></a>.</p>
|
||||
|
||||
<h2 id="r56">r56 (released 2011/03/16)</h2>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ hg pull
|
||||
hg update weekly.<i>YYYY-MM-DD</i>
|
||||
</pre>
|
||||
|
||||
<h2 id="2011-06-09">2011-06-09</h2>
|
||||
<h2 id="2011-06-09">2011-06-09 (<a href="release.html#r58">base for r58</a>)</h2>
|
||||
|
||||
<pre>
|
||||
This release includes changes to the strconv, http, and exp/draw packages.
|
||||
|
||||
@@ -23,6 +23,17 @@ concepts: syntax, types, allocation, constants, I/O, sorting, printing,
|
||||
goroutines, and channels.
|
||||
</p>
|
||||
|
||||
<h3 id="course_notes">Course Notes</h3>
|
||||
<p>
|
||||
Slides from a 3-day course about the Go programming language.
|
||||
A more thorough introduction than the tutorial.
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="GoCourseDay1.pdf">Day 1: Basics</a> <small>[270KB PDF]</small>
|
||||
<li><a href="GoCourseDay2.pdf">Day 2: Types, Methods, Interfaces</a> <small>[270KB PDF]</small>
|
||||
<li><a href="GoCourseDay3.pdf">Day 3: Concurrency and Communication</a> <small>[180KB PDF]</small>
|
||||
</ul>
|
||||
|
||||
<h3 id="effective_go"><a href="effective_go.html">Effective Go</a></h3>
|
||||
<p>
|
||||
A document that gives tips for writing clear, idiomatic Go code.
|
||||
@@ -209,7 +220,7 @@ from Hoare’s 1978 paper to Go provides insight into how and why Go works as it
|
||||
does.
|
||||
</i></p>
|
||||
|
||||
<h3 id="emerging_go"><a href="talks/gofrontend-gcc-summit-2010.pdf">The Go frontend for GCC</a></h3>
|
||||
<h3 id="go_frontend_gcc"><a href="talks/gofrontend-gcc-summit-2010.pdf">The Go frontend for GCC</a></h3>
|
||||
<p>
|
||||
A description of the Go language frontend for gcc.
|
||||
Ian Lance Taylor's paper delivered at the GCC Summit 2010.
|
||||
|
||||
@@ -183,16 +183,6 @@ easier to understand what happens when things combine.
|
||||
|
||||
<h2 id="Usage">Usage</h2>
|
||||
|
||||
<h3 id="Who_should_use_the_language">
|
||||
Who should use the language?</h3>
|
||||
|
||||
<p>
|
||||
Go is an experiment. We hope adventurous users will give it a try and see
|
||||
if they enjoy it. Not every programmer
|
||||
will, but we hope enough will find satisfaction in the approach it
|
||||
offers to justify further development.
|
||||
</p>
|
||||
|
||||
<h3 id="Is_Google_using_go_internally"> Is Google using Go internally?</h3>
|
||||
|
||||
<p>
|
||||
@@ -598,6 +588,24 @@ the interface idea. Sometimes, though, they're necessary to resolve ambiguities
|
||||
among similar interfaces.
|
||||
</p>
|
||||
|
||||
<h3 id="convert_slice_of_interface">
|
||||
Can I convert a []T to an []interface{}?</h3>
|
||||
|
||||
<p>
|
||||
Not directly because they do not have the same representation in memory.
|
||||
It is necessary to copy the elements individually to the destination
|
||||
slice. This example converts a slice of <code>int</code> to a slice of
|
||||
<code>interface{}</code>:
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
t := []int{1, 2, 3, 4}
|
||||
s := make([]interface{}, len(t))
|
||||
for i, v := range t {
|
||||
s[i] = v
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h2 id="values">Values</h2>
|
||||
|
||||
<h3 id="conversions">
|
||||
|
||||
@@ -10,8 +10,7 @@ After you've read this tutorial, you should look at
|
||||
which digs deeper into how the language is used and
|
||||
talks about the style and idioms of programming in Go.
|
||||
Also, slides from a 3-day course about Go are available.
|
||||
Although they're badly out of date, they provide some
|
||||
background and a lot of examples:
|
||||
They provide some background and a lot of examples:
|
||||
<a href='/doc/GoCourseDay1.pdf'>Day 1</a>,
|
||||
<a href='/doc/GoCourseDay2.pdf'>Day 2</a>,
|
||||
<a href='/doc/GoCourseDay3.pdf'>Day 3</a>.
|
||||
|
||||
@@ -11,8 +11,7 @@ After you've read this tutorial, you should look at
|
||||
which digs deeper into how the language is used and
|
||||
talks about the style and idioms of programming in Go.
|
||||
Also, slides from a 3-day course about Go are available.
|
||||
Although they're badly out of date, they provide some
|
||||
background and a lot of examples:
|
||||
They provide some background and a lot of examples:
|
||||
<a href='/doc/GoCourseDay1.pdf'>Day 1</a>,
|
||||
<a href='/doc/GoCourseDay2.pdf'>Day 2</a>,
|
||||
<a href='/doc/GoCourseDay3.pdf'>Day 3</a>.
|
||||
|
||||
@@ -124,6 +124,64 @@ newplist(void)
|
||||
return pl;
|
||||
}
|
||||
|
||||
void
|
||||
clearstk(void)
|
||||
{
|
||||
Plist *pl;
|
||||
Prog *p, *p1, *p2, *p3;
|
||||
Node dst, end, zero, con;
|
||||
|
||||
if(plast->firstpc->to.offset <= 0)
|
||||
return;
|
||||
|
||||
// reestablish context for inserting code
|
||||
// at beginning of function.
|
||||
pl = plast;
|
||||
p1 = pl->firstpc;
|
||||
p2 = p1->link;
|
||||
pc = mal(sizeof(*pc));
|
||||
clearp(pc);
|
||||
p1->link = pc;
|
||||
|
||||
// zero stack frame
|
||||
|
||||
// MOVW $4(SP), R1
|
||||
nodreg(&dst, types[tptr], 1);
|
||||
p = gins(AMOVW, N, &dst);
|
||||
p->from.type = D_CONST;
|
||||
p->from.reg = REGSP;
|
||||
p->from.offset = 4;
|
||||
|
||||
// MOVW $n(R1), R2
|
||||
nodreg(&end, types[tptr], 2);
|
||||
p = gins(AMOVW, N, &end);
|
||||
p->from.type = D_CONST;
|
||||
p->from.reg = 1;
|
||||
p->from.offset = p1->to.offset;
|
||||
|
||||
// MOVW $0, R3
|
||||
nodreg(&zero, types[TUINT32], 3);
|
||||
nodconst(&con, types[TUINT32], 0);
|
||||
gmove(&con, &zero);
|
||||
|
||||
// L:
|
||||
// MOVW.P R3, 0(R1) +4
|
||||
// CMP R1, R2
|
||||
// BNE L
|
||||
p = gins(AMOVW, &zero, &dst);
|
||||
p->to.type = D_OREG;
|
||||
p->to.offset = 4;
|
||||
p->scond |= C_PBIT;
|
||||
p3 = p;
|
||||
p = gins(ACMP, &dst, N);
|
||||
raddr(&end, p);
|
||||
patch(gbranch(ABNE, T), p3);
|
||||
|
||||
// continue with original code.
|
||||
gins(ANOP, N, N)->link = p2;
|
||||
pc = P;
|
||||
}
|
||||
|
||||
void
|
||||
gused(Node *n)
|
||||
{
|
||||
|
||||
@@ -120,6 +120,44 @@ newplist(void)
|
||||
return pl;
|
||||
}
|
||||
|
||||
void
|
||||
clearstk(void)
|
||||
{
|
||||
Plist *pl;
|
||||
Prog *p1, *p2;
|
||||
Node sp, di, cx, con, ax;
|
||||
|
||||
if((uint32)plast->firstpc->to.offset <= 0)
|
||||
return;
|
||||
|
||||
// reestablish context for inserting code
|
||||
// at beginning of function.
|
||||
pl = plast;
|
||||
p1 = pl->firstpc;
|
||||
p2 = p1->link;
|
||||
pc = mal(sizeof(*pc));
|
||||
clearp(pc);
|
||||
p1->link = pc;
|
||||
|
||||
// zero stack frame
|
||||
nodreg(&sp, types[tptr], D_SP);
|
||||
nodreg(&di, types[tptr], D_DI);
|
||||
nodreg(&cx, types[TUINT64], D_CX);
|
||||
nodconst(&con, types[TUINT64], (uint32)p1->to.offset / widthptr);
|
||||
gins(ACLD, N, N);
|
||||
gins(AMOVQ, &sp, &di);
|
||||
gins(AMOVQ, &con, &cx);
|
||||
nodconst(&con, types[TUINT64], 0);
|
||||
nodreg(&ax, types[TUINT64], D_AX);
|
||||
gins(AMOVQ, &con, &ax);
|
||||
gins(AREP, N, N);
|
||||
gins(ASTOSQ, N, N);
|
||||
|
||||
// continue with original code.
|
||||
gins(ANOP, N, N)->link = p2;
|
||||
pc = P;
|
||||
}
|
||||
|
||||
void
|
||||
gused(Node *n)
|
||||
{
|
||||
|
||||
@@ -122,6 +122,44 @@ newplist(void)
|
||||
return pl;
|
||||
}
|
||||
|
||||
void
|
||||
clearstk(void)
|
||||
{
|
||||
Plist *pl;
|
||||
Prog *p1, *p2;
|
||||
Node sp, di, cx, con, ax;
|
||||
|
||||
if(plast->firstpc->to.offset <= 0)
|
||||
return;
|
||||
|
||||
// reestablish context for inserting code
|
||||
// at beginning of function.
|
||||
pl = plast;
|
||||
p1 = pl->firstpc;
|
||||
p2 = p1->link;
|
||||
pc = mal(sizeof(*pc));
|
||||
clearp(pc);
|
||||
p1->link = pc;
|
||||
|
||||
// zero stack frame
|
||||
nodreg(&sp, types[tptr], D_SP);
|
||||
nodreg(&di, types[tptr], D_DI);
|
||||
nodreg(&cx, types[TUINT32], D_CX);
|
||||
nodconst(&con, types[TUINT32], p1->to.offset / widthptr);
|
||||
gins(ACLD, N, N);
|
||||
gins(AMOVL, &sp, &di);
|
||||
gins(AMOVL, &con, &cx);
|
||||
nodconst(&con, types[TUINT32], 0);
|
||||
nodreg(&ax, types[TUINT32], D_AX);
|
||||
gins(AMOVL, &con, &ax);
|
||||
gins(AREP, N, N);
|
||||
gins(ASTOSL, N, N);
|
||||
|
||||
// continue with original code.
|
||||
gins(ANOP, N, N)->link = p2;
|
||||
pc = P;
|
||||
}
|
||||
|
||||
void
|
||||
gused(Node *n)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
@@ -477,7 +478,27 @@ func (p *Package) loadDWARF(f *File, names []*Name) {
|
||||
fmt.Fprintf(&b, "enum { __cgo_enum__%d = %s };\n", i, n.C)
|
||||
}
|
||||
}
|
||||
d := p.gccDebug(b.Bytes())
|
||||
|
||||
// Apple's LLVM-based gcc does not include the enumeration
|
||||
// names and values in its DWARF debug output. In case we're
|
||||
// using such a gcc, create a data block initialized with the values.
|
||||
// We can read them out of the object file.
|
||||
fmt.Fprintf(&b, "long long __cgodebug_data[] = {\n")
|
||||
for _, n := range names {
|
||||
if n.Kind == "const" {
|
||||
fmt.Fprintf(&b, "\t%s,\n", n.C)
|
||||
} else {
|
||||
fmt.Fprintf(&b, "\t0,\n")
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&b, "\t0\n")
|
||||
fmt.Fprintf(&b, "};\n")
|
||||
|
||||
d, bo, debugData := p.gccDebug(b.Bytes())
|
||||
enumVal := make([]int64, len(debugData)/8)
|
||||
for i := range enumVal {
|
||||
enumVal[i] = int64(bo.Uint64(debugData[i*8:]))
|
||||
}
|
||||
|
||||
// Scan DWARF info for top-level TagVariable entries with AttrName __cgo__i.
|
||||
types := make([]dwarf.Type, len(names))
|
||||
@@ -569,9 +590,12 @@ func (p *Package) loadDWARF(f *File, names []*Name) {
|
||||
// Remove injected enum to ensure the value will deep-compare
|
||||
// equally in future loads of the same constant.
|
||||
n.Type.EnumValues[k] = 0, false
|
||||
} else if n.Kind == "const" && i < len(enumVal) {
|
||||
n.Const = strconv.Itoa64(enumVal[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// rewriteRef rewrites all the C.xxx references in f.AST to refer to the
|
||||
@@ -593,6 +617,9 @@ func (p *Package) rewriteRef(f *File) {
|
||||
// are trying to do a ,err call. Also check that
|
||||
// functions are only used in calls.
|
||||
for _, r := range f.Ref {
|
||||
if r.Name.Kind == "const" && r.Name.Const == "" {
|
||||
error(r.Pos(), "unable to find value of constant C.%s", r.Name.Go)
|
||||
}
|
||||
var expr ast.Expr = ast.NewIdent(r.Name.Mangle) // default
|
||||
switch r.Context {
|
||||
case "call", "call2":
|
||||
@@ -692,29 +719,57 @@ func (p *Package) gccCmd() []string {
|
||||
}
|
||||
|
||||
// gccDebug runs gcc -gdwarf-2 over the C program stdin and
|
||||
// returns the corresponding DWARF data and any messages
|
||||
// printed to standard error.
|
||||
func (p *Package) gccDebug(stdin []byte) *dwarf.Data {
|
||||
// returns the corresponding DWARF data and, if present, debug data block.
|
||||
func (p *Package) gccDebug(stdin []byte) (*dwarf.Data, binary.ByteOrder, []byte) {
|
||||
runGcc(stdin, p.gccCmd())
|
||||
|
||||
// Try to parse f as ELF and Mach-O and hope one works.
|
||||
var f interface {
|
||||
DWARF() (*dwarf.Data, os.Error)
|
||||
}
|
||||
var err os.Error
|
||||
if f, err = elf.Open(gccTmp); err != nil {
|
||||
if f, err = macho.Open(gccTmp); err != nil {
|
||||
if f, err = pe.Open(gccTmp); err != nil {
|
||||
fatalf("cannot parse gcc output %s as ELF or Mach-O or PE object", gccTmp)
|
||||
if f, err := macho.Open(gccTmp); err == nil {
|
||||
d, err := f.DWARF()
|
||||
if err != nil {
|
||||
fatalf("cannot load DWARF output from %s: %v", gccTmp, err)
|
||||
}
|
||||
var data []byte
|
||||
if f.Symtab != nil {
|
||||
for i := range f.Symtab.Syms {
|
||||
s := &f.Symtab.Syms[i]
|
||||
// Mach-O still uses a leading _ to denote non-assembly symbols.
|
||||
if s.Name == "_"+"__cgodebug_data" {
|
||||
// Found it. Now find data section.
|
||||
if i := int(s.Sect) - 1; 0 <= i && i < len(f.Sections) {
|
||||
sect := f.Sections[i]
|
||||
if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size {
|
||||
if sdat, err := sect.Data(); err == nil {
|
||||
data = sdat[s.Value-sect.Addr:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return d, f.ByteOrder, data
|
||||
}
|
||||
|
||||
d, err := f.DWARF()
|
||||
if err != nil {
|
||||
fatalf("cannot load DWARF debug information from %s: %s", gccTmp, err)
|
||||
// Can skip debug data block in ELF and PE for now.
|
||||
// The DWARF information is complete.
|
||||
|
||||
if f, err := elf.Open(gccTmp); err == nil {
|
||||
d, err := f.DWARF()
|
||||
if err != nil {
|
||||
fatalf("cannot load DWARF output from %s: %v", gccTmp, err)
|
||||
}
|
||||
return d, f.ByteOrder, nil
|
||||
}
|
||||
return d
|
||||
|
||||
if f, err := pe.Open(gccTmp); err == nil {
|
||||
d, err := f.DWARF()
|
||||
if err != nil {
|
||||
fatalf("cannot load DWARF output from %s: %v", gccTmp, err)
|
||||
}
|
||||
return d, binary.LittleEndian, nil
|
||||
}
|
||||
|
||||
fatalf("cannot parse gcc output %s as ELF, Mach-O, PE object", gccTmp)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// gccDefines runs gcc -E -dM -xc - over the C program stdin
|
||||
|
||||
@@ -1241,9 +1241,14 @@ funccompile(Node *n, int isclosure)
|
||||
stksize = 0;
|
||||
dclcontext = PAUTO;
|
||||
funcdepth = n->funcdepth + 1;
|
||||
hasgoto = 0;
|
||||
compile(n);
|
||||
if(hasgoto)
|
||||
clearstk();
|
||||
curfn = nil;
|
||||
funcdepth = 0;
|
||||
dclcontext = PEXTERN;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ gen(Node *n)
|
||||
break;
|
||||
|
||||
case OGOTO:
|
||||
hasgoto = 1;
|
||||
newlab(OGOTO, n, N);
|
||||
gjmp(P);
|
||||
break;
|
||||
|
||||
@@ -1255,3 +1255,6 @@ void zhist(Biobuf *b, int line, vlong offset);
|
||||
void zname(Biobuf *b, Sym *s, int t);
|
||||
void data(void);
|
||||
void text(void);
|
||||
|
||||
EXTERN int hasgoto;
|
||||
void clearstk(void);
|
||||
|
||||
@@ -14,6 +14,7 @@ GOFILES=\
|
||||
httpserver.go\
|
||||
procattr.go\
|
||||
reflect.go\
|
||||
signal.go\
|
||||
typecheck.go\
|
||||
|
||||
include ../../Make.cmd
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"go/token"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type fix struct {
|
||||
@@ -258,13 +259,28 @@ func walkBeforeAfter(x interface{}, before, after func(interface{})) {
|
||||
|
||||
// imports returns true if f imports path.
|
||||
func imports(f *ast.File, path string) bool {
|
||||
return importSpec(f, path) != nil
|
||||
}
|
||||
|
||||
// importSpec returns the import spec if f imports path,
|
||||
// or nil otherwise.
|
||||
func importSpec(f *ast.File, path string) *ast.ImportSpec {
|
||||
for _, s := range f.Imports {
|
||||
t, err := strconv.Unquote(s.Path.Value)
|
||||
if err == nil && t == path {
|
||||
return true
|
||||
if importPath(s) == path {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
// importPath returns the unquoted import path of s,
|
||||
// or "" if the path is not properly quoted.
|
||||
func importPath(s *ast.ImportSpec) string {
|
||||
t, err := strconv.Unquote(s.Path.Value)
|
||||
if err == nil {
|
||||
return t
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// isPkgDot returns true if t is the expression "pkg.name"
|
||||
@@ -420,3 +436,138 @@ func newPkgDot(pos token.Pos, pkg, name string) ast.Expr {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// addImport adds the import path to the file f, if absent.
|
||||
func addImport(f *ast.File, path string) {
|
||||
if imports(f, path) {
|
||||
return
|
||||
}
|
||||
|
||||
newImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: strconv.Quote(path),
|
||||
},
|
||||
}
|
||||
|
||||
var impdecl *ast.GenDecl
|
||||
|
||||
// Find an import decl to add to.
|
||||
for _, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
|
||||
if ok && gen.Tok == token.IMPORT {
|
||||
impdecl = gen
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// No import decl found. Add one.
|
||||
if impdecl == nil {
|
||||
impdecl = &ast.GenDecl{
|
||||
Tok: token.IMPORT,
|
||||
}
|
||||
f.Decls = append(f.Decls, nil)
|
||||
copy(f.Decls[1:], f.Decls)
|
||||
f.Decls[0] = impdecl
|
||||
}
|
||||
|
||||
// Ensure the import decl has parentheses, if needed.
|
||||
if len(impdecl.Specs) > 0 && !impdecl.Lparen.IsValid() {
|
||||
impdecl.Lparen = impdecl.Pos()
|
||||
}
|
||||
|
||||
// Assume the import paths are alphabetically ordered.
|
||||
// If they are not, the result is ugly, but legal.
|
||||
insertAt := len(impdecl.Specs) // default to end of specs
|
||||
for i, spec := range impdecl.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
if importPath(impspec) > path {
|
||||
insertAt = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
impdecl.Specs = append(impdecl.Specs, nil)
|
||||
copy(impdecl.Specs[insertAt+1:], impdecl.Specs[insertAt:])
|
||||
impdecl.Specs[insertAt] = newImport
|
||||
|
||||
f.Imports = append(f.Imports, newImport)
|
||||
}
|
||||
|
||||
// deleteImport deletes the import path from the file f, if present.
|
||||
func deleteImport(f *ast.File, path string) {
|
||||
oldImport := importSpec(f, path)
|
||||
|
||||
// Find the import node that imports path, if any.
|
||||
for i, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.IMPORT {
|
||||
continue
|
||||
}
|
||||
for j, spec := range gen.Specs {
|
||||
impspec := spec.(*ast.ImportSpec)
|
||||
|
||||
if oldImport != impspec {
|
||||
continue
|
||||
}
|
||||
|
||||
// We found an import spec that imports path.
|
||||
// Delete it.
|
||||
copy(gen.Specs[j:], gen.Specs[j+1:])
|
||||
gen.Specs = gen.Specs[:len(gen.Specs)-1]
|
||||
|
||||
// If this was the last import spec in this decl,
|
||||
// delete the decl, too.
|
||||
if len(gen.Specs) == 0 {
|
||||
copy(f.Decls[i:], f.Decls[i+1:])
|
||||
f.Decls = f.Decls[:len(f.Decls)-1]
|
||||
} else if len(gen.Specs) == 1 {
|
||||
gen.Lparen = token.NoPos // drop parens
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Delete it from f.Imports.
|
||||
for i, imp := range f.Imports {
|
||||
if imp == oldImport {
|
||||
copy(f.Imports[i:], f.Imports[i+1:])
|
||||
f.Imports = f.Imports[:len(f.Imports)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func usesImport(f *ast.File, path string) (used bool) {
|
||||
spec := importSpec(f, path)
|
||||
if spec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := spec.Name.String()
|
||||
switch name {
|
||||
case "<nil>":
|
||||
// If the package name is not explicitly specified,
|
||||
// make an educated guess. This is not guaranteed to be correct.
|
||||
lastSlash := strings.LastIndex(path, "/")
|
||||
if lastSlash == -1 {
|
||||
name = path
|
||||
} else {
|
||||
name = path[lastSlash+1:]
|
||||
}
|
||||
case "_", ".":
|
||||
// Not sure if this import is used - err on the side of caution.
|
||||
return true
|
||||
}
|
||||
|
||||
walk(f, func(n interface{}) {
|
||||
sel, ok := n.(*ast.SelectorExpr)
|
||||
if ok && isTopName(sel.X, name) {
|
||||
used = true
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
49
src/cmd/gofix/signal.go
Normal file
49
src/cmd/gofix/signal.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
register(fix{
|
||||
"signal",
|
||||
signal,
|
||||
`Adapt code to types moved from os/signal to signal.
|
||||
|
||||
http://codereview.appspot.com/4437091
|
||||
`,
|
||||
})
|
||||
}
|
||||
|
||||
func signal(f *ast.File) (fixed bool) {
|
||||
if !imports(f, "os/signal") {
|
||||
return
|
||||
}
|
||||
|
||||
walk(f, func(n interface{}) {
|
||||
s, ok := n.(*ast.SelectorExpr)
|
||||
|
||||
if !ok || !isTopName(s.X, "signal") {
|
||||
return
|
||||
}
|
||||
|
||||
sel := s.Sel.String()
|
||||
if sel == "Signal" || sel == "UnixSignal" || strings.HasPrefix(sel, "SIG") {
|
||||
s.X = &ast.Ident{Name: "os"}
|
||||
fixed = true
|
||||
}
|
||||
})
|
||||
|
||||
if fixed {
|
||||
addImport(f, "os")
|
||||
if !usesImport(f, "os/signal") {
|
||||
deleteImport(f, "os/signal")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
96
src/cmd/gofix/signal_test.go
Normal file
96
src/cmd/gofix/signal_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
package main
|
||||
|
||||
func init() {
|
||||
addTestCases(signalTests)
|
||||
}
|
||||
|
||||
var signalTests = []testCase{
|
||||
{
|
||||
Name: "signal.0",
|
||||
In: `package main
|
||||
|
||||
import (
|
||||
_ "a"
|
||||
"os/signal"
|
||||
_ "z"
|
||||
)
|
||||
|
||||
type T1 signal.UnixSignal
|
||||
type T2 signal.Signal
|
||||
|
||||
func f() {
|
||||
_ = signal.SIGHUP
|
||||
_ = signal.Incoming
|
||||
}
|
||||
`,
|
||||
Out: `package main
|
||||
|
||||
import (
|
||||
_ "a"
|
||||
"os"
|
||||
"os/signal"
|
||||
_ "z"
|
||||
)
|
||||
|
||||
type T1 os.UnixSignal
|
||||
type T2 os.Signal
|
||||
|
||||
func f() {
|
||||
_ = os.SIGHUP
|
||||
_ = signal.Incoming
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "signal.1",
|
||||
In: `package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
func f() {
|
||||
var _ os.Error
|
||||
_ = signal.SIGHUP
|
||||
}
|
||||
`,
|
||||
Out: `package main
|
||||
|
||||
import "os"
|
||||
|
||||
|
||||
func f() {
|
||||
var _ os.Error
|
||||
_ = os.SIGHUP
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "signal.2",
|
||||
In: `package main
|
||||
|
||||
import "os"
|
||||
import "os/signal"
|
||||
|
||||
func f() {
|
||||
var _ os.Error
|
||||
_ = signal.SIGHUP
|
||||
}
|
||||
`,
|
||||
Out: `package main
|
||||
|
||||
import "os"
|
||||
|
||||
|
||||
func f() {
|
||||
var _ os.Error
|
||||
_ = os.SIGHUP
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
@@ -1804,7 +1804,7 @@ mkvarname(char* name, int da)
|
||||
|
||||
// flush previous compilation unit.
|
||||
static void
|
||||
flushunit(DWDie *dwinfo, vlong pc, vlong unitstart)
|
||||
flushunit(DWDie *dwinfo, vlong pc, vlong unitstart, int32 header_length)
|
||||
{
|
||||
vlong here;
|
||||
|
||||
@@ -1820,7 +1820,9 @@ flushunit(DWDie *dwinfo, vlong pc, vlong unitstart)
|
||||
|
||||
here = cpos();
|
||||
seek(cout, unitstart, 0);
|
||||
LPUT(here - unitstart - sizeof(int32));
|
||||
LPUT(here - unitstart - sizeof(int32)); // unit_length
|
||||
WPUT(3); // dwarf version
|
||||
LPUT(header_length); // header lenght starting here
|
||||
cflush();
|
||||
seek(cout, here, 0);
|
||||
}
|
||||
@@ -1832,7 +1834,7 @@ writelines(void)
|
||||
Prog *q;
|
||||
Sym *s;
|
||||
Auto *a;
|
||||
vlong unitstart, offs;
|
||||
vlong unitstart, headerend, offs;
|
||||
vlong pc, epc, lc, llc, lline;
|
||||
int currfile;
|
||||
int i, lang, da, dt;
|
||||
@@ -1842,6 +1844,7 @@ writelines(void)
|
||||
char *n, *nn;
|
||||
|
||||
unitstart = -1;
|
||||
headerend = -1;
|
||||
pc = 0;
|
||||
epc = 0;
|
||||
lc = 1;
|
||||
@@ -1859,7 +1862,7 @@ writelines(void)
|
||||
// we're entering a new compilation unit
|
||||
|
||||
if (inithist(s->autom)) {
|
||||
flushunit(dwinfo, epc, unitstart);
|
||||
flushunit(dwinfo, epc, unitstart, headerend - unitstart - 10);
|
||||
unitstart = cpos();
|
||||
|
||||
if(debug['v'] > 1) {
|
||||
@@ -1880,10 +1883,10 @@ writelines(void)
|
||||
|
||||
// Write .debug_line Line Number Program Header (sec 6.2.4)
|
||||
// Fields marked with (*) must be changed for 64-bit dwarf
|
||||
LPUT(0); // unit_length (*), will be filled in later.
|
||||
LPUT(0); // unit_length (*), will be filled in by flushunit.
|
||||
WPUT(3); // dwarf version (appendix F)
|
||||
LPUT(11); // header_length (*), starting here.
|
||||
|
||||
LPUT(0); // header_length (*), filled in by flushunit.
|
||||
// cpos == unitstart + 4 + 2 + 4
|
||||
cput(1); // minimum_instruction_length
|
||||
cput(1); // default_is_stmt
|
||||
cput(LINE_BASE); // line_base
|
||||
@@ -1894,17 +1897,15 @@ writelines(void)
|
||||
cput(1); // standard_opcode_lengths[3]
|
||||
cput(1); // standard_opcode_lengths[4]
|
||||
cput(0); // include_directories (empty)
|
||||
cput(0); // file_names (empty) (emitted by DW_LNE's below)
|
||||
// header_length ends here.
|
||||
|
||||
for (i=1; i < histfilesize; i++) {
|
||||
cput(0); // start extended opcode
|
||||
uleb128put(1 + strlen(histfile[i]) + 4);
|
||||
cput(DW_LNE_define_file);
|
||||
strnput(histfile[i], strlen(histfile[i]) + 4);
|
||||
// 4 zeros: the string termination + 3 fields.
|
||||
}
|
||||
|
||||
cput(0); // terminate file_names.
|
||||
headerend = cpos();
|
||||
|
||||
pc = s->text->pc;
|
||||
epc = pc;
|
||||
currfile = 1;
|
||||
@@ -2009,7 +2010,7 @@ writelines(void)
|
||||
dwfunc->hash = nil;
|
||||
}
|
||||
|
||||
flushunit(dwinfo, epc, unitstart);
|
||||
flushunit(dwinfo, epc, unitstart, headerend - unitstart - 10);
|
||||
linesize = cpos() - lineo;
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,8 @@ pprof [options] <profile>
|
||||
The /<service> can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile,
|
||||
$GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall,
|
||||
or /pprof/filteredprofile.
|
||||
For instance: "pprof http://myserver.com:80$HEAP_PAGE".
|
||||
For instance:
|
||||
pprof http://myserver.com:80$HEAP_PAGE
|
||||
If /<service> is omitted, the service defaults to $PROFILE_PAGE (cpu profiling).
|
||||
pprof --symbols <program>
|
||||
Maps addresses to symbol names. In this mode, stdin should be a
|
||||
@@ -532,7 +533,7 @@ sub Init() {
|
||||
ConfigureObjTools($main::prog)
|
||||
}
|
||||
|
||||
# Break the opt_list_prefix into the prefix_list array
|
||||
# Break the opt_lib_prefix into the prefix_list array
|
||||
@prefix_list = split (',', $main::opt_lib_prefix);
|
||||
|
||||
# Remove trailing / from the prefixes, in the list to prevent
|
||||
@@ -626,7 +627,7 @@ sub Main() {
|
||||
if ($main::opt_disasm) {
|
||||
PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm, $total);
|
||||
} elsif ($main::opt_list) {
|
||||
PrintListing($libs, $flat, $cumulative, $main::opt_list);
|
||||
PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0);
|
||||
} elsif ($main::opt_text) {
|
||||
# Make sure the output is empty when have nothing to report
|
||||
# (only matters when --heapcheck is given but we must be
|
||||
@@ -814,7 +815,7 @@ sub InteractiveCommand {
|
||||
my $ignore;
|
||||
($routine, $ignore) = ParseInteractiveArgs($3);
|
||||
|
||||
my $profile = ProcessProfile($orig_profile, $symbols, "", $ignore);
|
||||
my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
|
||||
my $reduced = ReduceProfile($symbols, $profile);
|
||||
|
||||
# Get derived profiles
|
||||
@@ -841,21 +842,22 @@ sub InteractiveCommand {
|
||||
|
||||
return 1;
|
||||
}
|
||||
if (m/^\s*list\s*(.+)/) {
|
||||
if (m/^\s*(web)?list\s*(.+)/) {
|
||||
my $html = (defined($1) && ($1 eq "web"));
|
||||
$main::opt_list = 1;
|
||||
|
||||
my $routine;
|
||||
my $ignore;
|
||||
($routine, $ignore) = ParseInteractiveArgs($1);
|
||||
($routine, $ignore) = ParseInteractiveArgs($2);
|
||||
|
||||
my $profile = ProcessProfile($orig_profile, $symbols, "", $ignore);
|
||||
my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
|
||||
my $reduced = ReduceProfile($symbols, $profile);
|
||||
|
||||
# Get derived profiles
|
||||
my $flat = FlatProfile($reduced);
|
||||
my $cumulative = CumulativeProfile($reduced);
|
||||
|
||||
PrintListing($libs, $flat, $cumulative, $routine);
|
||||
PrintListing($total, $libs, $flat, $cumulative, $routine, $html);
|
||||
return 1;
|
||||
}
|
||||
if (m/^\s*disasm\s*(.+)/) {
|
||||
@@ -866,7 +868,7 @@ sub InteractiveCommand {
|
||||
($routine, $ignore) = ParseInteractiveArgs($1);
|
||||
|
||||
# Process current profile to account for various settings
|
||||
my $profile = ProcessProfile($orig_profile, $symbols, "", $ignore);
|
||||
my $profile = ProcessProfile($total, $orig_profile, $symbols, "", $ignore);
|
||||
my $reduced = ReduceProfile($symbols, $profile);
|
||||
|
||||
# Get derived profiles
|
||||
@@ -890,7 +892,7 @@ sub InteractiveCommand {
|
||||
($focus, $ignore) = ParseInteractiveArgs($2);
|
||||
|
||||
# Process current profile to account for various settings
|
||||
my $profile = ProcessProfile($orig_profile, $symbols, $focus, $ignore);
|
||||
my $profile = ProcessProfile($total, $orig_profile, $symbols, $focus, $ignore);
|
||||
my $reduced = ReduceProfile($symbols, $profile);
|
||||
|
||||
# Get derived profiles
|
||||
@@ -916,6 +918,7 @@ sub InteractiveCommand {
|
||||
|
||||
|
||||
sub ProcessProfile {
|
||||
my $total_count = shift;
|
||||
my $orig_profile = shift;
|
||||
my $symbols = shift;
|
||||
my $focus = shift;
|
||||
@@ -923,7 +926,6 @@ sub ProcessProfile {
|
||||
|
||||
# Process current profile to account for various settings
|
||||
my $profile = $orig_profile;
|
||||
my $total_count = TotalProfile($profile);
|
||||
printf("Total: %s %s\n", Unparse($total_count), Units());
|
||||
if ($focus ne '') {
|
||||
$profile = FocusProfile($symbols, $profile, $focus);
|
||||
@@ -970,6 +972,11 @@ Commands:
|
||||
list [routine_regexp] [-ignore1] [-ignore2]
|
||||
Show source listing of routines whose names match "routine_regexp"
|
||||
|
||||
weblist [routine_regexp] [-ignore1] [-ignore2]
|
||||
Displays a source listing of routines whose names match "routine_regexp"
|
||||
in a web browser. You can click on source lines to view the
|
||||
corresponding disassembly.
|
||||
|
||||
top [--cum] [-ignore1] [-ignore2]
|
||||
top20 [--cum] [-ignore1] [-ignore2]
|
||||
top37 [--cum] [-ignore1] [-ignore2]
|
||||
@@ -1144,7 +1151,7 @@ sub PrintText {
|
||||
$sym);
|
||||
}
|
||||
$lines++;
|
||||
last if ($line_limit >= 0 && $lines > $line_limit);
|
||||
last if ($line_limit >= 0 && $lines >= $line_limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1291,11 +1298,32 @@ sub ByName {
|
||||
|
||||
# Print source-listing for all all routines that match $main::opt_list
|
||||
sub PrintListing {
|
||||
my $total = shift;
|
||||
my $libs = shift;
|
||||
my $flat = shift;
|
||||
my $cumulative = shift;
|
||||
my $list_opts = shift;
|
||||
my $html = shift;
|
||||
|
||||
my $output = \*STDOUT;
|
||||
my $fname = "";
|
||||
|
||||
|
||||
if ($html) {
|
||||
# Arrange to write the output to a temporary file
|
||||
$fname = TempName($main::next_tmpfile, "html");
|
||||
$main::next_tmpfile++;
|
||||
if (!open(TEMP, ">$fname")) {
|
||||
print STDERR "$fname: $!\n";
|
||||
return;
|
||||
}
|
||||
$output = \*TEMP;
|
||||
print $output HtmlListingHeader();
|
||||
printf $output ("<div class=\"legend\">%s<br>Total: %s %s</div>\n",
|
||||
$main::prog, Unparse($total), Units());
|
||||
}
|
||||
|
||||
my $listed = 0;
|
||||
foreach my $lib (@{$libs}) {
|
||||
my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts);
|
||||
my $offset = AddressSub($lib->[1], $lib->[3]);
|
||||
@@ -1307,15 +1335,98 @@ sub PrintListing {
|
||||
my $addr = AddressAdd($start_addr, $offset);
|
||||
for (my $i = 0; $i < $length; $i++) {
|
||||
if (defined($cumulative->{$addr})) {
|
||||
PrintSource($lib->[0], $offset,
|
||||
$routine, $flat, $cumulative,
|
||||
$start_addr, $end_addr);
|
||||
$listed += PrintSource(
|
||||
$lib->[0], $offset,
|
||||
$routine, $flat, $cumulative,
|
||||
$start_addr, $end_addr,
|
||||
$html,
|
||||
$output);
|
||||
last;
|
||||
}
|
||||
$addr = AddressInc($addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($html) {
|
||||
if ($listed > 0) {
|
||||
print $output HtmlListingFooter();
|
||||
close($output);
|
||||
RunWeb($fname);
|
||||
} else {
|
||||
close($output);
|
||||
unlink($fname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub HtmlListingHeader {
|
||||
return <<'EOF';
|
||||
<DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Pprof listing</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.legend {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
.line {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
.livesrc {
|
||||
color: #0000ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.livesrc:hover {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
.asm {
|
||||
color: #888888;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function pprof_toggle_asm(e) {
|
||||
var target;
|
||||
if (!e) e = window.event;
|
||||
if (e.target) target = e.target;
|
||||
else if (e.srcElement) target = e.srcElement;
|
||||
|
||||
if (target && target.className == "livesrc") {
|
||||
var asm = target.nextSibling;
|
||||
if (asm && asm.className == "asm") {
|
||||
asm.style.display = (asm.style.display == "block" ? "none" : "block");
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
EOF
|
||||
}
|
||||
|
||||
sub HtmlListingFooter {
|
||||
return <<'EOF';
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
}
|
||||
|
||||
sub HtmlEscape {
|
||||
my $text = shift;
|
||||
$text =~ s/&/&/g;
|
||||
$text =~ s/</</g;
|
||||
$text =~ s/>/>/g;
|
||||
return $text;
|
||||
}
|
||||
|
||||
# Returns the indentation of the line, if it has any non-whitespace
|
||||
@@ -1338,6 +1449,8 @@ sub PrintSource {
|
||||
my $cumulative = shift;
|
||||
my $start_addr = shift;
|
||||
my $end_addr = shift;
|
||||
my $html = shift;
|
||||
my $output = shift;
|
||||
|
||||
# Disassemble all instructions (just to get line numbers)
|
||||
my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
|
||||
@@ -1353,7 +1466,7 @@ sub PrintSource {
|
||||
}
|
||||
if (!defined($filename)) {
|
||||
print STDERR "no filename found in $routine\n";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Hack 2: assume that the largest line number from $filename is the
|
||||
@@ -1386,7 +1499,7 @@ sub PrintSource {
|
||||
{
|
||||
if (!open(FILE, "<$filename")) {
|
||||
print STDERR "$filename: $!\n";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
my $l = 0;
|
||||
my $first_indentation = -1;
|
||||
@@ -1414,12 +1527,21 @@ sub PrintSource {
|
||||
# Assign all samples to the range $firstline,$lastline,
|
||||
# Hack 4: If an instruction does not occur in the range, its samples
|
||||
# are moved to the next instruction that occurs in the range.
|
||||
my $samples1 = {};
|
||||
my $samples2 = {};
|
||||
my $running1 = 0; # Unassigned flat counts
|
||||
my $running2 = 0; # Unassigned cumulative counts
|
||||
my $total1 = 0; # Total flat counts
|
||||
my $total2 = 0; # Total cumulative counts
|
||||
my $samples1 = {}; # Map from line number to flat count
|
||||
my $samples2 = {}; # Map from line number to cumulative count
|
||||
my $running1 = 0; # Unassigned flat counts
|
||||
my $running2 = 0; # Unassigned cumulative counts
|
||||
my $total1 = 0; # Total flat counts
|
||||
my $total2 = 0; # Total cumulative counts
|
||||
my %disasm = (); # Map from line number to disassembly
|
||||
my $running_disasm = ""; # Unassigned disassembly
|
||||
my $skip_marker = "---\n";
|
||||
if ($html) {
|
||||
$skip_marker = "";
|
||||
for (my $l = $firstline; $l <= $lastline; $l++) {
|
||||
$disasm{$l} = "";
|
||||
}
|
||||
}
|
||||
foreach my $e (@instructions) {
|
||||
# Add up counts for all address that fall inside this instruction
|
||||
my $c1 = 0;
|
||||
@@ -1428,6 +1550,15 @@ sub PrintSource {
|
||||
$c1 += GetEntry($flat, $a);
|
||||
$c2 += GetEntry($cumulative, $a);
|
||||
}
|
||||
|
||||
if ($html) {
|
||||
$running_disasm .= sprintf(" %6s %6s \t\t%8s: %s\n",
|
||||
HtmlPrintNumber($c1),
|
||||
HtmlPrintNumber($c2),
|
||||
$e->[0],
|
||||
CleanDisassembly($e->[3]));
|
||||
}
|
||||
|
||||
$running1 += $c1;
|
||||
$running2 += $c2;
|
||||
$total1 += $c1;
|
||||
@@ -1442,6 +1573,10 @@ sub PrintSource {
|
||||
AddEntry($samples2, $line, $running2);
|
||||
$running1 = 0;
|
||||
$running2 = 0;
|
||||
if ($html) {
|
||||
$disasm{$line} .= $running_disasm;
|
||||
$running_disasm = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1449,16 +1584,28 @@ sub PrintSource {
|
||||
AddEntry($samples1, $lastline, $running1);
|
||||
AddEntry($samples2, $lastline, $running2);
|
||||
|
||||
printf("ROUTINE ====================== %s in %s\n" .
|
||||
"%6s %6s Total %s (flat / cumulative)\n",
|
||||
ShortFunctionName($routine),
|
||||
$filename,
|
||||
Units(),
|
||||
Unparse($total1),
|
||||
Unparse($total2));
|
||||
if ($html) {
|
||||
printf $output (
|
||||
"<h1>%s</h1>%s\n<pre onClick=\"pprof_toggle_asm()\">\n" .
|
||||
"Total:%6s %6s (flat / cumulative %s)\n",
|
||||
HtmlEscape(ShortFunctionName($routine)),
|
||||
HtmlEscape($filename),
|
||||
Unparse($total1),
|
||||
Unparse($total2),
|
||||
Units());
|
||||
} else {
|
||||
printf $output (
|
||||
"ROUTINE ====================== %s in %s\n" .
|
||||
"%6s %6s Total %s (flat / cumulative)\n",
|
||||
ShortFunctionName($routine),
|
||||
$filename,
|
||||
Unparse($total1),
|
||||
Unparse($total2),
|
||||
Units());
|
||||
}
|
||||
if (!open(FILE, "<$filename")) {
|
||||
print STDERR "$filename: $!\n";
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
my $l = 0;
|
||||
while (<FILE>) {
|
||||
@@ -1468,16 +1615,47 @@ sub PrintSource {
|
||||
(($l <= $oldlastline + 5) || ($l <= $lastline))) {
|
||||
chop;
|
||||
my $text = $_;
|
||||
if ($l == $firstline) { printf("---\n"); }
|
||||
printf("%6s %6s %4d: %s\n",
|
||||
UnparseAlt(GetEntry($samples1, $l)),
|
||||
UnparseAlt(GetEntry($samples2, $l)),
|
||||
$l,
|
||||
$text);
|
||||
if ($l == $lastline) { printf("---\n"); }
|
||||
if ($l == $firstline) { print $output $skip_marker; }
|
||||
my $n1 = GetEntry($samples1, $l);
|
||||
my $n2 = GetEntry($samples2, $l);
|
||||
if ($html) {
|
||||
my $dis = $disasm{$l};
|
||||
if (!defined($dis) || $n1 + $n2 == 0) {
|
||||
# No samples/disassembly for this source line
|
||||
printf $output (
|
||||
"<span class=\"line\">%5d</span> " .
|
||||
"<span class=\"deadsrc\">%6s %6s %s</span>\n",
|
||||
$l,
|
||||
HtmlPrintNumber($n1),
|
||||
HtmlPrintNumber($n2),
|
||||
HtmlEscape($text));
|
||||
} else {
|
||||
printf $output (
|
||||
"<span class=\"line\">%5d</span> " .
|
||||
"<span class=\"livesrc\">%6s %6s %s</span>" .
|
||||
"<span class=\"asm\">%s</span>\n",
|
||||
$l,
|
||||
HtmlPrintNumber($n1),
|
||||
HtmlPrintNumber($n2),
|
||||
HtmlEscape($text),
|
||||
HtmlEscape($dis));
|
||||
}
|
||||
} else {
|
||||
printf $output(
|
||||
"%6s %6s %4d: %s\n",
|
||||
UnparseAlt($n1),
|
||||
UnparseAlt($n2),
|
||||
$l,
|
||||
$text);
|
||||
}
|
||||
if ($l == $lastline) { print $output $skip_marker; }
|
||||
};
|
||||
}
|
||||
close(FILE);
|
||||
if ($html) {
|
||||
print $output "</pre>\n";
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Return the source line for the specified file/linenumber.
|
||||
@@ -1625,16 +1803,11 @@ sub PrintDisassembledFunction {
|
||||
$address =~ s/^0x//;
|
||||
$address =~ s/^0*//;
|
||||
|
||||
# Trim symbols
|
||||
my $d = $e->[3];
|
||||
while ($d =~ s/\([^()%]*\)(\s*const)?//g) { } # Argument types, not (%rax)
|
||||
while ($d =~ s/(\w+)<[^<>]*>/$1/g) { } # Remove template arguments
|
||||
|
||||
printf("%6s %6s %8s: %6s\n",
|
||||
UnparseAlt($flat_count[$x]),
|
||||
UnparseAlt($cum_count[$x]),
|
||||
$address,
|
||||
$d);
|
||||
CleanDisassembly($e->[3]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2254,6 +2427,16 @@ sub UnparseAlt {
|
||||
}
|
||||
}
|
||||
|
||||
# Alternate pretty-printed form: 0 maps to ""
|
||||
sub HtmlPrintNumber {
|
||||
my $num = shift;
|
||||
if ($num == 0) {
|
||||
return "";
|
||||
} else {
|
||||
return Unparse($num);
|
||||
}
|
||||
}
|
||||
|
||||
# Return output units
|
||||
sub Units {
|
||||
if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
|
||||
@@ -2415,6 +2598,8 @@ sub RemoveUninterestingFrames {
|
||||
'copyin',
|
||||
'gostring',
|
||||
'gostringsize',
|
||||
'growslice1',
|
||||
'appendslice1',
|
||||
'hash_init',
|
||||
'hash_subtable_new',
|
||||
'hash_conv',
|
||||
@@ -2422,6 +2607,8 @@ sub RemoveUninterestingFrames {
|
||||
'hash_insert_internal',
|
||||
'hash_insert',
|
||||
'mapassign',
|
||||
'runtime.mapassign',
|
||||
'runtime.appendslice',
|
||||
'runtime.mapassign1',
|
||||
'makechan',
|
||||
'makemap',
|
||||
@@ -2433,11 +2620,13 @@ sub RemoveUninterestingFrames {
|
||||
'unsafe.New',
|
||||
'runtime.mallocgc',
|
||||
'runtime.catstring',
|
||||
'runtime.growslice',
|
||||
'runtime.ifaceT2E',
|
||||
'runtime.ifaceT2I',
|
||||
'runtime.makechan',
|
||||
'runtime.makechan_c',
|
||||
'runtime.makemap',
|
||||
'runtime.makemap_c',
|
||||
'runtime.makeslice',
|
||||
'runtime.mal',
|
||||
'runtime.slicebytetostring',
|
||||
@@ -4302,6 +4491,14 @@ sub ShortFunctionName {
|
||||
return $function;
|
||||
}
|
||||
|
||||
# Trim overly long symbols found in disassembler output
|
||||
sub CleanDisassembly {
|
||||
my $d = shift;
|
||||
while ($d =~ s/\([^()%]*\)(\s*const)?//g) { } # Argument types, not (%rax)
|
||||
while ($d =~ s/(\w+)<[^<>]*>/$1/g) { } # Remove template arguments
|
||||
return $d;
|
||||
}
|
||||
|
||||
##### Miscellaneous #####
|
||||
|
||||
# Find the right versions of the above object tools to use. The
|
||||
|
||||
@@ -76,7 +76,6 @@ DIRS=\
|
||||
encoding/hex\
|
||||
encoding/pem\
|
||||
exec\
|
||||
exp/datafmt\
|
||||
exp/eval\
|
||||
exp/gui\
|
||||
exp/gui/x11\
|
||||
|
||||
@@ -352,8 +352,8 @@ func (d *Data) Type(off Offset) (Type, os.Error) {
|
||||
}
|
||||
}
|
||||
if ndim == 0 {
|
||||
err = DecodeError{"info", e.Offset, "missing dimension for array"}
|
||||
goto Error
|
||||
// LLVM generates this for x[].
|
||||
t.Count = -1
|
||||
}
|
||||
|
||||
case TagBaseType:
|
||||
|
||||
@@ -546,6 +546,12 @@ func (f *File) DWARF() (*dwarf.Data, os.Error) {
|
||||
return dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str)
|
||||
}
|
||||
|
||||
// Symbols returns the symbol table for f.
|
||||
func (f *File) Symbols() ([]Symbol, os.Error) {
|
||||
sym, _, err := f.getSymbols(SHT_SYMTAB)
|
||||
return sym, err
|
||||
}
|
||||
|
||||
type ImportedSymbol struct {
|
||||
Name string
|
||||
Version string
|
||||
|
||||
@@ -15,7 +15,13 @@ fi
|
||||
|
||||
# Get list of directories from Makefile
|
||||
dirs=$(gomake --no-print-directory echo-dirs)
|
||||
dirpat=$(echo $dirs C | sed 's/ /|/g; s/.*/^(&)$/')
|
||||
dirpat=$(echo $dirs C | awk '{
|
||||
for(i=1;i<=NF;i++){
|
||||
x=$i
|
||||
gsub("/", "\\/", x)
|
||||
printf("/^(%s)$/\n", x)
|
||||
}
|
||||
}')
|
||||
|
||||
for dir in $dirs; do (
|
||||
cd $dir || exit 1
|
||||
@@ -30,7 +36,7 @@ for dir in $dirs; do (
|
||||
deps=$(
|
||||
sed -n '/^import.*"/p; /^import[ \t]*(/,/^)/p' $sources /dev/null |
|
||||
cut -d '"' -f2 |
|
||||
egrep "$dirpat" |
|
||||
awk "$dirpat" |
|
||||
grep -v "^$dir\$" |
|
||||
sed 's/$/.install/' |
|
||||
sed 's;^C\.install;runtime/cgo.install;' |
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# 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.
|
||||
|
||||
include ../../../Make.inc
|
||||
|
||||
TARG=exp/datafmt
|
||||
GOFILES=\
|
||||
datafmt.go\
|
||||
parser.go\
|
||||
|
||||
include ../../../Make.pkg
|
||||
@@ -1,731 +0,0 @@
|
||||
// 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.
|
||||
|
||||
/* Package datafmt implements syntax-directed, type-driven formatting
|
||||
of arbitrary data structures. Formatting a data structure consists of
|
||||
two phases: first, a parser reads a format specification and builds a
|
||||
"compiled" format. Then, the format can be applied repeatedly to
|
||||
arbitrary values. Applying a format to a value evaluates to a []byte
|
||||
containing the formatted value bytes, or nil.
|
||||
|
||||
A format specification is a set of package declarations and format rules:
|
||||
|
||||
Format = [ Entry { ";" Entry } [ ";" ] ] .
|
||||
Entry = PackageDecl | FormatRule .
|
||||
|
||||
(The syntax of a format specification is presented in the same EBNF
|
||||
notation as used in the Go language specification. The syntax of white
|
||||
space, comments, identifiers, and string literals is the same as in Go.)
|
||||
|
||||
A package declaration binds a package name (such as 'ast') to a
|
||||
package import path (such as '"go/ast"'). Each package used (in
|
||||
a type name, see below) must be declared once before use.
|
||||
|
||||
PackageDecl = PackageName ImportPath .
|
||||
PackageName = identifier .
|
||||
ImportPath = string .
|
||||
|
||||
A format rule binds a rule name to a format expression. A rule name
|
||||
may be a type name or one of the special names 'default' or '/'.
|
||||
A type name may be the name of a predeclared type (for example, 'int',
|
||||
'float32', etc.), the package-qualified name of a user-defined type
|
||||
(for example, 'ast.MapType'), or an identifier indicating the structure
|
||||
of unnamed composite types ('array', 'chan', 'func', 'interface', 'map',
|
||||
or 'ptr'). Each rule must have a unique name; rules can be declared in
|
||||
any order.
|
||||
|
||||
FormatRule = RuleName "=" Expression .
|
||||
RuleName = TypeName | "default" | "/" .
|
||||
TypeName = [ PackageName "." ] identifier .
|
||||
|
||||
To format a value, the value's type name is used to select the format rule
|
||||
(there is an override mechanism, see below). The format expression of the
|
||||
selected rule specifies how the value is formatted. Each format expression,
|
||||
when applied to a value, evaluates to a byte sequence or nil.
|
||||
|
||||
In its most general form, a format expression is a list of alternatives,
|
||||
each of which is a sequence of operands:
|
||||
|
||||
Expression = [ Sequence ] { "|" [ Sequence ] } .
|
||||
Sequence = Operand { Operand } .
|
||||
|
||||
The formatted result produced by an expression is the result of the first
|
||||
alternative sequence that evaluates to a non-nil result; if there is no
|
||||
such alternative, the expression evaluates to nil. The result produced by
|
||||
an operand sequence is the concatenation of the results of its operands.
|
||||
If any operand in the sequence evaluates to nil, the entire sequence
|
||||
evaluates to nil.
|
||||
|
||||
There are five kinds of operands:
|
||||
|
||||
Operand = Literal | Field | Group | Option | Repetition .
|
||||
|
||||
Literals evaluate to themselves, with two substitutions. First,
|
||||
%-formats expand in the manner of fmt.Printf, with the current value
|
||||
passed as the parameter. Second, the current indentation (see below)
|
||||
is inserted after every newline or form feed character.
|
||||
|
||||
Literal = string .
|
||||
|
||||
This table shows string literals applied to the value 42 and the
|
||||
corresponding formatted result:
|
||||
|
||||
"foo" foo
|
||||
"%x" 2a
|
||||
"x = %d" x = 42
|
||||
"%#x = %d" 0x2a = 42
|
||||
|
||||
A field operand is a field name optionally followed by an alternate
|
||||
rule name. The field name may be an identifier or one of the special
|
||||
names @ or *.
|
||||
|
||||
Field = FieldName [ ":" RuleName ] .
|
||||
FieldName = identifier | "@" | "*" .
|
||||
|
||||
If the field name is an identifier, the current value must be a struct,
|
||||
and there must be a field with that name in the struct. The same lookup
|
||||
rules apply as in the Go language (for instance, the name of an anonymous
|
||||
field is the unqualified type name). The field name denotes the field
|
||||
value in the struct. If the field is not found, formatting is aborted
|
||||
and an error message is returned. (TODO consider changing the semantics
|
||||
such that if a field is not found, it evaluates to nil).
|
||||
|
||||
The special name '@' denotes the current value.
|
||||
|
||||
The meaning of the special name '*' depends on the type of the current
|
||||
value:
|
||||
|
||||
array, slice types array, slice element (inside {} only, see below)
|
||||
interfaces value stored in interface
|
||||
pointers value pointed to by pointer
|
||||
|
||||
(Implementation restriction: channel, function and map types are not
|
||||
supported due to missing reflection support).
|
||||
|
||||
Fields are evaluated as follows: If the field value is nil, or an array
|
||||
or slice element does not exist, the result is nil (see below for details
|
||||
on array/slice elements). If the value is not nil the field value is
|
||||
formatted (recursively) using the rule corresponding to its type name,
|
||||
or the alternate rule name, if given.
|
||||
|
||||
The following example shows a complete format specification for a
|
||||
struct 'myPackage.Point'. Assume the package
|
||||
|
||||
package myPackage // in directory myDir/myPackage
|
||||
type Point struct {
|
||||
name string;
|
||||
x, y int;
|
||||
}
|
||||
|
||||
Applying the format specification
|
||||
|
||||
myPackage "myDir/myPackage";
|
||||
int = "%d";
|
||||
hexInt = "0x%x";
|
||||
string = "---%s---";
|
||||
myPackage.Point = name "{" x ", " y:hexInt "}";
|
||||
|
||||
to the value myPackage.Point{"foo", 3, 15} results in
|
||||
|
||||
---foo---{3, 0xf}
|
||||
|
||||
Finally, an operand may be a grouped, optional, or repeated expression.
|
||||
A grouped expression ("group") groups a more complex expression (body)
|
||||
so that it can be used in place of a single operand:
|
||||
|
||||
Group = "(" [ Indentation ">>" ] Body ")" .
|
||||
Indentation = Expression .
|
||||
Body = Expression .
|
||||
|
||||
A group body may be prefixed by an indentation expression followed by '>>'.
|
||||
The indentation expression is applied to the current value like any other
|
||||
expression and the result, if not nil, is appended to the current indentation
|
||||
during the evaluation of the body (see also formatting state, below).
|
||||
|
||||
An optional expression ("option") is enclosed in '[]' brackets.
|
||||
|
||||
Option = "[" Body "]" .
|
||||
|
||||
An option evaluates to its body, except that if the body evaluates to nil,
|
||||
the option expression evaluates to an empty []byte. Thus an option's purpose
|
||||
is to protect the expression containing the option from a nil operand.
|
||||
|
||||
A repeated expression ("repetition") is enclosed in '{}' braces.
|
||||
|
||||
Repetition = "{" Body [ "/" Separator ] "}" .
|
||||
Separator = Expression .
|
||||
|
||||
A repeated expression is evaluated as follows: The body is evaluated
|
||||
repeatedly and its results are concatenated until the body evaluates
|
||||
to nil. The result of the repetition is the (possibly empty) concatenation,
|
||||
but it is never nil. An implicit index is supplied for the evaluation of
|
||||
the body: that index is used to address elements of arrays or slices. If
|
||||
the corresponding elements do not exist, the field denoting the element
|
||||
evaluates to nil (which in turn may terminate the repetition).
|
||||
|
||||
The body of a repetition may be followed by a '/' and a "separator"
|
||||
expression. If the separator is present, it is invoked between repetitions
|
||||
of the body.
|
||||
|
||||
The following example shows a complete format specification for formatting
|
||||
a slice of unnamed type. Applying the specification
|
||||
|
||||
int = "%b";
|
||||
array = { * / ", " }; // array is the type name for an unnamed slice
|
||||
|
||||
to the value '[]int{2, 3, 5, 7}' results in
|
||||
|
||||
10, 11, 101, 111
|
||||
|
||||
Default rule: If a format rule named 'default' is present, it is used for
|
||||
formatting a value if no other rule was found. A common default rule is
|
||||
|
||||
default = "%v"
|
||||
|
||||
to provide default formatting for basic types without having to specify
|
||||
a specific rule for each basic type.
|
||||
|
||||
Global separator rule: If a format rule named '/' is present, it is
|
||||
invoked with the current value between literals. If the separator
|
||||
expression evaluates to nil, it is ignored.
|
||||
|
||||
For instance, a global separator rule may be used to punctuate a sequence
|
||||
of values with commas. The rules:
|
||||
|
||||
default = "%v";
|
||||
/ = ", ";
|
||||
|
||||
will format an argument list by printing each one in its default format,
|
||||
separated by a comma and a space.
|
||||
*/
|
||||
package datafmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Format representation
|
||||
|
||||
// Custom formatters implement the Formatter function type.
|
||||
// A formatter is invoked with the current formatting state, the
|
||||
// value to format, and the rule name under which the formatter
|
||||
// was installed (the same formatter function may be installed
|
||||
// under different names). The formatter may access the current state
|
||||
// to guide formatting and use State.Write to append to the state's
|
||||
// output.
|
||||
//
|
||||
// A formatter must return a boolean value indicating if it evaluated
|
||||
// to a non-nil value (true), or a nil value (false).
|
||||
//
|
||||
type Formatter func(state *State, value interface{}, ruleName string) bool
|
||||
|
||||
|
||||
// A FormatterMap is a set of custom formatters.
|
||||
// It maps a rule name to a formatter function.
|
||||
//
|
||||
type FormatterMap map[string]Formatter
|
||||
|
||||
|
||||
// A parsed format expression is built from the following nodes.
|
||||
//
|
||||
type (
|
||||
expr interface{}
|
||||
|
||||
alternatives []expr // x | y | z
|
||||
|
||||
sequence []expr // x y z
|
||||
|
||||
literal [][]byte // a list of string segments, possibly starting with '%'
|
||||
|
||||
field struct {
|
||||
fieldName string // including "@", "*"
|
||||
ruleName string // "" if no rule name specified
|
||||
}
|
||||
|
||||
group struct {
|
||||
indent, body expr // (indent >> body)
|
||||
}
|
||||
|
||||
option struct {
|
||||
body expr // [body]
|
||||
}
|
||||
|
||||
repetition struct {
|
||||
body, separator expr // {body / separator}
|
||||
}
|
||||
|
||||
custom struct {
|
||||
ruleName string
|
||||
fun Formatter
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
// A Format is the result of parsing a format specification.
|
||||
// The format may be applied repeatedly to format values.
|
||||
//
|
||||
type Format map[string]expr
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Formatting
|
||||
|
||||
// An application-specific environment may be provided to Format.Apply;
|
||||
// the environment is available inside custom formatters via State.Env().
|
||||
// Environments must implement copying; the Copy method must return an
|
||||
// complete copy of the receiver. This is necessary so that the formatter
|
||||
// can save and restore an environment (in case of an absent expression).
|
||||
//
|
||||
// If the Environment doesn't change during formatting (this is under
|
||||
// control of the custom formatters), the Copy function can simply return
|
||||
// the receiver, and thus can be very light-weight.
|
||||
//
|
||||
type Environment interface {
|
||||
Copy() Environment
|
||||
}
|
||||
|
||||
|
||||
// State represents the current formatting state.
|
||||
// It is provided as argument to custom formatters.
|
||||
//
|
||||
type State struct {
|
||||
fmt Format // format in use
|
||||
env Environment // user-supplied environment
|
||||
errors chan os.Error // not chan *Error (errors <- nil would be wrong!)
|
||||
hasOutput bool // true after the first literal has been written
|
||||
indent bytes.Buffer // current indentation
|
||||
output bytes.Buffer // format output
|
||||
linePos token.Position // position of line beginning (Column == 0)
|
||||
default_ expr // possibly nil
|
||||
separator expr // possibly nil
|
||||
}
|
||||
|
||||
|
||||
func newState(fmt Format, env Environment, errors chan os.Error) *State {
|
||||
s := new(State)
|
||||
s.fmt = fmt
|
||||
s.env = env
|
||||
s.errors = errors
|
||||
s.linePos = token.Position{Line: 1}
|
||||
|
||||
// if we have a default rule, cache it's expression for fast access
|
||||
if x, found := fmt["default"]; found {
|
||||
s.default_ = x
|
||||
}
|
||||
|
||||
// if we have a global separator rule, cache it's expression for fast access
|
||||
if x, found := fmt["/"]; found {
|
||||
s.separator = x
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
// Env returns the environment passed to Format.Apply.
|
||||
func (s *State) Env() interface{} { return s.env }
|
||||
|
||||
|
||||
// LinePos returns the position of the current line beginning
|
||||
// in the state's output buffer. Line numbers start at 1.
|
||||
//
|
||||
func (s *State) LinePos() token.Position { return s.linePos }
|
||||
|
||||
|
||||
// Pos returns the position of the next byte to be written to the
|
||||
// output buffer. Line numbers start at 1.
|
||||
//
|
||||
func (s *State) Pos() token.Position {
|
||||
offs := s.output.Len()
|
||||
return token.Position{Line: s.linePos.Line, Column: offs - s.linePos.Offset, Offset: offs}
|
||||
}
|
||||
|
||||
|
||||
// Write writes data to the output buffer, inserting the indentation
|
||||
// string after each newline or form feed character. It cannot return an error.
|
||||
//
|
||||
func (s *State) Write(data []byte) (int, os.Error) {
|
||||
n := 0
|
||||
i0 := 0
|
||||
for i, ch := range data {
|
||||
if ch == '\n' || ch == '\f' {
|
||||
// write text segment and indentation
|
||||
n1, _ := s.output.Write(data[i0 : i+1])
|
||||
n2, _ := s.output.Write(s.indent.Bytes())
|
||||
n += n1 + n2
|
||||
i0 = i + 1
|
||||
s.linePos.Offset = s.output.Len()
|
||||
s.linePos.Line++
|
||||
}
|
||||
}
|
||||
n3, _ := s.output.Write(data[i0:])
|
||||
return n + n3, nil
|
||||
}
|
||||
|
||||
|
||||
type checkpoint struct {
|
||||
env Environment
|
||||
hasOutput bool
|
||||
outputLen int
|
||||
linePos token.Position
|
||||
}
|
||||
|
||||
|
||||
func (s *State) save() checkpoint {
|
||||
saved := checkpoint{nil, s.hasOutput, s.output.Len(), s.linePos}
|
||||
if s.env != nil {
|
||||
saved.env = s.env.Copy()
|
||||
}
|
||||
return saved
|
||||
}
|
||||
|
||||
|
||||
func (s *State) restore(m checkpoint) {
|
||||
s.env = m.env
|
||||
s.output.Truncate(m.outputLen)
|
||||
}
|
||||
|
||||
|
||||
func (s *State) error(msg string) {
|
||||
s.errors <- os.NewError(msg)
|
||||
runtime.Goexit()
|
||||
}
|
||||
|
||||
|
||||
// TODO At the moment, unnamed types are simply mapped to the default
|
||||
// names below. For instance, all unnamed arrays are mapped to
|
||||
// 'array' which is not really sufficient. Eventually one may want
|
||||
// to be able to specify rules for say an unnamed slice of T.
|
||||
//
|
||||
|
||||
func typename(typ reflect.Type) string {
|
||||
switch typ.Kind() {
|
||||
case reflect.Array:
|
||||
return "array"
|
||||
case reflect.Slice:
|
||||
return "array"
|
||||
case reflect.Chan:
|
||||
return "chan"
|
||||
case reflect.Func:
|
||||
return "func"
|
||||
case reflect.Interface:
|
||||
return "interface"
|
||||
case reflect.Map:
|
||||
return "map"
|
||||
case reflect.Ptr:
|
||||
return "ptr"
|
||||
}
|
||||
return typ.String()
|
||||
}
|
||||
|
||||
func (s *State) getFormat(name string) expr {
|
||||
if fexpr, found := s.fmt[name]; found {
|
||||
return fexpr
|
||||
}
|
||||
|
||||
if s.default_ != nil {
|
||||
return s.default_
|
||||
}
|
||||
|
||||
s.error(fmt.Sprintf("no format rule for type: '%s'", name))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// eval applies a format expression fexpr to a value. If the expression
|
||||
// evaluates internally to a non-nil []byte, that slice is appended to
|
||||
// the state's output buffer and eval returns true. Otherwise, eval
|
||||
// returns false and the state remains unchanged.
|
||||
//
|
||||
func (s *State) eval(fexpr expr, value reflect.Value, index int) bool {
|
||||
// an empty format expression always evaluates
|
||||
// to a non-nil (but empty) []byte
|
||||
if fexpr == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch t := fexpr.(type) {
|
||||
case alternatives:
|
||||
// append the result of the first alternative that evaluates to
|
||||
// a non-nil []byte to the state's output
|
||||
mark := s.save()
|
||||
for _, x := range t {
|
||||
if s.eval(x, value, index) {
|
||||
return true
|
||||
}
|
||||
s.restore(mark)
|
||||
}
|
||||
return false
|
||||
|
||||
case sequence:
|
||||
// append the result of all operands to the state's output
|
||||
// unless a nil result is encountered
|
||||
mark := s.save()
|
||||
for _, x := range t {
|
||||
if !s.eval(x, value, index) {
|
||||
s.restore(mark)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case literal:
|
||||
// write separator, if any
|
||||
if s.hasOutput {
|
||||
// not the first literal
|
||||
if s.separator != nil {
|
||||
sep := s.separator // save current separator
|
||||
s.separator = nil // and disable it (avoid recursion)
|
||||
mark := s.save()
|
||||
if !s.eval(sep, value, index) {
|
||||
s.restore(mark)
|
||||
}
|
||||
s.separator = sep // enable it again
|
||||
}
|
||||
}
|
||||
s.hasOutput = true
|
||||
// write literal segments
|
||||
for _, lit := range t {
|
||||
if len(lit) > 1 && lit[0] == '%' {
|
||||
// segment contains a %-format at the beginning
|
||||
if lit[1] == '%' {
|
||||
// "%%" is printed as a single "%"
|
||||
s.Write(lit[1:])
|
||||
} else {
|
||||
// use s instead of s.output to get indentation right
|
||||
fmt.Fprintf(s, string(lit), value.Interface())
|
||||
}
|
||||
} else {
|
||||
// segment contains no %-formats
|
||||
s.Write(lit)
|
||||
}
|
||||
}
|
||||
return true // a literal never evaluates to nil
|
||||
|
||||
case *field:
|
||||
// determine field value
|
||||
switch t.fieldName {
|
||||
case "@":
|
||||
// field value is current value
|
||||
|
||||
case "*":
|
||||
// indirection: operation is type-specific
|
||||
switch v := value; v.Kind() {
|
||||
case reflect.Array:
|
||||
if v.Len() <= index {
|
||||
return false
|
||||
}
|
||||
value = v.Index(index)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() || v.Len() <= index {
|
||||
return false
|
||||
}
|
||||
value = v.Index(index)
|
||||
|
||||
case reflect.Map:
|
||||
s.error("reflection support for maps incomplete")
|
||||
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return false
|
||||
}
|
||||
value = v.Elem()
|
||||
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
return false
|
||||
}
|
||||
value = v.Elem()
|
||||
|
||||
case reflect.Chan:
|
||||
s.error("reflection support for chans incomplete")
|
||||
|
||||
case reflect.Func:
|
||||
s.error("reflection support for funcs incomplete")
|
||||
|
||||
default:
|
||||
s.error(fmt.Sprintf("error: * does not apply to `%s`", value.Type()))
|
||||
}
|
||||
|
||||
default:
|
||||
// value is value of named field
|
||||
var field reflect.Value
|
||||
if sval := value; sval.Kind() == reflect.Struct {
|
||||
field = sval.FieldByName(t.fieldName)
|
||||
if !field.IsValid() {
|
||||
// TODO consider just returning false in this case
|
||||
s.error(fmt.Sprintf("error: no field `%s` in `%s`", t.fieldName, value.Type()))
|
||||
}
|
||||
}
|
||||
value = field
|
||||
}
|
||||
|
||||
// determine rule
|
||||
ruleName := t.ruleName
|
||||
if ruleName == "" {
|
||||
// no alternate rule name, value type determines rule
|
||||
ruleName = typename(value.Type())
|
||||
}
|
||||
fexpr = s.getFormat(ruleName)
|
||||
|
||||
mark := s.save()
|
||||
if !s.eval(fexpr, value, index) {
|
||||
s.restore(mark)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
case *group:
|
||||
// remember current indentation
|
||||
indentLen := s.indent.Len()
|
||||
|
||||
// update current indentation
|
||||
mark := s.save()
|
||||
s.eval(t.indent, value, index)
|
||||
// if the indentation evaluates to nil, the state's output buffer
|
||||
// didn't change - either way it's ok to append the difference to
|
||||
// the current indentation
|
||||
s.indent.Write(s.output.Bytes()[mark.outputLen:s.output.Len()])
|
||||
s.restore(mark)
|
||||
|
||||
// format group body
|
||||
mark = s.save()
|
||||
b := true
|
||||
if !s.eval(t.body, value, index) {
|
||||
s.restore(mark)
|
||||
b = false
|
||||
}
|
||||
|
||||
// reset indentation
|
||||
s.indent.Truncate(indentLen)
|
||||
return b
|
||||
|
||||
case *option:
|
||||
// evaluate the body and append the result to the state's output
|
||||
// buffer unless the result is nil
|
||||
mark := s.save()
|
||||
if !s.eval(t.body, value, 0) { // TODO is 0 index correct?
|
||||
s.restore(mark)
|
||||
}
|
||||
return true // an option never evaluates to nil
|
||||
|
||||
case *repetition:
|
||||
// evaluate the body and append the result to the state's output
|
||||
// buffer until a result is nil
|
||||
for i := 0; ; i++ {
|
||||
mark := s.save()
|
||||
// write separator, if any
|
||||
if i > 0 && t.separator != nil {
|
||||
// nil result from separator is ignored
|
||||
mark := s.save()
|
||||
if !s.eval(t.separator, value, i) {
|
||||
s.restore(mark)
|
||||
}
|
||||
}
|
||||
if !s.eval(t.body, value, i) {
|
||||
s.restore(mark)
|
||||
break
|
||||
}
|
||||
}
|
||||
return true // a repetition never evaluates to nil
|
||||
|
||||
case *custom:
|
||||
// invoke the custom formatter to obtain the result
|
||||
mark := s.save()
|
||||
if !t.fun(s, value.Interface(), t.ruleName) {
|
||||
s.restore(mark)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
panic("unreachable")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Eval formats each argument according to the format
|
||||
// f and returns the resulting []byte and os.Error. If
|
||||
// an error occurred, the []byte contains the partially
|
||||
// formatted result. An environment env may be passed
|
||||
// in which is available in custom formatters through
|
||||
// the state parameter.
|
||||
//
|
||||
func (f Format) Eval(env Environment, args ...interface{}) ([]byte, os.Error) {
|
||||
if f == nil {
|
||||
return nil, os.NewError("format is nil")
|
||||
}
|
||||
|
||||
errors := make(chan os.Error)
|
||||
s := newState(f, env, errors)
|
||||
|
||||
go func() {
|
||||
for _, v := range args {
|
||||
fld := reflect.ValueOf(v)
|
||||
if !fld.IsValid() {
|
||||
errors <- os.NewError("nil argument")
|
||||
return
|
||||
}
|
||||
mark := s.save()
|
||||
if !s.eval(s.getFormat(typename(fld.Type())), fld, 0) { // TODO is 0 index correct?
|
||||
s.restore(mark)
|
||||
}
|
||||
}
|
||||
errors <- nil // no errors
|
||||
}()
|
||||
|
||||
err := <-errors
|
||||
return s.output.Bytes(), err
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Convenience functions
|
||||
|
||||
// Fprint formats each argument according to the format f
|
||||
// and writes to w. The result is the total number of bytes
|
||||
// written and an os.Error, if any.
|
||||
//
|
||||
func (f Format) Fprint(w io.Writer, env Environment, args ...interface{}) (int, os.Error) {
|
||||
data, err := f.Eval(env, args...)
|
||||
if err != nil {
|
||||
// TODO should we print partial result in case of error?
|
||||
return 0, err
|
||||
}
|
||||
return w.Write(data)
|
||||
}
|
||||
|
||||
|
||||
// Print formats each argument according to the format f
|
||||
// and writes to standard output. The result is the total
|
||||
// number of bytes written and an os.Error, if any.
|
||||
//
|
||||
func (f Format) Print(args ...interface{}) (int, os.Error) {
|
||||
return f.Fprint(os.Stdout, nil, args...)
|
||||
}
|
||||
|
||||
|
||||
// Sprint formats each argument according to the format f
|
||||
// and returns the resulting string. If an error occurs
|
||||
// during formatting, the result string contains the
|
||||
// partially formatted result followed by an error message.
|
||||
//
|
||||
func (f Format) Sprint(args ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
_, err := f.Fprint(&buf, nil, args...)
|
||||
if err != nil {
|
||||
var i interface{} = args
|
||||
fmt.Fprintf(&buf, "--- Sprint(%s) failed: %v", fmt.Sprint(i), err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package datafmt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
|
||||
|
||||
func parse(t *testing.T, form string, fmap FormatterMap) Format {
|
||||
f, err := Parse(fset, "", []byte(form), fmap)
|
||||
if err != nil {
|
||||
t.Errorf("Parse(%s): %v", form, err)
|
||||
return nil
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
|
||||
func verify(t *testing.T, f Format, expected string, args ...interface{}) {
|
||||
if f == nil {
|
||||
return // allow other tests to run
|
||||
}
|
||||
result := f.Sprint(args...)
|
||||
if result != expected {
|
||||
t.Errorf(
|
||||
"result : `%s`\nexpected: `%s`\n\n",
|
||||
result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func formatter(s *State, value interface{}, rule_name string) bool {
|
||||
switch rule_name {
|
||||
case "/":
|
||||
fmt.Fprintf(s, "%d %d %d", s.Pos().Line, s.LinePos().Column, s.Pos().Column)
|
||||
return true
|
||||
case "blank":
|
||||
s.Write([]byte{' '})
|
||||
return true
|
||||
case "int":
|
||||
if value.(int)&1 == 0 {
|
||||
fmt.Fprint(s, "even ")
|
||||
} else {
|
||||
fmt.Fprint(s, "odd ")
|
||||
}
|
||||
return true
|
||||
case "nil":
|
||||
return false
|
||||
case "testing.T":
|
||||
s.Write([]byte("testing.T"))
|
||||
return true
|
||||
}
|
||||
panic("unreachable")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
func TestCustomFormatters(t *testing.T) {
|
||||
fmap0 := FormatterMap{"/": formatter}
|
||||
fmap1 := FormatterMap{"int": formatter, "blank": formatter, "nil": formatter}
|
||||
fmap2 := FormatterMap{"testing.T": formatter}
|
||||
|
||||
f := parse(t, `int=`, fmap0)
|
||||
verify(t, f, ``, 1, 2, 3)
|
||||
|
||||
f = parse(t, `int="#"`, nil)
|
||||
verify(t, f, `###`, 1, 2, 3)
|
||||
|
||||
f = parse(t, `int="#";string="%s"`, fmap0)
|
||||
verify(t, f, "#1 0 1#1 0 7#1 0 13\n2 0 0foo2 0 8\n", 1, 2, 3, "\n", "foo", "\n")
|
||||
|
||||
f = parse(t, ``, fmap1)
|
||||
verify(t, f, `even odd even odd `, 0, 1, 2, 3)
|
||||
|
||||
f = parse(t, `/ =@:blank; float64="#"`, fmap1)
|
||||
verify(t, f, `# # #`, 0.0, 1.0, 2.0)
|
||||
|
||||
f = parse(t, `float64=@:nil`, fmap1)
|
||||
verify(t, f, ``, 0.0, 1.0, 2.0)
|
||||
|
||||
f = parse(t, `testing "testing"; ptr=*`, fmap2)
|
||||
verify(t, f, `testing.T`, t)
|
||||
|
||||
// TODO needs more tests
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Formatting of basic and simple composite types
|
||||
|
||||
func check(t *testing.T, form, expected string, args ...interface{}) {
|
||||
f := parse(t, form, nil)
|
||||
if f == nil {
|
||||
return // allow other tests to run
|
||||
}
|
||||
result := f.Sprint(args...)
|
||||
if result != expected {
|
||||
t.Errorf(
|
||||
"format : %s\nresult : `%s`\nexpected: `%s`\n\n",
|
||||
form, result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestBasicTypes(t *testing.T) {
|
||||
check(t, ``, ``)
|
||||
check(t, `bool=":%v"`, `:true:false`, true, false)
|
||||
check(t, `int="%b %d %o 0x%x"`, `101010 42 52 0x2a`, 42)
|
||||
|
||||
check(t, `int="%"`, `%`, 42)
|
||||
check(t, `int="%%"`, `%`, 42)
|
||||
check(t, `int="**%%**"`, `**%**`, 42)
|
||||
check(t, `int="%%%%%%"`, `%%%`, 42)
|
||||
check(t, `int="%%%d%%"`, `%42%`, 42)
|
||||
|
||||
const i = -42
|
||||
const is = `-42`
|
||||
check(t, `int ="%d"`, is, i)
|
||||
check(t, `int8 ="%d"`, is, int8(i))
|
||||
check(t, `int16="%d"`, is, int16(i))
|
||||
check(t, `int32="%d"`, is, int32(i))
|
||||
check(t, `int64="%d"`, is, int64(i))
|
||||
|
||||
const u = 42
|
||||
const us = `42`
|
||||
check(t, `uint ="%d"`, us, uint(u))
|
||||
check(t, `uint8 ="%d"`, us, uint8(u))
|
||||
check(t, `uint16="%d"`, us, uint16(u))
|
||||
check(t, `uint32="%d"`, us, uint32(u))
|
||||
check(t, `uint64="%d"`, us, uint64(u))
|
||||
|
||||
const f = 3.141592
|
||||
const fs = `3.141592`
|
||||
check(t, `float64="%g"`, fs, f)
|
||||
check(t, `float32="%g"`, fs, float32(f))
|
||||
check(t, `float64="%g"`, fs, float64(f))
|
||||
}
|
||||
|
||||
|
||||
func TestArrayTypes(t *testing.T) {
|
||||
var a0 [10]int
|
||||
check(t, `array="array";`, `array`, a0)
|
||||
|
||||
a1 := [...]int{1, 2, 3}
|
||||
check(t, `array="array";`, `array`, a1)
|
||||
check(t, `array={*}; int="%d";`, `123`, a1)
|
||||
check(t, `array={* / ", "}; int="%d";`, `1, 2, 3`, a1)
|
||||
check(t, `array={* / *}; int="%d";`, `12233`, a1)
|
||||
|
||||
a2 := []interface{}{42, "foo", 3.14}
|
||||
check(t, `array={* / ", "}; interface=*; string="bar"; default="%v";`, `42, bar, 3.14`, a2)
|
||||
}
|
||||
|
||||
|
||||
func TestChanTypes(t *testing.T) {
|
||||
var c0 chan int
|
||||
check(t, `chan="chan"`, `chan`, c0)
|
||||
|
||||
c1 := make(chan int)
|
||||
go func() { c1 <- 42 }()
|
||||
check(t, `chan="chan"`, `chan`, c1)
|
||||
// check(t, `chan=*`, `42`, c1); // reflection support for chans incomplete
|
||||
}
|
||||
|
||||
|
||||
func TestFuncTypes(t *testing.T) {
|
||||
var f0 func() int
|
||||
check(t, `func="func"`, `func`, f0)
|
||||
|
||||
f1 := func() int { return 42 }
|
||||
check(t, `func="func"`, `func`, f1)
|
||||
// check(t, `func=*`, `42`, f1); // reflection support for funcs incomplete
|
||||
}
|
||||
|
||||
|
||||
func TestMapTypes(t *testing.T) {
|
||||
var m0 map[string]int
|
||||
check(t, `map="map"`, `map`, m0)
|
||||
|
||||
m1 := map[string]int{}
|
||||
check(t, `map="map"`, `map`, m1)
|
||||
// check(t, `map=*`, ``, m1); // reflection support for maps incomplete
|
||||
}
|
||||
|
||||
|
||||
func TestPointerTypes(t *testing.T) {
|
||||
var p0 *int
|
||||
check(t, `ptr="ptr"`, `ptr`, p0)
|
||||
check(t, `ptr=*`, ``, p0)
|
||||
check(t, `ptr=*|"nil"`, `nil`, p0)
|
||||
|
||||
x := 99991
|
||||
p1 := &x
|
||||
check(t, `ptr="ptr"`, `ptr`, p1)
|
||||
check(t, `ptr=*; int="%d"`, `99991`, p1)
|
||||
}
|
||||
|
||||
|
||||
func TestDefaultRule(t *testing.T) {
|
||||
check(t, `default="%v"`, `42foo3.14`, 42, "foo", 3.14)
|
||||
check(t, `default="%v"; int="%x"`, `abcdef`, 10, 11, 12, 13, 14, 15)
|
||||
check(t, `default="%v"; int="%x"`, `ab**ef`, 10, 11, "**", 14, 15)
|
||||
check(t, `default="%x"; int=@:default`, `abcdef`, 10, 11, 12, 13, 14, 15)
|
||||
}
|
||||
|
||||
|
||||
func TestGlobalSeparatorRule(t *testing.T) {
|
||||
check(t, `int="%d"; / ="-"`, `1-2-3-4`, 1, 2, 3, 4)
|
||||
check(t, `int="%x%x"; / ="*"`, `aa*aa`, 10, 10)
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Formatting of a struct
|
||||
|
||||
type T1 struct {
|
||||
a int
|
||||
}
|
||||
|
||||
const F1 = `datafmt "datafmt";` +
|
||||
`int = "%d";` +
|
||||
`datafmt.T1 = "<" a ">";`
|
||||
|
||||
func TestStruct1(t *testing.T) { check(t, F1, "<42>", T1{42}) }
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Formatting of a struct with an optional field (ptr)
|
||||
|
||||
type T2 struct {
|
||||
s string
|
||||
p *T1
|
||||
}
|
||||
|
||||
const F2a = F1 +
|
||||
`string = "%s";` +
|
||||
`ptr = *;` +
|
||||
`datafmt.T2 = s ["-" p "-"];`
|
||||
|
||||
const F2b = F1 +
|
||||
`string = "%s";` +
|
||||
`ptr = *;` +
|
||||
`datafmt.T2 = s ("-" p "-" | "empty");`
|
||||
|
||||
func TestStruct2(t *testing.T) {
|
||||
check(t, F2a, "foo", T2{"foo", nil})
|
||||
check(t, F2a, "bar-<17>-", T2{"bar", &T1{17}})
|
||||
check(t, F2b, "fooempty", T2{"foo", nil})
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Formatting of a struct with a repetitive field (slice)
|
||||
|
||||
type T3 struct {
|
||||
s string
|
||||
a []int
|
||||
}
|
||||
|
||||
const F3a = `datafmt "datafmt";` +
|
||||
`default = "%v";` +
|
||||
`array = *;` +
|
||||
`datafmt.T3 = s {" " a a / ","};`
|
||||
|
||||
const F3b = `datafmt "datafmt";` +
|
||||
`int = "%d";` +
|
||||
`string = "%s";` +
|
||||
`array = *;` +
|
||||
`nil = ;` +
|
||||
`empty = *:nil;` +
|
||||
`datafmt.T3 = s [a:empty ": " {a / "-"}]`
|
||||
|
||||
func TestStruct3(t *testing.T) {
|
||||
check(t, F3a, "foo", T3{"foo", nil})
|
||||
check(t, F3a, "foo 00, 11, 22", T3{"foo", []int{0, 1, 2}})
|
||||
check(t, F3b, "bar", T3{"bar", nil})
|
||||
check(t, F3b, "bal: 2-3-5", T3{"bal", []int{2, 3, 5}})
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Formatting of a struct with alternative field
|
||||
|
||||
type T4 struct {
|
||||
x *int
|
||||
a []int
|
||||
}
|
||||
|
||||
const F4a = `datafmt "datafmt";` +
|
||||
`int = "%d";` +
|
||||
`ptr = *;` +
|
||||
`array = *;` +
|
||||
`nil = ;` +
|
||||
`empty = *:nil;` +
|
||||
`datafmt.T4 = "<" (x:empty x | "-") ">" `
|
||||
|
||||
const F4b = `datafmt "datafmt";` +
|
||||
`int = "%d";` +
|
||||
`ptr = *;` +
|
||||
`array = *;` +
|
||||
`nil = ;` +
|
||||
`empty = *:nil;` +
|
||||
`datafmt.T4 = "<" (a:empty {a / ", "} | "-") ">" `
|
||||
|
||||
func TestStruct4(t *testing.T) {
|
||||
x := 7
|
||||
check(t, F4a, "<->", T4{nil, nil})
|
||||
check(t, F4a, "<7>", T4{&x, nil})
|
||||
check(t, F4b, "<->", T4{nil, nil})
|
||||
check(t, F4b, "<2, 3, 7>", T4{nil, []int{2, 3, 7}})
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Formatting a struct (documentation example)
|
||||
|
||||
type Point struct {
|
||||
name string
|
||||
x, y int
|
||||
}
|
||||
|
||||
const FPoint = `datafmt "datafmt";` +
|
||||
`int = "%d";` +
|
||||
`hexInt = "0x%x";` +
|
||||
`string = "---%s---";` +
|
||||
`datafmt.Point = name "{" x ", " y:hexInt "}";`
|
||||
|
||||
func TestStructPoint(t *testing.T) {
|
||||
p := Point{"foo", 3, 15}
|
||||
check(t, FPoint, "---foo---{3, 0xf}", p)
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Formatting a slice (documentation example)
|
||||
|
||||
const FSlice = `int = "%b";` +
|
||||
`array = { * / ", " }`
|
||||
|
||||
func TestSlice(t *testing.T) { check(t, FSlice, "10, 11, 101, 111", []int{2, 3, 5, 7}) }
|
||||
|
||||
|
||||
// TODO add more tests
|
||||
@@ -1,386 +0,0 @@
|
||||
// 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.
|
||||
|
||||
package datafmt
|
||||
|
||||
import (
|
||||
"container/vector"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Parsing
|
||||
|
||||
type parser struct {
|
||||
scanner.ErrorVector
|
||||
scanner scanner.Scanner
|
||||
file *token.File
|
||||
pos token.Pos // token position
|
||||
tok token.Token // one token look-ahead
|
||||
lit string // token literal
|
||||
|
||||
packs map[string]string // PackageName -> ImportPath
|
||||
rules map[string]expr // RuleName -> Expression
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) next() {
|
||||
p.pos, p.tok, p.lit = p.scanner.Scan()
|
||||
switch p.tok {
|
||||
case token.CHAN, token.FUNC, token.INTERFACE, token.MAP, token.STRUCT:
|
||||
// Go keywords for composite types are type names
|
||||
// returned by reflect. Accept them as identifiers.
|
||||
p.tok = token.IDENT // p.lit is already set correctly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) init(fset *token.FileSet, filename string, src []byte) {
|
||||
p.ErrorVector.Reset()
|
||||
p.file = fset.AddFile(filename, fset.Base(), len(src))
|
||||
p.scanner.Init(p.file, src, p, scanner.AllowIllegalChars) // return '@' as token.ILLEGAL w/o error message
|
||||
p.next() // initializes pos, tok, lit
|
||||
p.packs = make(map[string]string)
|
||||
p.rules = make(map[string]expr)
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) error(pos token.Pos, msg string) {
|
||||
p.Error(p.file.Position(pos), msg)
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) errorExpected(pos token.Pos, msg string) {
|
||||
msg = "expected " + msg
|
||||
if pos == p.pos {
|
||||
// the error happened at the current position;
|
||||
// make the error message more specific
|
||||
msg += ", found '" + p.tok.String() + "'"
|
||||
if p.tok.IsLiteral() {
|
||||
msg += " " + p.lit
|
||||
}
|
||||
}
|
||||
p.error(pos, msg)
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) expect(tok token.Token) token.Pos {
|
||||
pos := p.pos
|
||||
if p.tok != tok {
|
||||
p.errorExpected(pos, "'"+tok.String()+"'")
|
||||
}
|
||||
p.next() // make progress in any case
|
||||
return pos
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseIdentifier() string {
|
||||
name := p.lit
|
||||
p.expect(token.IDENT)
|
||||
return name
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseTypeName() (string, bool) {
|
||||
pos := p.pos
|
||||
name, isIdent := p.parseIdentifier(), true
|
||||
if p.tok == token.PERIOD {
|
||||
// got a package name, lookup package
|
||||
if importPath, found := p.packs[name]; found {
|
||||
name = importPath
|
||||
} else {
|
||||
p.error(pos, "package not declared: "+name)
|
||||
}
|
||||
p.next()
|
||||
name, isIdent = name+"."+p.parseIdentifier(), false
|
||||
}
|
||||
return name, isIdent
|
||||
}
|
||||
|
||||
|
||||
// Parses a rule name and returns it. If the rule name is
|
||||
// a package-qualified type name, the package name is resolved.
|
||||
// The 2nd result value is true iff the rule name consists of a
|
||||
// single identifier only (and thus could be a package name).
|
||||
//
|
||||
func (p *parser) parseRuleName() (string, bool) {
|
||||
name, isIdent := "", false
|
||||
switch p.tok {
|
||||
case token.IDENT:
|
||||
name, isIdent = p.parseTypeName()
|
||||
case token.DEFAULT:
|
||||
name = "default"
|
||||
p.next()
|
||||
case token.QUO:
|
||||
name = "/"
|
||||
p.next()
|
||||
default:
|
||||
p.errorExpected(p.pos, "rule name")
|
||||
p.next() // make progress in any case
|
||||
}
|
||||
return name, isIdent
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseString() string {
|
||||
s := ""
|
||||
if p.tok == token.STRING {
|
||||
s, _ = strconv.Unquote(p.lit)
|
||||
// Unquote may fail with an error, but only if the scanner found
|
||||
// an illegal string in the first place. In this case the error
|
||||
// has already been reported.
|
||||
p.next()
|
||||
return s
|
||||
} else {
|
||||
p.expect(token.STRING)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseLiteral() literal {
|
||||
s := []byte(p.parseString())
|
||||
|
||||
// A string literal may contain %-format specifiers. To simplify
|
||||
// and speed up printing of the literal, split it into segments
|
||||
// that start with "%" possibly followed by a last segment that
|
||||
// starts with some other character.
|
||||
var list vector.Vector
|
||||
i0 := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '%' && i+1 < len(s) {
|
||||
// the next segment starts with a % format
|
||||
if i0 < i {
|
||||
// the current segment is not empty, split it off
|
||||
list.Push(s[i0:i])
|
||||
i0 = i
|
||||
}
|
||||
i++ // skip %; let loop skip over char after %
|
||||
}
|
||||
}
|
||||
// the final segment may start with any character
|
||||
// (it is empty iff the string is empty)
|
||||
list.Push(s[i0:])
|
||||
|
||||
// convert list into a literal
|
||||
lit := make(literal, list.Len())
|
||||
for i := 0; i < list.Len(); i++ {
|
||||
lit[i] = list.At(i).([]byte)
|
||||
}
|
||||
|
||||
return lit
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseField() expr {
|
||||
var fname string
|
||||
switch p.tok {
|
||||
case token.ILLEGAL:
|
||||
if p.lit != "@" {
|
||||
return nil
|
||||
}
|
||||
fname = "@"
|
||||
p.next()
|
||||
case token.MUL:
|
||||
fname = "*"
|
||||
p.next()
|
||||
case token.IDENT:
|
||||
fname = p.parseIdentifier()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
var ruleName string
|
||||
if p.tok == token.COLON {
|
||||
p.next()
|
||||
ruleName, _ = p.parseRuleName()
|
||||
}
|
||||
|
||||
return &field{fname, ruleName}
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseOperand() (x expr) {
|
||||
switch p.tok {
|
||||
case token.STRING:
|
||||
x = p.parseLiteral()
|
||||
|
||||
case token.LPAREN:
|
||||
p.next()
|
||||
x = p.parseExpression()
|
||||
if p.tok == token.SHR {
|
||||
p.next()
|
||||
x = &group{x, p.parseExpression()}
|
||||
}
|
||||
p.expect(token.RPAREN)
|
||||
|
||||
case token.LBRACK:
|
||||
p.next()
|
||||
x = &option{p.parseExpression()}
|
||||
p.expect(token.RBRACK)
|
||||
|
||||
case token.LBRACE:
|
||||
p.next()
|
||||
x = p.parseExpression()
|
||||
var div expr
|
||||
if p.tok == token.QUO {
|
||||
p.next()
|
||||
div = p.parseExpression()
|
||||
}
|
||||
x = &repetition{x, div}
|
||||
p.expect(token.RBRACE)
|
||||
|
||||
default:
|
||||
x = p.parseField() // may be nil
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseSequence() expr {
|
||||
var list vector.Vector
|
||||
|
||||
for x := p.parseOperand(); x != nil; x = p.parseOperand() {
|
||||
list.Push(x)
|
||||
}
|
||||
|
||||
// no need for a sequence if list.Len() < 2
|
||||
switch list.Len() {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return list.At(0).(expr)
|
||||
}
|
||||
|
||||
// convert list into a sequence
|
||||
seq := make(sequence, list.Len())
|
||||
for i := 0; i < list.Len(); i++ {
|
||||
seq[i] = list.At(i).(expr)
|
||||
}
|
||||
return seq
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseExpression() expr {
|
||||
var list vector.Vector
|
||||
|
||||
for {
|
||||
x := p.parseSequence()
|
||||
if x != nil {
|
||||
list.Push(x)
|
||||
}
|
||||
if p.tok != token.OR {
|
||||
break
|
||||
}
|
||||
p.next()
|
||||
}
|
||||
|
||||
// no need for an alternatives if list.Len() < 2
|
||||
switch list.Len() {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return list.At(0).(expr)
|
||||
}
|
||||
|
||||
// convert list into a alternatives
|
||||
alt := make(alternatives, list.Len())
|
||||
for i := 0; i < list.Len(); i++ {
|
||||
alt[i] = list.At(i).(expr)
|
||||
}
|
||||
return alt
|
||||
}
|
||||
|
||||
|
||||
func (p *parser) parseFormat() {
|
||||
for p.tok != token.EOF {
|
||||
pos := p.pos
|
||||
|
||||
name, isIdent := p.parseRuleName()
|
||||
switch p.tok {
|
||||
case token.STRING:
|
||||
// package declaration
|
||||
importPath := p.parseString()
|
||||
|
||||
// add package declaration
|
||||
if !isIdent {
|
||||
p.error(pos, "illegal package name: "+name)
|
||||
} else if _, found := p.packs[name]; !found {
|
||||
p.packs[name] = importPath
|
||||
} else {
|
||||
p.error(pos, "package already declared: "+name)
|
||||
}
|
||||
|
||||
case token.ASSIGN:
|
||||
// format rule
|
||||
p.next()
|
||||
x := p.parseExpression()
|
||||
|
||||
// add rule
|
||||
if _, found := p.rules[name]; !found {
|
||||
p.rules[name] = x
|
||||
} else {
|
||||
p.error(pos, "format rule already declared: "+name)
|
||||
}
|
||||
|
||||
default:
|
||||
p.errorExpected(p.pos, "package declaration or format rule")
|
||||
p.next() // make progress in any case
|
||||
}
|
||||
|
||||
if p.tok == token.SEMICOLON {
|
||||
p.next()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
p.expect(token.EOF)
|
||||
}
|
||||
|
||||
|
||||
func remap(p *parser, name string) string {
|
||||
i := strings.Index(name, ".")
|
||||
if i >= 0 {
|
||||
packageName, suffix := name[0:i], name[i:]
|
||||
// lookup package
|
||||
if importPath, found := p.packs[packageName]; found {
|
||||
name = importPath + suffix
|
||||
} else {
|
||||
var invalidPos token.Position
|
||||
p.Error(invalidPos, "package not declared: "+packageName)
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
|
||||
// Parse parses a set of format productions from source src. Custom
|
||||
// formatters may be provided via a map of formatter functions. If
|
||||
// there are no errors, the result is a Format and the error is nil.
|
||||
// Otherwise the format is nil and a non-empty ErrorList is returned.
|
||||
//
|
||||
func Parse(fset *token.FileSet, filename string, src []byte, fmap FormatterMap) (Format, os.Error) {
|
||||
// parse source
|
||||
var p parser
|
||||
p.init(fset, filename, src)
|
||||
p.parseFormat()
|
||||
|
||||
// add custom formatters, if any
|
||||
for name, form := range fmap {
|
||||
name = remap(&p, name)
|
||||
if _, found := p.rules[name]; !found {
|
||||
p.rules[name] = &custom{name, form}
|
||||
} else {
|
||||
var invalidPos token.Position
|
||||
p.Error(invalidPos, "formatter already declared: "+name)
|
||||
}
|
||||
}
|
||||
|
||||
return p.rules, p.GetError(scanner.NoMultiples)
|
||||
}
|
||||
@@ -62,7 +62,7 @@ type I int
|
||||
func (i I) String() string { return Sprintf("<%d>", int(i)) }
|
||||
|
||||
type B struct {
|
||||
i I
|
||||
I I
|
||||
j int
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@ func (g G) GoString() string {
|
||||
}
|
||||
|
||||
type S struct {
|
||||
f F // a struct field that Formats
|
||||
g G // a struct field that GoStrings
|
||||
F F // a struct field that Formats
|
||||
G G // a struct field that GoStrings
|
||||
}
|
||||
|
||||
// A type with a String method with pointer receiver for testing %p
|
||||
@@ -322,8 +322,8 @@ var fmttests = []struct {
|
||||
{"%+v", A{1, 2, "a", []int{1, 2}}, `{i:1 j:2 s:a x:[1 2]}`},
|
||||
|
||||
// +v on structs with Stringable items
|
||||
{"%+v", B{1, 2}, `{i:<1> j:2}`},
|
||||
{"%+v", C{1, B{2, 3}}, `{i:1 B:{i:<2> j:3}}`},
|
||||
{"%+v", B{1, 2}, `{I:<1> j:2}`},
|
||||
{"%+v", C{1, B{2, 3}}, `{i:1 B:{I:<2> j:3}}`},
|
||||
|
||||
// q on Stringable items
|
||||
{"%s", I(23), `<23>`},
|
||||
@@ -339,7 +339,7 @@ var fmttests = []struct {
|
||||
{"%#v", uint64(1<<64 - 1), "0xffffffffffffffff"},
|
||||
{"%#v", 1000000000, "1000000000"},
|
||||
{"%#v", map[string]int{"a": 1, "b": 2}, `map[string] int{"a":1, "b":2}`},
|
||||
{"%#v", map[string]B{"a": {1, 2}, "b": {3, 4}}, `map[string] fmt_test.B{"a":fmt_test.B{i:1, j:2}, "b":fmt_test.B{i:3, j:4}}`},
|
||||
{"%#v", map[string]B{"a": {1, 2}, "b": {3, 4}}, `map[string] fmt_test.B{"a":fmt_test.B{I:1, j:2}, "b":fmt_test.B{I:3, j:4}}`},
|
||||
{"%#v", []string{"a", "b"}, `[]string{"a", "b"}`},
|
||||
|
||||
// slices with other formats
|
||||
@@ -374,11 +374,11 @@ var fmttests = []struct {
|
||||
// Formatter
|
||||
{"%x", F(1), "<x=F(1)>"},
|
||||
{"%x", G(2), "2"},
|
||||
{"%+v", S{F(4), G(5)}, "{f:<v=F(4)> g:5}"},
|
||||
{"%+v", S{F(4), G(5)}, "{F:<v=F(4)> G:5}"},
|
||||
|
||||
// GoStringer
|
||||
{"%#v", G(6), "GoString(6)"},
|
||||
{"%#v", S{F(7), G(8)}, "fmt_test.S{f:<v=F(7)>, g:GoString(8)}"},
|
||||
{"%#v", S{F(7), G(8)}, "fmt_test.S{F:<v=F(7)>, G:GoString(8)}"},
|
||||
|
||||
// %T
|
||||
{"%T", (4 - 3i), "complex128"},
|
||||
|
||||
@@ -259,10 +259,8 @@ func Sprintln(a ...interface{}) string {
|
||||
// the thing inside the interface, not the interface itself.
|
||||
func getField(v reflect.Value, i int) reflect.Value {
|
||||
val := v.Field(i)
|
||||
if i := val; i.Kind() == reflect.Interface {
|
||||
if inter := i.Interface(); inter != nil {
|
||||
return reflect.ValueOf(inter)
|
||||
}
|
||||
if val.Kind() == reflect.Interface && !val.IsNil() {
|
||||
val = val.Elem()
|
||||
}
|
||||
return val
|
||||
}
|
||||
@@ -289,27 +287,32 @@ func (p *pp) unknownType(v interface{}) {
|
||||
p.buf.WriteByte('?')
|
||||
}
|
||||
|
||||
func (p *pp) badVerb(verb int, val interface{}) {
|
||||
func (p *pp) badVerb(verb int, val interface{}, val1 reflect.Value) {
|
||||
p.add('%')
|
||||
p.add('!')
|
||||
p.add(verb)
|
||||
p.add('(')
|
||||
if val == nil {
|
||||
p.buf.Write(nilAngleBytes)
|
||||
} else {
|
||||
switch {
|
||||
case val != nil:
|
||||
p.buf.WriteString(reflect.TypeOf(val).String())
|
||||
p.add('=')
|
||||
p.printField(val, 'v', false, false, 0)
|
||||
case val1.IsValid():
|
||||
p.buf.WriteString(val1.Type().String())
|
||||
p.add('=')
|
||||
p.printValue(val1, 'v', false, false, 0)
|
||||
default:
|
||||
p.buf.Write(nilAngleBytes)
|
||||
}
|
||||
p.add(')')
|
||||
}
|
||||
|
||||
func (p *pp) fmtBool(v bool, verb int, value interface{}) {
|
||||
func (p *pp) fmtBool(v bool, verb int, value interface{}, value1 reflect.Value) {
|
||||
switch verb {
|
||||
case 't', 'v':
|
||||
p.fmt.fmt_boolean(v)
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +326,7 @@ func (p *pp) fmtC(c int64) {
|
||||
p.fmt.pad(p.runeBuf[0:w])
|
||||
}
|
||||
|
||||
func (p *pp) fmtInt64(v int64, verb int, value interface{}) {
|
||||
func (p *pp) fmtInt64(v int64, verb int, value interface{}, value1 reflect.Value) {
|
||||
switch verb {
|
||||
case 'b':
|
||||
p.fmt.integer(v, 2, signed, ldigits)
|
||||
@@ -337,7 +340,7 @@ func (p *pp) fmtInt64(v int64, verb int, value interface{}) {
|
||||
if 0 <= v && v <= unicode.MaxRune {
|
||||
p.fmt.fmt_qc(v)
|
||||
} else {
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
case 'x':
|
||||
p.fmt.integer(v, 16, signed, ldigits)
|
||||
@@ -346,7 +349,7 @@ func (p *pp) fmtInt64(v int64, verb int, value interface{}) {
|
||||
case 'X':
|
||||
p.fmt.integer(v, 16, signed, udigits)
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +379,7 @@ func (p *pp) fmtUnicode(v int64) {
|
||||
p.fmt.precPresent = precPresent
|
||||
}
|
||||
|
||||
func (p *pp) fmtUint64(v uint64, verb int, goSyntax bool, value interface{}) {
|
||||
func (p *pp) fmtUint64(v uint64, verb int, goSyntax bool, value interface{}, value1 reflect.Value) {
|
||||
switch verb {
|
||||
case 'b':
|
||||
p.fmt.integer(int64(v), 2, unsigned, ldigits)
|
||||
@@ -396,7 +399,7 @@ func (p *pp) fmtUint64(v uint64, verb int, goSyntax bool, value interface{}) {
|
||||
if 0 <= v && v <= unicode.MaxRune {
|
||||
p.fmt.fmt_qc(int64(v))
|
||||
} else {
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
case 'x':
|
||||
p.fmt.integer(int64(v), 16, unsigned, ldigits)
|
||||
@@ -405,11 +408,11 @@ func (p *pp) fmtUint64(v uint64, verb int, goSyntax bool, value interface{}) {
|
||||
case 'U':
|
||||
p.fmtUnicode(int64(v))
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pp) fmtFloat32(v float32, verb int, value interface{}) {
|
||||
func (p *pp) fmtFloat32(v float32, verb int, value interface{}, value1 reflect.Value) {
|
||||
switch verb {
|
||||
case 'b':
|
||||
p.fmt.fmt_fb32(v)
|
||||
@@ -424,11 +427,11 @@ func (p *pp) fmtFloat32(v float32, verb int, value interface{}) {
|
||||
case 'G':
|
||||
p.fmt.fmt_G32(v)
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pp) fmtFloat64(v float64, verb int, value interface{}) {
|
||||
func (p *pp) fmtFloat64(v float64, verb int, value interface{}, value1 reflect.Value) {
|
||||
switch verb {
|
||||
case 'b':
|
||||
p.fmt.fmt_fb64(v)
|
||||
@@ -443,33 +446,33 @@ func (p *pp) fmtFloat64(v float64, verb int, value interface{}) {
|
||||
case 'G':
|
||||
p.fmt.fmt_G64(v)
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pp) fmtComplex64(v complex64, verb int, value interface{}) {
|
||||
func (p *pp) fmtComplex64(v complex64, verb int, value interface{}, value1 reflect.Value) {
|
||||
switch verb {
|
||||
case 'e', 'E', 'f', 'F', 'g', 'G':
|
||||
p.fmt.fmt_c64(v, verb)
|
||||
case 'v':
|
||||
p.fmt.fmt_c64(v, 'g')
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pp) fmtComplex128(v complex128, verb int, value interface{}) {
|
||||
func (p *pp) fmtComplex128(v complex128, verb int, value interface{}, value1 reflect.Value) {
|
||||
switch verb {
|
||||
case 'e', 'E', 'f', 'F', 'g', 'G':
|
||||
p.fmt.fmt_c128(v, verb)
|
||||
case 'v':
|
||||
p.fmt.fmt_c128(v, 'g')
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pp) fmtString(v string, verb int, goSyntax bool, value interface{}) {
|
||||
func (p *pp) fmtString(v string, verb int, goSyntax bool, value interface{}, value1 reflect.Value) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if goSyntax {
|
||||
@@ -486,11 +489,11 @@ func (p *pp) fmtString(v string, verb int, goSyntax bool, value interface{}) {
|
||||
case 'q':
|
||||
p.fmt.fmt_q(v)
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pp) fmtBytes(v []byte, verb int, goSyntax bool, depth int, value interface{}) {
|
||||
func (p *pp) fmtBytes(v []byte, verb int, goSyntax bool, depth int, value interface{}, value1 reflect.Value) {
|
||||
if verb == 'v' || verb == 'd' {
|
||||
if goSyntax {
|
||||
p.buf.Write(bytesBytes)
|
||||
@@ -525,7 +528,7 @@ func (p *pp) fmtBytes(v []byte, verb int, goSyntax bool, depth int, value interf
|
||||
case 'q':
|
||||
p.fmt.fmt_q(s)
|
||||
default:
|
||||
p.badVerb(verb, value)
|
||||
p.badVerb(verb, value, value1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,12 +538,12 @@ func (p *pp) fmtPointer(field interface{}, value reflect.Value, verb int, goSynt
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
|
||||
u = value.Pointer()
|
||||
default:
|
||||
p.badVerb(verb, field)
|
||||
p.badVerb(verb, field, value)
|
||||
return
|
||||
}
|
||||
if goSyntax {
|
||||
p.add('(')
|
||||
p.buf.WriteString(reflect.TypeOf(field).String())
|
||||
p.buf.WriteString(value.Type().String())
|
||||
p.add(')')
|
||||
p.add('(')
|
||||
if u == 0 {
|
||||
@@ -561,12 +564,48 @@ var (
|
||||
uintptrBits = reflect.TypeOf(uintptr(0)).Bits()
|
||||
)
|
||||
|
||||
func (p *pp) handleMethods(field interface{}, verb int, plus, goSyntax bool, depth int) (wasString, handled bool) {
|
||||
// Is it a Formatter?
|
||||
if formatter, ok := field.(Formatter); ok {
|
||||
handled = true
|
||||
wasString = false
|
||||
formatter.Format(p, verb)
|
||||
return
|
||||
}
|
||||
// Must not touch flags before Formatter looks at them.
|
||||
if plus {
|
||||
p.fmt.plus = false
|
||||
}
|
||||
|
||||
// If we're doing Go syntax and the field knows how to supply it, take care of it now.
|
||||
if goSyntax {
|
||||
p.fmt.sharp = false
|
||||
if stringer, ok := field.(GoStringer); ok {
|
||||
wasString = false
|
||||
handled = true
|
||||
// Print the result of GoString unadorned.
|
||||
p.fmtString(stringer.GoString(), 's', false, field, reflect.Value{})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Is it a Stringer?
|
||||
if stringer, ok := field.(Stringer); ok {
|
||||
wasString = false
|
||||
handled = true
|
||||
p.printField(stringer.String(), verb, plus, false, depth)
|
||||
return
|
||||
}
|
||||
}
|
||||
handled = false
|
||||
return
|
||||
}
|
||||
|
||||
func (p *pp) printField(field interface{}, verb int, plus, goSyntax bool, depth int) (wasString bool) {
|
||||
if field == nil {
|
||||
if verb == 'T' || verb == 'v' {
|
||||
p.buf.Write(nilAngleBytes)
|
||||
} else {
|
||||
p.badVerb(verb, field)
|
||||
p.badVerb(verb, field, reflect.Value{})
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -581,115 +620,133 @@ func (p *pp) printField(field interface{}, verb int, plus, goSyntax bool, depth
|
||||
p.fmtPointer(field, reflect.ValueOf(field), verb, goSyntax)
|
||||
return false
|
||||
}
|
||||
// Is it a Formatter?
|
||||
if formatter, ok := field.(Formatter); ok {
|
||||
formatter.Format(p, verb)
|
||||
return false // this value is not a string
|
||||
|
||||
}
|
||||
// Must not touch flags before Formatter looks at them.
|
||||
if plus {
|
||||
p.fmt.plus = false
|
||||
}
|
||||
// If we're doing Go syntax and the field knows how to supply it, take care of it now.
|
||||
if goSyntax {
|
||||
p.fmt.sharp = false
|
||||
if stringer, ok := field.(GoStringer); ok {
|
||||
// Print the result of GoString unadorned.
|
||||
p.fmtString(stringer.GoString(), 's', false, field)
|
||||
return false // this value is not a string
|
||||
}
|
||||
} else {
|
||||
// Is it a Stringer?
|
||||
if stringer, ok := field.(Stringer); ok {
|
||||
p.printField(stringer.String(), verb, plus, false, depth)
|
||||
return false // this value is not a string
|
||||
}
|
||||
if wasString, handled := p.handleMethods(field, verb, plus, goSyntax, depth); handled {
|
||||
return wasString
|
||||
}
|
||||
|
||||
// Some types can be done without reflection.
|
||||
switch f := field.(type) {
|
||||
case bool:
|
||||
p.fmtBool(f, verb, field)
|
||||
p.fmtBool(f, verb, field, reflect.Value{})
|
||||
return false
|
||||
case float32:
|
||||
p.fmtFloat32(f, verb, field)
|
||||
p.fmtFloat32(f, verb, field, reflect.Value{})
|
||||
return false
|
||||
case float64:
|
||||
p.fmtFloat64(f, verb, field)
|
||||
p.fmtFloat64(f, verb, field, reflect.Value{})
|
||||
return false
|
||||
case complex64:
|
||||
p.fmtComplex64(complex64(f), verb, field)
|
||||
p.fmtComplex64(complex64(f), verb, field, reflect.Value{})
|
||||
return false
|
||||
case complex128:
|
||||
p.fmtComplex128(f, verb, field)
|
||||
p.fmtComplex128(f, verb, field, reflect.Value{})
|
||||
return false
|
||||
case int:
|
||||
p.fmtInt64(int64(f), verb, field)
|
||||
p.fmtInt64(int64(f), verb, field, reflect.Value{})
|
||||
return false
|
||||
case int8:
|
||||
p.fmtInt64(int64(f), verb, field)
|
||||
p.fmtInt64(int64(f), verb, field, reflect.Value{})
|
||||
return false
|
||||
case int16:
|
||||
p.fmtInt64(int64(f), verb, field)
|
||||
p.fmtInt64(int64(f), verb, field, reflect.Value{})
|
||||
return false
|
||||
case int32:
|
||||
p.fmtInt64(int64(f), verb, field)
|
||||
p.fmtInt64(int64(f), verb, field, reflect.Value{})
|
||||
return false
|
||||
case int64:
|
||||
p.fmtInt64(f, verb, field)
|
||||
p.fmtInt64(f, verb, field, reflect.Value{})
|
||||
return false
|
||||
case uint:
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field)
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field, reflect.Value{})
|
||||
return false
|
||||
case uint8:
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field)
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field, reflect.Value{})
|
||||
return false
|
||||
case uint16:
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field)
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field, reflect.Value{})
|
||||
return false
|
||||
case uint32:
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field)
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field, reflect.Value{})
|
||||
return false
|
||||
case uint64:
|
||||
p.fmtUint64(f, verb, goSyntax, field)
|
||||
p.fmtUint64(f, verb, goSyntax, field, reflect.Value{})
|
||||
return false
|
||||
case uintptr:
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field)
|
||||
p.fmtUint64(uint64(f), verb, goSyntax, field, reflect.Value{})
|
||||
return false
|
||||
case string:
|
||||
p.fmtString(f, verb, goSyntax, field)
|
||||
p.fmtString(f, verb, goSyntax, field, reflect.Value{})
|
||||
return verb == 's' || verb == 'v'
|
||||
case []byte:
|
||||
p.fmtBytes(f, verb, goSyntax, depth, field)
|
||||
p.fmtBytes(f, verb, goSyntax, depth, field, reflect.Value{})
|
||||
return verb == 's'
|
||||
}
|
||||
|
||||
// Need to use reflection
|
||||
value := reflect.ValueOf(field)
|
||||
return p.printReflectValue(reflect.ValueOf(field), verb, plus, goSyntax, depth)
|
||||
}
|
||||
|
||||
// printValue is like printField but starts with a reflect value, not an interface{} value.
|
||||
func (p *pp) printValue(value reflect.Value, verb int, plus, goSyntax bool, depth int) (wasString bool) {
|
||||
if !value.IsValid() {
|
||||
if verb == 'T' || verb == 'v' {
|
||||
p.buf.Write(nilAngleBytes)
|
||||
} else {
|
||||
p.badVerb(verb, nil, value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Special processing considerations.
|
||||
// %T (the value's type) and %p (its address) are special; we always do them first.
|
||||
switch verb {
|
||||
case 'T':
|
||||
p.printField(value.Type().String(), 's', false, false, 0)
|
||||
return false
|
||||
case 'p':
|
||||
p.fmtPointer(nil, value, verb, goSyntax)
|
||||
return false
|
||||
}
|
||||
|
||||
// Handle values with special methods.
|
||||
// Call always, even when field == nil, because handleMethods clears p.fmt.plus for us.
|
||||
var field interface{}
|
||||
if value.CanInterface() {
|
||||
field = value.Interface()
|
||||
}
|
||||
if wasString, handled := p.handleMethods(field, verb, plus, goSyntax, depth); handled {
|
||||
return wasString
|
||||
}
|
||||
|
||||
return p.printReflectValue(value, verb, plus, goSyntax, depth)
|
||||
}
|
||||
|
||||
// printReflectValue is the fallback for both printField and printValue.
|
||||
// It uses reflect to print the value.
|
||||
func (p *pp) printReflectValue(value reflect.Value, verb int, plus, goSyntax bool, depth int) (wasString bool) {
|
||||
BigSwitch:
|
||||
switch f := value; f.Kind() {
|
||||
case reflect.Bool:
|
||||
p.fmtBool(f.Bool(), verb, field)
|
||||
p.fmtBool(f.Bool(), verb, nil, value)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p.fmtInt64(f.Int(), verb, field)
|
||||
p.fmtInt64(f.Int(), verb, nil, value)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
p.fmtUint64(uint64(f.Uint()), verb, goSyntax, field)
|
||||
p.fmtUint64(uint64(f.Uint()), verb, goSyntax, nil, value)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if f.Type().Size() == 4 {
|
||||
p.fmtFloat32(float32(f.Float()), verb, field)
|
||||
p.fmtFloat32(float32(f.Float()), verb, nil, value)
|
||||
} else {
|
||||
p.fmtFloat64(float64(f.Float()), verb, field)
|
||||
p.fmtFloat64(float64(f.Float()), verb, nil, value)
|
||||
}
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
if f.Type().Size() == 8 {
|
||||
p.fmtComplex64(complex64(f.Complex()), verb, field)
|
||||
p.fmtComplex64(complex64(f.Complex()), verb, nil, value)
|
||||
} else {
|
||||
p.fmtComplex128(complex128(f.Complex()), verb, field)
|
||||
p.fmtComplex128(complex128(f.Complex()), verb, nil, value)
|
||||
}
|
||||
case reflect.String:
|
||||
p.fmtString(f.String(), verb, goSyntax, field)
|
||||
p.fmtString(f.String(), verb, goSyntax, nil, value)
|
||||
case reflect.Map:
|
||||
if goSyntax {
|
||||
p.buf.WriteString(f.Type().String())
|
||||
@@ -706,9 +763,9 @@ BigSwitch:
|
||||
p.buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
p.printField(key.Interface(), verb, plus, goSyntax, depth+1)
|
||||
p.printValue(key, verb, plus, goSyntax, depth+1)
|
||||
p.buf.WriteByte(':')
|
||||
p.printField(f.MapIndex(key).Interface(), verb, plus, goSyntax, depth+1)
|
||||
p.printValue(f.MapIndex(key), verb, plus, goSyntax, depth+1)
|
||||
}
|
||||
if goSyntax {
|
||||
p.buf.WriteByte('}')
|
||||
@@ -717,7 +774,7 @@ BigSwitch:
|
||||
}
|
||||
case reflect.Struct:
|
||||
if goSyntax {
|
||||
p.buf.WriteString(reflect.TypeOf(field).String())
|
||||
p.buf.WriteString(value.Type().String())
|
||||
}
|
||||
p.add('{')
|
||||
v := f
|
||||
@@ -736,20 +793,20 @@ BigSwitch:
|
||||
p.buf.WriteByte(':')
|
||||
}
|
||||
}
|
||||
p.printField(getField(v, i).Interface(), verb, plus, goSyntax, depth+1)
|
||||
p.printValue(getField(v, i), verb, plus, goSyntax, depth+1)
|
||||
}
|
||||
p.buf.WriteByte('}')
|
||||
case reflect.Interface:
|
||||
value := f.Elem()
|
||||
if !value.IsValid() {
|
||||
if goSyntax {
|
||||
p.buf.WriteString(reflect.TypeOf(field).String())
|
||||
p.buf.WriteString(value.Type().String())
|
||||
p.buf.Write(nilParenBytes)
|
||||
} else {
|
||||
p.buf.Write(nilAngleBytes)
|
||||
}
|
||||
} else {
|
||||
return p.printField(value.Interface(), verb, plus, goSyntax, depth+1)
|
||||
return p.printValue(value, verb, plus, goSyntax, depth+1)
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
// Byte slices are special.
|
||||
@@ -765,11 +822,11 @@ BigSwitch:
|
||||
for i := range bytes {
|
||||
bytes[i] = byte(f.Index(i).Uint())
|
||||
}
|
||||
p.fmtBytes(bytes, verb, goSyntax, depth, field)
|
||||
p.fmtBytes(bytes, verb, goSyntax, depth, nil, value)
|
||||
return verb == 's'
|
||||
}
|
||||
if goSyntax {
|
||||
p.buf.WriteString(reflect.TypeOf(field).String())
|
||||
p.buf.WriteString(value.Type().String())
|
||||
p.buf.WriteByte('{')
|
||||
} else {
|
||||
p.buf.WriteByte('[')
|
||||
@@ -782,7 +839,7 @@ BigSwitch:
|
||||
p.buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
p.printField(f.Index(i).Interface(), verb, plus, goSyntax, depth+1)
|
||||
p.printValue(f.Index(i), verb, plus, goSyntax, depth+1)
|
||||
}
|
||||
if goSyntax {
|
||||
p.buf.WriteByte('}')
|
||||
@@ -797,17 +854,17 @@ BigSwitch:
|
||||
switch a := f.Elem(); a.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
p.buf.WriteByte('&')
|
||||
p.printField(a.Interface(), verb, plus, goSyntax, depth+1)
|
||||
p.printValue(a, verb, plus, goSyntax, depth+1)
|
||||
break BigSwitch
|
||||
case reflect.Struct:
|
||||
p.buf.WriteByte('&')
|
||||
p.printField(a.Interface(), verb, plus, goSyntax, depth+1)
|
||||
p.printValue(a, verb, plus, goSyntax, depth+1)
|
||||
break BigSwitch
|
||||
}
|
||||
}
|
||||
if goSyntax {
|
||||
p.buf.WriteByte('(')
|
||||
p.buf.WriteString(reflect.TypeOf(field).String())
|
||||
p.buf.WriteString(value.Type().String())
|
||||
p.buf.WriteByte(')')
|
||||
p.buf.WriteByte('(')
|
||||
if v == 0 {
|
||||
@@ -824,7 +881,7 @@ BigSwitch:
|
||||
}
|
||||
p.fmt0x64(uint64(v), true)
|
||||
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
||||
p.fmtPointer(field, value, verb, goSyntax)
|
||||
p.fmtPointer(nil, value, verb, goSyntax)
|
||||
default:
|
||||
p.unknownType(f)
|
||||
}
|
||||
|
||||
@@ -855,13 +855,13 @@ func TestIsNil(t *testing.T) {
|
||||
|
||||
func TestInterfaceExtraction(t *testing.T) {
|
||||
var s struct {
|
||||
w io.Writer
|
||||
W io.Writer
|
||||
}
|
||||
|
||||
s.w = os.Stdout
|
||||
s.W = os.Stdout
|
||||
v := Indirect(ValueOf(&s)).Field(0).Interface()
|
||||
if v != s.w.(interface{}) {
|
||||
t.Error("Interface() on interface: ", v, s.w)
|
||||
if v != s.W.(interface{}) {
|
||||
t.Error("Interface() on interface: ", v, s.W)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1156,18 +1156,18 @@ type D2 struct {
|
||||
}
|
||||
|
||||
type S0 struct {
|
||||
a, b, c int
|
||||
A, B, C int
|
||||
D1
|
||||
D2
|
||||
}
|
||||
|
||||
type S1 struct {
|
||||
b int
|
||||
B int
|
||||
S0
|
||||
}
|
||||
|
||||
type S2 struct {
|
||||
a int
|
||||
A int
|
||||
*S1
|
||||
}
|
||||
|
||||
@@ -1182,36 +1182,36 @@ type S1y struct {
|
||||
type S3 struct {
|
||||
S1x
|
||||
S2
|
||||
d, e int
|
||||
D, E int
|
||||
*S1y
|
||||
}
|
||||
|
||||
type S4 struct {
|
||||
*S4
|
||||
a int
|
||||
A int
|
||||
}
|
||||
|
||||
var fieldTests = []FTest{
|
||||
{struct{}{}, "", nil, 0},
|
||||
{struct{}{}, "foo", nil, 0},
|
||||
{S0{a: 'a'}, "a", []int{0}, 'a'},
|
||||
{S0{}, "d", nil, 0},
|
||||
{S1{S0: S0{a: 'a'}}, "a", []int{1, 0}, 'a'},
|
||||
{S1{b: 'b'}, "b", []int{0}, 'b'},
|
||||
{struct{}{}, "Foo", nil, 0},
|
||||
{S0{A: 'a'}, "A", []int{0}, 'a'},
|
||||
{S0{}, "D", nil, 0},
|
||||
{S1{S0: S0{A: 'a'}}, "A", []int{1, 0}, 'a'},
|
||||
{S1{B: 'b'}, "B", []int{0}, 'b'},
|
||||
{S1{}, "S0", []int{1}, 0},
|
||||
{S1{S0: S0{c: 'c'}}, "c", []int{1, 2}, 'c'},
|
||||
{S2{a: 'a'}, "a", []int{0}, 'a'},
|
||||
{S1{S0: S0{C: 'c'}}, "C", []int{1, 2}, 'c'},
|
||||
{S2{A: 'a'}, "A", []int{0}, 'a'},
|
||||
{S2{}, "S1", []int{1}, 0},
|
||||
{S2{S1: &S1{b: 'b'}}, "b", []int{1, 0}, 'b'},
|
||||
{S2{S1: &S1{S0: S0{c: 'c'}}}, "c", []int{1, 1, 2}, 'c'},
|
||||
{S2{}, "d", nil, 0},
|
||||
{S2{S1: &S1{B: 'b'}}, "B", []int{1, 0}, 'b'},
|
||||
{S2{S1: &S1{S0: S0{C: 'c'}}}, "C", []int{1, 1, 2}, 'c'},
|
||||
{S2{}, "D", nil, 0},
|
||||
{S3{}, "S1", nil, 0},
|
||||
{S3{S2: S2{a: 'a'}}, "a", []int{1, 0}, 'a'},
|
||||
{S3{}, "b", nil, 0},
|
||||
{S3{d: 'd'}, "d", []int{2}, 0},
|
||||
{S3{e: 'e'}, "e", []int{3}, 'e'},
|
||||
{S4{a: 'a'}, "a", []int{1}, 'a'},
|
||||
{S4{}, "b", nil, 0},
|
||||
{S3{S2: S2{A: 'a'}}, "A", []int{1, 0}, 'a'},
|
||||
{S3{}, "B", nil, 0},
|
||||
{S3{D: 'd'}, "D", []int{2}, 0},
|
||||
{S3{E: 'e'}, "E", []int{3}, 'e'},
|
||||
{S4{A: 'a'}, "A", []int{1}, 'a'},
|
||||
{S4{}, "B", nil, 0},
|
||||
}
|
||||
|
||||
func TestFieldByIndex(t *testing.T) {
|
||||
@@ -1508,3 +1508,68 @@ func TestVariadic(t *testing.T) {
|
||||
t.Errorf("after Fprintf CallSlice: %q != %q", b.String(), "hello 42 world")
|
||||
}
|
||||
}
|
||||
|
||||
type Private struct {
|
||||
x int
|
||||
y **int
|
||||
}
|
||||
|
||||
func (p *Private) m() {
|
||||
}
|
||||
|
||||
type Public struct {
|
||||
X int
|
||||
Y **int
|
||||
}
|
||||
|
||||
func (p *Public) M() {
|
||||
}
|
||||
|
||||
func TestUnexported(t *testing.T) {
|
||||
var pub Public
|
||||
v := ValueOf(&pub)
|
||||
isValid(v.Elem().Field(0))
|
||||
isValid(v.Elem().Field(1))
|
||||
isValid(v.Elem().FieldByName("X"))
|
||||
isValid(v.Elem().FieldByName("Y"))
|
||||
isValid(v.Type().Method(0).Func)
|
||||
isNonNil(v.Elem().Field(0).Interface())
|
||||
isNonNil(v.Elem().Field(1).Interface())
|
||||
isNonNil(v.Elem().FieldByName("X").Interface())
|
||||
isNonNil(v.Elem().FieldByName("Y").Interface())
|
||||
isNonNil(v.Type().Method(0).Func.Interface())
|
||||
|
||||
var priv Private
|
||||
v = ValueOf(&priv)
|
||||
isValid(v.Elem().Field(0))
|
||||
isValid(v.Elem().Field(1))
|
||||
isValid(v.Elem().FieldByName("x"))
|
||||
isValid(v.Elem().FieldByName("y"))
|
||||
isValid(v.Type().Method(0).Func)
|
||||
shouldPanic(func() { v.Elem().Field(0).Interface() })
|
||||
shouldPanic(func() { v.Elem().Field(1).Interface() })
|
||||
shouldPanic(func() { v.Elem().FieldByName("x").Interface() })
|
||||
shouldPanic(func() { v.Elem().FieldByName("y").Interface() })
|
||||
shouldPanic(func() { v.Type().Method(0).Func.Interface() })
|
||||
}
|
||||
|
||||
func shouldPanic(f func()) {
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
panic("did not panic")
|
||||
}
|
||||
}()
|
||||
f()
|
||||
}
|
||||
|
||||
func isNonNil(x interface{}) {
|
||||
if x == nil {
|
||||
panic("nil interface")
|
||||
}
|
||||
}
|
||||
|
||||
func isValid(v Value) {
|
||||
if !v.IsValid() {
|
||||
panic("zero Value")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func deepValueEqual(v1, v2 Value, visited map[uintptr]*visit, depth int) (b bool
|
||||
return true
|
||||
default:
|
||||
// Normal equality suffices
|
||||
return v1.Interface() == v2.Interface()
|
||||
return valueInterface(v1, false) == valueInterface(v2, false)
|
||||
}
|
||||
|
||||
panic("Not reached")
|
||||
|
||||
@@ -841,14 +841,7 @@ func (v Value) CanInterface() bool {
|
||||
if iv.kind == Invalid {
|
||||
panic(&ValueError{"reflect.Value.CanInterface", iv.kind})
|
||||
}
|
||||
// TODO(rsc): Check flagRO too. Decide what to do about asking for
|
||||
// interface for a value obtained via an unexported field.
|
||||
// If the field were of a known type, say chan int or *sync.Mutex,
|
||||
// the caller could interfere with the data after getting the
|
||||
// interface. But fmt.Print depends on being able to look.
|
||||
// Now that reflect is more efficient the special cases in fmt
|
||||
// might be less important.
|
||||
return v.InternalMethod == 0
|
||||
return v.InternalMethod == 0 && iv.flag&flagRO == 0
|
||||
}
|
||||
|
||||
// Interface returns v's value as an interface{}.
|
||||
@@ -856,19 +849,25 @@ func (v Value) CanInterface() bool {
|
||||
// (as opposed to Type.Method), Interface cannot return an
|
||||
// interface value, so it panics.
|
||||
func (v Value) Interface() interface{} {
|
||||
return v.internal().Interface()
|
||||
return valueInterface(v, true)
|
||||
}
|
||||
|
||||
func (iv internalValue) Interface() interface{} {
|
||||
func valueInterface(v Value, safe bool) interface{} {
|
||||
iv := v.internal()
|
||||
return iv.valueInterface(safe)
|
||||
}
|
||||
|
||||
func (iv internalValue) valueInterface(safe bool) interface{} {
|
||||
if iv.method {
|
||||
panic("reflect.Value.Interface: cannot create interface value for method with bound receiver")
|
||||
}
|
||||
/*
|
||||
if v.flag()&noExport != 0 {
|
||||
panic("reflect.Value.Interface: cannot return value obtained from unexported struct field")
|
||||
}
|
||||
*/
|
||||
|
||||
if safe && iv.flag&flagRO != 0 {
|
||||
// Do not allow access to unexported values via Interface,
|
||||
// because they might be pointers that should not be
|
||||
// writable or methods or function that should not be callable.
|
||||
panic("reflect.Value.Interface: cannot return value obtained from unexported field or method")
|
||||
}
|
||||
if iv.kind == Interface {
|
||||
// Special case: return the element inside the interface.
|
||||
// Won't recurse further because an interface cannot contain an interface.
|
||||
@@ -1669,7 +1668,7 @@ func convertForAssignment(what string, addr unsafe.Pointer, dst Type, iv interna
|
||||
if addr == nil {
|
||||
addr = unsafe.Pointer(new(interface{}))
|
||||
}
|
||||
x := iv.Interface()
|
||||
x := iv.valueInterface(false)
|
||||
if dst.NumMethod() == 0 {
|
||||
*(*interface{})(addr) = x
|
||||
} else {
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
static void* threadentry(void*);
|
||||
static pthread_key_t k1, k2;
|
||||
|
||||
#define magic1 (0x23581321U)
|
||||
|
||||
static void
|
||||
inittls(void)
|
||||
{
|
||||
uint32 x, y;
|
||||
pthread_key_t tofree[16], k;
|
||||
pthread_key_t tofree[128], k;
|
||||
int i, ntofree;
|
||||
int havek1, havek2;
|
||||
|
||||
@@ -35,9 +37,8 @@ inittls(void)
|
||||
* 0x48+4*0x108 = 0x468 and 0x48+4*0x109 = 0x46c.
|
||||
*
|
||||
* The linker and runtime hard-code these constant offsets
|
||||
* from %gs where we expect to find m and g. The code
|
||||
* below verifies that the constants are correct once it has
|
||||
* obtained the keys. Known to ../cmd/8l/obj.c:/468
|
||||
* from %gs where we expect to find m and g.
|
||||
* Known to ../cmd/8l/obj.c:/468
|
||||
* and to ../pkg/runtime/darwin/386/sys.s:/468
|
||||
*
|
||||
* This is truly disgusting and a bit fragile, but taking care
|
||||
@@ -48,55 +49,54 @@ inittls(void)
|
||||
* require an extra instruction and memory reference in
|
||||
* every stack growth prolog and would also require
|
||||
* rewriting the code that 8c generates for extern registers.
|
||||
*
|
||||
* Things get more disgusting on OS X 10.7 Lion.
|
||||
* The 0x48 base mentioned above is the offset of the tsd
|
||||
* array within the per-thread structure on Leopard and Snow Leopard.
|
||||
* On Lion, the base moved a little, so while the math above
|
||||
* still applies, the base is different. Thus, we cannot
|
||||
* look for specific key values if we want to build binaries
|
||||
* that run on both systems. Instead, forget about the
|
||||
* specific key values and just allocate and initialize per-thread
|
||||
* storage until we find a key that writes to the memory location
|
||||
* we want. Then keep that key.
|
||||
*/
|
||||
havek1 = 0;
|
||||
havek2 = 0;
|
||||
ntofree = 0;
|
||||
while(!havek1 || !havek2) {
|
||||
if(pthread_key_create(&k, nil) < 0) {
|
||||
fprintf(stderr, "libcgo: pthread_key_create failed\n");
|
||||
fprintf(stderr, "runtime/cgo: pthread_key_create failed\n");
|
||||
abort();
|
||||
}
|
||||
if(k == 0x108) {
|
||||
pthread_setspecific(k, (void*)magic1);
|
||||
asm volatile("movl %%gs:0x468, %0" : "=r"(x));
|
||||
asm volatile("movl %%gs:0x46c, %0" : "=r"(y));
|
||||
if(x == magic1) {
|
||||
havek1 = 1;
|
||||
k1 = k;
|
||||
continue;
|
||||
}
|
||||
if(k == 0x109) {
|
||||
} else if(y == magic1) {
|
||||
havek2 = 1;
|
||||
k2 = k;
|
||||
continue;
|
||||
} else {
|
||||
if(ntofree >= nelem(tofree)) {
|
||||
fprintf(stderr, "runtime/cgo: could not obtain pthread_keys\n");
|
||||
fprintf(stderr, "\ttried");
|
||||
for(i=0; i<ntofree; i++)
|
||||
fprintf(stderr, " %#x", (unsigned)tofree[i]);
|
||||
fprintf(stderr, "\n");
|
||||
abort();
|
||||
}
|
||||
tofree[ntofree++] = k;
|
||||
}
|
||||
if(ntofree >= nelem(tofree)) {
|
||||
fprintf(stderr, "libcgo: could not obtain pthread_keys\n");
|
||||
fprintf(stderr, "\twanted 0x108 and 0x109\n");
|
||||
fprintf(stderr, "\tgot");
|
||||
for(i=0; i<ntofree; i++)
|
||||
fprintf(stderr, " %#lx", tofree[i]);
|
||||
fprintf(stderr, "\n");
|
||||
abort();
|
||||
}
|
||||
tofree[ntofree++] = k;
|
||||
pthread_setspecific(k, 0);
|
||||
}
|
||||
|
||||
for(i=0; i<ntofree; i++)
|
||||
pthread_key_delete(tofree[i]);
|
||||
|
||||
/*
|
||||
* We got the keys we wanted. Make sure that we observe
|
||||
* updates to k1 at 0x468, to verify that the TLS array
|
||||
* offset from %gs hasn't changed.
|
||||
* We got the keys we wanted. Free the others.
|
||||
*/
|
||||
pthread_setspecific(k1, (void*)0x12345678);
|
||||
asm volatile("movl %%gs:0x468, %0" : "=r"(x));
|
||||
|
||||
pthread_setspecific(k1, (void*)0x87654321);
|
||||
asm volatile("movl %%gs:0x468, %0" : "=r"(y));
|
||||
|
||||
if(x != 0x12345678 || y != 0x87654321) {
|
||||
printf("libcgo: thread-local storage %#lx not at %%gs:0x468 - x=%#x y=%#x\n", k1, x, y);
|
||||
abort();
|
||||
}
|
||||
for(i=0; i<ntofree; i++)
|
||||
pthread_key_delete(tofree[i]);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -8,24 +8,25 @@
|
||||
static void* threadentry(void*);
|
||||
static pthread_key_t k1, k2;
|
||||
|
||||
#define magic1 (0x23581321345589ULL)
|
||||
|
||||
static void
|
||||
inittls(void)
|
||||
{
|
||||
uint64 x, y;
|
||||
pthread_key_t tofree[16], k;
|
||||
pthread_key_t tofree[128], k;
|
||||
int i, ntofree;
|
||||
int havek1, havek2;
|
||||
|
||||
/*
|
||||
* Same logic, code as darwin_386.c:/inittls, except that words
|
||||
* are 8 bytes long now, and the thread-local storage starts at 0x60.
|
||||
* So the offsets are
|
||||
* are 8 bytes long now, and the thread-local storage starts
|
||||
* at 0x60 on Leopard / Snow Leopard. So the offsets are
|
||||
* 0x60+8*0x108 = 0x8a0 and 0x60+8*0x109 = 0x8a8.
|
||||
*
|
||||
* The linker and runtime hard-code these constant offsets
|
||||
* from %gs where we expect to find m and g. The code
|
||||
* below verifies that the constants are correct once it has
|
||||
* obtained the keys. Known to ../cmd/6l/obj.c:/8a0
|
||||
* from %gs where we expect to find m and g.
|
||||
* Known to ../cmd/6l/obj.c:/8a0
|
||||
* and to ../pkg/runtime/darwin/amd64/sys.s:/8a0
|
||||
*
|
||||
* As disgusting as on the 386; same justification.
|
||||
@@ -35,49 +36,37 @@ inittls(void)
|
||||
ntofree = 0;
|
||||
while(!havek1 || !havek2) {
|
||||
if(pthread_key_create(&k, nil) < 0) {
|
||||
fprintf(stderr, "libcgo: pthread_key_create failed\n");
|
||||
fprintf(stderr, "runtime/cgo: pthread_key_create failed\n");
|
||||
abort();
|
||||
}
|
||||
if(k == 0x108) {
|
||||
pthread_setspecific(k, (void*)magic1);
|
||||
asm volatile("movq %%gs:0x8a0, %0" : "=r"(x));
|
||||
asm volatile("movq %%gs:0x8a8, %0" : "=r"(y));
|
||||
if(x == magic1) {
|
||||
havek1 = 1;
|
||||
k1 = k;
|
||||
continue;
|
||||
}
|
||||
if(k == 0x109) {
|
||||
} else if(y == magic1) {
|
||||
havek2 = 1;
|
||||
k2 = k;
|
||||
continue;
|
||||
} else {
|
||||
if(ntofree >= nelem(tofree)) {
|
||||
fprintf(stderr, "runtime/cgo: could not obtain pthread_keys\n");
|
||||
fprintf(stderr, "\ttried");
|
||||
for(i=0; i<ntofree; i++)
|
||||
fprintf(stderr, " %#x", (unsigned)tofree[i]);
|
||||
fprintf(stderr, "\n");
|
||||
abort();
|
||||
}
|
||||
tofree[ntofree++] = k;
|
||||
}
|
||||
if(ntofree >= nelem(tofree)) {
|
||||
fprintf(stderr, "libcgo: could not obtain pthread_keys\n");
|
||||
fprintf(stderr, "\twanted 0x108 and 0x109\n");
|
||||
fprintf(stderr, "\tgot");
|
||||
for(i=0; i<ntofree; i++)
|
||||
fprintf(stderr, " %#x", (unsigned)tofree[i]);
|
||||
fprintf(stderr, "\n");
|
||||
abort();
|
||||
}
|
||||
tofree[ntofree++] = k;
|
||||
pthread_setspecific(k, 0);
|
||||
}
|
||||
|
||||
for(i=0; i<ntofree; i++)
|
||||
pthread_key_delete(tofree[i]);
|
||||
|
||||
/*
|
||||
* We got the keys we wanted. Make sure that we observe
|
||||
* updates to k1 at 0x8a0, to verify that the TLS array
|
||||
* offset from %gs hasn't changed.
|
||||
* We got the keys we wanted. Free the others.
|
||||
*/
|
||||
pthread_setspecific(k1, (void*)0x123456789abcdef0ULL);
|
||||
asm volatile("movq %%gs:0x8a0, %0" : "=r"(x));
|
||||
|
||||
pthread_setspecific(k2, (void*)0x0fedcba987654321);
|
||||
asm volatile("movq %%gs:0x8a8, %0" : "=r"(y));
|
||||
|
||||
if(x != 0x123456789abcdef0ULL || y != 0x0fedcba987654321) {
|
||||
printf("libcgo: thread-local storage %#x not at %%gs:0x8a0 - x=%#llx y=%#llx\n", (unsigned)k1, x, y);
|
||||
abort();
|
||||
}
|
||||
for(i=0; i<ntofree; i++)
|
||||
pthread_key_delete(tofree[i]);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright 2010 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.
|
||||
|
||||
#include "libcgo.h"
|
||||
|
||||
static void
|
||||
xinitcgo(void)
|
||||
{
|
||||
}
|
||||
|
||||
void (*initcgo)(void) = xinitcgo;
|
||||
|
||||
void
|
||||
libcgo_sys_thread_start(ThreadStart *ts)
|
||||
{
|
||||
// unimplemented
|
||||
*(int*)0 = 0;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ xlibcgo_thread_start(ThreadStart *arg)
|
||||
/* Make our own copy that can persist after we return. */
|
||||
ts = malloc(sizeof *ts);
|
||||
if(ts == nil) {
|
||||
fprintf(stderr, "libcgo: out of memory in thread_start\n");
|
||||
fprintf(stderr, "runtime/cgo: out of memory in thread_start\n");
|
||||
abort();
|
||||
}
|
||||
*ts = *arg;
|
||||
|
||||
22
test/fixedbugs/bug344.go
Normal file
22
test/fixedbugs/bug344.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// $G $D/$F.go && $L $F.$A && ./$A.out || echo BUG: bug344
|
||||
|
||||
// Copyright 2011 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.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
// invalid use of goto.
|
||||
// do whatever you like, just don't crash.
|
||||
i := 42
|
||||
a := []*int{&i, &i, &i, &i}
|
||||
x := a[0]
|
||||
goto start
|
||||
for _, x = range a {
|
||||
start:
|
||||
fmt.Sprint(*x)
|
||||
}
|
||||
}
|
||||
@@ -12,20 +12,20 @@ package main
|
||||
import "reflect"
|
||||
|
||||
type T struct {
|
||||
f float32
|
||||
g float32
|
||||
F float32
|
||||
G float32
|
||||
|
||||
s string
|
||||
t string
|
||||
S string
|
||||
T string
|
||||
|
||||
u uint32
|
||||
v uint32
|
||||
U uint32
|
||||
V uint32
|
||||
|
||||
w uint32
|
||||
x uint32
|
||||
W uint32
|
||||
X uint32
|
||||
|
||||
y uint32
|
||||
z uint32
|
||||
Y uint32
|
||||
Z uint32
|
||||
}
|
||||
|
||||
func add(s, t string) string {
|
||||
@@ -40,16 +40,16 @@ func assert(b bool) {
|
||||
|
||||
func main() {
|
||||
var x T
|
||||
x.f = 1.0
|
||||
x.g = x.f
|
||||
x.s = add("abc", "def")
|
||||
x.t = add("abc", "def")
|
||||
x.u = 1
|
||||
x.v = 2
|
||||
x.w = 1 << 28
|
||||
x.x = 2 << 28
|
||||
x.y = 0x12345678
|
||||
x.z = x.y
|
||||
x.F = 1.0
|
||||
x.G = x.F
|
||||
x.S = add("abc", "def")
|
||||
x.T = add("abc", "def")
|
||||
x.U = 1
|
||||
x.V = 2
|
||||
x.W = 1 << 28
|
||||
x.X = 2 << 28
|
||||
x.Y = 0x12345678
|
||||
x.Z = x.Y
|
||||
|
||||
// check mem and string
|
||||
v := reflect.ValueOf(x)
|
||||
|
||||
Reference in New Issue
Block a user