872 lines
24 KiB
Go
872 lines
24 KiB
Go
package gojs
|
|
|
|
import (
|
|
"apigo.cc/apigo/plugin"
|
|
"apigo.cc/apigo/quickjs-go"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/ssgo/log"
|
|
"github.com/ssgo/u"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`)
|
|
var exportMatcher = regexp.MustCompile(`(?im)^\s*export\s+([\w{}, ]+)\s*;?`)
|
|
var importMatcher = regexp.MustCompile(`(?im)^\s*import\s+([\w{}, ]+)\s+from\s+['"]([\w./\\\- ]+)['"]`)
|
|
var flowMethodTypeMatcher = regexp.MustCompile(`\):\s*([\w<>\[\]]+)\s*{`)
|
|
var functionArgsForFlowMatcher = regexp.MustCompile(`\([\w<>\[\]:,\s]+`)
|
|
var flowVarTypeMatcher = regexp.MustCompile(`(\w+)\s*:\s*([\w<>\[\]]+)\s*(=|,|\)|$)`)
|
|
|
|
type RuntimeOption struct {
|
|
Globals map[string]interface{}
|
|
//Imports map[string]string
|
|
Logger *log.Logger
|
|
DevMode bool
|
|
}
|
|
|
|
//type watchInfo struct {
|
|
// mtime int64
|
|
// code string
|
|
// codePath string
|
|
//}
|
|
|
|
type JSRuntime struct {
|
|
//imports map[string]string
|
|
imported map[string]string
|
|
freeJsValues []quickjs.Value
|
|
rt quickjs.Runtime
|
|
JsCtx *quickjs.Context
|
|
GoCtx *plugin.Context
|
|
logger *log.Logger
|
|
//plugins map[string]*plugin.Plugin
|
|
rootPath string
|
|
currentPath string
|
|
devMode bool
|
|
codeLines map[string][]string
|
|
realCodeLines map[string][]string
|
|
anonymousIndex uint
|
|
//watchList map[string]watchInfo // watch file changed, for dev mode
|
|
}
|
|
|
|
type JSError struct {
|
|
error
|
|
Stack string
|
|
}
|
|
|
|
func (rt *JSRuntime) Close() {
|
|
for _, v := range rt.freeJsValues {
|
|
v.Free()
|
|
}
|
|
rt.freeJsValues = make([]quickjs.Value, 0)
|
|
rt.JsCtx.Close()
|
|
rt.rt.Close()
|
|
}
|
|
|
|
var pluginIdFixer = regexp.MustCompile("[^a-zA-Z0-9_]")
|
|
|
|
func fixPluginId(id string) string {
|
|
return "_" + pluginIdFixer.ReplaceAllString(id, "_")
|
|
}
|
|
|
|
type PreCompiledCode struct {
|
|
FixedCode string
|
|
Plugins []RequirePlugins
|
|
Filename string
|
|
CodeLines map[string][]string
|
|
RealCodeLines map[string][]string
|
|
Imports map[string]string
|
|
ImportedVar map[string]string
|
|
}
|
|
|
|
type RequirePlugins struct {
|
|
Id string
|
|
Objects map[string]interface{}
|
|
JsCode string
|
|
}
|
|
|
|
func makeImport(moduleName, currentPath string, imported *map[string]string, logger *log.Logger) (varName, importCode, importFile string, searchList []string, jsErr *JSError) {
|
|
searchList = make([]string, 0)
|
|
|
|
// if imported
|
|
importVar := (*imported)[moduleName]
|
|
if importVar != "" {
|
|
return importVar, "", "", searchList, nil
|
|
}
|
|
|
|
//importCode = imports[moduleName]
|
|
importCode = ""
|
|
importFile = ""
|
|
|
|
// search file
|
|
//if importCode == "" {
|
|
hasJsExt := strings.HasSuffix(moduleName, ".js")
|
|
importFile = appendSearchFiles(&searchList, "", moduleName, hasJsExt)
|
|
if importFile == "" && !filepath.IsAbs(moduleName) {
|
|
importFile = appendSearchFiles(&searchList, currentPath, moduleName, hasJsExt)
|
|
if importFile == "" {
|
|
parentPath := currentPath
|
|
for i := 0; i < 100; i++ {
|
|
nodeModuleDir := filepath.Join(parentPath, "node_modules")
|
|
if u.FileExists(nodeModuleDir) {
|
|
importFile = appendSearchFiles(&searchList, nodeModuleDir, moduleName, hasJsExt)
|
|
break
|
|
}
|
|
parentPath = filepath.Dir(parentPath)
|
|
if parentPath == "" || parentPath == "." {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if importFile != "" {
|
|
if !filepath.IsAbs(importFile) {
|
|
importFile, _ = filepath.Abs(importFile)
|
|
}
|
|
importVar = (*imported)[importFile]
|
|
if importVar != "" {
|
|
return importVar, "", "", searchList, nil
|
|
}
|
|
|
|
if code, err := u.ReadFile(importFile); err == nil {
|
|
importCode = code
|
|
} else {
|
|
logger.Error(err.Error())
|
|
}
|
|
}
|
|
//}
|
|
|
|
if importCode == "" {
|
|
return "", "", "", searchList, nil
|
|
}
|
|
//importVar = (*fileToVar)[importFile]
|
|
//if importVar == "" {
|
|
importVar = fmt.Sprintf("_import_%d_%s", len(*imported), u.UniqueId())
|
|
// (*fileToVar)[importFile] = importVar
|
|
// fmt.Println("_import_=====", importFile, importVar)
|
|
//}
|
|
(*imported)[importFile] = importVar
|
|
importCode = exportMatcher.ReplaceAllStringFunc(importCode, func(exportStr string) string {
|
|
if strings.Contains(exportStr, "export default") {
|
|
exportStr = strings.Replace(exportStr, "export default", "return", 1)
|
|
}
|
|
exportStr = strings.Replace(exportStr, "export", "return", 1)
|
|
return exportStr
|
|
})
|
|
return importVar, importCode, importFile, searchList, nil
|
|
}
|
|
|
|
func PreCompileFile(filename string, logger *log.Logger) (*PreCompiledCode, error) {
|
|
if code, err := u.ReadFile(filename); err == nil {
|
|
return PreCompile(code, filename, logger)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
func PreCompile(code, filename string, logger *log.Logger) (*PreCompiledCode, error) {
|
|
if logger == nil {
|
|
logger = log.DefaultLogger
|
|
}
|
|
cc := PreCompiledCode{
|
|
FixedCode: code,
|
|
Plugins: make([]RequirePlugins, 0),
|
|
Filename: filename,
|
|
CodeLines: make(map[string][]string),
|
|
RealCodeLines: make(map[string][]string),
|
|
Imports: make(map[string]string),
|
|
ImportedVar: make(map[string]string),
|
|
}
|
|
var outErr error
|
|
|
|
tryPlugins := map[string]bool{}
|
|
//fmt.Println(filename, "===============\n", cc.FixedCode, "\n=================")
|
|
cc.FixedCode = importMatcher.ReplaceAllStringFunc(cc.FixedCode, func(importStr string) string {
|
|
m := importMatcher.FindStringSubmatch(importStr)
|
|
importVar := cc.ImportedVar[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 {
|
|
if plugin.Get(m[2]) != nil {
|
|
isTS = true
|
|
} else {
|
|
jsFile += ".js"
|
|
}
|
|
}
|
|
if isTS {
|
|
if plg := plugin.Get(m[2]); plg != nil {
|
|
tryPlugins[m[2]] = true
|
|
return "let " + m[1] + " = " + fixPluginId(m[2])
|
|
} else {
|
|
logger.Error("unknown plugin: " + m[2])
|
|
return ""
|
|
}
|
|
} else {
|
|
if varName, importCode, importFile, searchList, err := makeImport(m[2], filepath.Dir(cc.Filename), &cc.ImportedVar, logger); err == nil {
|
|
if varName != "" && importCode != "" && importFile != "" {
|
|
cc.Imports[importFile] = importCode
|
|
// merge plugins from new import
|
|
cc2, err := PreCompile(importCode, importFile, logger)
|
|
if err != nil {
|
|
outErr = err
|
|
}
|
|
for _, plg2 := range cc2.Plugins {
|
|
found := false
|
|
for _, plg1 := range cc.Plugins {
|
|
if plg2.Id == plg1.Id {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
cc.Plugins = append(cc.Plugins, plg2)
|
|
}
|
|
}
|
|
// merge import code from new import
|
|
for impFilename, impCode := range cc2.Imports {
|
|
if cc.Imports[impFilename] == "" {
|
|
cc.Imports[impFilename] = impCode
|
|
}
|
|
}
|
|
// merge code lines from new import
|
|
for codeFilename, codeLines := range cc2.CodeLines {
|
|
if cc.CodeLines[codeFilename] == nil {
|
|
cc.CodeLines[codeFilename] = codeLines
|
|
cc.RealCodeLines[codeFilename] = cc2.RealCodeLines[codeFilename]
|
|
}
|
|
}
|
|
return "let " + m[1] + " = " + varName
|
|
} else {
|
|
// import 未知模块是保留代码
|
|
//return "throw new Error('import file not found: " + jsFile + " in [" + strings.Join(searchList, ", ") + "]')"
|
|
return importStr
|
|
}
|
|
} else {
|
|
outErr = err
|
|
return "throw new Error('import file not found: " + jsFile + " in [" + strings.Join(searchList, ", ") + "]')"
|
|
}
|
|
}
|
|
} else {
|
|
return "let " + m[1] + " = " + importVar
|
|
}
|
|
})
|
|
//fmt.Println(filename, "^^^^^^^^^^^^^^^^^^^\n", cc.FixedCode, "\n^^^^^^^^^^^^^^^^^^^==")
|
|
|
|
cc.FixedCode = flowMethodTypeMatcher.ReplaceAllString(cc.FixedCode, ") {")
|
|
cc.FixedCode = functionArgsForFlowMatcher.ReplaceAllStringFunc(cc.FixedCode, func(str string) string {
|
|
return flowVarTypeMatcher.ReplaceAllString(str, "$1 $3")
|
|
})
|
|
|
|
for _, plg := range plugin.List() {
|
|
if tryPlugins[plg.Id] {
|
|
if plg.JsCode != "" {
|
|
cc.CodeLines[plg.Id+".js"] = strings.Split(plg.JsCode, "\n")
|
|
cc.RealCodeLines[plg.Id+".js"] = strings.Split(plg.JsCode, "\n")
|
|
}
|
|
cc.Plugins = append(cc.Plugins, RequirePlugins{
|
|
Id: plg.Id,
|
|
Objects: plg.Objects,
|
|
JsCode: plg.JsCode,
|
|
})
|
|
}
|
|
}
|
|
|
|
cc.CodeLines[cc.Filename] = strings.Split(code, "\n")
|
|
cc.RealCodeLines[cc.Filename] = strings.Split(cc.FixedCode, "\n")
|
|
return &cc, outErr
|
|
}
|
|
|
|
func (rt *JSRuntime) ExecPreCompiled(cc *PreCompiledCode) (jsErr *JSError) {
|
|
_, err := rt.runPreCompiled(cc, false, "")
|
|
return err
|
|
}
|
|
|
|
func (rt *JSRuntime) StartPreCompiled(cc *PreCompiledCode) (jsErr *JSError) {
|
|
err := rt.ExecPreCompiled(cc)
|
|
rt.JsCtx.Loop()
|
|
return err
|
|
}
|
|
|
|
func (rt *JSRuntime) RunPreCompiled(cc *PreCompiledCode) (out interface{}, jsErr *JSError) {
|
|
return rt.runPreCompiled(cc, true, "")
|
|
}
|
|
|
|
func (rt *JSRuntime) runPreCompiled(cc *PreCompiledCode, isClosure bool, setToVar string) (out interface{}, jsErr *JSError) {
|
|
fixedCode := cc.FixedCode
|
|
if isClosure {
|
|
if setToVar == "" {
|
|
fixedCode = "(function(){" + fixedCode + "})()"
|
|
} else {
|
|
fixedCode = "let " + setToVar + " = (function(){" + fixedCode + "})()"
|
|
}
|
|
}
|
|
for _, plg := range cc.Plugins {
|
|
plgImpVar := fixPluginId(plg.Id)
|
|
rt.JsCtx.Globals().Set(plgImpVar, MakeJsValueForPlugin(rt.GoCtx, plg.Objects, plg.Id, false))
|
|
if plg.JsCode != "" {
|
|
jsCode := strings.ReplaceAll(plg.JsCode, "${OBJECT}", plgImpVar)
|
|
//if result, err := rt.JsCtx.EvalFile(jsCode, plg.Id+".js"); err != nil {
|
|
if result, err := rt.JsCtx.Eval(jsCode, quickjs.EvalFileName(plg.Id+".js")); err != nil {
|
|
stack := rt.getJSError(err)
|
|
rt.logger.Error(err.Error(), "stack", stack)
|
|
} else {
|
|
result.Free()
|
|
}
|
|
}
|
|
}
|
|
|
|
for impFilename, impCode := range cc.Imports {
|
|
importVar := rt.imported[impFilename]
|
|
if importVar == "" {
|
|
importVar = cc.ImportedVar[impFilename]
|
|
if importVar == "" {
|
|
importVar = fmt.Sprintf("_import_%d_%s", len(rt.imported), u.UniqueId())
|
|
cc.ImportedVar[impFilename] = importVar
|
|
}
|
|
if _, err := rt.run(impCode, true, importVar, impFilename); err != nil {
|
|
return nil, &JSError{error: err}
|
|
}
|
|
rt.imported[impFilename] = importVar
|
|
}
|
|
}
|
|
|
|
//if r, err := rt.JsCtx.EvalFile(fixedCode, cc.Filename); err == nil {
|
|
if r, err := rt.JsCtx.Eval(fixedCode, quickjs.EvalFileName(cc.Filename)); err == nil {
|
|
result := MakeFromJsValue(r, rt.GoCtx)
|
|
r.Free()
|
|
return result, nil
|
|
} else {
|
|
// 检查错误
|
|
stack := rt.getJSError(err)
|
|
//stack2 := getJSError(err, fixedCode)
|
|
rt.logger.Error(err.Error(), "stack", stack) //, "stack2", stack2)
|
|
return nil, &JSError{error: err}
|
|
}
|
|
}
|
|
|
|
func (rt *JSRuntime) run(code string, isClosure bool, setToVar string, fromFilename string) (out interface{}, jsErr *JSError) {
|
|
cc, _ := PreCompile(code, fromFilename, rt.logger)
|
|
return rt.runPreCompiled(cc, isClosure, setToVar)
|
|
|
|
//tryPlugins := map[string]bool{}
|
|
//fixedCode := ""
|
|
//if isClosure {
|
|
// if setToVar == "" {
|
|
// fixedCode = "(function(){" + code + "})()"
|
|
// } else {
|
|
// fixedCode = "let " + setToVar + " = (function(){" + code + "})()"
|
|
// }
|
|
//} else {
|
|
// fixedCode = code
|
|
//}
|
|
//fixedCode = importMatcher.ReplaceAllStringFunc(fixedCode, 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 {
|
|
// if plugin.Get(m[2]) != nil {
|
|
// isTS = true
|
|
// } else {
|
|
// jsFile += ".js"
|
|
// }
|
|
// }
|
|
// if isTS {
|
|
// if plg := plugin.Get(m[2]); plg != nil {
|
|
// tryPlugins[m[2]] = true
|
|
// return "let " + m[1] + " = " + fixPluginId(m[2])
|
|
// } else {
|
|
// rt.logger.Error("unknown plugin: " + m[2])
|
|
// return ""
|
|
// }
|
|
// } else {
|
|
// if varName, searchList, err := rt.Import(m[2]); err == nil {
|
|
// return "let " + m[1] + " = " + varName
|
|
// } else {
|
|
// return "throw new Error('import file not found: " + jsFile + " in [" + strings.Join(searchList, ", ") + "]')"
|
|
// }
|
|
// }
|
|
// } else {
|
|
// return "let " + m[1] + " = " + importVar
|
|
// }
|
|
//})
|
|
//
|
|
//fixedCode = flowMethodTypeMatcher.ReplaceAllString(fixedCode, ") {")
|
|
//fixedCode = functionArgsForFlowMatcher.ReplaceAllStringFunc(fixedCode, func(str string) string {
|
|
// //if flowVarTypeMatcher.MatchString(str) {
|
|
// // fmt.Println(">>>>>>>>", str, u.BCyan(flowVarTypeMatcher.ReplaceAllString(str, "$1 $3")))
|
|
// //}
|
|
// return flowVarTypeMatcher.ReplaceAllString(str, "$1 $3")
|
|
//})
|
|
////tryPlugins := map[string]bool{}
|
|
////for _, m := range pluginNameMatcher.FindAllStringSubmatch(fixedCode, 1024) {
|
|
//// tryPlugins[m[1]] = true
|
|
////}
|
|
//
|
|
//for _, plg := range plugin.List() {
|
|
// if tryPlugins[plg.Id] {
|
|
// rt.JsCtx.Globals().Set(fixPluginId(plg.Id), MakeJsValueForPlugin(rt.GoCtx, plg.Objects, plg.Id, false))
|
|
// if plg.JsCode != "" {
|
|
// rt.codeLines[plg.Id+".js"] = strings.Split(plg.JsCode, "\n")
|
|
// rt.realCodeLines[plg.Id+".js"] = strings.Split(plg.JsCode, "\n")
|
|
// if result, err := rt.JsCtx.EvalFile(plg.JsCode, plg.Id+".js"); err != nil {
|
|
// stack := rt.getJSError(err)
|
|
// rt.logger.Error(err.Error(), "stack", stack)
|
|
// } else {
|
|
// result.Free()
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
//
|
|
//rt.codeLines[fromFilename] = strings.Split(code, "\n")
|
|
//rt.realCodeLines[fromFilename] = strings.Split(fixedCode, "\n")
|
|
//if r, err := rt.JsCtx.EvalFile(fixedCode, fromFilename); err == nil {
|
|
// result := MakeFromJsValue(r, rt.GoCtx)
|
|
// r.Free()
|
|
// return result, nil
|
|
//} else {
|
|
// // 检查错误
|
|
// stack := rt.getJSError(err)
|
|
// //stack2 := getJSError(err, fixedCode)
|
|
// rt.logger.Error(err.Error(), "stack", stack) //, "stack2", stack2)
|
|
// return nil, &JSError{error: err}
|
|
//}
|
|
}
|
|
|
|
func (rt *JSRuntime) Exec(code string) (jsErr *JSError) {
|
|
return rt.ExecAt(code, "")
|
|
}
|
|
|
|
func (rt *JSRuntime) ExecAt(code string, dir string) (jsErr *JSError) {
|
|
rt.anonymousIndex++
|
|
filename := filepath.Join(dir, fmt.Sprintf("anonymous%d.js", rt.anonymousIndex))
|
|
return rt.ExecAtFile(code, filename)
|
|
}
|
|
|
|
func (rt *JSRuntime) ExecFile(filename string) (jsErr *JSError) {
|
|
if code, err := u.ReadFile(filename); err == nil {
|
|
return rt.ExecAtFile(code, filename)
|
|
} else {
|
|
rt.logger.Error(err.Error())
|
|
return &JSError{error: err}
|
|
}
|
|
}
|
|
|
|
func (rt *JSRuntime) ExecAtFile(code string, filename string) (jsErr *JSError) {
|
|
if filename == "" {
|
|
rt.currentPath = rt.rootPath
|
|
} else {
|
|
if !filepath.IsAbs(filename) {
|
|
filename, _ = filepath.Abs(filename)
|
|
}
|
|
rt.currentPath = filepath.Dir(filename)
|
|
}
|
|
_, jsErr = rt.run(code, false, "", filename)
|
|
return jsErr
|
|
}
|
|
|
|
func (rt *JSRuntime) Start(code string) (jsErr *JSError) {
|
|
err := rt.ExecAt(code, "")
|
|
rt.JsCtx.Loop()
|
|
return err
|
|
}
|
|
|
|
func (rt *JSRuntime) StartAt(code string, dir string) (jsErr *JSError) {
|
|
err := rt.ExecAt(code, dir)
|
|
rt.JsCtx.Loop()
|
|
return err
|
|
}
|
|
|
|
func (rt *JSRuntime) StartFile(filename string) (jsErr *JSError) {
|
|
err := rt.ExecFile(filename)
|
|
rt.JsCtx.Loop()
|
|
return err
|
|
}
|
|
|
|
func (rt *JSRuntime) StartAtFile(code string, filename string) (jsErr *JSError) {
|
|
err := rt.ExecAtFile(code, filename)
|
|
rt.JsCtx.Loop()
|
|
return err
|
|
}
|
|
|
|
func (rt *JSRuntime) Run(code string) (out interface{}, jsErr *JSError) {
|
|
return rt.RunAt(code, "")
|
|
}
|
|
|
|
func (rt *JSRuntime) RunAt(code string, dir string) (out interface{}, jsErr *JSError) {
|
|
rt.anonymousIndex++
|
|
filename := filepath.Join(dir, fmt.Sprintf("anonymous%d.js", rt.anonymousIndex))
|
|
return rt.RunAtFile(code, filename)
|
|
}
|
|
|
|
func (rt *JSRuntime) RunFile(filename string) (out interface{}, jsErr *JSError) {
|
|
rt.currentPath = filepath.Dir(filename)
|
|
if code, err := u.ReadFile(filename); err == nil {
|
|
return rt.RunAtFile(code, filename)
|
|
} else {
|
|
rt.logger.Error(err.Error())
|
|
return nil, &JSError{error: err}
|
|
}
|
|
}
|
|
|
|
func (rt *JSRuntime) RunAtFile(code string, filename string) (out interface{}, jsErr *JSError) {
|
|
if filename == "" {
|
|
rt.currentPath = rt.rootPath
|
|
} else {
|
|
if !filepath.IsAbs(filename) {
|
|
filename, _ = filepath.Abs(filename)
|
|
}
|
|
rt.currentPath = filepath.Dir(filename)
|
|
}
|
|
return rt.run(code, true, "", filename)
|
|
}
|
|
|
|
func appendSearchFiles(list *[]string, dir, moduleName string, hasJsExt bool) string {
|
|
filename := moduleName
|
|
if dir != "" {
|
|
filename = filepath.Join(dir, moduleName)
|
|
}
|
|
if hasJsExt {
|
|
if u.FileExists(filename) {
|
|
return filename
|
|
}
|
|
*list = append(*list, filename)
|
|
} else {
|
|
tryFilename := filename + ".js"
|
|
if u.FileExists(tryFilename) {
|
|
return tryFilename
|
|
}
|
|
*list = append(*list, tryFilename)
|
|
tryFilename = filepath.Join(filename, "index.js")
|
|
if u.FileExists(tryFilename) {
|
|
return tryFilename
|
|
}
|
|
*list = append(*list, tryFilename)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
//func (rt *JSRuntime) Import(moduleName string) (varName string, searchList []string, jsErr *JSError) {
|
|
// searchList = make([]string, 0)
|
|
//
|
|
// // if imported
|
|
// importVar := rt.imported[moduleName]
|
|
// if importVar != "" {
|
|
// return importVar, searchList, nil
|
|
// }
|
|
//
|
|
// // check imports set
|
|
// importCode := "" // rt.imports[moduleName]
|
|
// importFile := ""
|
|
//
|
|
// // search file
|
|
// //if importCode == "" {
|
|
// hasJsExt := strings.HasSuffix(moduleName, ".js")
|
|
// importFile = appendSearchFiles(&searchList, "", moduleName, hasJsExt)
|
|
// if importFile == "" && !filepath.IsAbs(moduleName) {
|
|
// importFile = appendSearchFiles(&searchList, rt.currentPath, moduleName, hasJsExt)
|
|
// if importFile == "" && rt.rootPath != rt.currentPath {
|
|
// importFile = appendSearchFiles(&searchList, rt.rootPath, moduleName, hasJsExt)
|
|
// }
|
|
// usedNodeModuleDir := ""
|
|
// if importFile == "" {
|
|
// parentPath := rt.currentPath
|
|
// for i := 0; i < 100; i++ {
|
|
// nodeModuleDir := filepath.Join(parentPath, "node_modules")
|
|
// if u.FileExists(nodeModuleDir) {
|
|
// importFile = appendSearchFiles(&searchList, nodeModuleDir, moduleName, hasJsExt)
|
|
// break
|
|
// }
|
|
// parentPath = filepath.Dir(parentPath)
|
|
// if parentPath == "" || parentPath == "." {
|
|
// break
|
|
// }
|
|
// }
|
|
// }
|
|
// if importFile == "" && rt.rootPath != rt.currentPath {
|
|
// parentPath := rt.rootPath
|
|
// for i := 0; i < 100; i++ {
|
|
// nodeModuleDir := filepath.Join(parentPath, "node_modules")
|
|
// if u.FileExists(nodeModuleDir) {
|
|
// if nodeModuleDir != usedNodeModuleDir {
|
|
// importFile = appendSearchFiles(&searchList, nodeModuleDir, moduleName, hasJsExt)
|
|
// }
|
|
// break
|
|
// }
|
|
// parentPath = filepath.Dir(parentPath)
|
|
// if parentPath == "" || parentPath == "." {
|
|
// break
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// if importFile != "" {
|
|
// if !filepath.IsAbs(importFile) {
|
|
// importFile, _ = filepath.Abs(importFile)
|
|
// }
|
|
// importVar = rt.imported[importFile]
|
|
// if importVar != "" {
|
|
// return importVar, searchList, nil
|
|
// }
|
|
//
|
|
// if code, err := u.ReadFile(importFile); err == nil {
|
|
// importCode = code
|
|
// } else {
|
|
// rt.logger.Error(err.Error())
|
|
// }
|
|
// }
|
|
// //}
|
|
//
|
|
// if importCode != "" {
|
|
// importVar = fmt.Sprintf("_import_%d_%s", len(rt.imported), u.UniqueId())
|
|
// rt.imported[importFile] = importVar
|
|
// importCode = exportMatcher.ReplaceAllStringFunc(importCode, func(exportStr string) string {
|
|
// if strings.Contains(exportStr, "export default") {
|
|
// exportStr = strings.Replace(exportStr, "export default", "return", 1)
|
|
// }
|
|
// exportStr = strings.Replace(exportStr, "export", "return", 1)
|
|
// return exportStr
|
|
// })
|
|
// if _, err := rt.run(importCode, true, importVar, importFile); err == nil {
|
|
// return importVar, searchList, nil
|
|
// } else {
|
|
// return "", searchList, err
|
|
// }
|
|
// }
|
|
// return "", searchList, nil
|
|
//}
|
|
|
|
func SetPluginsConfig(conf map[string]plugin.Config) {
|
|
for _, plg := range plugin.List() {
|
|
if plg.Init != nil {
|
|
plg.Init(conf[plg.Id])
|
|
}
|
|
}
|
|
}
|
|
|
|
func LoadPluginsConfig(filename string) {
|
|
configs := map[string]plugin.Config{}
|
|
if u.LoadYaml(filename, &configs) == nil {
|
|
SetPluginsConfig(configs)
|
|
}
|
|
}
|
|
|
|
func New(option *RuntimeOption) *JSRuntime {
|
|
if option == nil {
|
|
option = &RuntimeOption{nil, nil, false}
|
|
}
|
|
|
|
//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": option.Logger,
|
|
"*quickjs.Context": jsCtx,
|
|
})
|
|
|
|
rt := &JSRuntime{
|
|
//imports: map[string]string{},
|
|
imported: map[string]string{},
|
|
freeJsValues: make([]quickjs.Value, 0),
|
|
rt: jsRt,
|
|
JsCtx: jsCtx,
|
|
GoCtx: goCtx,
|
|
logger: option.Logger,
|
|
codeLines: map[string][]string{},
|
|
realCodeLines: map[string][]string{},
|
|
}
|
|
//if rt.imports["console"] == "" {
|
|
// rt.imports["console"] = "return _console"
|
|
//}
|
|
//if rt.imports["logger"] == "" {
|
|
// rt.imports["logger"] = "return _logger"
|
|
//}
|
|
|
|
rt.GoCtx.SetData("_freeJsValues", &rt.freeJsValues)
|
|
|
|
rt.rootPath, _ = os.Getwd()
|
|
rt.currentPath = rt.rootPath
|
|
//goCtx.SetData("_rootPath", rt.rootPath)
|
|
//goCtx.SetData("_currentPath", rt.rootPath)
|
|
//rt.JsCtx.Globals().Set("_rootPath", jsCtx.String(rt.rootPath))
|
|
//rt.JsCtx.Globals().Set("_currentPath", jsCtx.String(rt.rootPath))
|
|
|
|
// 全局变量
|
|
if option.Globals != nil {
|
|
for k, obj := range option.Globals {
|
|
rt.JsCtx.Globals().Set(k, MakeJsValue(rt.GoCtx, obj, false))
|
|
}
|
|
}
|
|
|
|
return rt
|
|
}
|
|
|
|
func makeMapToArray(args *map[string]interface{}) []interface{} {
|
|
outArgs := make([]interface{}, 0)
|
|
if args != nil {
|
|
for k, v := range *args {
|
|
outArgs = append(outArgs, k, v)
|
|
}
|
|
}
|
|
return outArgs
|
|
}
|
|
|
|
func makeStringArray(args []interface{}, color u.TextColor, bg u.BgColor) []interface{} {
|
|
stringArgs := make([]interface{}, len(args))
|
|
for i, v := range args {
|
|
if color != u.TextNone || bg != u.BgNone {
|
|
stringArgs[i] = u.Color(u.StringP(v), color, bg)
|
|
} else {
|
|
stringArgs[i] = u.StringP(v)
|
|
}
|
|
}
|
|
return stringArgs
|
|
}
|
|
|
|
func Run(code string, option *RuntimeOption) (out interface{}, jsErr *JSError) {
|
|
rt := New(option)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
rt.logger.Error(u.String(err))
|
|
}
|
|
rt.Close()
|
|
}()
|
|
return rt.Run(code)
|
|
}
|
|
|
|
func RunAt(code, dir string, option *RuntimeOption) (out interface{}, jsErr *JSError) {
|
|
rt := New(option)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
rt.logger.Error(u.String(err))
|
|
}
|
|
rt.Close()
|
|
}()
|
|
return rt.RunAt(code, dir)
|
|
}
|
|
|
|
func RunFile(filename string, option *RuntimeOption) (out interface{}, jsErr *JSError) {
|
|
rt := New(option)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
rt.logger.Error(u.String(err))
|
|
}
|
|
rt.Close()
|
|
}()
|
|
return rt.RunFile(filename)
|
|
}
|
|
|
|
func RunPreCompiled(cc *PreCompiledCode, option *RuntimeOption) (out interface{}, jsErr *JSError) {
|
|
rt := New(option)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
rt.logger.Error(u.String(err))
|
|
}
|
|
rt.Close()
|
|
}()
|
|
return rt.RunPreCompiled(cc)
|
|
}
|
|
|
|
func Start(code string, option *RuntimeOption) *JSError {
|
|
rt := New(option)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
rt.logger.Error(u.String(err))
|
|
}
|
|
rt.Close()
|
|
}()
|
|
return rt.Start(code)
|
|
}
|
|
|
|
func StartAt(code, dir string, option *RuntimeOption) *JSError {
|
|
rt := New(option)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
rt.logger.Error(u.String(err))
|
|
}
|
|
rt.Close()
|
|
}()
|
|
return rt.StartAt(code, dir)
|
|
}
|
|
|
|
func StartFile(filename string, option *RuntimeOption) *JSError {
|
|
rt := New(option)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
rt.logger.Error(u.String(err))
|
|
}
|
|
rt.Close()
|
|
}()
|
|
return rt.StartFile(filename)
|
|
}
|
|
|
|
func StartPreCompiled(cc *PreCompiledCode, option *RuntimeOption) *JSError {
|
|
rt := New(option)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
rt.logger.Error(u.String(err))
|
|
}
|
|
rt.Close()
|
|
}()
|
|
return rt.StartPreCompiled(cc)
|
|
}
|
|
|
|
var jsErrorCodeMatcher = regexp.MustCompile(`([\w./\\\-]+):(\d+)`)
|
|
|
|
func (rt *JSRuntime) getJSError(err error) string {
|
|
if err != nil {
|
|
var jsErr *quickjs.Error
|
|
if errors.As(err, &jsErr) {
|
|
// 在错误信息中加入代码
|
|
return jsErrorCodeMatcher.ReplaceAllStringFunc(jsErr.Stack, func(s2 string) string {
|
|
m := jsErrorCodeMatcher.FindStringSubmatch(s2)
|
|
filename := m[1]
|
|
errorLineNumber := u.Int(m[2])
|
|
errorLine := ""
|
|
codeLines := rt.codeLines[filename]
|
|
realCodeLines := rt.realCodeLines[filename]
|
|
realCodeLineStr := ""
|
|
if codeLines != nil && len(codeLines) >= errorLineNumber {
|
|
errorLine = strings.TrimSpace(codeLines[errorLineNumber-1])
|
|
realErrorLine := strings.TrimSpace(realCodeLines[errorLineNumber-1])
|
|
if errorLine != realErrorLine {
|
|
realCodeLineStr = " > ```" + realErrorLine + "```"
|
|
}
|
|
}
|
|
return s2 + " ```" + errorLine + "```" + realCodeLineStr
|
|
})
|
|
} else {
|
|
return err.Error()
|
|
}
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|