123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- /*An example to demonstrate an alternative way to run tengo functions from go.
- */
- package main
- import (
- "container/list"
- "context"
- "errors"
- "fmt"
- "math/rand"
- "sync"
- "time"
- "github.com/d5/tengo/v2"
- )
- // CallArgs holds function name to be executed and its required parameters with
- // a channel to listen result of function.
- type CallArgs struct {
- Func string
- Params []tengo.Object
- Result chan<- tengo.Object
- }
- // NewGoProxy creates GoProxy object.
- func NewGoProxy(ctx context.Context) *GoProxy {
- mod := new(GoProxy)
- mod.ctx = ctx
- mod.callbacks = make(map[string]tengo.Object)
- mod.callChan = make(chan *CallArgs, 1)
- mod.moduleMap = map[string]tengo.Object{
- "next": &tengo.UserFunction{Value: mod.next},
- "register": &tengo.UserFunction{Value: mod.register},
- "args": &tengo.UserFunction{Value: mod.args},
- }
- mod.tasks = list.New()
- return mod
- }
- // GoProxy is a builtin tengo module to register tengo functions and run them.
- type GoProxy struct {
- tengo.ObjectImpl
- ctx context.Context
- moduleMap map[string]tengo.Object
- callbacks map[string]tengo.Object
- callChan chan *CallArgs
- tasks *list.List
- mtx sync.Mutex
- }
- // TypeName returns type name.
- func (mod *GoProxy) TypeName() string {
- return "GoProxy"
- }
- func (mod *GoProxy) String() string {
- m := tengo.ImmutableMap{Value: mod.moduleMap}
- return m.String()
- }
- // ModuleMap returns a map to add a builtin tengo module.
- func (mod *GoProxy) ModuleMap() map[string]tengo.Object {
- return mod.moduleMap
- }
- // CallChan returns call channel which expects arguments to run a tengo
- // function.
- func (mod *GoProxy) CallChan() chan<- *CallArgs {
- return mod.callChan
- }
- func (mod *GoProxy) next(args ...tengo.Object) (tengo.Object, error) {
- mod.mtx.Lock()
- defer mod.mtx.Unlock()
- select {
- case <-mod.ctx.Done():
- return tengo.FalseValue, nil
- case args := <-mod.callChan:
- if args != nil {
- mod.tasks.PushBack(args)
- }
- return tengo.TrueValue, nil
- }
- }
- func (mod *GoProxy) register(args ...tengo.Object) (tengo.Object, error) {
- if len(args) == 0 {
- return nil, tengo.ErrWrongNumArguments
- }
- mod.mtx.Lock()
- defer mod.mtx.Unlock()
- switch v := args[0].(type) {
- case *tengo.Map:
- mod.callbacks = v.Value
- case *tengo.ImmutableMap:
- mod.callbacks = v.Value
- default:
- return nil, tengo.ErrInvalidArgumentType{
- Name: "first",
- Expected: "map",
- Found: args[0].TypeName(),
- }
- }
- return tengo.UndefinedValue, nil
- }
- func (mod *GoProxy) args(args ...tengo.Object) (tengo.Object, error) {
- mod.mtx.Lock()
- defer mod.mtx.Unlock()
- if mod.tasks.Len() == 0 {
- return tengo.UndefinedValue, nil
- }
- el := mod.tasks.Front()
- callArgs, ok := el.Value.(*CallArgs)
- if !ok || callArgs == nil {
- return nil, errors.New("invalid call arguments")
- }
- mod.tasks.Remove(el)
- f, ok := mod.callbacks[callArgs.Func]
- if !ok {
- return tengo.UndefinedValue, nil
- }
- compiledFunc, ok := f.(*tengo.CompiledFunction)
- if !ok {
- return tengo.UndefinedValue, nil
- }
- params := callArgs.Params
- if params == nil {
- params = make([]tengo.Object, 0)
- }
- // callable.VarArgs implementation is omitted.
- return &tengo.ImmutableMap{
- Value: map[string]tengo.Object{
- "result": &tengo.UserFunction{
- Value: func(args ...tengo.Object) (tengo.Object, error) {
- if len(args) > 0 {
- callArgs.Result <- args[0]
- return tengo.UndefinedValue, nil
- }
- callArgs.Result <- &tengo.Error{
- Value: &tengo.String{
- Value: tengo.ErrWrongNumArguments.Error()},
- }
- return tengo.UndefinedValue, nil
- }},
- "num_params": &tengo.Int{Value: int64(compiledFunc.NumParameters)},
- "callable": compiledFunc,
- "params": &tengo.Array{Value: params},
- },
- }, nil
- }
- // ProxySource is a tengo script to handle bidirectional arguments flow between
- // go and pure tengo functions. Note: you should add more if conditions for
- // different number of parameters.
- // TODO: handle variadic functions.
- var ProxySource = `
- export func(args) {
- if is_undefined(args) {
- return
- }
- callable := args.callable
- if is_undefined(callable) {
- return
- }
- result := args.result
- num_params := args.num_params
- v := undefined
- // add more else if conditions for different number of parameters.
- if num_params == 0 {
- v = callable()
- } else if num_params == 1 {
- v = callable(args.params[0])
- } else if num_params == 2 {
- v = callable(args.params[0], args.params[1])
- } else if num_params == 3 {
- v = callable(args.params[0], args.params[1], args.params[2])
- }
- result(v)
- }
- `
- func main() {
- src := `
- // goproxy and proxy must be imported.
- goproxy := import("goproxy")
- proxy := import("proxy")
-
- global := 0
-
- callbacks := {
- sum: func(a, b) {
- return a + b
- },
- multiply: func(a, b) {
- return a * b
- },
- increment: func() {
- global++
- return global
- }
- }
-
- // Register callbacks to call them in goproxy loop.
- goproxy.register(callbacks)
-
- // goproxy loop waits for new call requests and run them with the help of
- // "proxy" source module. Cancelling the context breaks the loop.
- for goproxy.next() {
- proxy(goproxy.args())
- }
- `
- // 5 seconds context timeout is enough for an example.
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- script := tengo.NewScript([]byte(src))
- moduleMap := tengo.NewModuleMap()
- goproxy := NewGoProxy(ctx)
- // register modules
- moduleMap.AddBuiltinModule("goproxy", goproxy.ModuleMap())
- moduleMap.AddSourceModule("proxy", []byte(ProxySource))
- script.SetImports(moduleMap)
- compiled, err := script.Compile()
- if err != nil {
- panic(err)
- }
- // call "sum", "multiply", "increment" functions from tengo in a new goroutine
- go func() {
- callChan := goproxy.CallChan()
- result := make(chan tengo.Object, 1)
- // TODO: check tengo error from result channel.
- loop:
- for {
- select {
- case <-ctx.Done():
- break loop
- default:
- }
- fmt.Println("Calling tengo sum function")
- i1, i2 := rand.Int63n(100), rand.Int63n(100)
- callChan <- &CallArgs{Func: "sum",
- Params: []tengo.Object{&tengo.Int{Value: i1},
- &tengo.Int{Value: i2}},
- Result: result,
- }
- v := <-result
- fmt.Printf("%d + %d = %v\n", i1, i2, v)
- fmt.Println("Calling tengo multiply function")
- i1, i2 = rand.Int63n(20), rand.Int63n(20)
- callChan <- &CallArgs{Func: "multiply",
- Params: []tengo.Object{&tengo.Int{Value: i1},
- &tengo.Int{Value: i2}},
- Result: result,
- }
- v = <-result
- fmt.Printf("%d * %d = %v\n", i1, i2, v)
- fmt.Println("Calling tengo increment function")
- callChan <- &CallArgs{Func: "increment", Result: result}
- v = <-result
- fmt.Printf("increment = %v\n", v)
- time.Sleep(1 * time.Second)
- }
- }()
- if err := compiled.RunContext(ctx); err != nil {
- fmt.Println(err)
- }
- }
|