migrate
This commit is contained in:
@@ -3,83 +3,60 @@ package runtime
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
"modernc.org/quickjs"
|
||||
"github.com/fastschema/qjs"
|
||||
|
||||
"reichard.io/poiesis/internal/functions"
|
||||
_ "reichard.io/poiesis/internal/stdlib"
|
||||
)
|
||||
|
||||
type Runtime struct {
|
||||
vm *quickjs.VM
|
||||
ctx context.Context
|
||||
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
ctx *qjs.Context
|
||||
opts qjs.Option
|
||||
}
|
||||
|
||||
func New(ctx context.Context, opts ...RuntimeOption) (*Runtime, error) {
|
||||
// Create VM
|
||||
vm, err := quickjs.NewVM()
|
||||
// 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
|
||||
}
|
||||
vm.SetCanBlock(true)
|
||||
r.ctx = rt.Context()
|
||||
|
||||
// Create Runtime
|
||||
r := &Runtime{vm: vm, ctx: ctx, stdout: os.Stdout, stderr: os.Stderr}
|
||||
// Populate Globals
|
||||
if err := r.populateGlobals(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply Options
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Runtime) populateGlobals() error {
|
||||
// Add Helpers
|
||||
if err := r.vm.StdAddHelpers(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add Log Hook
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if _, err := r.vm.Eval("console.log = customLog;", quickjs.EvalGlobal); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Register Custom Functions
|
||||
for name, fn := range functions.GetRegisteredFunctions() {
|
||||
// Register Main Function
|
||||
if err := r.vm.RegisterFunc(name, func(allArgs ...any) (any, error) {
|
||||
return fn.Call(r.ctx, allArgs)
|
||||
}, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wrap Exception - The QuickJS library does not allow us to throw exceptions, so we
|
||||
// wrap the function with native JS to appropriately throw on error.
|
||||
wrappedFunc := wrapFunc(fn.Name(), fn.IsAsync())
|
||||
if _, err := r.vm.Eval(wrappedFunc, quickjs.EvalGlobal); err != nil {
|
||||
return err
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +78,10 @@ func (r *Runtime) RunCode(tsCode string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.vm.Eval(string(transformedCode), quickjs.EvalGlobal)
|
||||
result, err := r.ctx.Eval("code.ts", qjs.Code(string(transformedCode)))
|
||||
if result != nil {
|
||||
result.Free()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -125,41 +105,28 @@ func (r *Runtime) transformCode(tsCode string) ([]byte, error) {
|
||||
return result.Code, nil
|
||||
}
|
||||
|
||||
func wrapFunc(funcName string, isAsync bool) string {
|
||||
if isAsync {
|
||||
return fmt.Sprintf(`
|
||||
(function() {
|
||||
const original = globalThis[%q];
|
||||
func callFunc(this *qjs.This, fn functions.Function) (*qjs.Value, error) {
|
||||
qjsArgs := this.Args()
|
||||
fnArgs := fn.Arguments()
|
||||
|
||||
globalThis[%q] = function(...args) {
|
||||
console.log("calling")
|
||||
return new Promise((resolve, reject) => {
|
||||
const [result, error] = original.apply(this, args);
|
||||
console.log("result", result)
|
||||
if (error) {
|
||||
console.log("reject")
|
||||
reject(new Error(error));
|
||||
} else {
|
||||
console.log("resolve")
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
`, funcName, funcName)
|
||||
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())
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`
|
||||
(function() {
|
||||
const original = globalThis[%q];
|
||||
result, err := fn.Call(this.Context(), allArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
globalThis[%q] = function(...args) {
|
||||
const [result, error] = original.apply(this, args);
|
||||
if (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
`, funcName, funcName)
|
||||
val, err := qjs.ToJsValue(this.Context(), result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user