add export plugins code to typescript

add RunFile
This commit is contained in:
Star 2024-03-15 13:40:20 +08:00
parent c4fcb3fb21
commit 8179d73ae0
5 changed files with 522 additions and 37 deletions

View File

@ -165,9 +165,9 @@ func _makeJsValue(ctx *plugin.Context, in interface{}, n int, key string, plugin
// pluginConf := GetPluginConfig(pluginName)
// realArgs[i] = reflect.ValueOf(pluginConf)
// continue
} else if injectObject := ctx.GetInject(inTypeString); injectObject != nil {
realArgs[i] = reflect.ValueOf(injectObject)
continue
//} else if injectObject := ctx.GetInject(inTypeString); injectObject != nil {
// realArgs[i] = reflect.ValueOf(injectObject)
// continue
}
if !realArgs[i].IsValid() {

8
go.mod
View File

@ -5,12 +5,12 @@ go 1.17
require (
apigo.cloud/git/apigo/plugin v1.0.1
apigo.cloud/git/apigo/qjs v0.0.1
github.com/ssgo/log v0.6.12
github.com/ssgo/u v0.6.12
github.com/ssgo/log v1.7.2
github.com/ssgo/u v1.7.2
)
require (
github.com/ssgo/config v0.6.12 // indirect
github.com/ssgo/standard v0.6.12 // indirect
github.com/ssgo/config v1.7.2 // indirect
github.com/ssgo/standard v1.7.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

113
gojs.go
View File

@ -7,13 +7,24 @@ import (
"fmt"
"github.com/ssgo/log"
"github.com/ssgo/u"
"path"
"regexp"
"strings"
)
var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`)
var exportMatcher = regexp.MustCompile(`export\s+([\w{}, ]+)\s*;?`)
var importMatcher = regexp.MustCompile(`import\s+([\w{}, ]+)\s+from\s+['"]([\w./\- ]+)['"]`)
type RuntimeOption struct {
Globals map[string]interface{}
Imports map[string]string
Logger *log.Logger
}
type JSRuntime struct {
imports map[string]string
imported map[string]string
freeJsValues []quickjs.Value
rt quickjs.Runtime
JsCtx *quickjs.Context
@ -31,7 +42,52 @@ func (rt *JSRuntime) Close() {
rt.rt.Close()
}
func (rt *JSRuntime) initCode(code string) {
func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string) {
// support import
code = importMatcher.ReplaceAllStringFunc(code, func(importStr string) string {
m := importMatcher.FindStringSubmatch(importStr)
importVar := rt.imported[m[2]]
if importVar == "" {
baseName := path.Base(m[2])
jsFile := m[2]
isTS := false
if strings.HasSuffix(baseName, ".ts") {
isTS = true
baseName = baseName[0 : len(baseName)-3]
}
if strings.HasSuffix(baseName, ".js") {
baseName = baseName[0 : len(baseName)-3]
} else {
jsFile += ".js"
}
if !isTS && (rt.imports[m[2]] != "" || u.FileExists(jsFile)) {
importCode := rt.imports[m[2]]
if importCode == "" {
importCode, _ = u.ReadFile(jsFile)
}
if importCode != "" {
importVar = "import_" + u.UniqueId()
rt.imported[m[2]] = importVar
importedCode := exportMatcher.ReplaceAllStringFunc(importCode, func(exportStr string) string {
return strings.Replace(exportStr, "export", "return", 1)
})
err, stack := rt.Exec("let " + importVar + " = (function(){" + importedCode + "})()")
if err != nil {
rt.logger.Error(err.Error(), "stack", stack)
}
} else {
importVar = "{}"
}
return "let " + m[1] + " = " + importVar
} else {
// ignore ts and plugin
return ""
}
} else {
return "let " + m[1] + " = " + importVar
}
})
tryPlugins := map[string]bool{}
for _, m := range pluginNameMatcher.FindAllStringSubmatch(code, 1024) {
tryPlugins[m[1]] = true
@ -51,9 +107,7 @@ func (rt *JSRuntime) initCode(code string) {
}
}
}
}
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()
@ -67,16 +121,30 @@ func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string)
}
func (rt *JSRuntime) Exec(code string) (err error, stack string) {
rt.initCode(code)
_, err, stack = rt.run(code)
return err, stack
}
func (rt *JSRuntime) ExecFile(filename string) (err error, stack string) {
if code, err := u.ReadFile(filename); err == nil {
return rt.Exec(code)
} else {
return err, ""
}
}
func (rt *JSRuntime) Run(code string) (out interface{}, err error, stack string) {
rt.initCode(code)
return rt.run("(function(){" + code + "})()")
}
func (rt *JSRuntime) RunFile(filename string) (out interface{}, err error, stack string) {
if code, err := u.ReadFile(filename); err == nil {
return rt.Run(code)
} else {
return nil, err, ""
}
}
func SetPluginsConfig(conf map[string]plugin.Config) {
for _, plg := range plugin.List() {
if plg.Init != nil {
@ -85,33 +153,42 @@ func SetPluginsConfig(conf map[string]plugin.Config) {
}
}
func New(globals map[string]interface{}, logger *log.Logger) *JSRuntime {
if logger == nil {
logger = log.DefaultLogger
func New(option *RuntimeOption) *JSRuntime {
if option == nil {
option = &RuntimeOption{nil, map[string]string{}, log.DefaultLogger}
}
if option.Imports == nil {
option.Imports = map[string]string{}
}
if option.Logger == nil {
option.Logger = log.DefaultLogger
}
// 初始化JS虚拟机
jsRt := quickjs.NewRuntime()
jsCtx := jsRt.NewContext()
goCtx := plugin.NewContext(map[string]interface{}{
"*log.Logger": logger,
"*log.Logger": option.Logger,
"*quickjs.Context": jsCtx,
})
rt := &JSRuntime{
imports: option.Imports,
imported: map[string]string{},
freeJsValues: make([]quickjs.Value, 0),
rt: jsRt,
JsCtx: jsCtx,
GoCtx: goCtx,
logger: logger,
logger: option.Logger,
plugins: map[string]*plugin.Plugin{},
}
rt.GoCtx.SetData("_freeJsValues", &rt.freeJsValues)
// 全局变量
if globals != nil {
for k, obj := range globals {
if option.Globals != nil {
for k, obj := range option.Globals {
rt.JsCtx.Globals().Set(k, MakeJsValue(rt.GoCtx, obj, false))
}
}
@ -173,8 +250,8 @@ func makeStringArray(args []interface{}, color u.TextColor, bg u.BgColor) []inte
return stringArgs
}
func Run(code string, globals map[string]interface{}, logger *log.Logger) (out interface{}, err error, stack string) {
rt := New(globals, logger)
func Run(code string, option *RuntimeOption) (out interface{}, err error, stack string) {
rt := New(option)
defer func() {
if err := recover(); err != nil {
rt.logger.Error(u.String(err))
@ -184,6 +261,14 @@ func Run(code string, globals map[string]interface{}, logger *log.Logger) (out i
return rt.Run(code)
}
func RunFile(filename string, option *RuntimeOption) (out interface{}, err error, stack string) {
if code, err := u.ReadFile(filename); err == nil {
return Run(code, option)
} else {
return nil, err, ""
}
}
var jsErrorCodeMatcher = regexp.MustCompile(`code:(\d+)`)
func GetJSError(err error, code string) string {

View File

@ -7,11 +7,11 @@ import (
"github.com/ssgo/log"
"github.com/ssgo/u"
"runtime"
"strings"
"testing"
"time"
)
type Object struct {
id string
}
@ -20,18 +20,48 @@ func (obj *Object) GetId() string {
return obj.id
}
type TestBirthday struct {
Year int
Month int
Day int
}
type TestBaseUser struct {
Id int
Name string
}
type TestUser struct {
TestBaseUser
Birthday *TestBirthday
}
func (b *TestBirthday) String() string {
return fmt.Sprintln(b.Year, b.Month, b.Day)
}
func (u *TestUser) GetBirthdayString() string {
return u.Birthday.String()
}
func init() {
defaultObject := Object{id: "o-00"}
plugin.Register(plugin.Plugin{
plg := plugin.Plugin{
Id: "obj",
Name: "test obj plugin",
Objects: map[string]interface{}{
"name": "1233",
"list1": []interface{}{1, "2", map[string]interface{}{"aaa": 111, "bbb": "222"}, true},
"log": log.DefaultLogger.Info,
"getId": defaultObject.GetId,
"new": func(id string) interface{} {
return &Object{id: id}
},
"echo": func(text string, echoFunc func(text string) string) interface{} {
return echoFunc(text)
"test1": func(id int, id2 uint16, id3 float64) *TestUser {
return nil
},
"echo": func(text string, echoFunc func(text string, t2 int) string, t3 []uint32, t4 *bool) interface{} {
return echoFunc(text, 0)
},
"echoTimes": func(echoFunc func(text string)) {
for i := 0; i < 5; i++ {
@ -40,6 +70,10 @@ func init() {
}
},
},
}
plugin.Register(plg)
gojs.SetPluginsConfig(map[string]plugin.Config{
"obj": plugin.Config{},
})
}
@ -52,6 +86,12 @@ func test(t *testing.T, name string, check bool, extArgs ...interface{}) {
}
}
func TestTS(t *testing.T) {
plg := plugin.Get("obj")
plgCode := gojs.MakePluginCode(plg)
test(t, "ts code", strings.Contains(plgCode, "(echoFunc: (text: string) => void)"))
}
func TestGlobal(t *testing.T) {
code := `
log('test', 'name', 'log')
@ -63,18 +103,20 @@ return plus(number,2)
"plus": func(i, j int) int { return i + j },
}
r, _, _ := gojs.Run(code, globals, log.DefaultLogger)
r, _, _ := gojs.Run(code, &gojs.RuntimeOption{
Globals: globals,
})
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)
test(t, "obj.getId()", u.String(r) == "o-00", r)
r, _, _ = gojs.Run(`
o = obj.new('o-01')
return o.getId()
`, nil, log.DefaultLogger)
`, nil)
test(t, "new obj.getId()", u.String(r) == "o-01", r)
t1 := time.Now()
@ -82,11 +124,11 @@ return o.getId()
out = ''
obj.echo('123', function(text){
out = text
})
}, null)
return out
`, nil, log.DefaultLogger)
`, nil)
t2 := time.Now()
fmt.Println("time:", t2.UnixMicro() - t1.UnixMicro())
fmt.Println("time:", t2.UnixMicro()-t1.UnixMicro())
test(t, "callback", u.String(r) == "123", r)
t1 = time.Now()
@ -96,20 +138,19 @@ obj.echoTimes(function(text){
out += text
})
return out
`, nil, log.DefaultLogger)
`, nil)
t2 = time.Now()
fmt.Println("time:", t2.UnixMicro() - t1.UnixMicro())
fmt.Println("time:", t2.UnixMicro()-t1.UnixMicro())
test(t, "callbacks", u.String(r) == "01234", r)
}
func BenchmarkEcho(tb *testing.B) {
tb.StopTimer()
ms1 := runtime.MemStats{}
runtime.ReadMemStats(&ms1)
tb.StartTimer()
for i := 0; i < tb.N; i++ {
gojs.Run(`return 1`, nil, log.DefaultLogger)
gojs.Run(`return 1`, nil)
}
tb.StopTimer()
@ -122,7 +163,6 @@ func BenchmarkEcho(tb *testing.B) {
fmt.Println(">>", ms1.HeapInuse, ms2.HeapInuse, ms3.HeapInuse)
}
func BenchmarkCallback(tb *testing.B) {
tb.StopTimer()
ms1 := runtime.MemStats{}
@ -135,7 +175,7 @@ obj.echoTimes(function(text){
out += text
})
return out
`, nil, log.DefaultLogger)
`, nil)
}
tb.StopTimer()

360
ts.go Normal file
View File

@ -0,0 +1,360 @@
package gojs
import (
"apigo.cloud/git/apigo/plugin"
"github.com/ssgo/u"
"path"
"reflect"
"regexp"
"runtime"
"strings"
)
type ArgInfo struct {
index int
isVariadic bool
isOutArg bool
isFunc bool
isSkip bool
funcInArgs []ArgInfo
funcOutArgs []ArgInfo
Name string
Type string
}
var numberMatcher = regexp.MustCompile("[a-z0-9]*(int|float)[a-z0-9]*")
var mapMatcher = regexp.MustCompile("map\\[(\\w+)](\\w+)")
func makeArgTypeString(argType string) string {
if strings.Contains(argType, "interface {}") {
argType = strings.ReplaceAll(argType, "interface {}", "any")
}
if strings.HasPrefix(argType, "*") {
argType = argType[1:]
}
if strings.HasPrefix(argType, "[]") {
argType = argType[2:] + "[]"
}
if strings.Contains(argType, "int") || strings.Contains(argType, "float") {
argType = numberMatcher.ReplaceAllString(argType, "number")
}
if strings.Contains(argType, "bool") {
argType = strings.ReplaceAll(argType, "bool", "boolean")
}
if strings.Contains(argType, "booleanean") {
argType = strings.ReplaceAll(argType, "booleanean", "boolean")
}
if strings.HasPrefix(argType, "map[") {
argType = mapMatcher.ReplaceAllString(argType, "Map<$1, $2>")
}
//if strings.ContainsRune(argType, '.') {
// argType = argType[strings.LastIndexByte(argType, '.')+1:]
//}
return argType
}
func (argInfo *ArgInfo) String() string {
argType := argInfo.Type
if argInfo.isFunc {
argType = "(" + makeInArgsString(argInfo.funcInArgs) + ") => " + makeOutArgsString(argInfo.funcOutArgs)
} else {
argType = makeArgTypeString(argType)
}
if argInfo.isOutArg {
return argType
} else {
argName := argInfo.Name
if argName == "" {
argName = "arg" + u.String(argInfo.index+1)
}
if argInfo.isVariadic {
argName = "..." + argName
}
return argName + ": " + argType
}
}
func makeInArgs(args []ArgInfo) []string {
arr := make([]string, 0)
for _, arg := range args {
if !arg.isSkip {
arr = append(arr, arg.String())
}
}
return arr
}
func makeInArgsString(args []ArgInfo) string {
return strings.Join(makeInArgs(args), ", ")
}
func makeOutArgsString(args []ArgInfo) string {
arr := makeInArgs(args)
if len(arr) == 0 {
return "void"
} else if len(arr) == 1 {
return arr[0]
} else {
return "[" + strings.Join(arr, ", ") + "]"
}
}
func makeFuncArgsNames(v reflect.Value, inArgs []ArgInfo, isMethod bool) {
fp := runtime.FuncForPC(v.Pointer())
file, lineNo := fp.FileLine(fp.Entry())
if file != "<autogenerated>" && u.FileExists(file) {
lines, _ := u.ReadFileLines(file)
if len(lines) >= lineNo {
line := lines[lineNo-1]
if !strings.Contains(line, "func") && lineNo-2 >= 0 && strings.Contains(lines[lineNo-2], "func") {
line = lines[lineNo-2]
}
line = strings.ReplaceAll(line, "interface{}", "any")
pos := strings.Index(line, "func")
if pos != -1 {
line = strings.TrimSpace(line[pos+4:])
if isMethod {
// skip method this arg
pos = strings.Index(line, "(")
if pos != -1 {
line = strings.TrimSpace(line[pos+1:])
makeFuncArgsName(line, inArgs[1:])
}
} else {
makeFuncArgsName(line, inArgs)
}
}
}
}
}
func makeFuncArgsName(line string, inArgs []ArgInfo) {
pos := strings.Index(line, "(")
if pos != -1 {
line = strings.TrimSpace(line[pos+1:])
for i := 0; i < len(inArgs); i++ {
// find param name
pos = strings.Index(line, " ")
// support combined args a, b string
pos1 := strings.Index(line, ",")
if pos1 != -1 && pos1 < pos {
inArgs[i].Name = line[0:pos1]
line = strings.TrimSpace(line[pos1+1:])
continue
}
if pos != -1 {
inArgs[i].Name = line[0:pos]
line = strings.TrimSpace(line[pos+1:])
}
// skip inline func
if strings.HasPrefix(line, "func") {
line = strings.TrimSpace(line[4:])
leftQuotes := 0
quoteStarted := false
for pos = 0; pos < len(line); pos++ {
if line[pos] == '(' {
leftQuotes++
quoteStarted = true
continue
}
if quoteStarted && line[pos] == ')' {
leftQuotes--
}
if quoteStarted && leftQuotes == 0 {
break
}
}
makeFuncArgsName(line, inArgs[i].funcInArgs)
line = strings.TrimSpace(line[pos+1:])
}
// skip ,
pos = strings.Index(line, ",")
if pos != -1 {
line = strings.TrimSpace(line[pos+1:])
}
}
}
}
func makeFieldElemType(t reflect.Type, existsClasses *map[string]bool, classes *[]string, isSkip bool) string {
originT := t
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if t.Kind() == reflect.Map {
if t.Elem().Kind() == reflect.Struct && !isSkip {
makeClass(t.Elem(), existsClasses, classes)
}
return "Map<" + makeArgTypeString(t.Key().String()) + ", " + makeFieldElemType(t.Elem(), existsClasses, classes, isSkip) + ">"
} else if t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 {
if t.Elem().Kind() == reflect.Struct && !isSkip {
makeClass(t.Elem(), existsClasses, classes)
}
return "Array<" + makeFieldElemType(t.Elem(), existsClasses, classes, isSkip) + ">"
} else if t.Kind() == reflect.Struct {
if !isSkip {
makeClass(originT, existsClasses, classes)
}
return makeArgTypeString(t.Name())
} else {
return makeArgTypeString(t.String())
}
}
func makeClass(t reflect.Type, existsClasses *map[string]bool, classes *[]string) {
originT := t
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if !(*existsClasses)[t.Name()] {
(*existsClasses)[t.Name()] = true
classItems := make([]string, 0)
implements := make([]string, 0)
for i := 0; i < t.NumField(); i++ {
if !t.Field(i).IsExported() {
continue
}
ft := t.Field(i).Type
if ft.Kind() == reflect.Pointer {
ft = ft.Elem()
}
ftStr := makeFieldElemType(ft, existsClasses, classes, false)
if t.Field(i).Anonymous {
implements = append(implements, ftStr)
continue
}
classItems = append(classItems, " "+u.GetLowerName(t.Field(i).Name)+": "+ftStr)
}
for i := 0; i < originT.NumMethod(); i++ {
if !originT.Method(i).IsExported() {
continue
}
inArgs, outArgs := makeFuncInOutArgs(originT.Method(i).Type, existsClasses, classes)
if len(inArgs) > 0 {
inArgs[0].isSkip = true
}
makeFuncArgsNames(originT.Method(i).Func, inArgs, true)
classItems = append(classItems, " "+u.GetLowerName(originT.Method(i).Name)+"("+makeInArgsString(inArgs)+"): "+makeOutArgsString(outArgs))
}
implementsStr := ""
if len(implements) > 0 {
implementsStr = " extends " + strings.Join(implements, ", ")
}
newClass := append([]string{}, "interface "+t.Name()+implementsStr+" {")
newClass = append(newClass, classItems...)
newClass = append(newClass, "}")
*classes = append(*classes, strings.Join(newClass, "\n"))
}
}
func makeFuncInOutArgs(t reflect.Type, existsClasses *map[string]bool, classes *[]string) (inArgs []ArgInfo, outArgs []ArgInfo) {
inArgs = make([]ArgInfo, t.NumIn())
outArgs = make([]ArgInfo, t.NumOut())
// *plugin.Context
for i := t.NumIn() - 1; i >= 0; i-- {
arg := t.In(i)
argInfo := ArgInfo{
index: i,
isOutArg: false,
isFunc: false,
isSkip: false,
isVariadic: t.IsVariadic() && i == t.NumIn()-1,
Name: "",
Type: makeFieldElemType(arg, existsClasses, classes, true),
}
if arg.String() == "*plugin.Context" {
argInfo.isSkip = true
}
originArg := arg
if arg.Kind() == reflect.Pointer {
arg = arg.Elem()
}
if arg.Kind() == reflect.Func {
argInfo.isFunc = true
argInfo.funcInArgs, argInfo.funcOutArgs = makeFuncInOutArgs(arg, existsClasses, classes)
}
if !argInfo.isSkip && arg.Kind() == reflect.Struct {
makeClass(originArg, existsClasses, classes)
}
inArgs[i] = argInfo
}
for i := t.NumOut() - 1; i >= 0; i-- {
arg := t.Out(i)
argInfo := ArgInfo{
index: i,
isOutArg: true,
isFunc: false,
isSkip: false,
isVariadic: false,
Name: "",
Type: makeFieldElemType(arg, existsClasses, classes, true),
}
if arg.String() == "error" {
argInfo.isSkip = true
}
originArg := arg
if arg.Kind() == reflect.Pointer {
arg = arg.Elem()
}
if !argInfo.isSkip && arg.Kind() == reflect.Struct {
makeClass(originArg, existsClasses, classes)
}
outArgs[i] = argInfo
}
return inArgs, outArgs
}
func findObject(v reflect.Value, level int, existsClasses *map[string]bool) (codes []string, classes []string) {
codes = make([]string, 0)
classes = make([]string, 0)
v = u.FinalValue(v)
indent := strings.Repeat(" ", level)
if v.Kind() == reflect.Map {
codes = append(codes, "{")
for _, k := range v.MapKeys() {
codes2, classes2 := findObject(v.MapIndex(k), level+1, existsClasses)
classes = append(classes, classes2...)
codes = append(codes, indent+" \""+k.String()+"\": "+strings.Join(codes2, "\n")+",")
}
codes = append(codes, indent+"}")
} else if v.Kind() == reflect.Struct {
//codes = append(codes, "{")
//for _, k := range v.MapKeys() {
// codes2, classes2 := findObject(v.MapIndex(k), level+1, existsClasses)
// classes = append(classes, classes2...)
// codes = append(codes, indent+" \""+k.String()+"\": "+strings.Join(codes2, "\n")+",")
//}
//codes = append(codes, indent+"}")
} else if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
codes = append(codes, "[")
for i := 0; i < v.Len(); i++ {
codes2, classes2 := findObject(v.Index(i), level+1, existsClasses)
classes = append(classes, classes2...)
codes = append(codes, indent+" "+strings.Join(codes2, "\n")+",")
}
codes = append(codes, indent+"]")
} else if v.Kind() == reflect.Func {
inArgs, outArgs := makeFuncInOutArgs(v.Type(), existsClasses, &classes)
makeFuncArgsNames(v, inArgs, false)
codes = append(codes, "function ("+makeInArgsString(inArgs)+"): "+makeOutArgsString(outArgs)+" {return}")
} else {
codes = append(codes, u.Json(v.Interface()))
}
return codes, classes
}
func MakeAllPluginCode() {
for _, plg := range plugin.List() {
code := MakePluginCode(&plg)
_ = u.WriteFile(path.Join("plugins", plg.Id+".ts"), code)
}
}
func MakePluginCode(plg *plugin.Plugin) string {
if plg == nil {
return ""
}
existsClasses := make(map[string]bool)
codes, classes := findObject(reflect.ValueOf(plg.Objects), 0, &existsClasses)
return strings.Join(classes, "\n\n") + "\n\nexport default " + strings.Join(codes, "\n")
}