diff options
author | Robert Griesemer <gri@golang.org> | 2009-04-21 18:37:48 -0700 |
---|---|---|
committer | Robert Griesemer <gri@golang.org> | 2009-04-21 18:37:48 -0700 |
commit | 543a25f8d50d3617670e80e80f2e1ef24183ccb4 (patch) | |
tree | b03e6a6434c1e1c27cb08eb30e5da96c45c7c244 | |
parent | 484d1c871002643133fa5555fb8f7cafef59a072 (diff) | |
download | golang-543a25f8d50d3617670e80e80f2e1ef24183ccb4.tar.gz |
remove lots of accumulated crud:
- delete utility files which contained functionality that is now elsewhere
(or saved the files away for now)
- cleanup Makefile (remove unnecessary deps)
- minor adjustments to godoc, fixed a couple of bugs
- make pretty.go self-contained
TBR=r
DELTA=625 (81 added, 510 deleted, 34 changed)
OCL=27700
CL=27702
-rw-r--r-- | usr/gri/pretty/Makefile | 18 | ||||
-rw-r--r-- | usr/gri/pretty/astprinter.go | 18 | ||||
-rw-r--r-- | usr/gri/pretty/compilation.go | 190 | ||||
-rw-r--r-- | usr/gri/pretty/docprinter.go | 4 | ||||
-rw-r--r-- | usr/gri/pretty/godoc.go | 38 | ||||
-rw-r--r-- | usr/gri/pretty/package.html | 6 | ||||
-rw-r--r-- | usr/gri/pretty/platform.go | 84 | ||||
-rw-r--r-- | usr/gri/pretty/pretty.go | 115 | ||||
-rw-r--r-- | usr/gri/pretty/selftest2.go | 2 | ||||
-rw-r--r-- | usr/gri/pretty/typechecker.go | 93 | ||||
-rw-r--r-- | usr/gri/pretty/utils.go | 91 |
11 files changed, 113 insertions, 546 deletions
diff --git a/usr/gri/pretty/Makefile b/usr/gri/pretty/Makefile index 3546aaa21..c00163fb6 100644 --- a/usr/gri/pretty/Makefile +++ b/usr/gri/pretty/Makefile @@ -23,28 +23,16 @@ smoketest: pretty ./test.sh astprinter.go install: pretty godoc untab - cp pretty $(HOME)/bin/pretty cp godoc $(HOME)/bin/godoc + cp pretty $(HOME)/bin/pretty cp untab $(HOME)/bin/untab clean: rm -f pretty untab godoc *.6 *.a 6.out *~ -godoc.6: docprinter.6 compilation.6 comment.6 - -pretty.6: platform.6 astprinter.6 compilation.6 - -compilation.6: platform.6 typechecker.6 - -symboltable.6: - -platform.6: utils.6 - -astprinter.6: utils.6 symboltable.6 - -docprinter.6: astprinter.6 +godoc.6: astprinter.6 comment.6 docprinter.6 -comment.6: +pretty.6: astprinter.6 %.6: %.go $(G) $(F) $< diff --git a/usr/gri/pretty/astprinter.go b/usr/gri/pretty/astprinter.go index 70662e5f7..033cb1a3a 100644 --- a/usr/gri/pretty/astprinter.go +++ b/usr/gri/pretty/astprinter.go @@ -5,17 +5,12 @@ package astPrinter import ( - "container/vector"; "flag"; "fmt"; "go/ast"; "go/token"; "io"; "os"; - "strings"; - "tabwriter"; - "unicode"; - "utf8"; ) @@ -56,16 +51,9 @@ func assert(pred bool) { } -// TODO this should be an AST method -func isExported(name *ast.Ident) bool { - ch, len := utf8.DecodeRuneInString(name.Value, 0); - return unicode.IsUpper(ch); -} - - func hasExportedNames(names []*ast.Ident) bool { for i, name := range names { - if isExported(name) { + if name.IsExported() { return true; } } @@ -315,7 +303,7 @@ func (P *Printer) TaggedString(pos token.Position, tag, s, endtag string) { nlcount := 0; if P.full { for ; P.hasComment(pos); P.nextComments() { - // we have a comment group that comes before the string + // we have a comment that comes before the string comment := P.comments[P.cindex]; ctext := string(comment.Text); // TODO get rid of string conversion here @@ -508,7 +496,7 @@ func (P *Printer) Idents(list []*ast.Ident, full bool) int { P.separator = blank; P.state = inside_list; } - if full || isExported(x) { + if full || x.IsExported() { P.Expr(x); n++; } diff --git a/usr/gri/pretty/compilation.go b/usr/gri/pretty/compilation.go deleted file mode 100644 index b03d6b33f..000000000 --- a/usr/gri/pretty/compilation.go +++ /dev/null @@ -1,190 +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 Compilation - -import ( - "container/vector"; - "fmt"; - "go/ast"; - "go/parser"; - "go/scanner"; - "go/token"; - "os"; - "platform"; - "sort"; - "typechecker"; - "utf8"; - "utils"; -) - - -func assert(b bool) { - if !b { - panic("assertion failed"); - } -} - - -type Flags struct { - Verbose bool; - Deps bool; - Columns bool; -} - - -type Error struct { - Pos token.Position; - Msg string; -} - - -type ErrorList []Error - -func (list ErrorList) Len() int { return len(list); } -func (list ErrorList) Less(i, j int) bool { return list[i].Pos.Offset < list[j].Pos.Offset; } -func (list ErrorList) Swap(i, j int) { list[i], list[j] = list[j], list[i]; } - - -type errorHandler struct { - filename string; - columns bool; - errline int; - errors vector.Vector; -} - - -func (h *errorHandler) Init(filename string, columns bool) { - h.filename = filename; - h.columns = columns; - h.errors.Init(0); -} - - -func (h *errorHandler) Error(pos token.Position, msg string) { - // only report errors that are on a new line - // in the hope to avoid most follow-up errors - if pos.Line == h.errline { - return; - } - - // report error - fmt.Printf("%s:%d:", h.filename, pos.Line); - if h.columns { - fmt.Printf("%d:", pos.Column); - } - fmt.Printf(" %s\n", msg); - - // collect the error - h.errors.Push(Error{pos, msg}); - h.errline = pos.Line; -} - - -func Compile(filename string, flags *Flags) (*ast.Program, ErrorList) { - src, os_err := os.Open(filename, os.O_RDONLY, 0); - defer src.Close(); - if os_err != nil { - fmt.Printf("cannot open %s (%s)\n", filename, os_err.String()); - return nil, nil; - } - - var err errorHandler; - err.Init(filename, flags.Columns); - - mode := parser.ParseComments; - if flags.Verbose { - mode |= parser.Trace; - } - prog, ok2 := parser.Parse(src, &err, mode); - - if ok2 { - TypeChecker.CheckProgram(&err, prog); - } - - // convert error list and sort it - errors := make(ErrorList, err.errors.Len()); - for i := 0; i < err.errors.Len(); i++ { - errors[i] = err.errors.At(i).(Error); - } - sort.Sort(errors); - - return prog, errors; -} - - -func fileExists(name string) bool { - dir, err := os.Stat(name); - return err == nil; -} - -/* -func printDep(localset map [string] bool, wset *vector.Vector, decl ast.Decl2) { - src := decl.Val.(*ast.BasicLit).Val; - src = src[1 : len(src) - 1]; // strip "'s - - // ignore files when they are seen a 2nd time - dummy, found := localset[src]; - if !found { - localset[src] = true; - if fileExists(src + ".go") { - wset.Push(src); - fmt.Printf(" %s.6", src); - } else if - fileExists(Platform.GOROOT + "/pkg/" + src + ".6") || - fileExists(Platform.GOROOT + "/pkg/" + src + ".a") { - - } else { - // TODO should collect these and print later - //print("missing file: ", src, "\n"); - } - } -} -*/ - - -func addDeps(globalset map [string] bool, wset *vector.Vector, src_file string, flags *Flags) { - dummy, found := globalset[src_file]; - if !found { - globalset[src_file] = true; - - prog, errors := Compile(src_file, flags); - if errors == nil || len(errors) > 0 { - return; - } - - nimports := len(prog.Decls); - if nimports > 0 { - fmt.Printf("%s.6:\t", src_file); - - localset := make(map [string] bool); - for i := 0; i < nimports; i++ { - decl := prog.Decls[i]; - panic(); - /* - assert(decl.Tok == scanner.IMPORT); - if decl.List == nil { - printDep(localset, wset, decl); - } else { - for j := 0; j < decl.List.Len(); j++ { - printDep(localset, wset, decl.List.At(j).(*ast.Decl)); - } - } - */ - } - print("\n\n"); - } - } -} - - -func ComputeDeps(src_file string, flags *Flags) { - panic("dependency printing currently disabled"); - globalset := make(map [string] bool); - wset := vector.New(0); - wset.Push(Utils.TrimExt(src_file, ".go")); - for wset.Len() > 0 { - addDeps(globalset, wset, wset.Pop().(string), flags); - } -} diff --git a/usr/gri/pretty/docprinter.go b/usr/gri/pretty/docprinter.go index 9f053e4d4..87b973e27 100644 --- a/usr/gri/pretty/docprinter.go +++ b/usr/gri/pretty/docprinter.go @@ -16,10 +16,6 @@ import ( "regexp"; "sort"; "strings"; - "unicode"; - "utf8"; - - "astprinter"; ) diff --git a/usr/gri/pretty/godoc.go b/usr/gri/pretty/godoc.go index 300081ec6..3f91d6510 100644 --- a/usr/gri/pretty/godoc.go +++ b/usr/gri/pretty/godoc.go @@ -53,36 +53,30 @@ import ( ) -// TODO -// - uniform use of path, filename, dirname, pakname, etc. -// - fix weirdness with double-/'s in paths -// - split http service into its own source file - // TODO: tell flag package about usage string const usageString = "usage: godoc package [name ...]\n" " godoc -http=:6060\n" -var ( - goroot string; +const Pkg = "/pkg/" // name for auto-generated package documentation tree + +var ( verbose = flag.Bool("v", false, "verbose mode"); - // server control - httpaddr = flag.String("http", "", "HTTP service address (e.g., ':6060')"); + // file system roots + goroot string; + pkgroot = flag.String("pkgroot", "src/lib", "root package source directory (if unrooted, relative to goroot)"); // layout control tabwidth = flag.Int("tabwidth", 4, "tab width"); usetabs = flag.Bool("tabs", false, "align with tabs instead of spaces"); - html = flag.Bool("html", false, "print HTML in command-line mode"); - pkgroot = flag.String("pkgroot", "src/lib", "root package source directory (if unrooted, relative to goroot)"); + // server control + httpaddr = flag.String("http", "", "HTTP service address (e.g., ':6060')"); ) -const ( - Pkg = "/pkg/" // name for auto-generated package documentation tree -) func init() { var err os.Error; @@ -140,13 +134,16 @@ type rawError struct { msg string; } + type rawErrorVector struct { vector.Vector; } + func (v *rawErrorVector) At(i int) rawError { return v.Vector.At(i).(rawError) } func (v *rawErrorVector) Less(i, j int) bool { return v.At(i).pos.Offset < v.At(j).pos.Offset; } + func (v *rawErrorVector) Error(pos token.Position, msg string) { // only collect errors that are on a new line // in the hope to avoid most follow-up errors @@ -167,17 +164,20 @@ type parseError struct { msg string; // error message } + // All the errors in the parsed file, plus surrounding source code. // Each error has a slice giving the source text preceding it // (starting where the last error occurred). The final element in list[] // has msg = "", to give the remainder of the source code. // This data structure is handed to the templates parseerror.txt and parseerror.html. +// type parseErrors struct { filename string; // path to file list []parseError; // the errors src []byte; // the file's entire source code } + // Parses a file (path) and returns the corresponding AST and // a sorted list (by file position) of errors, if any. // @@ -425,12 +425,15 @@ func serveFile(c *http.Conn, req *http.Request) { case req.Url.Path == "/doc/root.html": // hide landing page from its real name + // TODO why - there is no reason for this (remove eventually) http.NotFound(c, req); case pathutil.Ext(req.Url.Path) == ".go": - serveGoSource(c, req.Url.Path[1:len(req.Url.Path)]); + serveGoSource(c, req.Url.Path[1 : len(req.Url.Path)]); // strip leading '/' from name default: + // TODO not good enough - don't want to download files + // want to see them fileServer.ServeHTTP(c, req); } } @@ -611,13 +614,13 @@ func servePackageList(c *http.Conn, info *pakInfo) { // Return package or packages named by name. // Name is either an import string or a directory, -// like you'd see in $GOROOT/pkg/ once the 6g -// tools can handle a hierarchy there. +// like you'd see in $GOROOT/pkg/. // // Examples: // "math" - single package made up of directory // "container" - directory listing // "container/vector" - single package in container directory +// func findPackages(name string) *pakInfo { info := new(pakInfo); @@ -733,6 +736,7 @@ func main() { log.Stderrf("Go Documentation Server\n"); log.Stderrf("address = %s\n", *httpaddr); log.Stderrf("goroot = %s\n", goroot); + log.Stderrf("pkgroot = %s\n", *pkgroot); handler = LoggingHandler(handler); } diff --git a/usr/gri/pretty/package.html b/usr/gri/pretty/package.html index 5e3cbc40b..f9fd76373 100644 --- a/usr/gri/pretty/package.html +++ b/usr/gri/pretty/package.html @@ -26,11 +26,11 @@ {.end} {.end} {.section Types} - <hr /> {.repeated section @} + <hr /> <h2>type {.section Type}{Name|html}{.end}</h2> - {Doc} - <p><code>{Decl|html}</code></p> + {Doc|html-comment} + <p><pre>{Decl|html}</pre></p> {.repeated section Factories} <h3>func {Name|html}</h3> <p><code>{Decl|html}</code></p> diff --git a/usr/gri/pretty/platform.go b/usr/gri/pretty/platform.go deleted file mode 100644 index cbd4678e3..000000000 --- a/usr/gri/pretty/platform.go +++ /dev/null @@ -1,84 +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 Platform - -import IO "io" -import OS "os" -import Utils "utils" - - -// ---------------------------------------------------------------------------- -// Environment - -var - GOARCH, - GOOS, - GOROOT, - USER string; - -func init() { - var e OS.Error; - - GOARCH, e = OS.Getenv("GOARCH"); - GOOS, e = OS.Getenv("GOOS"); - GOROOT, e = OS.Getenv("GOROOT"); - USER, e = OS.Getenv("USER"); -} - - -// ---------------------------------------------------------------------------- -// I/O - -const ( - MAGIC_obj_file = "@gri-go.7@v0"; // make it clear that it cannot be a source file - Src_file_ext = ".go"; - Obj_file_ext = ".7"; -) - -func readfile(filename string) ([]byte, OS.Error) { - f, err := OS.Open(filename, OS.O_RDONLY, 0); - if err != nil { - return []byte{}, err; - } - var buf [1<<20]byte; - n, err1 := IO.FullRead(f, &buf); - f.Close(); - if err1 == IO.ErrEOF { - err1 = nil; - } - return buf[0:n], err1; -} - -func writefile(name, data string) OS.Error { - fd, err := OS.Open(name, OS.O_WRONLY, 0); - if err != nil { - return err; - } - n, err1 := IO.WriteString(fd, data); - fd.Close(); - return err1; -} - -func ReadObjectFile(filename string) ([]byte, bool) { - data, err := readfile(filename + Obj_file_ext); - magic := MAGIC_obj_file; // TODO remove once len(constant) works - if err == nil && len(data) >= len(magic) && string(data[0 : len(magic)]) == magic { - return data, true; - } - return []byte{}, false; -} - - -func ReadSourceFile(name string) ([]byte, bool) { - name = Utils.TrimExt(name, Src_file_ext) + Src_file_ext; - data, err := readfile(name); - return data, err == nil; -} - - -func WriteObjectFile(name string, data string) bool { - name = Utils.TrimExt(Utils.BaseName(name), Src_file_ext) + Obj_file_ext; - return writefile(name, data) != nil; -} diff --git a/usr/gri/pretty/pretty.go b/usr/gri/pretty/pretty.go index 2db790d93..c342b72c2 100644 --- a/usr/gri/pretty/pretty.go +++ b/usr/gri/pretty/pretty.go @@ -5,31 +5,37 @@ package main import ( - "astprinter"; - "compilation"; "flag"; + "fmt"; "go/ast"; + "go/parser"; + "go/token"; + "io"; + "log"; "os"; - "platform"; "tabwriter"; + + "astprinter"; ) var ( - flags Compilation.Flags; + columnsDefault bool; + + // operation modes + columns = flag.Bool("columns", columnsDefault, "report column no. in error messages"); silent = flag.Bool("s", false, "silent mode: no pretty print output"); + verbose = flag.Bool("v", false, "verbose mode: trace parsing"); // layout control - html = flag.Bool("html", false, "generate html"); - tabwidth = flag.Int("pretty_tabwidth", 4, "tab width"); - usetabs = flag.Bool("pretty_usetabs", false, "align with tabs instead of blanks"); + tabwidth = flag.Int("tabwidth", 4, "tab width"); + usetabs = flag.Bool("usetabs", false, "align with tabs instead of blanks"); ) func init() { - flag.BoolVar(&flags.Verbose, "v", false, "verbose mode: trace parsing"); - flag.BoolVar(&flags.Deps, "d", false, "print dependency information only"); - flag.BoolVar(&flags.Columns, "columns", Platform.USER == "gri", "print column info in error messages"); + user, err := os.Getenv("USER"); + columnsDefault = user == "gri"; } @@ -40,47 +46,90 @@ func usage() { } -func print(prog *ast.Program) { - // initialize tabwriter for nicely aligned output +// TODO(gri) use library function for this once it exists +func readFile(filename string) ([]byte, os.Error) { + f, err := os.Open(filename, os.O_RDONLY, 0); + if err != nil { + return nil, err; + } + defer f.Close(); + var b io.ByteBuffer; + if n, err := io.Copy(f, &b); err != nil { + return nil, err; + } + return b.Data(), nil; +} + + +// TODO(gri) move this function into tabwriter.go? (also used in godoc) +func makeTabwriter(writer io.Write) *tabwriter.Writer { padchar := byte(' '); if *usetabs { padchar = '\t'; } - writer := tabwriter.NewWriter(os.Stdout, *tabwidth, 1, padchar, tabwriter.FilterHTML); + return tabwriter.NewWriter(writer, *tabwidth, 1, padchar, tabwriter.FilterHTML); +} + - // initialize printer - var printer astPrinter.Printer; - printer.Init(writer, nil, prog.Comments, *html); +// TODO(gri) move this into parser as default handler +type ErrorHandler struct { + filename string; + lastline int; + columns bool; +} - printer.DoProgram(prog); - // flush any pending output - writer.Flush(); +func (h *ErrorHandler) Error(pos token.Position, msg string) { + // only report errors that are on a new line + // in the hope to avoid most follow-up errors + if pos.Line == h.lastline { + return; + } + + // report error + fmt.Printf("%s:%d:", h.filename, pos.Line); + if h.columns { + fmt.Printf("%d:", pos.Column); + } + fmt.Printf(" %s\n", msg); } func main() { + // handle flags flag.Parse(); - if flag.NFlag() == 0 && flag.NArg() == 0 { usage(); } + // determine parsing mode + mode := parser.ParseComments; + if *verbose { + mode |= parser.Trace; + } + // process files for i := 0; i < flag.NArg(); i++ { - src_file := flag.Arg(i); - - if flags.Deps { - Compilation.ComputeDeps(src_file, &flags); - - } else { - prog, errors := Compilation.Compile(src_file, &flags); - if errors == nil || len(errors) > 0 { - sys.Exit(1); - } - if !*silent { - print(prog); - } + filename := flag.Arg(i); + + src, err := readFile(filename); + if err != nil { + log.Stderrf("ReadFile %s: %v", filename, err); + continue; + } + + prog, ok := parser.Parse(src, &ErrorHandler{filename, 0, false}, mode); + if !ok { + log.Stderr("Parse %s: syntax errors", filename); + continue; + } + + if !*silent { + var printer astPrinter.Printer; + writer := makeTabwriter(os.Stdout); + printer.Init(writer, nil, nil /*prog.Comments*/, false); + printer.DoProgram(prog); + writer.Flush(); } } } diff --git a/usr/gri/pretty/selftest2.go b/usr/gri/pretty/selftest2.go index a3de38b1b..783d72d9c 100644 --- a/usr/gri/pretty/selftest2.go +++ b/usr/gri/pretty/selftest2.go @@ -5,7 +5,7 @@ package main import ( - "vector"; // not needed + "container/vector"; // not needed "utf8"; // not needed Fmt "fmt" ) diff --git a/usr/gri/pretty/typechecker.go b/usr/gri/pretty/typechecker.go deleted file mode 100644 index 4dde63b1f..000000000 --- a/usr/gri/pretty/typechecker.go +++ /dev/null @@ -1,93 +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 TypeChecker - -import ( - "go/ast"; - "go/scanner"; - "go/token"; -) - - -type state struct { - // setup - err scanner.ErrorHandler; -} - - -func (s *state) Init(err scanner.ErrorHandler) { - s.err = err; -} - - -// ---------------------------------------------------------------------------- -// Support - -func unimplemented() { - panic("unimplemented"); -} - - -func unreachable() { - panic("unreachable"); -} - - -func assert(pred bool) { - if !pred { - panic("assertion failed"); - } -} - - -func (s *state) Error(pos token.Position, msg string) { - s.err.Error(pos, msg); -} - - -// ---------------------------------------------------------------------------- - -func (s *state) CheckType() { -} - - -/* -func (s *state) CheckDeclaration(d *AST.Decl) { - if d.Tok != token.FUNC && d.List != nil { - // group of parenthesized declarations - for i := 0; i < d.List.Len(); i++ { - s.CheckDeclaration(d.List.At(i).(*AST.Decl)) - } - - } else { - // single declaration - switch d.Tok { - case token.IMPORT: - case token.CONST: - case token.VAR: - case token.TYPE: - case token.FUNC: - default: - unreachable(); - } - } -} -*/ - - -func (s *state) CheckProgram(p *ast.Program) { - for i := 0; i < len(p.Decls); i++ { - //s.CheckDeclaration(p.Decls[i].(*AST.Decl)); - } -} - - -// ---------------------------------------------------------------------------- - -func CheckProgram(err scanner.ErrorHandler, p *ast.Program) { - var s state; - s.Init(err); - s.CheckProgram(p); -} diff --git a/usr/gri/pretty/utils.go b/usr/gri/pretty/utils.go deleted file mode 100644 index baacc75de..000000000 --- a/usr/gri/pretty/utils.go +++ /dev/null @@ -1,91 +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 Utils - - -func BaseName(s string) string { - // TODO this is not correct for non-ASCII strings! - i := len(s) - 1; - for i >= 0 && s[i] != '/' { - if s[i] > 128 { - panic("non-ASCII string"); - } - i--; - } - return s[i + 1 : len(s)]; -} - - -func cleanPath(s string) string { - for i := 0; i < len(s); i++ { - if s[i] == '/' { - i++; - j := i; - for j < len(s) && s[j] == '/' { - j++; - } - if j > i { // more then one '/' - return s[0 : i] + cleanPath(s[j : len(s)]); - } - } - } - return s; -} - - -// Reduce sequences of multiple '/'s into a single '/' and -// strip any trailing '/' (may result in the empty string). -func SanitizePath(s string) string { - s = cleanPath(s); - if s[len(s)-1] == '/' { // strip trailing '/' - s = s[0 : len(s)-1]; - } - return s; -} - - -func Contains(s, sub string, pos int) bool { - end := pos + len(sub); - return pos >= 0 && end <= len(s) && s[pos : end] == sub; -} - - -func TrimExt(s, ext string) string { - i := len(s) - len(ext); - if i >= 0 && s[i : len(s)] == ext { - s = s[0 : i]; - } - return s; -} - - -func IntToString(x, base int) string { - x0 := x; - if x < 0 { - x = -x; - if x < 0 { - panic("smallest int not handled"); - } - } else if x == 0 { - return "0"; - } - - // x > 0 - hex := "0123456789ABCDEF"; - var buf [32] byte; - i := len(buf); - for x > 0 { - i--; - buf[i] = hex[x % base]; - x /= base; - } - - if x0 < 0 { - i--; - buf[i] = '-'; - } - - return string(buf[i : len(buf)]); -} |