diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 |
commit | 28592ee1ea1f5cdffcf85472f9de0285d928cf12 (patch) | |
tree | 32944e18b23f7fe4a0818a694aa2a6dfb1835463 /src/pkg/fmt | |
parent | e836bee4716dc0d4d913537ad3ad1925a7ac32d0 (diff) | |
download | golang-upstream/59.tar.gz |
Imported Upstream version 59upstream/59
Diffstat (limited to 'src/pkg/fmt')
-rw-r--r-- | src/pkg/fmt/doc.go | 6 | ||||
-rw-r--r-- | src/pkg/fmt/fmt_test.go | 62 | ||||
-rw-r--r-- | src/pkg/fmt/format.go | 34 | ||||
-rw-r--r-- | src/pkg/fmt/print.go | 79 | ||||
-rw-r--r-- | src/pkg/fmt/scan.go | 23 | ||||
-rw-r--r-- | src/pkg/fmt/scan_test.go | 4 |
6 files changed, 173 insertions, 35 deletions
diff --git a/src/pkg/fmt/doc.go b/src/pkg/fmt/doc.go index 79fe5758c..35a11e19f 100644 --- a/src/pkg/fmt/doc.go +++ b/src/pkg/fmt/doc.go @@ -63,11 +63,13 @@ number of characters to output, truncating if necessary. Other flags: - + always print a sign for numeric values + + always print a sign for numeric values; + guarantee ASCII-only output for %q (%+q) - pad with spaces on the right rather than the left (left-justify the field) # alternate format: add leading 0 for octal (%#o), 0x for hex (%#x); 0X for hex (%#X); suppress 0x for %p (%#p); - print a raw (backquoted) string if possible for %q (%#q) + print a raw (backquoted) string if possible for %q (%#q); + write e.g. U+0078 'x' if the character is printable for %U (%#U). ' ' (space) leave a space for elided sign in numbers (% d); put spaces between bytes printing strings or slices in hex (% x, % X) 0 pad with leading zeros rather than spaces diff --git a/src/pkg/fmt/fmt_test.go b/src/pkg/fmt/fmt_test.go index 122b9516b..9a8024528 100644 --- a/src/pkg/fmt/fmt_test.go +++ b/src/pkg/fmt/fmt_test.go @@ -133,6 +133,7 @@ var fmttests = []struct { {"%q", "\a\b\f\r\n\t\v", `"\a\b\f\r\n\t\v"`}, {"%q", "abc\xffdef", `"abc\xffdef"`}, {"%q", "\u263a", `"☺"`}, + {"%+q", "\u263a", `"\u263a"`}, {"%q", "\U0010ffff", `"\U0010ffff"`}, // escaped characters @@ -145,6 +146,8 @@ var fmttests = []struct { {"%q", uint64(0xFFFFFFFF), `%!q(uint64=4294967295)`}, {"%q", '"', `'"'`}, {"%q", '\'', `'\''`}, + {"%q", "\u263a", `"☺"`}, + {"%+q", "\u263a", `"\u263a"`}, // width {"%5s", "abc", " abc"}, @@ -187,6 +190,12 @@ var fmttests = []struct { {"%U", 0x12345, "U+12345"}, {"%10.6U", 0xABC, " U+000ABC"}, {"%-10.6U", 0xABC, "U+000ABC "}, + {"%U", '\n', `U+000A`}, + {"%#U", '\n', `U+000A`}, + {"%U", 'x', `U+0078`}, + {"%#U", 'x', `U+0078 'x'`}, + {"%U", '\u263a', `U+263A`}, + {"%#U", '\u263a', `U+263A '☺'`}, // floats {"%+.3e", 0.0, "+0.000e+00"}, @@ -674,3 +683,56 @@ func TestWidthAndPrecision(t *testing.T) { } } } + +// A type that panics in String. +type Panic struct { + message interface{} +} + +// Value receiver. +func (p Panic) GoString() string { + panic(p.message) +} + +// Value receiver. +func (p Panic) String() string { + panic(p.message) +} + +// A type that panics in Format. +type PanicF struct { + message interface{} +} + +// Value receiver. +func (p PanicF) Format(f State, c int) { + panic(p.message) +} + +var panictests = []struct { + fmt string + in interface{} + out string +}{ + // String + {"%d", (*Panic)(nil), "<nil>"}, // nil pointer special case + {"%d", Panic{io.ErrUnexpectedEOF}, "%d(PANIC=unexpected EOF)"}, + {"%d", Panic{3}, "%d(PANIC=3)"}, + // GoString + {"%#v", (*Panic)(nil), "<nil>"}, // nil pointer special case + {"%#v", Panic{io.ErrUnexpectedEOF}, "%v(PANIC=unexpected EOF)"}, + {"%#v", Panic{3}, "%v(PANIC=3)"}, + // Format + {"%s", (*PanicF)(nil), "<nil>"}, // nil pointer special case + {"%s", PanicF{io.ErrUnexpectedEOF}, "%s(PANIC=unexpected EOF)"}, + {"%s", PanicF{3}, "%s(PANIC=3)"}, +} + +func TestPanics(t *testing.T) { + for _, tt := range panictests { + s := Sprintf(tt.fmt, tt.in) + if s != tt.out { + t.Errorf("%q: got %q expected %q", tt.fmt, s, tt.out) + } + } +} diff --git a/src/pkg/fmt/format.go b/src/pkg/fmt/format.go index 5dcfb9677..bec55f75b 100644 --- a/src/pkg/fmt/format.go +++ b/src/pkg/fmt/format.go @@ -7,6 +7,7 @@ package fmt import ( "bytes" "strconv" + "unicode" "utf8" ) @@ -50,6 +51,7 @@ type fmt struct { sharp bool space bool unicode bool + uniQuote bool // Use 'x'= prefix for %U if printable. zero bool } @@ -63,6 +65,7 @@ func (f *fmt) clearflags() { f.sharp = false f.space = false f.unicode = false + f.uniQuote = false f.zero = false } @@ -232,6 +235,24 @@ func (f *fmt) integer(a int64, base uint64, signedness bool, digits string) { i-- buf[i] = ' ' } + + // If we want a quoted char for %#U, move the data up to make room. + if f.unicode && f.uniQuote && a >= 0 && a <= unicode.MaxRune && unicode.IsPrint(int(a)) { + runeWidth := utf8.RuneLen(int(a)) + width := 1 + 1 + runeWidth + 1 // space, quote, rune, quote + copy(buf[i-width:], buf[i:]) // guaranteed to have enough room. + i -= width + // Now put " 'x'" at the end. + j := len(buf) - width + buf[j] = ' ' + j++ + buf[j] = '\'' + j++ + utf8.EncodeRune(buf[j:], int(a)) + j += runeWidth + buf[j] = '\'' + } + f.pad(buf[i:]) } @@ -291,7 +312,11 @@ func (f *fmt) fmt_q(s string) { if f.sharp && strconv.CanBackquote(s) { quoted = "`" + s + "`" } else { - quoted = strconv.Quote(s) + if f.plus { + quoted = strconv.QuoteToASCII(s) + } else { + quoted = strconv.Quote(s) + } } f.padString(quoted) } @@ -299,7 +324,12 @@ func (f *fmt) fmt_q(s string) { // fmt_qc formats the integer as a single-quoted, escaped Go character constant. // If the character is not valid Unicode, it will print '\ufffd'. func (f *fmt) fmt_qc(c int64) { - quoted := strconv.QuoteRune(int(c)) + var quoted string + if f.plus { + quoted = strconv.QuoteRuneToASCII(int(c)) + } else { + quoted = strconv.QuoteRune(int(c)) + } f.padString(quoted) } diff --git a/src/pkg/fmt/print.go b/src/pkg/fmt/print.go index c18a8ea38..5c083e5e9 100644 --- a/src/pkg/fmt/print.go +++ b/src/pkg/fmt/print.go @@ -22,6 +22,7 @@ var ( nilBytes = []byte("nil") mapBytes = []byte("map[") missingBytes = []byte("(MISSING)") + panicBytes = []byte("(PANIC=") extraBytes = []byte("%!(EXTRA ") irparenBytes = []byte("i)") bytesBytes = []byte("[]byte{") @@ -69,10 +70,11 @@ type GoStringer interface { } type pp struct { - n int - buf bytes.Buffer - runeBuf [utf8.UTFMax]byte - fmt fmt + n int + panicking bool + buf bytes.Buffer + runeBuf [utf8.UTFMax]byte + fmt fmt } // A cache holds a set of reusable objects. @@ -111,6 +113,7 @@ var ppFree = newCache(func() interface{} { return new(pp) }) // Allocate a new pp struct or grab a cached one. func newPrinter() *pp { p := ppFree.get().(*pp) + p.panicking = false p.fmt.init(&p.buf) return p } @@ -159,19 +162,18 @@ func (p *pp) Write(b []byte) (ret int, err os.Error) { // Fprintf formats according to a format specifier and writes to w. // It returns the number of bytes written and any write error encountered. -func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error os.Error) { +func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err os.Error) { p := newPrinter() p.doPrintf(format, a) - n64, error := p.buf.WriteTo(w) + n64, err := p.buf.WriteTo(w) p.free() - return int(n64), error + return int(n64), err } // Printf formats according to a format specifier and writes to standard output. // It returns the number of bytes written and any write error encountered. -func Printf(format string, a ...interface{}) (n int, errno os.Error) { - n, errno = Fprintf(os.Stdout, format, a...) - return n, errno +func Printf(format string, a ...interface{}) (n int, err os.Error) { + return Fprintf(os.Stdout, format, a...) } // Sprintf formats according to a format specifier and returns the resulting string. @@ -186,7 +188,7 @@ func Sprintf(format string, a ...interface{}) string { // Errorf formats according to a format specifier and returns the string // converted to an os.ErrorString, which satisfies the os.Error interface. func Errorf(format string, a ...interface{}) os.Error { - return os.ErrorString(Sprintf(format, a...)) + return os.NewError(Sprintf(format, a...)) } // These routines do not take a format string @@ -194,20 +196,19 @@ func Errorf(format string, a ...interface{}) os.Error { // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. -func Fprint(w io.Writer, a ...interface{}) (n int, error os.Error) { +func Fprint(w io.Writer, a ...interface{}) (n int, err os.Error) { p := newPrinter() p.doPrint(a, false, false) - n64, error := p.buf.WriteTo(w) + n64, err := p.buf.WriteTo(w) p.free() - return int(n64), error + return int(n64), err } // Print formats using the default formats for its operands and writes to standard output. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. -func Print(a ...interface{}) (n int, errno os.Error) { - n, errno = Fprint(os.Stdout, a...) - return n, errno +func Print(a ...interface{}) (n int, err os.Error) { + return Fprint(os.Stdout, a...) } // Sprint formats using the default formats for its operands and returns the resulting string. @@ -227,20 +228,19 @@ func Sprint(a ...interface{}) string { // Fprintln formats using the default formats for its operands and writes to w. // Spaces are always added between operands and a newline is appended. // It returns the number of bytes written and any write error encountered. -func Fprintln(w io.Writer, a ...interface{}) (n int, error os.Error) { +func Fprintln(w io.Writer, a ...interface{}) (n int, err os.Error) { p := newPrinter() p.doPrint(a, true, true) - n64, error := p.buf.WriteTo(w) + n64, err := p.buf.WriteTo(w) p.free() - return int(n64), error + return int(n64), err } // Println formats using the default formats for its operands and writes to standard output. // Spaces are always added between operands and a newline is appended. // It returns the number of bytes written and any write error encountered. -func Println(a ...interface{}) (n int, errno os.Error) { - n, errno = Fprintln(os.Stdout, a...) - return n, errno +func Println(a ...interface{}) (n int, err os.Error) { + return Fprintln(os.Stdout, a...) } // Sprintln formats using the default formats for its operands and returns the resulting string. @@ -363,6 +363,8 @@ func (p *pp) fmt0x64(v uint64, leading0x bool) { // temporarily turning on the unicode flag and tweaking the precision. func (p *pp) fmtUnicode(v int64) { precPresent := p.fmt.precPresent + sharp := p.fmt.sharp + p.fmt.sharp = false prec := p.fmt.prec if !precPresent { // If prec is already set, leave it alone; otherwise 4 is minimum. @@ -370,10 +372,13 @@ func (p *pp) fmtUnicode(v int64) { p.fmt.precPresent = true } p.fmt.unicode = true // turn on U+ + p.fmt.uniQuote = sharp p.fmt.integer(int64(v), 16, unsigned, udigits) p.fmt.unicode = false + p.fmt.uniQuote = false p.fmt.prec = prec p.fmt.precPresent = precPresent + p.fmt.sharp = sharp } func (p *pp) fmtUint64(v uint64, verb int, goSyntax bool, value interface{}) { @@ -561,6 +566,31 @@ var ( uintptrBits = reflect.TypeOf(uintptr(0)).Bits() ) +func (p *pp) catchPanic(val interface{}, verb int) { + if err := recover(); err != nil { + // If it's a nil pointer, just say "<nil>". The likeliest causes are a + // Stringer that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "<nil>" is a nice result. + if v := reflect.ValueOf(val); v.Kind() == reflect.Ptr && v.IsNil() { + p.buf.Write(nilAngleBytes) + return + } + // Otherwise print a concise panic message. Most of the time the panic + // value will print itself nicely. + if p.panicking { + // Nested panics; the recursion in printField cannot succeed. + panic(err) + } + p.buf.WriteByte('%') + p.add(verb) + p.buf.Write(panicBytes) + p.panicking = true + p.printField(err, 'v', false, false, 0) + p.panicking = false + p.buf.WriteByte(')') + } +} + func (p *pp) printField(field interface{}, verb int, plus, goSyntax bool, depth int) (wasString bool) { if field == nil { if verb == 'T' || verb == 'v' { @@ -583,6 +613,7 @@ func (p *pp) printField(field interface{}, verb int, plus, goSyntax bool, depth } // Is it a Formatter? if formatter, ok := field.(Formatter); ok { + defer p.catchPanic(field, verb) formatter.Format(p, verb) return false // this value is not a string @@ -595,6 +626,7 @@ func (p *pp) printField(field interface{}, verb int, plus, goSyntax bool, depth if goSyntax { p.fmt.sharp = false if stringer, ok := field.(GoStringer); ok { + defer p.catchPanic(field, verb) // Print the result of GoString unadorned. p.fmtString(stringer.GoString(), 's', false, field) return false // this value is not a string @@ -602,6 +634,7 @@ func (p *pp) printField(field interface{}, verb int, plus, goSyntax bool, depth } else { // Is it a Stringer? if stringer, ok := field.(Stringer); ok { + defer p.catchPanic(field, verb) p.printField(stringer.String(), verb, plus, false, depth) return false // this value is not a string } diff --git a/src/pkg/fmt/scan.go b/src/pkg/fmt/scan.go index dd8548ceb..d93a8c1da 100644 --- a/src/pkg/fmt/scan.go +++ b/src/pkg/fmt/scan.go @@ -35,6 +35,10 @@ type ScanState interface { ReadRune() (rune int, size int, err os.Error) // UnreadRune causes the next call to ReadRune to return the same rune. UnreadRune() os.Error + // SkipSpace skips space in the input. Newlines are treated as space + // unless the scan operation is Scanln, Fscanln or Sscanln, in which case + // a newline is treated as EOF. + SkipSpace() // Token skips space in the input if skipSpace is true, then returns the // run of Unicode code points c satisfying f(c). If f is nil, // !unicode.IsSpace(c) is used; that is, the token will hold non-space @@ -167,7 +171,7 @@ type ssave struct { // satisfies io.Reader. It will never be called when used as // intended, so there is no need to make it actually work. func (s *ss) Read(buf []byte) (n int, err os.Error) { - return 0, os.ErrorString("ScanState's Read should not be called. Use ReadRune") + return 0, os.NewError("ScanState's Read should not be called. Use ReadRune") } func (s *ss) ReadRune() (rune int, size int, err os.Error) { @@ -241,7 +245,7 @@ func (s *ss) error(err os.Error) { } func (s *ss) errorString(err string) { - panic(scanError{os.ErrorString(err)}) + panic(scanError{os.NewError(err)}) } func (s *ss) Token(skipSpace bool, f func(int) bool) (tok []byte, err os.Error) { @@ -267,6 +271,14 @@ func notSpace(r int) bool { return !unicode.IsSpace(r) } + +// skipSpace provides Scan() methods the ability to skip space and newline characters +// in keeping with the current scanning mode set by format strings and Scan()/Scanln(). +func (s *ss) SkipSpace() { + s.skipSpace(false) +} + + // readRune is a structure to enable reading UTF-8 encoded code points // from an io.Reader. It is used if the Reader given to the scanner does // not already implement io.RuneReader. @@ -427,8 +439,8 @@ func (s *ss) typeError(field interface{}, expected string) { s.errorString("expected field of type pointer to " + expected + "; found " + reflect.TypeOf(field).String()) } -var complexError = os.ErrorString("syntax error scanning complex number") -var boolError = os.ErrorString("syntax error scanning boolean") +var complexError = os.NewError("syntax error scanning complex number") +var boolError = os.NewError("syntax error scanning boolean") // consume reads the next rune in the input and reports whether it is in the ok string. // If accept is true, it puts the character into the input token. @@ -945,7 +957,7 @@ func (s *ss) scanOne(verb int, field interface{}) { // For now, can only handle (renamed) []byte. typ := v.Type() if typ.Elem().Kind() != reflect.Uint8 { - goto CantHandle + s.errorString("Scan: can't handle type: " + val.Type().String()) } str := s.convertString(verb) v.Set(reflect.MakeSlice(typ, len(str), len(str))) @@ -959,7 +971,6 @@ func (s *ss) scanOne(verb int, field interface{}) { case reflect.Complex64, reflect.Complex128: v.SetComplex(s.scanComplex(verb, v.Type().Bits())) default: - CantHandle: s.errorString("Scan: can't handle type: " + val.Type().String()) } } diff --git a/src/pkg/fmt/scan_test.go b/src/pkg/fmt/scan_test.go index a4de8adb1..98b3b5493 100644 --- a/src/pkg/fmt/scan_test.go +++ b/src/pkg/fmt/scan_test.go @@ -94,7 +94,7 @@ func (x *Xs) Scan(state ScanState, verb int) os.Error { } s := string(tok) if !regexp.MustCompile("^" + string(verb) + "+$").MatchString(s) { - return os.ErrorString("syntax error for xs") + return os.NewError("syntax error for xs") } *x = Xs(s) return nil @@ -818,7 +818,7 @@ func (r *RecursiveInt) Scan(state ScanState, verb int) (err os.Error) { next := new(RecursiveInt) _, err = Fscanf(state, ".%v", next) if err != nil { - if err == os.ErrorString("input does not match format") || err == io.ErrUnexpectedEOF { + if err == os.NewError("input does not match format") || err == io.ErrUnexpectedEOF { err = nil } return |