package runtime import ( "context" "fmt" "os" "strings" "github.com/evanw/esbuild/pkg/api" "github.com/fastschema/qjs" "reichard.io/poiesis/internal/functions" _ "reichard.io/poiesis/internal/stdlib" ) type Runtime struct { ctx *qjs.Context opts qjs.Option } func New(ctx context.Context, opts ...RuntimeOption) (*Runtime, error) { // Create Runtime r := &Runtime{opts: qjs.Option{Context: ctx}} for _, opt := range opts { opt(r) } // Create QuickJS Context rt, err := qjs.New(r.opts) if err != nil { return nil, err } r.ctx = rt.Context() // Populate Globals if err := r.populateGlobals(); err != nil { return nil, err } return r, nil } func (r *Runtime) populateGlobals() error { for name, fn := range functions.GetRegisteredFunctions() { // Register Main Function if fn.IsAsync() { r.ctx.SetAsyncFunc(name, func(this *qjs.This) { qjsVal, err := callFunc(this, fn) if err != nil { _ = this.Promise().Reject(this.Context().NewError(err)) return } _ = this.Promise().Resolve(qjsVal) }) } else { r.ctx.SetFunc(name, func(this *qjs.This) (*qjs.Value, error) { return callFunc(this, fn) }) } } return nil } func (r *Runtime) RunFile(filePath string) error { tsFileContent, err := os.ReadFile(filePath) if err != nil { return fmt.Errorf("error reading file: %w", err) } return r.RunCode(string(tsFileContent)) } func (r *Runtime) RunCode(tsCode string) error { transformedCode, err := r.transformCode(tsCode) if err != nil { return err } result, err := r.ctx.Eval("code.ts", qjs.Code(string(transformedCode))) if result != nil { result.Free() } return err } func (r *Runtime) transformCode(tsCode string) ([]byte, error) { result := api.Transform(tsCode, api.TransformOptions{ Loader: api.LoaderTS, Target: api.ES2022, // Format: api.FormatIIFE, Sourcemap: api.SourceMapNone, TreeShaking: api.TreeShakingFalse, }) if len(result.Errors) > 0 { var allErrs []string for _, e := range result.Errors { allErrs = append(allErrs, e.Text) } return nil, fmt.Errorf("transpilation failed: %s", strings.Join(allErrs, ", ")) } return result.Code, nil } func callFunc(this *qjs.This, fn functions.Function) (*qjs.Value, error) { qjsArgs := this.Args() fnArgs := fn.Arguments() var allArgs []any for i := range min(len(fnArgs), len(qjsArgs)) { rVal, err := qjs.JsArgToGo(qjsArgs[i], fnArgs[i]) if err != nil { panic(err) } allArgs = append(allArgs, rVal.Interface()) } result, err := fn.Call(this.Context(), allArgs) if err != nil { return nil, err } val, err := qjs.ToJsValue(this.Context(), result) if err != nil { return nil, err } return val, nil }