add interoperability example (#266)
This commit is contained in:
parent
3b53bc4f6d
commit
d8b50efec5
1 changed files with 274 additions and 0 deletions
274
examples/interoperability/main.go
Normal file
274
examples/interoperability/main.go
Normal file
|
@ -0,0 +1,274 @@
|
|||
/*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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue