Comments out of order after adding item to Go AST - go

The following test attempts to use AST to add fields to a struct. The fields are added correctly, but the comments are added out of order. I gather the position may need to be specified manually, but I've so far drawn a blank finding an answer.
Here's a failing test: http://play.golang.org/p/RID4N30FZK
Here's the code:
package generator
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"testing"
)
func TestAst(t *testing.T) {
source := `package a
// B comment
type B struct {
// C comment
C string
}`
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", []byte(source), parser.ParseComments)
if err != nil {
t.Error(err)
}
v := &visitor{
file: file,
}
ast.Walk(v, file)
var output []byte
buf := bytes.NewBuffer(output)
if err := printer.Fprint(buf, fset, file); err != nil {
t.Error(err)
}
expected := `package a
// B comment
type B struct {
// C comment
C string
// D comment
D int
// E comment
E float64
}
`
if buf.String() != expected {
t.Error(fmt.Sprintf("Test failed. Expected:\n%s\nGot:\n%s", expected, buf.String()))
}
/*
actual output = `package a
// B comment
type B struct {
// C comment
// D comment
// E comment
C string
D int
E float64
}
`
*/
}
type visitor struct {
file *ast.File
}
func (v *visitor) Visit(node ast.Node) (w ast.Visitor) {
if node == nil {
return v
}
switch n := node.(type) {
case *ast.GenDecl:
if n.Tok != token.TYPE {
break
}
ts := n.Specs[0].(*ast.TypeSpec)
if ts.Name.Name == "B" {
fields := ts.Type.(*ast.StructType).Fields
addStructField(fields, v.file, "int", "D", "D comment")
addStructField(fields, v.file, "float64", "E", "E comment")
}
}
return v
}
func addStructField(fields *ast.FieldList, file *ast.File, typ string, name string, comment string) {
c := &ast.Comment{Text: fmt.Sprint("// ", comment)}
cg := &ast.CommentGroup{List: []*ast.Comment{c}}
f := &ast.Field{
Doc: cg,
Names: []*ast.Ident{ast.NewIdent(name)},
Type: ast.NewIdent(typ),
}
fields.List = append(fields.List, f)
file.Comments = append(file.Comments, cg)
}

I believe I have gotten it to work. As stated in my comment above, the main points required are:
Specifically set the buffer locations including the Slash and NamePos
Use token.File.AddLine to add new lines at specific offsets (calculated using the positions from item 1)
Overallocate the source buffer so token.File.Position (used by printer.Printer and token.File.Addline don't fail range checks on the source buffer
Code:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"testing"
)
func main() {
tests := []testing.InternalTest{{"TestAst", TestAst}}
matchAll := func(t string, pat string) (bool, error) { return true, nil }
testing.Main(matchAll, tests, nil, nil)
}
func TestAst(t *testing.T) {
source := `package a
// B comment
type B struct {
// C comment
C string
}`
buffer := make([]byte, 1024, 1024)
for idx,_ := range buffer {
buffer[idx] = 0x20
}
copy(buffer[:], source)
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", buffer, parser.ParseComments)
if err != nil {
t.Error(err)
}
v := &visitor{
file: file,
fset: fset,
}
ast.Walk(v, file)
var output []byte
buf := bytes.NewBuffer(output)
if err := printer.Fprint(buf, fset, file); err != nil {
t.Error(err)
}
expected := `package a
// B comment
type B struct {
// C comment
C string
// D comment
D int
// E comment
E float64
}
`
if buf.String() != expected {
t.Error(fmt.Sprintf("Test failed. Expected:\n%s\nGot:\n%s", expected, buf.String()))
}
}
type visitor struct {
file *ast.File
fset *token.FileSet
}
func (v *visitor) Visit(node ast.Node) (w ast.Visitor) {
if node == nil {
return v
}
switch n := node.(type) {
case *ast.GenDecl:
if n.Tok != token.TYPE {
break
}
ts := n.Specs[0].(*ast.TypeSpec)
if ts.Name.Name == "B" {
fields := ts.Type.(*ast.StructType).Fields
addStructField(v.fset, fields, v.file, "int", "D", "D comment")
addStructField(v.fset, fields, v.file, "float64", "E", "E comment")
}
}
return v
}
func addStructField(fset *token.FileSet, fields *ast.FieldList, file *ast.File, typ string, name string, comment string) {
prevField := fields.List[fields.NumFields()-1]
c := &ast.Comment{Text: fmt.Sprint("// ", comment), Slash: prevField.End() + 1}
cg := &ast.CommentGroup{List: []*ast.Comment{c}}
o := ast.NewObj(ast.Var, name)
f := &ast.Field{
Doc: cg,
Names: []*ast.Ident{&ast.Ident{Name: name, Obj: o, NamePos: cg.End() + 1}},
}
o.Decl = f
f.Type = &ast.Ident{Name: typ, NamePos: f.Names[0].End() + 1}
fset.File(c.End()).AddLine(int(c.End()))
fset.File(f.End()).AddLine(int(f.End()))
fields.List = append(fields.List, f)
file.Comments = append(file.Comments, cg)
}
Example: http://play.golang.org/p/_q1xh3giHm
For Item (3), it is also important to set all the overallocated bytes to spaces (0x20), so that the printer doesn't complain about null bytes when processing them.

I know that this answer might be a little late. But for the benefit of others, I found a reference to this library in the following GitHub issue
https://github.com/golang/go/issues/20744
The library is called dst and it can convert a go ast to dst and vice versa.
https://github.com/dave/dst
In ast, Comments are stored by their byte offset instead of attached to nodes. Dst solves this by attaching the comments to its respective nodes so that re-arranging nodes doesn't break the output/tree.
The library works as advertized and I haven't found any issues so far.
Note: There is also a subpackage called dst/dstutil which is compatible with golang.org/x/tools/go/ast/astutil

Related

Using regular expressions in Go to Identify a common pattern

I'm trying to parse this string goats=1\r\nalligators=false\r\ntext=works.
contents := "goats=1\r\nalligators=false\r\ntext=works"
compile, err := regexp.Compile("([^#\\s=]+)=([a-zA-Z0-9.]+)")
if err != nil {
return
}
matchString := compile.FindAllStringSubmatch(contents, -1)
my Output looks like [[goats=1 goats 1] [alligators=false alligators false] [text=works text works]]
What I'm I doing wrong in my expression to cause goats=1 to be valid too? I only want [[goats 1]...]
For another approach, you can use the strings package instead:
package main
import (
"fmt"
"strings"
)
func parse(s string) map[string]string {
m := make(map[string]string)
for _, kv := range strings.Split(s, "\r\n") {
a := strings.Split(kv, "=")
m[a[0]] = a[1]
}
return m
}
func main() {
m := parse("goats=1\r\nalligators=false\r\ntext=works")
fmt.Println(m) // map[alligators:false goats:1 text:works]
}
https://golang.org/pkg/strings

How to make a slice from a mapset.Set?

I'm reading Donovan's "The Go Programming Language" book and trying to implement an exercise which prints duplicate lines from several files and the files in which they occur:
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
mapset "github.com/deckarep/golang-set"
)
func main() {
counts := make(map[string]int)
occurrences := make(map[string]mapset.Set)
for _, filename := range os.Args[1:] {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
continue
}
for _, line := range strings.Split(string(data), "\n") {
counts[line]++
occurrences[line].Add(filename)
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\t%s\n", n, line, strings.Join(occurrences[line], ", "))
}
}
}
To accomplish the exercise, I've used the https://godoc.org/github.com/deckarep/golang-set package. However, I'm not sure how to print out the elements of the set joined by a ", ". With this code, I get a
./hello.go:23:30: first argument to append must be slice; have interface { Add(interface {}) bool; Cardinality() int; CartesianProduct(mapset.Set) mapset.Set; Clear(); Clone() mapset.Set; Contains(...interface {}) bool; Difference(mapset.Set) mapset.Set; Each(func(interface {}) bool); Equal(mapset.Set) bool; Intersect(mapset.Set) mapset.Set; IsProperSubset(mapset.Set) bool; IsProperSuperset(mapset.Set) bool; IsSubset(mapset.Set) bool; IsSuperset(mapset.Set) bool; Iter() <-chan interface {}; Iterator() *mapset.Iterator; Pop() interface {}; PowerSet() mapset.Set; Remove(interface {}); String() string; SymmetricDifference(mapset.Set) mapset.Set; ToSlice() []interface {}; Union(mapset.Set) mapset.Set }
./hello.go:28:64: cannot use occurrences[line] (type mapset.Set) as type []string in argument to strings.Join
I wasn't able to easily find out how to convert the Set to a slice though. Any idea how I might accomplish this?
The XY problem is asking about your attempted solution rather than your actual problem: The XY Problem.
The Go Programming Language by Alan A. A. Donovan and Brian W. Kernighan, Exercise 1.4 is designed to use Go maps.
For example,
// Modify dup3 to print the names of all files in which each duplicated line occurs.
package main
import (
"fmt"
"io/ioutil"
"os"
"strings"
)
func main() {
// counts = [line][file]count
counts := make(map[string]map[string]int)
for _, filename := range os.Args[1:] {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "Exercise 1.4: %v\n", err)
continue
}
for _, line := range strings.Split(string(data), "\n") {
files := counts[line]
if files == nil {
files = make(map[string]int)
counts[line] = files
}
files[filename]++
}
}
for line, files := range counts {
n := 0
for _, count := range files {
n += count
}
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
for name := range files {
fmt.Printf("%s\n", name)
}
}
}
}

How to use random arguments with the help of flags in Golang

I want the input from the console to be of anonymous parameters.
My current way of execution is
./app -infc=eth0 -ip=192.168.0.1
I don't want this as I need this app to be universal so that I can use it for other purposes as well.
I want the CLI to be like this
./app -firstparam={{infc},eth0} -secondparam={{ip},192.168.0.1}
So this should basically work by reading the two columns in the parameters.
So it should parse the parameters as a an internal key value pair
Need help on how to store each of the parameter as a key value pair and later use them individually
Here's a barebones example to give you an idea how to process os.Args
$ go run main.go --foo asdf --bar xxx --baz ccc
map[--foo:asdf --bar:xxx --baz:ccc]
jsandrew-Mac:osarg jsandrew$ cat main.go
package main
import (
"fmt"
"os"
)
func manyRandomArg() map[string]string {
rv := make(map[string]string)
for ix, x := range os.Args {
if x[:2] == "--" {
rv[x] = os.Args[ix+1]
}
}
return rv
}
func main() {
fmt.Printf("%v\n", manyRandomArg())
}
solved it thanks to #Vorsprung
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
)
var key0, key1, key2, key3, key4, filename string
var fileext = regexp.MustCompile(`([a-z]+)\.yaml`)
func manyRandomArg() map[string]string {
rv := make(map[string]string)
for ix, x := range os.Args {
if x[:2] == "--" {
rv[x] = os.Args[ix+1]
}
}
return rv
}
func main() {
fmt.Printf("\n%v\n", manyRandomArg())
readargs()
}
func readargs() {
rv := manyRandomArg()
keys := make([]string, 0, len(rv))
for key, _ := range rv {
keys = append(keys, key)
}
// Convert map to slice of values.
values := []string{}
for _, value := range rv {
values = append(values, value)
}
for keys, values := range rv {
fmt.Printf("key[%s] value[%s]\n", keys, values)
}
if fileext.MatchString(values[0]) {
fmt.Printf("Value %s\n", values[0])
filename = values[0]
} else if fileext.MatchString(values[1]) {
fmt.Printf("Value %s\n", values[1])
filename = values[1]
} else if fileext.MatchString(values[2]) {
fmt.Printf("Value %s\n", values[2])
filename = values[2]
} else if fileext.MatchString(values[3]) {
fmt.Printf("Value %s\n", values[3])
filename = values[3]
} else if fileext.MatchString(values[4]) {
fmt.Printf("Value %s\n", values[4])
filename = values[4]
} else {
log.Fatal("index 4 fail")
os.Exit(1)
}
b, err := ioutil.ReadFile(filename) // just pass the file name
if err != nil {
fmt.Print(err)
}
str := string(b) // convert content to a 'string'
key0 = trimLeftChars(keys[0], 2)
key1 = trimLeftChars(keys[1], 2)
key2 = trimLeftChars(keys[2], 2)
key3 = trimLeftChars(keys[3], 2)
key4 = trimLeftChars(keys[4], 2)
// Create replacer with pairs as arguments.
r := strings.NewReplacer(key0, values[0], key1, values[1], key2, values[2], key3, values[3], key4, values[4])
// Replace all pairs.
result := r.Replace(str)
fmt.Println(result)
newContents := []byte(result)
err = ioutil.WriteFile("new3.yaml", newContents, 0664)
if err != nil {
panic(err)
}
}
func trimLeftChars(s string, n int) string {
m := 0
for i := range s {
if m >= n {
return s[i:]
}
m++
}
return s[:0]
}

Is there an equivalent of os.Args() for functions?

To help debug GO programs, I want to write two generic functions that will be called on entry and exit, which will print the values of input and output parameters respectively:
printInputParameters(input ...interface{})
printOutputParameters(output ...interface{})
Is there an equivalent of os.Args() for functions? I looked at runtime package and didn't find such functions.
For example lets say I have two functions with different input parameters and output parameters
func f1(int i, float f) (e error) {
... some code here
}
func f2(s string, b []byte) (u uint64, e error) {
.. some code here
}
I want to be able to do the following
func f1(int i, float f) (e error) {
printInputparameters( ? )
defer func() {
printOutputParameters( ? )
}()
... some code here
}
func f2(s string, b []byte) (u uint64, e error) {
printInputparameters( ? )
defer func() {
printOutputParameters( ? )
}()
... some code here
}
You cannot do this in Go since there is no way you can get the stack frame of the currently active function in the current goroutine. It is not impossible to do this as I'll show further below but the problem is that there is no public API to get this done reliably. That it can be done can be seen in the stack traces printed when a panic is raised: all values on the stack are dumped in that case.
Should you be interested in how the stack trace is actually generated then have a look at genstacktrace in the runtime package.
As for a solution to your problem, you can the source code parsing route as already suggested. If you feel adventurous, you can parse the stack trace provided by runtime.Stack. But beware, there are so many drawbacks that you will quickly realize that any solution is better than this one.
To parse the stack trace, just get the line of the previously called function (from the viewpoint of printInputParameters), get the name of that function and parse the parameter values according to the parameter types provided by reflection. Some examples of stack trace outputs of various function invocations:
main.Test1(0x2) // Test1(int64(2))
main.Test1(0xc820043ed5, 0x3, 0x3) // Test1([]byte{'A','B','C'})
main.Test1(0x513350, 0x4) // Test1("AAAA")
You can see that complex types (those which do not fit into a register) may use more than one 'parameter'. A string for example is a pointer to the data and the length. So you have to use the unsafe package to access these pointers and reflection to create values from this data.
If you want to try yourself, here's some example code:
import (
"fmt"
"math"
"reflect"
"runtime"
"strconv"
"strings"
"unsafe"
)
// Parses the second call's parameters in a stack trace of the form:
//
// goroutine 1 [running]:
// main.printInputs(0x4c4c60, 0x539038)
// /.../go/src/debug/main.go:16 +0xe0
// main.Test1(0x2)
// /.../go/src/debug/main.go:23
//
func parseParams(st string) (string, []uintptr) {
line := 1
start, stop := 0, 0
for i, c := range st {
if c == '\n' {
line++
}
if line == 4 && c == '\n' {
start = i + 1
}
if line == 5 && c == '\n' {
stop = i
}
}
call := st[start:stop]
fname := call[0:strings.IndexByte(call, '(')]
param := call[strings.IndexByte(call, '(')+1 : strings.IndexByte(call, ')')]
params := strings.Split(param, ", ")
parsedParams := make([]uintptr, len(params))
for i := range params {
iv, err := strconv.ParseInt(params[i], 0, 64)
if err != nil {
panic(err.Error())
}
parsedParams[i] = uintptr(iv)
}
return fname, parsedParams
}
func fromAddress(t reflect.Type, addr uintptr) reflect.Value {
return reflect.NewAt(t, unsafe.Pointer(&addr)).Elem()
}
func printInputs(fn interface{}) {
v := reflect.ValueOf(fn)
vt := v.Type()
b := make([]byte, 500)
if v.Kind() != reflect.Func {
return
}
runtime.Stack(b, false)
name, params := parseParams(string(b))
pidx := 0
fmt.Print(name + "(")
for i := 0; i < vt.NumIn(); i++ {
t := vt.In(i)
switch t.Kind() {
case reflect.Int64:
case reflect.Int:
// Just use the value from the stack
fmt.Print(params[pidx], ",")
pidx++
case reflect.Float64:
fmt.Print(math.Float64frombits(uint64(params[pidx])), ",")
pidx++
case reflect.Slice:
// create []T pointing to slice content
data := reflect.ArrayOf(int(params[pidx+2]), t.Elem())
svp := reflect.NewAt(data, unsafe.Pointer(params[pidx]))
fmt.Printf("%v,", svp.Elem())
pidx += 3
case reflect.String:
sv := fromAddress(t, params[pidx])
fmt.Printf("%v,", sv)
pidx += 2
case reflect.Map:
// points to hmap struct
mv := fromAddress(t,params[pidx])
fmt.Printf("%v,", mv)
pidx++
} /* switch */
}
fmt.Println(")")
}
Test:
func Test1(in int, b []byte, in2 int, m string) {
printInputs(Test1)
}
func main() {
b := []byte{'A', 'B', 'C'}
s := "AAAA"
Test1(2, b, 9, s)
}
Output:
main.Test1(2,[65 66 67],9,"AAAA",)
A slightly advanced version of this can be found on github:
go get github.com/githubnemo/pdump
To generically print your functions' arguments, you can do this:
func printInputParameters(input ...interface{}) {
fmt.Printf("Args: %v", input)
}
printInputParameters is a variadic function, and input is of type []interface{}.

Reading bytes into structs using reflection

I'm trying to write functions that will allow me to marshal/unmarshal simple structs into byte arrays. I've succeeded in writing Marshal, with help from the kind folks at #go-nuts, but I'm running into trouble writing Unmarshal.
// Unmarshal unpacks the binary data and stores it in the packet using
// reflection.
func Unmarshal(b []byte, t reflect.Type) (pkt interface{}, err error) {
buf := bytes.NewBuffer(b)
p := reflect.New(t)
v := reflect.ValueOf(p)
for i := 0; i < t.NumField(); i++ {
f := v.Field(i)
switch f.Kind() {
case reflect.String:
// length of string
var l int16
var e error
e = binary.Read(buf, binary.BigEndian, &l)
if e != nil {
err = e
return
}
// read length-of-string bytes from the buffer
raw := make([]byte, l)
_, e = buf.Read(raw)
if e != nil {
err = e
return
}
// convert the bytes to a string
f.SetString(bytes.NewBuffer(raw).String())
default:
e := binary.Read(buf, binary.BigEndian, f.Addr())
if e != nil {
err = e
return
}
}
}
pkt = p
return
}
The problem with the code above is that the call to f.Addr() near the end is apparently trying to get the address of an unaddressable value.
If there is an alternative solution, I would appreciate that as well. Either way, any help would be much appreciated.
Thanks!
I think you should use
v := p.Elem() // Get the value that 'p' points to
instead of
v := reflect.ValueOf(p)
Working example with lots of assumptions and a trivial data format:
package main
import (
"fmt"
"reflect"
"strconv"
)
// example marshalled format. lets say that marshalled data will have
// four bytes of a formatted floating point number followed by two more
// printable bytes.
type m42 []byte
// example struct we'd like to unmarshal into.
type packet struct {
S string // exported fields required for reflection
F float64
}
// example usage
func main() {
var p packet
if err := Unmarshal(m42("3.14Pi"), &p); err == nil {
fmt.Println(p)
} else {
fmt.Println(err)
}
}
func Unmarshal(data m42, structPtr interface{}) error {
vp := reflect.ValueOf(structPtr)
ve := vp.Elem() // settable struct Value
vt := ve.Type() // type info for struct
nStructFields := ve.NumField()
for i := 0; i < nStructFields; i++ {
fv := ve.Field(i) // settable field Value
sf := vt.Field(i) // StructField type information
// struct field name indicates which m42 field to unmarshal.
switch sf.Name {
case "S":
fv.SetString(string(data[4:6]))
case "F":
s := string(data[0:4])
if n, err := strconv.ParseFloat(s, 64); err == nil {
fv.SetFloat(n)
} else {
return err
}
}
}
return nil
}
Appropriate alternative solutions would depend heavily on the real data you need to support.
I'm going to bet that the reason f.Addr() has the problem because it actually isn't addressable.
the reflect package Type object has a method that will tell you if the type is addressable called CanAddr(). Assuming the field is addressable if it's not a string is not always true. If the struct is not passed in as a pointer to a struct then it's fields won't be addressable. For more details about what is and isn't addressable see: http://weekly.golang.org/pkg/reflect/#Value.CanAddr which outlines the correct rules.
Essentially for your code to work I think you need to ensure you always call it with a pointer to a struct.

Resources