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 "" } }