package runtime import ( "fmt" "io" "os" "github.com/dop251/goja" "github.com/evanw/esbuild/pkg/api" "reichard.io/poiesis/internal/builtin" ) type Runtime struct { vm *goja.Runtime stdout io.Writer stderr io.Writer } func New() *Runtime { vm := goja.New() r := &Runtime{vm: vm, stdout: os.Stdout, stderr: os.Stderr} r.setupConsole() builtin.RegisterBuiltins(vm) return r } func (r *Runtime) setupConsole() { console := r.vm.NewObject() _ = r.vm.Set("console", console) _ = console.Set("log", func(call goja.FunctionCall) goja.Value { args := call.Arguments for i, arg := range args { if i > 0 { _, _ = fmt.Fprint(r.stdout, " ") } _, _ = fmt.Fprint(r.stdout, arg.String()) } _, _ = fmt.Fprintln(r.stdout) return goja.Undefined() }) } func (r *Runtime) SetOutput(stdout, stderr io.Writer) { r.stdout = stdout r.stderr = stderr consoleObj := r.vm.Get("console") if consoleObj != nil { console := consoleObj.ToObject(r.vm) if console != nil { 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.RunString(content.code) 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.RunString(content.code) 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.ES2020, Format: api.FormatIIFE, Sourcemap: api.SourceMapNone, TreeShaking: api.TreeShakingFalse, }) return &transformResult{ code: string(result.Code), errors: result.Errors, } }