-
Notifications
You must be signed in to change notification settings - Fork 307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Calling *CompiledFunction from Go #275
Comments
Hi, it is not possible to call |
Hey, thanks for the reply. What I'm trying to do is essentially use Tengo functions as callbacks. So the user provides a script with a few named functions, which the Go code will call into when certain things happen. |
Here is a simple snippet for you I hope it helps; package main
import (
"log"
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
)
func main() {
src := `
text := import("text")
m := {
contains: func(args) {
return text.contains(args.str, args.substr)
}
}
out := undefined
if f := m[argsMap.function]; !is_undefined(f) {
out = f(argsMap)
} else {
out = error("unknown function")
}
`
script := tengo.NewScript([]byte(src))
script.SetImports(stdlib.GetModuleMap("text"))
argsMap := map[string]interface{}{
"function": "contains", "str": "foo bar", "substr": "bar"}
if err := script.Add("argsMap", argsMap); err != nil {
log.Fatal(err)
}
c, err := script.Run()
if err != nil {
log.Fatal(err)
}
out := c.Get("out")
if err := out.Error(); err != nil {
log.Fatal(err)
}
// If it returns a dynamic type use type switch using out.Value()
log.Println("result =", out.Bool())
} OR you can create a script for each function. |
Thanks Ozan, that looks like a decent solution. My only concern is that I'm planning to have lots of these scripts, and this extra boilerplate around each of them would harm readability. I've hacked together a solution which pokes the correct sequence of instructions into a I'd be fine with keeping this code on a private branch for my purposes (assuming it works in practice), but I wonder if you have any thoughts on this approach and if something similar may be accepted in a PR? |
Hi Tom, thank you for sharing your work. It will be hard for you to keep sync with upstream repository, I guess. I studied Bytecode today and created an example, which has some code from your hack and my Go love :). Code is not tested! I used main script to define functions not source module. Return value of called function is set to a global variable instead of accessing VM.stack. const mathFunctions = `
mul := func(a, b) {
return a * b
}
square := func(a) {
return mul(a, a)
}
add := func(a, b) {
return a + b
}
`
func main() {
s := NewScript()
if err := s.Compile([]byte(mathFunctions)); err != nil {
panic(err)
}
v, err := s.Call("add", 1, 2)
if err != nil {
panic(err)
}
fmt.Println(v)
v, err = s.Call("square", 11)
if err != nil {
panic(err)
}
fmt.Println(v)
v, err = s.Call("mul", 6, 6)
if err != nil {
panic(err)
}
fmt.Println(v)
} |
Nice! That looks perfect. I'll pull this into my local branch and have a play around with it tomorrow, thank you. Perhaps this could be extended to support functions passed into Go from Tengo as well? Something like this:
And then have the Go side call that func at a later time? I believe the first operand to |
I just revised the gist (rev3), I forgot to use only new instructions instead of adding to original bytecode. |
Thanks again Ozan, I've integrated that with my code and it seems to work perfectly. I've extended it a bit to support modules, and to pull in the It seems to work fine with closures too, at least in my trivial test case. Here's my final code: https://gist.github.com/tgascoigne/f8d6c6538a5841bcb5f135668279b93b Many thanks for all your help! |
Thanks Tom, it is excellent. Everything works as expected with all features, why not. We only run new instructions with current constants and globals which looks harmless. Tengo functions can easily be called. It would be better if it did not run script once to get compiled functions from globals but anyway it works, may be later we can figure out how to get them easily from constants. If you allow me, I will create a new open source repo for this module and try to improve it in time using the final draft. |
I think compiling and running the script ahead of time is the right thing to do - without it, I think things like non-constant globals would not be initialized correctly when referred to by functions. In my case, the overhead from doing so is not an issue as I'm storing the compiled result and reusing it multiple times.
Please do :) |
Yes you are right keeping as it is is only solution. After adding context support, interface{} conversion wrappers and documentation, couple of days later it will be released :). Finally, I can call tengo functions from my rule engine! |
Sounds like there were some meaningful outcomes here! Sorry that I missed the discussions, but please let me know if there's anything else I can do. |
@d5 I created a repo for calling Tengo functions from Go with @tgascoigne . Code is mostly ported from Tengo script.go file. Please have a look at https://github.com/ozanh/tengox. We need your expertise. Docs and examples are still missing, but tests look good. |
tengox is brilliant! 😄 Good job! One thing I'd suggest is that you make them more portable or detached. A similar example would be the relationship between Another thing we can consider is to bring that concept back to Just a thought. |
I'd love to see this included in Tengo, as there's a good chunk of code that had to be duplicated to make this work. If we were to change the API, there's a few qualities of Ozan's implementation I'd like to preserve:
|
@tgascoigne we need some time to integrate it in tengo, library must be mature enough to propose any changes. Although we can easily integrate this implementation to tengo passing callbacks to interfere compilation and run processes to change current behavior, we need some time. We can change the
scr.Add("pass", &tengo.UserFunction{
Value: func(args ...tengo.Object) (tengo.Object, error) {
_, _, = scr.MakeCallback(args[0]).Call()
return tengo.UndefinedValue, nil
},
}) Above usage looks idiomatic but pauses VM indefinitely due to mutex. We can return a promise object or a channel I don't know may be I am missing something but Murphy's law, it can happen. Please guide me. Edit I created a new branch, please check this out |
Any advance on this issue? Would love to be able to execute Tengo functions from Go. |
PoC (I don't know any bugs, down-sides - didn't test in production, event not on sandbox) here: #372 I made this, because this repo looks more active than https://github.com/ozanh/ugo and those 2 projects are best in class for Go scripting which I need for new clients. |
I'm playing around to implement games with go and I really feel in love with allowing to attach tengo scripts to the game objects. I also have a event system, which I want to be able to be used from within a tengo script. I was think something like this: init := func(node) {
node.bind("myevent", func(e) {
fmt.println(e)
})
node.x = ...
}
update := func(node) {
....
}
draw := func(node) {
node.image.fillRect(...)
} I realize this ticket is quite old, but was anything done to the tengo core to make it possible to work with callbacks? great work! |
I'm trying to load a script and then call a tengo function by name. I'm currently doing something like this:
It looks like
fn
is a*CompiledFunction
. ItsCanCall
method returns true, but it doesn't appear to actually implement theCall
function, only via its embedding ofObjectImpl
.Is it possible to call into a
*CompiledFunction
from Go?The text was updated successfully, but these errors were encountered: