add JSRuntime

This commit is contained in:
Star 2024-02-18 13:20:58 +08:00
parent 7d632c5021
commit c58bf345c6
3 changed files with 116 additions and 44 deletions

View File

@ -2,9 +2,9 @@ package gojs
import ( import (
"apigo.cloud/git/apigo/plugin" "apigo.cloud/git/apigo/plugin"
"apigo.cloud/git/apigo/qjs"
"errors" "errors"
"fmt" "fmt"
"apigo.cloud/git/apigo/qjs"
"github.com/ssgo/log" "github.com/ssgo/log"
"github.com/ssgo/u" "github.com/ssgo/u"
"reflect" "reflect"

142
gojs.go
View File

@ -4,6 +4,7 @@ import (
"apigo.cloud/git/apigo/plugin" "apigo.cloud/git/apigo/plugin"
"apigo.cloud/git/apigo/qjs" "apigo.cloud/git/apigo/qjs"
"errors" "errors"
"fmt"
"github.com/ssgo/log" "github.com/ssgo/log"
"github.com/ssgo/u" "github.com/ssgo/u"
"regexp" "regexp"
@ -12,66 +13,137 @@ import (
var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`) var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`)
func Run(code string, globals map[string]interface{}, logger *log.Logger) (out interface{}) { type JSRuntime struct {
// 初始化JS虚拟机 freeJsValues []quickjs.Value
freeJsValues := make([]quickjs.Value, 0) rt quickjs.Runtime
rt := quickjs.NewRuntime() JsCtx *quickjs.Context
jsCtx := rt.NewContext() GoCtx *plugin.Context
defer func() { logger *log.Logger
if err := recover(); err != nil { plugins map[string]*plugin.Plugin
logger.Error(u.String(err)) }
}
for _, v := range freeJsValues { func (rt *JSRuntime) Close() {
for _, v := range rt.freeJsValues {
v.Free() v.Free()
} }
freeJsValues = make([]quickjs.Value, 0) rt.freeJsValues = make([]quickjs.Value, 0)
jsCtx.Close() rt.JsCtx.Close()
rt.Close() rt.rt.Close()
}() }
func (rt *JSRuntime) initCode(code string) {
tryPlugins := map[string]bool{} tryPlugins := map[string]bool{}
for _, m := range pluginNameMatcher.FindAllStringSubmatch(code, 1024) { for _, m := range pluginNameMatcher.FindAllStringSubmatch(code, 1024) {
tryPlugins[m[1]] = true 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() { for _, plg := range plugin.List() {
if tryPlugins[plg.Id] { if tryPlugins[plg.Id] && rt.plugins[plg.Id] == nil {
jsCtx.Globals().Set(plg.Id, MakeJsValueForPlugin(goCtx, plg.Objects, plg.Id, false)) rt.plugins[plg.Id] = &plg
rt.JsCtx.Globals().Set(plg.Id, MakeJsValueForPlugin(rt.GoCtx, plg.Objects, plg.Id, false))
if plg.JsCode != "" { 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) stack := GetJSError(err, plg.JsCode)
logger.Error(err.Error(), "stack", stack) rt.logger.Error(err.Error(), "stack", stack)
} else { } else {
result.Free() 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 { if globals != nil {
for k, obj := range globals { for k, obj := range globals {
jsCtx.Globals().Set(k, MakeJsValue(goCtx, obj, false)) rt.JsCtx.Globals().Set(k, MakeJsValue(rt.GoCtx, obj, false))
} }
} }
// 运行API // 注入 console
if r, err := jsCtx.Eval("(function(){" + code + "})()"); err == nil { rt.JsCtx.Globals().Set("console", MakeJsValue(rt.GoCtx, map[string]interface{}{
result := MakeFromJsValue(r) "log": func(args ...interface{}) {
r.Free() fmt.Println(args...)
return result },
} else { "info": func(args ...interface{}) {
// 检查错误 fmt.Println(u.Cyan(fmt.Sprintln(args...)))
stack := GetJSError(err, code) },
logger.Error(err.Error(), "stack", stack) "warn": func(args ...interface{}) {
return nil 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+)`) var jsErrorCodeMatcher = regexp.MustCompile(`code:(\d+)`)

View File

@ -63,22 +63,22 @@ return plus(number,2)
"plus": func(i, j int) int { return i + j }, "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) test(t, "call", u.Int(r) == 11, r)
} }
func TestPlugin(t *testing.T) { 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) test(t, "obj.getId()", u.String(r) == "o-00", r)
r = gojs.Run(` r, _, _ = gojs.Run(`
o = obj.new('o-01') o = obj.new('o-01')
return o.getId() return o.getId()
`, nil, log.DefaultLogger) `, nil, log.DefaultLogger)
test(t, "new obj.getId()", u.String(r) == "o-01", r) test(t, "new obj.getId()", u.String(r) == "o-01", r)
t1 := time.Now() t1 := time.Now()
r = gojs.Run(` r, _, _ = gojs.Run(`
out = '' out = ''
obj.echo('123', function(text){ obj.echo('123', function(text){
out = text out = text
@ -90,7 +90,7 @@ return out
test(t, "callback", u.String(r) == "123", r) test(t, "callback", u.String(r) == "123", r)
t1 = time.Now() t1 = time.Now()
r = gojs.Run(` r, _, _ = gojs.Run(`
out = '' out = ''
obj.echoTimes(function(text){ obj.echoTimes(function(text){
out += text out += text