package runtime import ( "fmt" "io" "os" "github.com/evanw/esbuild/pkg/api" "modernc.org/quickjs" "reichard.io/poiesis/internal/builtin" ) type Runtime struct { vm *quickjs.VM stdout io.Writer stderr io.Writer consoleSetup bool } func New() *Runtime { vm, err := quickjs.NewVM() if err != nil { panic(err) } vm.SetCanBlock(true) r := &Runtime{vm: vm, stdout: os.Stdout, stderr: os.Stderr} r.setupConsole() builtin.RegisterBuiltins(vm) return r } func (r *Runtime) setupConsole() { if r.consoleSetup { return } if err := r.vm.StdAddHelpers(); err != nil { panic(fmt.Sprintf("failed to add std helpers: %v", err)) } if err := r.vm.RegisterFunc("customLog", func(args ...any) { for i, arg := range args { if i > 0 { _, _ = fmt.Fprint(r.stdout, " ") } _, _ = fmt.Fprint(r.stdout, arg) } _, _ = fmt.Fprintln(r.stdout) }, false); err != nil { panic(fmt.Sprintf("failed to register customLog: %v", err)) } _, _ = r.vm.Eval("console.log = customLog;", quickjs.EvalGlobal) r.consoleSetup = true } func (r *Runtime) SetOutput(stdout, stderr io.Writer) { r.stdout = stdout r.stderr = stderr r.setupConsole() } func (r *Runtime) RunFile(filePath string, stdout, stderr io.Writer) error { r.stdout = stdout r.stderr = stderr r.setupConsole() content, err := r.transformFile(filePath) if err != nil { _, _ = fmt.Fprintf(stderr, "Error: %v\n", err) return err } if len(content.errors) > 0 { _, _ = fmt.Fprintf(stderr, "Transpilation errors:\n") for _, err := range content.errors { _, _ = fmt.Fprintf(stderr, " %s\n", err.Text) } return fmt.Errorf("transpilation failed") } _, err = r.vm.Eval(content.code, quickjs.EvalGlobal) if err != nil { _, _ = fmt.Fprintf(stderr, "Execution error: %v\n", err) return err } return nil } func (r *Runtime) RunCode(tsCode string, stdout, stderr io.Writer) error { r.stdout = stdout r.stderr = stderr r.setupConsole() content := r.transformCode(tsCode) if len(content.errors) > 0 { _, _ = fmt.Fprintf(stderr, "Transpilation errors:\n") for _, err := range content.errors { _, _ = fmt.Fprintf(stderr, " %s\n", err.Text) } return fmt.Errorf("transpilation failed") } _, err := r.vm.Eval(content.code, quickjs.EvalGlobal) if err != nil { _, _ = fmt.Fprintf(stderr, "Execution error: %v\n", err) return err } return nil } type transformResult struct { code string errors []api.Message } func (r *Runtime) transformFile(filePath string) (*transformResult, error) { tsFileContent, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("error reading file: %w", err) } return r.transformCode(string(tsFileContent)), nil } func (r *Runtime) transformCode(tsCode string) *transformResult { result := api.Transform(tsCode, api.TransformOptions{ Loader: api.LoaderTS, Target: api.ES2022, Format: api.FormatIIFE, Sourcemap: api.SourceMapNone, TreeShaking: api.TreeShakingFalse, }) return &transformResult{ code: string(result.Code), errors: result.Errors, } }