147 lines
3.0 KiB
Go
147 lines
3.0 KiB
Go
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,
|
|
}
|
|
}
|