traefik / yaegi

Yaegi is Another Elegant Go Interpreter
https://pkg.go.dev/github.com/traefik/yaegi
Apache License 2.0
6.78k stars 341 forks source link

Change the stdout dynamically within the same interpreter instance? #1631

Closed rcoreilly closed 1 month ago

rcoreilly commented 1 month ago

Proposal

It would be nice if we could change the default stdout etc used by fmt.Print* within the same running interpreter instance. For example we're writing a shell using yaegi (which is working really well and would not have been possible without this amazing package!), and we want to be able to dynamically redirect output based on standard shell > kind of syntax.

Background

In fixStdlib in interp/use.go, it looks like you replace these methods with functions that use local copies of the interp.std* variables. Thus, even if you had a way of updating those interp.std* values, it wouldn't actually update the Print* functions, which seem to be permanently tied to these initial values.

Thus, one simple change would be to change this code to use interp.std* and then add a SetStdIO or similar such method that sets those values. Or a SetOptions to use the Options values to set them, but that might be more problematic in terms of making the new settings take effect everywhere.

e.g., the new code would be, on line 168:

    p["Print"] = reflect.ValueOf(func(a ...interface{}) (n int, err error) { return fmt.Fprint(interp.stdout, a...) })

would you accept a PR along these lines? Any thoughts about the SetStdIO function name? Separate SetStdout etc instead?

Workarounds

for now, we will try to revert the "fixed" mapTypes to use the original os.Stdout functions so we can control by changing os.Stdout.

rcoreilly commented 1 month ago

Unfortunately, it looks like the Use function always calls fixStdlib so it is impossible to overcome this problem.

rcoreilly commented 1 month ago

Sorry for the churn here: the proper workaround here is to give yaegi a set of wrappers that we can then redirect ourselves.

ldez commented 1 month ago

Hello, have you tried interp.Options.Stdout/interp.Options.Stderr?

https://github.com/traefik/yaegi/blob/381e045966b079ca0ac364ba111fcb603226a74b/interp/interp.go#L301-L304

rcoreilly commented 1 month ago

Yes, but I need to be able to change those after creating the interpreter!

ldez commented 1 month ago

You can use a writer wrapper, something like that:

https://go.dev/play/p/sxFkJGLgmH1

You wrap the writer inside another writer and you can change the writer when you want. But take care of a concurrency problem, a lock can be useful.

ldez commented 1 month ago

After more thinking, your suggestion can lead to concurrency problems. I think the best approach is the writer wrapper with a lock inside. WDTY?

rcoreilly commented 1 month ago

Yep that sounds good -- I implemented the wrapper and it works well. It seems that some writers are already concurrency-safe and others are not, so a lock is a good idea. I will go ahead and close this issue and anyone else looking for this functionality could presumably find this ticket. You could also maybe add a note in the relevant docs at some point.. :)