Skip to content
Browse files

improvements in imports processing

  • Loading branch information...
1 parent 7990ef2 commit b779b3ad6feafd850d876b0853a2feedc6a98148 Petar Maymounkov committed May 26, 2012
Showing with 373 additions and 245 deletions.
  1. +5 −4 README.md
  2. +1 −0 cmd/vitamixc/main.go
  3. +45 −0 vrewrite/call.go
  4. +18 −27 vrewrite/fix.go
  5. +224 −0 vrewrite/gofix-imports.go
  6. +22 −205 vrewrite/imports.go
  7. +2 −1 vrewrite/rewrite.go
  8. +56 −8 vrewrite/rewrite_test.go
View
9 README.md
@@ -10,11 +10,12 @@ Control software (like robotic control or congestion control) is software that i
form or another listens to external events in real time and reacts with carefully-timed
responses.
-Control software is notoriously hard to get right, because even the computational micro-delays
-in the software itself (not to mention the hardware) can affect the overall outcomes.
+Control software is notoriously hard to get right, because even the time to
+compute a response (which depends on the specific implementation and the
+hardware) can and does affect the overall outcomes in a non-negligible manner.
-While developing control software, ideally, one wants to be able to perform
-sandbox simulations and experiments, while meeting the following demands:
+In developing control software, one wants to be able to perform
+sandbox simulations, while meeting the following demands:
* _The computational time (i.e. the time it takes the software to compute a reaction to an event) is zero._
Being able to do this is a way of decoupling the outcomes of the response logic
View
1 cmd/vitamixc/main.go
@@ -70,6 +70,7 @@ func main() {
RewriteFile(fileSet, fileFile)
position := fileSet.Position(fileFile.Package)
if err = PrintToFile(path.Join(tgt, position.Filename), fileSet, fileFile); err != nil {
+ fmt.Fprintf(os.Stderr, "Problem determining source filename (%s)", err)
os.Exit(1)
}
}
View
45 vrewrite/call.go
@@ -0,0 +1,45 @@
+// Copyright 2012 Petar Maymounkov. All rights reserved.
+// Use of this source code is governed by a
+// license that can be found in the LICENSE file.
+
+package vrewrite
+
+import (
+ "go/ast"
+)
+
+func rewriteTimeCalls(file *ast.File) (needVtime, needTime bool) {
+ v := &callVisitor{}
+ ast.Walk(v, file)
+ return v.NeedPkgVtime, v.NeedPkgTime
+}
+
+type callVisitor struct {
+ NeedPkgVtime bool
+ NeedPkgTime bool // True if after rewriting invokations to time.Sleep and time.Now, other references to pkg "time" remain
+}
+
+func (v *callVisitor) Visit(x ast.Node) ast.Visitor {
+ callexpr, ok := x.(*ast.CallExpr)
+ if !ok || callexpr.Fun == nil {
+ return v
+ }
+ sexpr, ok := callexpr.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return v
+ }
+ sx, ok := sexpr.X.(*ast.Ident)
+ if !ok {
+ return v
+ }
+ if sx.Name != "time" {
+ return v
+ }
+ if sexpr.Sel.Name == "Now" || sexpr.Sel.Name == "Sleep" {
+ sx.Name = "vtime"
+ v.NeedPkgVtime = true
+ } else {
+ v.NeedPkgTime = true
+ }
+ return v
+}
View
45 vrewrite/fix.go
@@ -10,9 +10,25 @@ import (
)
func RewriteFile(fileSet *token.FileSet, file *ast.File) error {
+
+ // addImport will automatically rename any existing package references with
+ // conflicting name vtime to vtime_
addImport(file, "github.com/petar/vitamix/vtime")
- fixCall(file)
- fixChan(fileSet, file)
+
+ // rewriteTimeCalls will rewrite time.Now and time.Sleep to
+ // vtime.Now and vtime.Sleep
+ needVtime, needTime := rewriteTimeCalls(file)
+
+ if !needVtime {
+ removeImport(file, "github.com/petar/vitamix/vtime")
+ }
+
+ if !needTime {
+ removeImport(file, "time")
+ }
+
+ rewriteChanOps(fileSet, file)
+
return nil
}
@@ -25,28 +41,3 @@ func RewritePackage(fileSet *token.FileSet, pkg *ast.Package) error {
}
return err
}
-
-func fixCall(file *ast.File) {
- ast.Walk(VisitorNoReturnFunc(fixCallVisit), file)
-}
-
-func fixCallVisit(x ast.Node) {
- callexpr, ok := x.(*ast.CallExpr)
- if !ok || callexpr.Fun == nil {
- return
- }
- sexpr, ok := callexpr.Fun.(*ast.SelectorExpr)
- if !ok {
- return
- }
- sx, ok := sexpr.X.(*ast.Ident)
- if !ok {
- return
- }
- if sx.Name != "time" {
- return
- }
- if sexpr.Sel.Name == "Now" || sexpr.Sel.Name == "Sleep" {
- sx.Name = "vtime"
- }
-}
View
224 vrewrite/gofix-imports.go
@@ -0,0 +1,224 @@
+// Copyright 2012 Petar Maymounkov. All rights reserved.
+// Use of this source code is governed by a
+// license that can be found in the LICENSE file.
+
+package vrewrite
+
+// The routines in this file are copied from the implementation of the gofix tool.
+
+import (
+ "go/ast"
+ "go/token"
+ "path"
+ "strconv"
+)
+
+// isTopName returns true if n is a top-level unresolved identifier with the given name.
+func isTopName(n ast.Expr, name string) bool {
+ id, ok := n.(*ast.Ident)
+ return ok && id.Name == name && id.Obj == nil
+}
+
+// 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 {
+ if importPath(s) == path {
+ return s
+ }
+ }
+ 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 ""
+}
+
+// matchLen returns the length of the longest prefix shared by x and y.
+func matchLen(x, y string) int {
+ i := 0
+ for i < len(x) && i < len(y) && x[i] == y[i] {
+ i++
+ }
+ return i
+}
+
+// addImport adds the import path to the file f, if absent.
+func addImport(f *ast.File, ipath string) (added bool) {
+ if imports(f, ipath) {
+ return false
+ }
+
+ // Determine name of import.
+ // Assume added imports follow convention of using last element.
+ _, name := path.Split(ipath)
+
+ // Rename any conflicting top-level references from name to name_.
+ renameTop(f, name, name+"_")
+
+ newImport := &ast.ImportSpec{
+ Path: &ast.BasicLit{
+ Kind: token.STRING,
+ Value: strconv.Quote(ipath),
+ },
+ }
+
+ // Find an import decl to add to.
+ var (
+ bestMatch = -1
+ lastImport = -1
+ impDecl *ast.GenDecl
+ impIndex = -1
+ )
+ for i, decl := range f.Decls {
+ gen, ok := decl.(*ast.GenDecl)
+ if ok && gen.Tok == token.IMPORT {
+ lastImport = i
+ // Do not add to import "C", to avoid disrupting the
+ // association with its doc comment, breaking cgo.
+ if declImports(gen, "C") {
+ continue
+ }
+
+ // Compute longest shared prefix with imports in this block.
+ for j, spec := range gen.Specs {
+ impspec := spec.(*ast.ImportSpec)
+ n := matchLen(importPath(impspec), ipath)
+ if n > bestMatch {
+ bestMatch = n
+ impDecl = gen
+ impIndex = j
+ }
+ }
+ }
+ }
+
+ // If no import decl found, add one after the last import.
+ if impDecl == nil {
+ impDecl = &ast.GenDecl{
+ Tok: token.IMPORT,
+ }
+ f.Decls = append(f.Decls, nil)
+ copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
+ f.Decls[lastImport+1] = impDecl
+ }
+
+ // Ensure the import decl has parentheses, if needed.
+ if len(impDecl.Specs) > 0 && !impDecl.Lparen.IsValid() {
+ impDecl.Lparen = impDecl.Pos()
+ }
+
+ insertAt := impIndex + 1
+ if insertAt == 0 {
+ insertAt = len(impDecl.Specs)
+ }
+ impDecl.Specs = append(impDecl.Specs, nil)
+ copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
+ impDecl.Specs[insertAt] = newImport
+ if insertAt > 0 {
+ // Assign same position as the previous import,
+ // so that the sorter sees it as being in the same block.
+ prev := impDecl.Specs[insertAt-1]
+ newImport.Path.ValuePos = prev.Pos()
+ newImport.EndPos = prev.Pos()
+ }
+
+ f.Imports = append(f.Imports, newImport)
+ return true
+}
+
+// renameTop renames all references to the top-level name old.
+// It returns true if it makes any changes.
+func renameTop(f *ast.File, old, new string) bool {
+ var fixed bool
+
+ // Rename any conflicting imports
+ // (assuming package name is last element of path).
+ for _, s := range f.Imports {
+ if s.Name != nil {
+ if s.Name.Name == old {
+ s.Name.Name = new
+ fixed = true
+ }
+ } else {
+ _, thisName := path.Split(importPath(s))
+ if thisName == old {
+ s.Name = ast.NewIdent(new)
+ fixed = true
+ }
+ }
+ }
+
+ // Rename any top-level declarations.
+ for _, d := range f.Decls {
+ switch d := d.(type) {
+ case *ast.FuncDecl:
+ if d.Recv == nil && d.Name.Name == old {
+ d.Name.Name = new
+ d.Name.Obj.Name = new
+ fixed = true
+ }
+ case *ast.GenDecl:
+ for _, s := range d.Specs {
+ switch s := s.(type) {
+ case *ast.TypeSpec:
+ if s.Name.Name == old {
+ s.Name.Name = new
+ s.Name.Obj.Name = new
+ fixed = true
+ }
+ case *ast.ValueSpec:
+ for _, n := range s.Names {
+ if n.Name == old {
+ n.Name = new
+ n.Obj.Name = new
+ fixed = true
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Rename top-level old to new, both unresolved names
+ // (probably defined in another file) and names that resolve
+ // to a declaration we renamed.
+ walk(f, func(n interface{}) {
+ id, ok := n.(*ast.Ident)
+ if ok && isTopName(id, old) {
+ id.Name = new
+ fixed = true
+ }
+ if ok && id.Obj != nil && id.Name == old && id.Obj.Name == new {
+ id.Name = id.Obj.Name
+ fixed = true
+ }
+ })
+
+ return fixed
+}
+
+// declImports reports whether gen contains an import of path.
+func declImports(gen *ast.GenDecl, path string) bool {
+ if gen.Tok != token.IMPORT {
+ return false
+ }
+ for _, spec := range gen.Specs {
+ impspec := spec.(*ast.ImportSpec)
+ if importPath(impspec) == path {
+ return true
+ }
+ }
+ return false
+}
View
227 vrewrite/imports.go
@@ -7,216 +7,33 @@ package vrewrite
import (
"go/ast"
"go/token"
- "path"
- "strconv"
)
-// isTopName returns true if n is a top-level unresolved identifier with the given name.
-func isTopName(n ast.Expr, name string) bool {
- id, ok := n.(*ast.Ident)
- return ok && id.Name == name && id.Obj == nil
-}
-
-// 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 {
- if importPath(s) == path {
- return s
- }
- }
- 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 ""
-}
-
-// matchLen returns the length of the longest prefix shared by x and y.
-func matchLen(x, y string) int {
- i := 0
- for i < len(x) && i < len(y) && x[i] == y[i] {
- i++
- }
- return i
-}
-
-// addImport adds the import path to the file f, if absent.
-func addImport(f *ast.File, ipath string) (added bool) {
- if imports(f, ipath) {
- return false
- }
-
- // Determine name of import.
- // Assume added imports follow convention of using last element.
- _, name := path.Split(ipath)
-
- // Rename any conflicting top-level references from name to name_.
- renameTop(f, name, name+"_")
-
- newImport := &ast.ImportSpec{
- Path: &ast.BasicLit{
- Kind: token.STRING,
- Value: strconv.Quote(ipath),
- },
- }
-
- // Find an import decl to add to.
- var (
- bestMatch = -1
- lastImport = -1
- impDecl *ast.GenDecl
- impIndex = -1
- )
- for i, decl := range f.Decls {
- gen, ok := decl.(*ast.GenDecl)
- if ok && gen.Tok == token.IMPORT {
- lastImport = i
- // Do not add to import "C", to avoid disrupting the
- // association with its doc comment, breaking cgo.
- if declImports(gen, "C") {
+func removeImport(file *ast.File, ipath string) {
+ var j int
+ for j < len(file.Decls) {
+ gen, ok := file.Decls[j].(*ast.GenDecl)
+ if !ok || gen.Tok != token.IMPORT {
+ j++
+ continue
+ }
+ var i int
+ for i < len(gen.Specs) {
+ impspec := gen.Specs[i].(*ast.ImportSpec)
+ if importPath(impspec) != ipath {
+ i++
continue
}
-
- // Compute longest shared prefix with imports in this block.
- for j, spec := range gen.Specs {
- impspec := spec.(*ast.ImportSpec)
- n := matchLen(importPath(impspec), ipath)
- if n > bestMatch {
- bestMatch = n
- impDecl = gen
- impIndex = j
- }
- }
- }
- }
-
- // If no import decl found, add one after the last import.
- if impDecl == nil {
- impDecl = &ast.GenDecl{
- Tok: token.IMPORT,
+ // If we found a match, pop the spec from gen.Specs
+ gen.Specs[i] = gen.Specs[len(gen.Specs)-1]
+ gen.Specs = gen.Specs[:len(gen.Specs)-1]
}
- f.Decls = append(f.Decls, nil)
- copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:])
- f.Decls[lastImport+1] = impDecl
- }
-
- // Ensure the import decl has parentheses, if needed.
- if len(impDecl.Specs) > 0 && !impDecl.Lparen.IsValid() {
- impDecl.Lparen = impDecl.Pos()
- }
-
- insertAt := impIndex + 1
- if insertAt == 0 {
- insertAt = len(impDecl.Specs)
- }
- impDecl.Specs = append(impDecl.Specs, nil)
- copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:])
- impDecl.Specs[insertAt] = newImport
- if insertAt > 0 {
- // Assign same position as the previous import,
- // so that the sorter sees it as being in the same block.
- prev := impDecl.Specs[insertAt-1]
- newImport.Path.ValuePos = prev.Pos()
- newImport.EndPos = prev.Pos()
- }
-
- f.Imports = append(f.Imports, newImport)
- return true
-}
-
-// renameTop renames all references to the top-level name old.
-// It returns true if it makes any changes.
-func renameTop(f *ast.File, old, new string) bool {
- var fixed bool
-
- // Rename any conflicting imports
- // (assuming package name is last element of path).
- for _, s := range f.Imports {
- if s.Name != nil {
- if s.Name.Name == old {
- s.Name.Name = new
- fixed = true
- }
- } else {
- _, thisName := path.Split(importPath(s))
- if thisName == old {
- s.Name = ast.NewIdent(new)
- fixed = true
- }
- }
- }
-
- // Rename any top-level declarations.
- for _, d := range f.Decls {
- switch d := d.(type) {
- case *ast.FuncDecl:
- if d.Recv == nil && d.Name.Name == old {
- d.Name.Name = new
- d.Name.Obj.Name = new
- fixed = true
- }
- case *ast.GenDecl:
- for _, s := range d.Specs {
- switch s := s.(type) {
- case *ast.TypeSpec:
- if s.Name.Name == old {
- s.Name.Name = new
- s.Name.Obj.Name = new
- fixed = true
- }
- case *ast.ValueSpec:
- for _, n := range s.Names {
- if n.Name == old {
- n.Name = new
- n.Obj.Name = new
- fixed = true
- }
- }
- }
- }
- }
- }
-
- // Rename top-level old to new, both unresolved names
- // (probably defined in another file) and names that resolve
- // to a declaration we renamed.
- walk(f, func(n interface{}) {
- id, ok := n.(*ast.Ident)
- if ok && isTopName(id, old) {
- id.Name = new
- fixed = true
- }
- if ok && id.Obj != nil && id.Name == old && id.Obj.Name == new {
- id.Name = id.Obj.Name
- fixed = true
- }
- })
-
- return fixed
-}
-
-// declImports reports whether gen contains an import of path.
-func declImports(gen *ast.GenDecl, path string) bool {
- if gen.Tok != token.IMPORT {
- return false
- }
- for _, spec := range gen.Specs {
- impspec := spec.(*ast.ImportSpec)
- if importPath(impspec) == path {
- return true
+ if len(gen.Specs) > 0 {
+ j++
+ continue
}
+ // Remove entire import decl if no imports left in it
+ file.Decls[j] = file.Decls[len(file.Decls)-1]
+ file.Decls = file.Decls[:len(file.Decls)-1]
}
- return false
}
View
3 vrewrite/rewrite.go
@@ -15,8 +15,9 @@ import (
// XXX: Take care of label statements
// XXX: Check imports for newline printing insights
// XXX: Remove import for time if all use cases are rewritten to vtime
+// XXX: Cannot fix invokations to Now and Sleep, if not in the form time.Now and time.Sleep
-func fixChan(fset *token.FileSet, file *ast.File) {
+func rewriteChanOps(fset *token.FileSet, file *ast.File) {
if err := rewrite(fset, file); err != nil {
//fmt.Fprintf(os.Stderr, "Rewrite errors parsing '%s':\n%s\n", file.Name.Name, err)
fmt.Fprintf(os.Stderr, "—— Encountered errors while parsing\n")
View
64 vrewrite/rewrite_test.go
@@ -11,8 +11,15 @@ import (
"testing"
)
+type testPair struct {
+ Source string
+ Output string
+}
+
var (
- testSources = []string{
+ tests = []testPair{
+ testPair{
+ Source:
`
package main
import "time"
@@ -31,6 +38,14 @@ func main() {
println("C", time.Now().UnixNano())
}
`,
+ Output:
+`B 1000000000
+A 2000000000
+C 3000000000
+`,
+ },
+ testPair{
+ Source:
`
package main
import "time"
@@ -55,24 +70,57 @@ func main() {
println("OK")
}
`,
+ Output:
+`A
+1000000000
+B
+2000000000
+OK
+`,
+ },
+ testPair{
+ Source:
+`
+package main
+import "time"
+func main() {
+ ch1 := make(chan int)
+ ch2 := make(chan int)
+ go func() {
+ time.Sleep(2*time.Second)
+ ch1 <- 1
+ }()
+ go func() {
+ time.Sleep(1*time.Second)
+ ch2 <- 1
+ }()
+ goto __Label
+__Label:
+ select {
+ case <-ch1:
+ println("B")
+ println(time.Now().UnixNano())
+ case <-ch2:
+ println("A")
+ println(time.Now().UnixNano())
}
- expectedOutputs = []string{
-`B 1000000000
-A 2000000000
-C 3000000000
+ println("OK")
+}
`,
+ Output:
`A
1000000000
B
2000000000
OK
`,
-}
+ },
+ }
)
func TestRewriteFile(t *testing.T) {
- for i, src := range testSources {
- testSnippet(i, src, expectedOutputs[i], t)
+ for i, pair := range tests {
+ testSnippet(i, pair.Source, pair.Output, t)
}
}

0 comments on commit b779b3a

Please sign in to comment.
Something went wrong with that request. Please try again.