mirror of
git://gcc.gnu.org/git/gcc.git
synced 2025-01-29 06:24:07 +08:00
f8d9fa9e80
This upgrades all of libgo other than the runtime package to the Go 1.4 release. In Go 1.4 much of the runtime was rewritten into Go. Merging that code will take more time and will not change the API, so I'm putting it off for now. There are a few runtime changes anyhow, to accomodate other packages that rely on minor modifications to the runtime support. The compiler changes slightly to add a one-bit flag to each type descriptor kind that is stored directly in an interface, which for gccgo is currently only pointer types. Another one-bit flag (gcprog) is reserved because it is used by the gc compiler, but gccgo does not currently use it. There is another error check in the compiler since I ran across it during testing. gotools/: * Makefile.am (go_cmd_go_files): Sort entries. Add generate.go. * Makefile.in: Rebuild. From-SVN: r219627
388 lines
9.0 KiB
Go
388 lines
9.0 KiB
Go
// 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 main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/printer"
|
|
"go/scanner"
|
|
"go/token"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime/pprof"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
// main operation modes
|
|
list = flag.Bool("l", false, "list files whose formatting differs from gofmt's")
|
|
write = flag.Bool("w", false, "write result to (source) file instead of stdout")
|
|
rewriteRule = flag.String("r", "", "rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')")
|
|
simplifyAST = flag.Bool("s", false, "simplify code")
|
|
doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
|
|
allErrors = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)")
|
|
|
|
// debugging
|
|
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file")
|
|
)
|
|
|
|
const (
|
|
tabWidth = 8
|
|
printerMode = printer.UseSpaces | printer.TabIndent
|
|
)
|
|
|
|
var (
|
|
fileSet = token.NewFileSet() // per process FileSet
|
|
exitCode = 0
|
|
rewrite func(*ast.File) *ast.File
|
|
parserMode parser.Mode
|
|
)
|
|
|
|
func report(err error) {
|
|
scanner.PrintError(os.Stderr, err)
|
|
exitCode = 2
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintf(os.Stderr, "usage: gofmt [flags] [path ...]\n")
|
|
flag.PrintDefaults()
|
|
os.Exit(2)
|
|
}
|
|
|
|
func initParserMode() {
|
|
parserMode = parser.ParseComments
|
|
if *allErrors {
|
|
parserMode |= parser.AllErrors
|
|
}
|
|
}
|
|
|
|
func isGoFile(f os.FileInfo) bool {
|
|
// ignore non-Go files
|
|
name := f.Name()
|
|
return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
|
|
}
|
|
|
|
// If in == nil, the source is the contents of the file with the given filename.
|
|
func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error {
|
|
if in == nil {
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
in = f
|
|
}
|
|
|
|
src, err := ioutil.ReadAll(in)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, stdin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if rewrite != nil {
|
|
if sourceAdj == nil {
|
|
file = rewrite(file)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "warning: rewrite ignored for incomplete programs\n")
|
|
}
|
|
}
|
|
|
|
ast.SortImports(fileSet, file)
|
|
|
|
if *simplifyAST {
|
|
simplify(file)
|
|
}
|
|
|
|
res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !bytes.Equal(src, res) {
|
|
// formatting has changed
|
|
if *list {
|
|
fmt.Fprintln(out, filename)
|
|
}
|
|
if *write {
|
|
err = ioutil.WriteFile(filename, res, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if *doDiff {
|
|
data, err := diff(src, res)
|
|
if err != nil {
|
|
return fmt.Errorf("computing diff: %s", err)
|
|
}
|
|
fmt.Printf("diff %s gofmt/%s\n", filename, filename)
|
|
out.Write(data)
|
|
}
|
|
}
|
|
|
|
if !*list && !*write && !*doDiff {
|
|
_, err = out.Write(res)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func visitFile(path string, f os.FileInfo, err error) error {
|
|
if err == nil && isGoFile(f) {
|
|
err = processFile(path, nil, os.Stdout, false)
|
|
}
|
|
if err != nil {
|
|
report(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func walkDir(path string) {
|
|
filepath.Walk(path, visitFile)
|
|
}
|
|
|
|
func main() {
|
|
// call gofmtMain in a separate function
|
|
// so that it can use defer and have them
|
|
// run before the exit.
|
|
gofmtMain()
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func gofmtMain() {
|
|
flag.Usage = usage
|
|
flag.Parse()
|
|
|
|
if *cpuprofile != "" {
|
|
f, err := os.Create(*cpuprofile)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "creating cpu profile: %s\n", err)
|
|
exitCode = 2
|
|
return
|
|
}
|
|
defer f.Close()
|
|
pprof.StartCPUProfile(f)
|
|
defer pprof.StopCPUProfile()
|
|
}
|
|
|
|
initParserMode()
|
|
initRewrite()
|
|
|
|
if flag.NArg() == 0 {
|
|
if *write {
|
|
fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
|
|
exitCode = 2
|
|
return
|
|
}
|
|
if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
|
|
report(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
for i := 0; i < flag.NArg(); i++ {
|
|
path := flag.Arg(i)
|
|
switch dir, err := os.Stat(path); {
|
|
case err != nil:
|
|
report(err)
|
|
case dir.IsDir():
|
|
walkDir(path)
|
|
default:
|
|
if err := processFile(path, nil, os.Stdout, false); err != nil {
|
|
report(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func diff(b1, b2 []byte) (data []byte, err error) {
|
|
f1, err := ioutil.TempFile("", "gofmt")
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer os.Remove(f1.Name())
|
|
defer f1.Close()
|
|
|
|
f2, err := ioutil.TempFile("", "gofmt")
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer os.Remove(f2.Name())
|
|
defer f2.Close()
|
|
|
|
f1.Write(b1)
|
|
f2.Write(b2)
|
|
|
|
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
|
if len(data) > 0 {
|
|
// diff exits with a non-zero status when the files don't match.
|
|
// Ignore that failure as long as we get output.
|
|
err = nil
|
|
}
|
|
return
|
|
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Support functions
|
|
//
|
|
// The functions parse, format, and isSpace below are identical to the
|
|
// respective functions in src/go/format/format.go - keep them in sync!
|
|
//
|
|
// TODO(gri) Factor out this functionality, eventually.
|
|
|
|
// parse parses src, which was read from the named file,
|
|
// as a Go source file, declaration, or statement list.
|
|
func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) (
|
|
file *ast.File,
|
|
sourceAdj func(src []byte, indent int) []byte,
|
|
indentAdj int,
|
|
err error,
|
|
) {
|
|
// Try as whole source file.
|
|
file, err = parser.ParseFile(fset, filename, src, parserMode)
|
|
// If there's no error, return. If the error is that the source file didn't begin with a
|
|
// package line and source fragments are ok, fall through to
|
|
// try as a source fragment. Stop and return on any other error.
|
|
if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") {
|
|
return
|
|
}
|
|
|
|
// If this is a declaration list, make it a source file
|
|
// by inserting a package clause.
|
|
// Insert using a ;, not a newline, so that the line numbers
|
|
// in psrc match the ones in src.
|
|
psrc := append([]byte("package p;"), src...)
|
|
file, err = parser.ParseFile(fset, filename, psrc, parserMode)
|
|
if err == nil {
|
|
sourceAdj = func(src []byte, indent int) []byte {
|
|
// Remove the package clause.
|
|
// Gofmt has turned the ; into a \n.
|
|
src = src[indent+len("package p\n"):]
|
|
return bytes.TrimSpace(src)
|
|
}
|
|
return
|
|
}
|
|
// If the error is that the source file didn't begin with a
|
|
// declaration, fall through to try as a statement list.
|
|
// Stop and return on any other error.
|
|
if !strings.Contains(err.Error(), "expected declaration") {
|
|
return
|
|
}
|
|
|
|
// If this is a statement list, make it a source file
|
|
// by inserting a package clause and turning the list
|
|
// into a function body. This handles expressions too.
|
|
// Insert using a ;, not a newline, so that the line numbers
|
|
// in fsrc match the ones in src.
|
|
fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}')
|
|
file, err = parser.ParseFile(fset, filename, fsrc, parserMode)
|
|
if err == nil {
|
|
sourceAdj = func(src []byte, indent int) []byte {
|
|
// Cap adjusted indent to zero.
|
|
if indent < 0 {
|
|
indent = 0
|
|
}
|
|
// Remove the wrapping.
|
|
// Gofmt has turned the ; into a \n\n.
|
|
// There will be two non-blank lines with indent, hence 2*indent.
|
|
src = src[2*indent+len("package p\n\nfunc _() {"):]
|
|
src = src[:len(src)-(indent+len("\n}\n"))]
|
|
return bytes.TrimSpace(src)
|
|
}
|
|
// Gofmt has also indented the function body one level.
|
|
// Adjust that with indentAdj.
|
|
indentAdj = -1
|
|
}
|
|
|
|
// Succeeded, or out of options.
|
|
return
|
|
}
|
|
|
|
// format formats the given package file originally obtained from src
|
|
// and adjusts the result based on the original source via sourceAdj
|
|
// and indentAdj.
|
|
func format(
|
|
fset *token.FileSet,
|
|
file *ast.File,
|
|
sourceAdj func(src []byte, indent int) []byte,
|
|
indentAdj int,
|
|
src []byte,
|
|
cfg printer.Config,
|
|
) ([]byte, error) {
|
|
if sourceAdj == nil {
|
|
// Complete source file.
|
|
var buf bytes.Buffer
|
|
err := cfg.Fprint(&buf, fset, file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// Partial source file.
|
|
// Determine and prepend leading space.
|
|
i, j := 0, 0
|
|
for j < len(src) && isSpace(src[j]) {
|
|
if src[j] == '\n' {
|
|
i = j + 1 // byte offset of last line in leading space
|
|
}
|
|
j++
|
|
}
|
|
var res []byte
|
|
res = append(res, src[:i]...)
|
|
|
|
// Determine and prepend indentation of first code line.
|
|
// Spaces are ignored unless there are no tabs,
|
|
// in which case spaces count as one tab.
|
|
indent := 0
|
|
hasSpace := false
|
|
for _, b := range src[i:j] {
|
|
switch b {
|
|
case ' ':
|
|
hasSpace = true
|
|
case '\t':
|
|
indent++
|
|
}
|
|
}
|
|
if indent == 0 && hasSpace {
|
|
indent = 1
|
|
}
|
|
for i := 0; i < indent; i++ {
|
|
res = append(res, '\t')
|
|
}
|
|
|
|
// Format the source.
|
|
// Write it without any leading and trailing space.
|
|
cfg.Indent = indent + indentAdj
|
|
var buf bytes.Buffer
|
|
err := cfg.Fprint(&buf, fset, file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...)
|
|
|
|
// Determine and append trailing space.
|
|
i = len(src)
|
|
for i > 0 && isSpace(src[i-1]) {
|
|
i--
|
|
}
|
|
return append(res, src[i:]...), nil
|
|
}
|
|
|
|
func isSpace(b byte) bool {
|
|
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
|
|
}
|