main.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*An example to demonstrate an alternative way to run tengo functions from go.
  2. */
  3. package main
  4. import (
  5. "container/list"
  6. "context"
  7. "errors"
  8. "fmt"
  9. "math/rand"
  10. "sync"
  11. "time"
  12. "github.com/d5/tengo/v2"
  13. )
  14. // CallArgs holds function name to be executed and its required parameters with
  15. // a channel to listen result of function.
  16. type CallArgs struct {
  17. Func string
  18. Params []tengo.Object
  19. Result chan<- tengo.Object
  20. }
  21. // NewGoProxy creates GoProxy object.
  22. func NewGoProxy(ctx context.Context) *GoProxy {
  23. mod := new(GoProxy)
  24. mod.ctx = ctx
  25. mod.callbacks = make(map[string]tengo.Object)
  26. mod.callChan = make(chan *CallArgs, 1)
  27. mod.moduleMap = map[string]tengo.Object{
  28. "next": &tengo.UserFunction{Value: mod.next},
  29. "register": &tengo.UserFunction{Value: mod.register},
  30. "args": &tengo.UserFunction{Value: mod.args},
  31. }
  32. mod.tasks = list.New()
  33. return mod
  34. }
  35. // GoProxy is a builtin tengo module to register tengo functions and run them.
  36. type GoProxy struct {
  37. tengo.PtrObjectImpl
  38. ctx context.Context
  39. moduleMap map[string]tengo.Object
  40. callbacks map[string]tengo.Object
  41. callChan chan *CallArgs
  42. tasks *list.List
  43. mtx sync.Mutex
  44. }
  45. // TypeName returns type name.
  46. func (mod *GoProxy) TypeName() string {
  47. return "GoProxy"
  48. }
  49. func (mod *GoProxy) String() string {
  50. m := tengo.ImmutableMap{Value: mod.moduleMap}
  51. return m.String()
  52. }
  53. // ModuleMap returns a map to add a builtin tengo module.
  54. func (mod *GoProxy) ModuleMap() map[string]tengo.Object {
  55. return mod.moduleMap
  56. }
  57. // CallChan returns call channel which expects arguments to run a tengo
  58. // function.
  59. func (mod *GoProxy) CallChan() chan<- *CallArgs {
  60. return mod.callChan
  61. }
  62. func (mod *GoProxy) next(args ...tengo.Object) (tengo.Object, error) {
  63. mod.mtx.Lock()
  64. defer mod.mtx.Unlock()
  65. select {
  66. case <-mod.ctx.Done():
  67. return tengo.FalseValue, nil
  68. case args := <-mod.callChan:
  69. if args != nil {
  70. mod.tasks.PushBack(args)
  71. }
  72. return tengo.TrueValue, nil
  73. }
  74. }
  75. func (mod *GoProxy) register(args ...tengo.Object) (tengo.Object, error) {
  76. if len(args) == 0 {
  77. return nil, tengo.ErrWrongNumArguments
  78. }
  79. mod.mtx.Lock()
  80. defer mod.mtx.Unlock()
  81. switch v := args[0].(type) {
  82. case *tengo.Map:
  83. mod.callbacks = v.Value
  84. case *tengo.ImmutableMap:
  85. mod.callbacks = v.Value
  86. default:
  87. return nil, tengo.ErrInvalidArgumentType{
  88. Name: "first",
  89. Expected: "map",
  90. Found: args[0].TypeName(),
  91. }
  92. }
  93. return tengo.UndefinedValue, nil
  94. }
  95. func (mod *GoProxy) args(args ...tengo.Object) (tengo.Object, error) {
  96. mod.mtx.Lock()
  97. defer mod.mtx.Unlock()
  98. if mod.tasks.Len() == 0 {
  99. return tengo.UndefinedValue, nil
  100. }
  101. el := mod.tasks.Front()
  102. callArgs, ok := el.Value.(*CallArgs)
  103. if !ok || callArgs == nil {
  104. return nil, errors.New("invalid call arguments")
  105. }
  106. mod.tasks.Remove(el)
  107. f, ok := mod.callbacks[callArgs.Func]
  108. if !ok {
  109. return tengo.UndefinedValue, nil
  110. }
  111. compiledFunc, ok := f.(*tengo.CompiledFunction)
  112. if !ok {
  113. return tengo.UndefinedValue, nil
  114. }
  115. params := callArgs.Params
  116. if params == nil {
  117. params = make([]tengo.Object, 0)
  118. }
  119. // callable.VarArgs implementation is omitted.
  120. return &tengo.ImmutableMap{
  121. Value: map[string]tengo.Object{
  122. "result": &tengo.UserFunction{
  123. Value: func(args ...tengo.Object) (tengo.Object, error) {
  124. if len(args) > 0 {
  125. callArgs.Result <- args[0]
  126. return tengo.UndefinedValue, nil
  127. }
  128. callArgs.Result <- &tengo.Error{
  129. Value: &tengo.String{
  130. Value: tengo.ErrWrongNumArguments.Error()},
  131. }
  132. return tengo.UndefinedValue, nil
  133. }},
  134. "num_params": tengo.Int{Value: int64(compiledFunc.NumParameters)},
  135. "callable": compiledFunc,
  136. "params": &tengo.Array{Value: params},
  137. },
  138. }, nil
  139. }
  140. // ProxySource is a tengo script to handle bidirectional arguments flow between
  141. // go and pure tengo functions. Note: you should add more if conditions for
  142. // different number of parameters.
  143. // TODO: handle variadic functions.
  144. var ProxySource = `
  145. export func(args) {
  146. if is_undefined(args) {
  147. return
  148. }
  149. callable := args.callable
  150. if is_undefined(callable) {
  151. return
  152. }
  153. result := args.result
  154. num_params := args.num_params
  155. v := undefined
  156. // add more else if conditions for different number of parameters.
  157. if num_params == 0 {
  158. v = callable()
  159. } else if num_params == 1 {
  160. v = callable(args.params[0])
  161. } else if num_params == 2 {
  162. v = callable(args.params[0], args.params[1])
  163. } else if num_params == 3 {
  164. v = callable(args.params[0], args.params[1], args.params[2])
  165. }
  166. result(v)
  167. }
  168. `
  169. func main() {
  170. src := `
  171. // goproxy and proxy must be imported.
  172. goproxy := import("goproxy")
  173. proxy := import("proxy")
  174. global := 0
  175. callbacks := {
  176. sum: func(a, b) {
  177. return a + b
  178. },
  179. multiply: func(a, b) {
  180. return a * b
  181. },
  182. increment: func() {
  183. global++
  184. return global
  185. }
  186. }
  187. // Register callbacks to call them in goproxy loop.
  188. goproxy.register(callbacks)
  189. // goproxy loop waits for new call requests and run them with the help of
  190. // "proxy" source module. Cancelling the context breaks the loop.
  191. for goproxy.next() {
  192. proxy(goproxy.args())
  193. }
  194. `
  195. // 5 seconds context timeout is enough for an example.
  196. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  197. defer cancel()
  198. script := tengo.NewScript([]byte(src))
  199. moduleMap := tengo.NewModuleMap()
  200. goproxy := NewGoProxy(ctx)
  201. // register modules
  202. moduleMap.AddBuiltinModule("goproxy", goproxy.ModuleMap())
  203. moduleMap.AddSourceModule("proxy", []byte(ProxySource))
  204. script.SetImports(moduleMap)
  205. compiled, err := script.Compile()
  206. if err != nil {
  207. panic(err)
  208. }
  209. // call "sum", "multiply", "increment" functions from tengo in a new goroutine
  210. go func() {
  211. callChan := goproxy.CallChan()
  212. result := make(chan tengo.Object, 1)
  213. // TODO: check tengo error from result channel.
  214. loop:
  215. for {
  216. select {
  217. case <-ctx.Done():
  218. break loop
  219. default:
  220. }
  221. fmt.Println("Calling tengo sum function")
  222. i1, i2 := rand.Int63n(100), rand.Int63n(100)
  223. callChan <- &CallArgs{Func: "sum",
  224. Params: []tengo.Object{tengo.Int{Value: i1},
  225. tengo.Int{Value: i2}},
  226. Result: result,
  227. }
  228. v := <-result
  229. fmt.Printf("%d + %d = %v\n", i1, i2, v)
  230. fmt.Println("Calling tengo multiply function")
  231. i1, i2 = rand.Int63n(20), rand.Int63n(20)
  232. callChan <- &CallArgs{Func: "multiply",
  233. Params: []tengo.Object{tengo.Int{Value: i1},
  234. tengo.Int{Value: i2}},
  235. Result: result,
  236. }
  237. v = <-result
  238. fmt.Printf("%d * %d = %v\n", i1, i2, v)
  239. fmt.Println("Calling tengo increment function")
  240. callChan <- &CallArgs{Func: "increment", Result: result}
  241. v = <-result
  242. fmt.Printf("increment = %v\n", v)
  243. time.Sleep(1 * time.Second)
  244. }
  245. }()
  246. if err := compiled.RunContext(ctx); err != nil {
  247. fmt.Println(err)
  248. }
  249. }