pre migrate

This commit is contained in:
2026-01-28 20:58:26 -05:00
parent a613283539
commit 513674b0c8
11 changed files with 206 additions and 263 deletions

View File

@@ -0,0 +1,17 @@
package runtime
import "io"
type RuntimeOption func(*Runtime)
func WithStdout(stdout io.Writer) RuntimeOption {
return func(r *Runtime) {
r.stdout = stdout
}
}
func WithStderr(stderr io.Writer) RuntimeOption {
return func(r *Runtime) {
r.stderr = stderr
}
}

View File

@@ -5,10 +5,13 @@ import (
"fmt"
"io"
"os"
"strings"
"github.com/evanw/esbuild/pkg/api"
"modernc.org/quickjs"
"reichard.io/poiesis/internal/functions"
_ "reichard.io/poiesis/internal/stdlib"
)
type Runtime struct {
@@ -19,7 +22,7 @@ type Runtime struct {
stderr io.Writer
}
func New(ctx context.Context) (*Runtime, error) {
func New(ctx context.Context, opts ...RuntimeOption) (*Runtime, error) {
// Create VM
vm, err := quickjs.NewVM()
if err != nil {
@@ -33,6 +36,11 @@ func New(ctx context.Context) (*Runtime, error) {
return nil, err
}
// Apply Options
for _, opt := range opts {
opt(r)
}
return r, nil
}
@@ -59,27 +67,18 @@ func (r *Runtime) populateGlobals() error {
}
// Register Custom Functions
for name, builtin := range functions.GetRegisteredFunctions() {
for name, fn := range functions.GetRegisteredFunctions() {
// Register Main Function
if err := r.vm.RegisterFunc(name, builtin.WrapFn(r.ctx), false); err != nil {
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.
if _, err := r.vm.Eval(fmt.Sprintf(`
(function() {
const original = globalThis[%q];
globalThis[%q] = function(...args) {
const [result, error] = original.apply(this, args);
if (error) {
throw new Error(error);
}
return result;
};
})();
`, name, name), quickjs.EvalGlobal); err != nil {
wrappedFunc := wrapFunc(fn.Name(), fn.IsAsync())
if _, err := r.vm.Eval(wrappedFunc, quickjs.EvalGlobal); err != nil {
return err
}
}
@@ -87,89 +86,80 @@ func (r *Runtime) populateGlobals() error {
return nil
}
func (r *Runtime) RunFile(filePath string, stdout, stderr io.Writer) error {
r.stdout = stdout
r.stderr = stderr
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
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) {
func (r *Runtime) RunFile(filePath string) error {
tsFileContent, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("error reading file: %w", err)
return fmt.Errorf("error reading file: %w", err)
}
return r.transformCode(string(tsFileContent)), nil
return r.RunCode(string(tsFileContent))
}
func (r *Runtime) transformCode(tsCode string) *transformResult {
// wrappedCode := `(async () => {
// try {
// ` + tsCode + `
// } catch (err) {
// console.error(err);
// }
// })()`
func (r *Runtime) RunCode(tsCode string) error {
transformedCode, err := r.transformCode(tsCode)
if err != nil {
return err
}
_, err = r.vm.Eval(string(transformedCode), quickjs.EvalGlobal)
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,
Loader: api.LoaderTS,
Target: api.ES2022,
// Format: api.FormatIIFE,
Sourcemap: api.SourceMapNone,
TreeShaking: api.TreeShakingFalse,
})
return &transformResult{
code: string(result.Code),
errors: result.Errors,
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 wrapFunc(funcName string, isAsync bool) string {
if isAsync {
return fmt.Sprintf(`
(function() {
const original = globalThis[%q];
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)
}
return fmt.Sprintf(`
(function() {
const original = globalThis[%q];
globalThis[%q] = function(...args) {
const [result, error] = original.apply(this, args);
if (error) {
throw new Error(error);
}
return result;
};
})();
`, funcName, funcName)
}

View File

@@ -6,19 +6,27 @@ import (
"strings"
"testing"
_ "reichard.io/poiesis/internal/standard"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"modernc.org/quickjs"
"reichard.io/poiesis/internal/functions"
)
type TestArgs struct {
Field1 string `json:"field1"`
}
func (t TestArgs) Validate() error {
return nil
}
func TestExecuteTypeScript(t *testing.T) {
var stdout, stderr bytes.Buffer
rt, err := New(context.Background())
rt, err := New(context.Background(), WithStderr(&stderr), WithStdout(&stdout))
assert.NoError(t, err, "Expected no error")
err = rt.RunFile("../../test_data/test.ts", &stdout, &stderr)
err = rt.RunFile("../../test_data/test.ts")
assert.NoError(t, err, "Expected no error")
assert.Empty(t, stderr.String(), "Expected no error output")
@@ -34,17 +42,47 @@ func TestExecuteTypeScript(t *testing.T) {
assert.GreaterOrEqual(t, len(lines), 3, "Should have at least 3 output lines")
}
func TestFetchBuiltinIntegration(t *testing.T) {
rt, err := New(context.Background())
assert.NoError(t, err, "Expected no error")
func TestAsyncFunctionResolution(t *testing.T) {
functions.RegisterAsyncFunction("resolveTest", func(_ context.Context, args TestArgs) (string, error) {
return "test-result", nil
})
tsContent := `
const result = add({a: 5, b: 10});
console.log("Result:", result);
`
var stdout, stderr bytes.Buffer
err = rt.RunCode(tsContent, &stdout, &stderr)
r, err := New(context.Background())
require.NoError(t, err)
assert.Contains(t, stdout.String(), "Result:")
result, err := r.vm.Eval(`resolveTest("hello")`, quickjs.EvalGlobal)
require.NoError(t, err)
assert.NotNil(t, result)
}
func TestAsyncFunctionRejection(t *testing.T) {
functions.RegisterAsyncFunction("rejectTest", func(_ context.Context, args TestArgs) (string, error) {
return "", assert.AnError
})
r, err := New(context.Background())
require.NoError(t, err)
result, err := r.vm.Eval(`rejectTest({field1: "hello"})`, quickjs.EvalGlobal)
require.NoError(t, err)
assert.NotNil(t, result)
}
func TestNonPromise(t *testing.T) {
functions.RegisterFunction("nonPromiseTest", func(_ context.Context, args TestArgs) (string, error) {
return "sync-result", nil
})
r, err := New(context.Background())
require.NoError(t, err)
result, err := r.vm.Eval(`nonPromiseTest({field1: "hello"})`, quickjs.EvalGlobal)
require.NoError(t, err)
if obj, ok := result.(*quickjs.Object); ok {
var arr []any
if err := obj.Into(&arr); err == nil && len(arr) > 0 {
assert.Equal(t, "sync-result", arr[0])
}
}
}