1package reflectsource
2
3import (
4	"bytes"
5	"fmt"
6	"go/ast"
7	"io/ioutil"
8	"runtime"
9	"strings"
10
11	"github.com/shurcooL/go/parserutil"
12	"github.com/shurcooL/go/printerutil"
13	"github.com/shurcooL/go/reflectfind"
14)
15
16// GetParentFuncAsString gets the parent func as a string.
17func GetParentFuncAsString() string {
18	// TODO: Replace use of debug.Stack() with direct use of runtime package...
19	// TODO: Use runtime.FuncForPC(runtime.Caller()).Name() to get func name if source code not found.
20	stack := string(stack())
21
22	funcName := getLine(stack, 3)
23	funcName = funcName[1:strings.Index(funcName, ": ")]
24	if dotPos := strings.LastIndex(funcName, "."); dotPos != -1 { // Trim package prefix.
25		funcName = funcName[dotPos+1:]
26	}
27
28	funcArgs := getLine(stack, 5)
29	funcArgs = funcArgs[strings.Index(funcArgs, ": ")+len(": "):]
30	funcArgs = funcArgs[strings.Index(funcArgs, "(") : strings.LastIndex(funcArgs, ")")+len(")")] // TODO: This may fail if there are 2+ func calls on one line.
31
32	return funcName + funcArgs
33}
34
35// GetParentFuncArgsAsString gets the parent func with its args as a string.
36func GetParentFuncArgsAsString(args ...interface{}) string {
37	// TODO: Replace use of debug.Stack() with direct use of runtime package...
38	// TODO: Use runtime.FuncForPC(runtime.Caller()).Name() to get func name if source code not found.
39	stack := string(stack())
40
41	funcName := getLine(stack, 3)
42	funcName = funcName[1:strings.Index(funcName, ": ")]
43	if dotPos := strings.LastIndex(funcName, "."); dotPos != -1 { // Trim package prefix.
44		funcName = funcName[dotPos+1:]
45	}
46
47	funcArgs := "("
48	for i, arg := range args {
49		// TODO: Add arg names. Maybe not?
50		if i != 0 {
51			funcArgs += ", "
52		}
53		funcArgs += fmt.Sprintf("%#v", arg) // TODO: Maybe use goon instead. Need to move elsewhere to avoid import cycle.
54	}
55	funcArgs += ")"
56
57	return funcName + funcArgs
58}
59
60// GetExprAsString gets the expression as a string.
61func GetExprAsString(_ interface{}) string {
62	return GetParentArgExprAsString(0)
63}
64
65func getParent2ArgExprAllAsAst() []ast.Expr {
66	// TODO: Replace use of debug.Stack() with direct use of runtime package...
67	stack := string(stack())
68
69	// TODO: Bounds error checking, get rid of GetLine gists, etc.
70	parentName := getLine(stack, 5)
71	if !strings.Contains(parentName, ": ") {
72		// TODO: This happens when source file isn't present in same location as when built. See if can do anything better
73		//       via direct use of runtime package (instead of debug.Stack(), which will exclude any func names)...
74		return nil
75	}
76	parentName = parentName[1:strings.Index(parentName, ": ")]
77	if dotPos := strings.LastIndex(parentName, "."); dotPos != -1 { // Trim package prefix.
78		parentName = parentName[dotPos+1:]
79	}
80
81	str := getLine(stack, 7)
82	str = str[strings.Index(str, ": ")+len(": "):]
83	p, err := parserutil.ParseStmt(str)
84	if err != nil {
85		return nil
86	}
87
88	innerQuery := func(i interface{}) bool {
89		if ident, ok := i.(*ast.Ident); ok && ident.Name == parentName {
90			return true
91		}
92		return false
93	}
94
95	query := func(i interface{}) bool {
96		if c, ok := i.(*ast.CallExpr); ok && nil != reflectfind.First(c.Fun, innerQuery) {
97			return true
98		}
99		return false
100	}
101	callExpr, _ := reflectfind.First(p, query).(*ast.CallExpr)
102
103	if callExpr == nil {
104		return nil
105	}
106	return callExpr.Args
107}
108
109// GetParentArgExprAsString gets the argIndex argument expression of parent func call as a string.
110func GetParentArgExprAsString(argIndex uint32) string {
111	args := getParent2ArgExprAllAsAst()
112	if args == nil {
113		return "<expr not found>"
114	}
115	if argIndex >= uint32(len(args)) {
116		return "<out of range>"
117	}
118
119	return printerutil.SprintAstBare(args[argIndex])
120}
121
122// GetParentArgExprAllAsString gets all argument expressions of parent func call as a string.
123func GetParentArgExprAllAsString() []string {
124	args := getParent2ArgExprAllAsAst()
125	if args == nil {
126		return nil
127	}
128
129	out := make([]string, len(args))
130	for i := range args {
131		out[i] = printerutil.SprintAstBare(args[i])
132	}
133	return out
134}
135
136func getMySecondArgExprAsString(int, int) string {
137	return GetParentArgExprAsString(1)
138}
139
140func getLine(s string, lineIndex int) string {
141	return strings.Split(s, "\n")[lineIndex]
142}
143
144var (
145	dunno     = []byte("???")
146	centerDot = []byte("·")
147	dot       = []byte(".")
148	slash     = []byte("/")
149)
150
151// stack returns a formatted stack trace of the goroutine that calls it.
152// For each routine, it includes the source line information and PC value,
153// then attempts to discover, for Go functions, the calling function or
154// method and the text of the line containing the invocation.
155//
156// It was deprecated in Go 1.5, suggested to use package runtime's Stack instead,
157// and replaced by another implementation in Go 1.6.
158//
159// stack implements the Go 1.5 version of debug.Stack(), skipping 1 frame,
160// instead of 2, since it's being called directly (rather than via debug.Stack()).
161func stack() []byte {
162	buf := new(bytes.Buffer) // the returned data
163	// As we loop, we open files and read them. These variables record the currently
164	// loaded file.
165	var lines [][]byte
166	var lastFile string
167	for i := 1; ; i++ { // Caller we care about is the user, 1 frame up
168		pc, file, line, ok := runtime.Caller(i)
169		if !ok {
170			break
171		}
172		// Print this much at least.  If we can't find the source, it won't show.
173		fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
174		if file != lastFile {
175			data, err := ioutil.ReadFile(file)
176			if err != nil {
177				continue
178			}
179			lines = bytes.Split(data, []byte{'\n'})
180			lastFile = file
181		}
182		line-- // in stack trace, lines are 1-indexed but our array is 0-indexed
183		fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
184	}
185	return buf.Bytes()
186}
187
188// source returns a space-trimmed slice of the n'th line.
189func source(lines [][]byte, n int) []byte {
190	if n < 0 || n >= len(lines) {
191		return dunno
192	}
193	return bytes.Trim(lines[n], " \t")
194}
195
196// function returns, if possible, the name of the function containing the PC.
197func function(pc uintptr) []byte {
198	fn := runtime.FuncForPC(pc)
199	if fn == nil {
200		return dunno
201	}
202	name := []byte(fn.Name())
203	// The name includes the path name to the package, which is unnecessary
204	// since the file name is already included.  Plus, it has center dots.
205	// That is, we see
206	// 	runtime/debug.*T·ptrmethod
207	// and want
208	// 	*T.ptrmethod
209	// Since the package path might contains dots (e.g. code.google.com/...),
210	// we first remove the path prefix if there is one.
211	if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
212		name = name[lastslash+1:]
213	}
214	if period := bytes.Index(name, dot); period >= 0 {
215		name = name[period+1:]
216	}
217	name = bytes.Replace(name, centerDot, dot, -1)
218	return name
219}
220