Getting the source of anon func as a string?

241 views
Skip to first unread message

Dmitri Shuralyov

unread,
Sep 2, 2013, 5:57:49 AM9/2/13
to golan...@googlegroups.com
Say I have a button with a func() var for the press action.

button.action = func() { cmd.Process.Stop() }

I'd like to be able to get the anon func's source as a string so I can have it in a tooltip or something, in case I forget what the button does.

var actionSrc string
actionSrc := GetSourceAsString(button.action)
fmt.Printf("%q", actionSrc) // "func() { cmd.Process.Stop() }"

Any thoughts about how this can be done (other than manually copying the source of the anon func into a string)? From what I'm seeing "reflect" pkg can't help here, can it?

I'm currently thinking of parsing the program's source code and figuring it from there. runtime/debug's Stack() can be used to figure out the source code path. But the fact it's an anon func and not a named func in global scope makes getting its source from the AST somewhat trickier.

Kyle Lemons

unread,
Sep 2, 2013, 12:52:42 PM9/2/13
to Dmitri Shuralyov, golang-nuts
There is no easy way to do what you ask.  My suggestion is to redefine action to be a `type ButtonAction struct { Tooltip string; Action func() }`.  I wouldn't copy/paste the source code, I'd just describe it.

button.action = ButtonAction{"Stop the process", cmd.Process.Stop}


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Dmitri Shuralyov

unread,
Sep 2, 2013, 9:37:10 PM9/2/13
to golan...@googlegroups.com
Kyle, thanks, that's a very practical alternative solution, and anyone in their right mind would just do that.

However, for the sake of solving a fun challenge and being able to do something that I am currently unable to do, I'd like to try and solve the original problem anyway.

Some leads I got so far:

1. Parsing the .go file where the func is defined can give me the AST, and the anon func will be one of the *ast.FuncLit types. It might be tricky to figure out which one it is, if there are many of them and the assignment logic isn't trivial.

2. If I were to execute the anon func, it's possible to get its pc (program counter). e.g. func() { pc, _, _, _ := runtime.Caller(0); println(pc); ... } Then you can do file, line := runtime.FuncForPC(pc).FileLine(pc) to get the line where the func is defined. This can help greatly with finding the correct *ast.FuncLit in the AST.

Is there any way to find the pc of a given func entry point without resorting to calling it and doing runtime.Caller(0)?

Steven Blenkinsop

unread,
Sep 3, 2013, 2:52:29 AM9/3/13
to Dmitri Shuralyov, golan...@googlegroups.com
You can use reflect.Value.Pointer, but it won't necessarily work, specifically if your function value comes from a method expression.

Dmitri Shuralyov

unread,
Sep 3, 2013, 3:26:37 AM9/3/13
to golan...@googlegroups.com, Dmitri Shuralyov
Incredible! Thanks Steven, somehow I looked over Pointer() in "reflect" package.

It seems to do exactly what I want! Now I'm very close. I know which file and line the func literal is on. So unless there are more than 1 func declarations on the same line (not possible with gofmt'ed code anyway), it should be fairly straightforward to get it now! :D


Thank you again!
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.

Gerard

unread,
Sep 3, 2013, 3:49:39 AM9/3/13
to golan...@googlegroups.com, Dmitri Shuralyov
This might also solve the problem (link). The goplay doesn't work because of the unsafe package.

Dmitri Shuralyov

unread,
Sep 5, 2013, 1:37:18 AM9/5/13
to golan...@googlegroups.com, Dmitri Shuralyov
Woohoo! I've been able to solve my original question and get the GetSourceAsString() func working! :D

Thanks to people who've replied, it wouldn't be possible without your help. Especially Steven's tip.

It will work only if the source file where the func was declared is present (and readable, which rules out this running on go playground, sadly; ioutil.ReadFile() of the source code file results in no permission error there). That's good enough for now.

Here's the working source:

https://gist.github.com/shurcooL/6418462
(GetLineStartEndIndicies() is defined here; FindFirst() here, SprintAst() here)

If you run the main() there (requires changing package name to "main"), the output is:

func() {
println("Hello from anon func!")
}

Here's a screenshot of it in action in my app... Now I won't forget what that temporary unlabelled button does hehe.


Thanks again!
Reply all
Reply to author
Forward
0 new messages