From c58bf345c6c146409b76d302a75c590461686f69 Mon Sep 17 00:00:00 2001 From: Star <> Date: Sun, 18 Feb 2024 13:20:58 +0800 Subject: [PATCH] add JSRuntime --- bridge.go | 2 +- gojs.go | 148 ++++++++++++++++++++++++++++++++++++++------------- gojs_test.go | 10 ++-- 3 files changed, 116 insertions(+), 44 deletions(-) diff --git a/bridge.go b/bridge.go index 60b4ac9..d7380fc 100644 --- a/bridge.go +++ b/bridge.go @@ -2,9 +2,9 @@ package gojs import ( "apigo.cloud/git/apigo/plugin" + "apigo.cloud/git/apigo/qjs" "errors" "fmt" - "apigo.cloud/git/apigo/qjs" "github.com/ssgo/log" "github.com/ssgo/u" "reflect" diff --git a/gojs.go b/gojs.go index c5ba58f..eabf3e6 100644 --- a/gojs.go +++ b/gojs.go @@ -4,6 +4,7 @@ import ( "apigo.cloud/git/apigo/plugin" "apigo.cloud/git/apigo/qjs" "errors" + "fmt" "github.com/ssgo/log" "github.com/ssgo/u" "regexp" @@ -12,66 +13,137 @@ import ( var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`) -func Run(code string, globals map[string]interface{}, logger *log.Logger) (out interface{}) { - // 初始化JS虚拟机 - freeJsValues := make([]quickjs.Value, 0) - rt := quickjs.NewRuntime() - jsCtx := rt.NewContext() - defer func() { - if err := recover(); err != nil { - logger.Error(u.String(err)) - } - for _, v := range freeJsValues { - v.Free() - } - freeJsValues = make([]quickjs.Value, 0) - jsCtx.Close() - rt.Close() - }() +type JSRuntime struct { + freeJsValues []quickjs.Value + rt quickjs.Runtime + JsCtx *quickjs.Context + GoCtx *plugin.Context + logger *log.Logger + plugins map[string]*plugin.Plugin +} +func (rt *JSRuntime) Close() { + for _, v := range rt.freeJsValues { + v.Free() + } + rt.freeJsValues = make([]quickjs.Value, 0) + rt.JsCtx.Close() + rt.rt.Close() +} + +func (rt *JSRuntime) initCode(code string) { tryPlugins := map[string]bool{} for _, m := range pluginNameMatcher.FindAllStringSubmatch(code, 1024) { tryPlugins[m[1]] = true } - goCtx := plugin.NewContext(map[string]interface{}{ - "*log.Logger": logger, - "*quickjs.Context": jsCtx, - }) - goCtx.SetData("_freeJsValues", &freeJsValues) - for _, plg := range plugin.List() { - if tryPlugins[plg.Id] { - jsCtx.Globals().Set(plg.Id, MakeJsValueForPlugin(goCtx, plg.Objects, plg.Id, false)) + if tryPlugins[plg.Id] && rt.plugins[plg.Id] == nil { + rt.plugins[plg.Id] = &plg + rt.JsCtx.Globals().Set(plg.Id, MakeJsValueForPlugin(rt.GoCtx, plg.Objects, plg.Id, false)) if plg.JsCode != "" { - if result, err := jsCtx.Eval(plg.JsCode); err != nil { + if result, err := rt.JsCtx.Eval(plg.JsCode); err != nil { stack := GetJSError(err, plg.JsCode) - logger.Error(err.Error(), "stack", stack) + rt.logger.Error(err.Error(), "stack", stack) } else { result.Free() } } } } +} + +func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string) { + if r, err := rt.JsCtx.Eval(code); err == nil { + result := MakeFromJsValue(r) + r.Free() + return result, nil, "" + } else { + // 检查错误 + stack := GetJSError(err, code) + rt.logger.Error(err.Error(), "stack", stack) + return nil, err, stack + } +} + +func (rt *JSRuntime) Exec(code string) (err error, stack string) { + rt.initCode(code) + _, err, stack = rt.run(code) + return err, stack +} + +func (rt *JSRuntime) Run(code string) (out interface{}, err error, stack string) { + rt.initCode(code) + return rt.run("(function(){" + code + "})()") +} + +func SetPluginsConfig(conf map[string]plugin.Config) { + for _, plg := range plugin.List() { + if plg.Init != nil { + plg.Init(conf[plg.Id]) + } + } +} + +func New(globals map[string]interface{}, logger *log.Logger) *JSRuntime { + if logger == nil { + logger = log.DefaultLogger + } + + // 初始化JS虚拟机 + jsRt := quickjs.NewRuntime() + jsCtx := jsRt.NewContext() + goCtx := plugin.NewContext(map[string]interface{}{ + "*log.Logger": logger, + "*quickjs.Context": jsCtx, + }) + + rt := &JSRuntime{ + freeJsValues: make([]quickjs.Value, 0), + rt: jsRt, + JsCtx: jsCtx, + GoCtx: goCtx, + logger: logger, + plugins: map[string]*plugin.Plugin{}, + } + + rt.GoCtx.SetData("_freeJsValues", &rt.freeJsValues) // 全局变量 if globals != nil { for k, obj := range globals { - jsCtx.Globals().Set(k, MakeJsValue(goCtx, obj, false)) + rt.JsCtx.Globals().Set(k, MakeJsValue(rt.GoCtx, obj, false)) } } - // 运行API - if r, err := jsCtx.Eval("(function(){" + code + "})()"); err == nil { - result := MakeFromJsValue(r) - r.Free() - return result - } else { - // 检查错误 - stack := GetJSError(err, code) - logger.Error(err.Error(), "stack", stack) - return nil - } + // 注入 console + rt.JsCtx.Globals().Set("console", MakeJsValue(rt.GoCtx, map[string]interface{}{ + "log": func(args ...interface{}) { + fmt.Println(args...) + }, + "info": func(args ...interface{}) { + fmt.Println(u.Cyan(fmt.Sprintln(args...))) + }, + "warn": func(args ...interface{}) { + fmt.Println(u.BYellow(fmt.Sprintln(args...))) + }, + "error": func(args ...interface{}) { + fmt.Println(u.BRed(fmt.Sprintln(args...))) + }, + }, false)) + + return rt +} + +func Run(code string, globals map[string]interface{}, logger *log.Logger) (out interface{}, err error, stack string) { + rt := New(globals, logger) + defer func() { + if err := recover(); err != nil { + rt.logger.Error(u.String(err)) + } + rt.Close() + }() + return rt.Run(code) } var jsErrorCodeMatcher = regexp.MustCompile(`code:(\d+)`) diff --git a/gojs_test.go b/gojs_test.go index 9b1c827..ccc6ad4 100644 --- a/gojs_test.go +++ b/gojs_test.go @@ -63,22 +63,22 @@ return plus(number,2) "plus": func(i, j int) int { return i + j }, } - r := gojs.Run(code, globals, log.DefaultLogger) + r, _, _ := gojs.Run(code, globals, log.DefaultLogger) test(t, "call", u.Int(r) == 11, r) } func TestPlugin(t *testing.T) { - r := gojs.Run("return obj.getId()", nil, log.DefaultLogger) + r, _, _ := gojs.Run("return obj.getId()", nil, log.DefaultLogger) test(t, "obj.getId()", u.String(r) == "o-00", r) - r = gojs.Run(` + r, _, _ = gojs.Run(` o = obj.new('o-01') return o.getId() `, nil, log.DefaultLogger) test(t, "new obj.getId()", u.String(r) == "o-01", r) t1 := time.Now() - r = gojs.Run(` + r, _, _ = gojs.Run(` out = '' obj.echo('123', function(text){ out = text @@ -90,7 +90,7 @@ return out test(t, "callback", u.String(r) == "123", r) t1 = time.Now() - r = gojs.Run(` + r, _, _ = gojs.Run(` out = '' obj.echoTimes(function(text){ out += text