wip3
This commit is contained in:
327
internal/runtime/pkg/builtin/builtin.go
Normal file
327
internal/runtime/pkg/builtin/builtin.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type Builtin struct {
|
||||
Name string
|
||||
Function any
|
||||
Definition string
|
||||
}
|
||||
|
||||
var (
|
||||
builtinRegistry = make(map[string]Builtin)
|
||||
registryMutex sync.RWMutex
|
||||
customConverters = make(map[reflect.Type]func(*goja.Runtime, reflect.Value) goja.Value)
|
||||
)
|
||||
|
||||
func RegisterBuiltin(name string, fn any) {
|
||||
fnValue := reflect.ValueOf(fn)
|
||||
fnType := fnValue.Type()
|
||||
|
||||
wrapper := createGenericWrapper(fnValue, fnType)
|
||||
definition := generateTypeScriptDefinition(name, fnType)
|
||||
|
||||
registryMutex.Lock()
|
||||
builtinRegistry[name] = Builtin{
|
||||
Name: name,
|
||||
Function: wrapper,
|
||||
Definition: definition,
|
||||
}
|
||||
registryMutex.Unlock()
|
||||
}
|
||||
|
||||
func RegisterCustomConverter[T any](converter func(vm *goja.Runtime, value T) goja.Value) {
|
||||
var t T
|
||||
typeOf := reflect.TypeOf(t)
|
||||
|
||||
registryMutex.Lock()
|
||||
wrappedConverter := func(vm *goja.Runtime, value reflect.Value) goja.Value {
|
||||
return converter(vm, value.Interface().(T))
|
||||
}
|
||||
customConverters[typeOf] = wrappedConverter
|
||||
|
||||
if typeOf.Kind() == reflect.Pointer {
|
||||
elemType := typeOf.Elem()
|
||||
customConverters[elemType] = func(vm *goja.Runtime, value reflect.Value) goja.Value {
|
||||
if value.IsNil() {
|
||||
return goja.Null()
|
||||
}
|
||||
return converter(vm, value.Interface().(T))
|
||||
}
|
||||
}
|
||||
registryMutex.Unlock()
|
||||
}
|
||||
|
||||
func createGenericWrapper(fnValue reflect.Value, fnType reflect.Type) any {
|
||||
return func(vm *goja.Runtime) any {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
args := make([]reflect.Value, fnType.NumIn())
|
||||
|
||||
for i := 0; i < fnType.NumIn(); i++ {
|
||||
argType := fnType.In(i)
|
||||
var jsArg goja.Value
|
||||
|
||||
if i < len(call.Arguments) {
|
||||
jsArg = call.Arguments[i]
|
||||
} else {
|
||||
jsArg = goja.Undefined()
|
||||
}
|
||||
|
||||
if goja.IsUndefined(jsArg) || goja.IsNull(jsArg) {
|
||||
if argType.Kind() == reflect.Map {
|
||||
args[i] = reflect.MakeMap(argType)
|
||||
continue
|
||||
}
|
||||
if argType.Kind() == reflect.Interface {
|
||||
args[i] = reflect.Zero(argType)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
converted, err := convertJSValueToGo(vm, jsArg, argType)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("argument %d: %v", i, err))
|
||||
}
|
||||
args[i] = reflect.ValueOf(converted)
|
||||
}
|
||||
|
||||
results := fnValue.Call(args)
|
||||
|
||||
if len(results) == 0 {
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
lastResult := results[len(results)-1]
|
||||
if lastResult.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
||||
if !lastResult.IsNil() {
|
||||
panic(fmt.Sprintf("error: %v", lastResult.Interface()))
|
||||
}
|
||||
if len(results) == 1 {
|
||||
return goja.Undefined()
|
||||
}
|
||||
return convertGoValueToJS(vm, results[0])
|
||||
}
|
||||
|
||||
return convertGoValueToJS(vm, results[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func convertJSValueToGo(vm *goja.Runtime, jsValue goja.Value, targetType reflect.Type) (any, error) {
|
||||
if goja.IsUndefined(jsValue) || goja.IsNull(jsValue) {
|
||||
if targetType.Kind() == reflect.Interface || targetType.Kind() == reflect.Pointer {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("cannot convert null/undefined to %v", targetType)
|
||||
}
|
||||
|
||||
switch targetType.Kind() {
|
||||
case reflect.String:
|
||||
return jsValue.String(), nil
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
n, ok := jsValue.Export().(int64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected int, got %T", jsValue.Export())
|
||||
}
|
||||
return reflect.ValueOf(n).Convert(targetType).Interface(), nil
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
n, ok := jsValue.Export().(int64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected uint, got %T", jsValue.Export())
|
||||
}
|
||||
return reflect.ValueOf(uint(n)).Convert(targetType).Interface(), nil
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
n, ok := jsValue.Export().(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected float, got %T", jsValue.Export())
|
||||
}
|
||||
return reflect.ValueOf(n).Convert(targetType).Interface(), nil
|
||||
|
||||
case reflect.Bool:
|
||||
return jsValue.ToBoolean(), nil
|
||||
|
||||
case reflect.Interface:
|
||||
return jsValue.Export(), nil
|
||||
|
||||
case reflect.Map:
|
||||
if targetType.Key().Kind() == reflect.String && targetType.Elem().Kind() == reflect.Interface {
|
||||
obj := jsValue.ToObject(vm)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("not an object")
|
||||
}
|
||||
|
||||
result := make(map[string]any)
|
||||
for _, key := range obj.Keys() {
|
||||
result[key] = obj.Get(key).Export()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported map type: %v", targetType)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %v", targetType)
|
||||
}
|
||||
}
|
||||
|
||||
func convertGoValueToJS(vm *goja.Runtime, goValue reflect.Value) goja.Value {
|
||||
value := goValue.Interface()
|
||||
valueType := goValue.Type()
|
||||
|
||||
registryMutex.RLock()
|
||||
converter, ok := customConverters[valueType]
|
||||
registryMutex.RUnlock()
|
||||
|
||||
if ok {
|
||||
return converter(vm, goValue)
|
||||
}
|
||||
|
||||
if goValue.Kind() == reflect.Pointer && !goValue.IsNil() {
|
||||
elemType := goValue.Type().Elem()
|
||||
registryMutex.RLock()
|
||||
converter, ok := customConverters[elemType]
|
||||
registryMutex.RUnlock()
|
||||
|
||||
if ok {
|
||||
return converter(vm, goValue.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
|
||||
return vm.ToValue(v)
|
||||
|
||||
case error:
|
||||
return vm.ToValue(v.Error())
|
||||
|
||||
case map[string]string:
|
||||
obj := vm.NewObject()
|
||||
for key, val := range v {
|
||||
_ = obj.Set(key, val)
|
||||
}
|
||||
return obj
|
||||
|
||||
case map[string]any:
|
||||
obj := vm.NewObject()
|
||||
for key, val := range v {
|
||||
_ = obj.Set(key, val)
|
||||
}
|
||||
return obj
|
||||
|
||||
case []any:
|
||||
arr := make([]goja.Value, len(v))
|
||||
for i, item := range v {
|
||||
arr[i] = convertGoValueToJS(vm, reflect.ValueOf(item))
|
||||
}
|
||||
return vm.ToValue(arr)
|
||||
|
||||
default:
|
||||
return vm.ToValue(v)
|
||||
}
|
||||
}
|
||||
|
||||
func generateTypeScriptDefinition(name string, fnType reflect.Type) string {
|
||||
if fnType.Kind() != reflect.Func {
|
||||
return ""
|
||||
}
|
||||
|
||||
var params []string
|
||||
for i := 0; i < fnType.NumIn(); i++ {
|
||||
paramName := fmt.Sprintf("arg%d", i)
|
||||
if fnType.In(i).Kind() == reflect.Pointer {
|
||||
ptrType := fnType.In(i).Elem()
|
||||
if ptrType.Kind() == reflect.Struct {
|
||||
if s, ok := extractStructParamName(ptrType); ok {
|
||||
paramName = s
|
||||
}
|
||||
}
|
||||
}
|
||||
params = append(params, fmt.Sprintf("%s: %s", paramName, goTypeToTSType(fnType.In(i))))
|
||||
}
|
||||
|
||||
returnSignature := "void"
|
||||
if fnType.NumOut() > 0 {
|
||||
lastIndex := fnType.NumOut() - 1
|
||||
lastType := fnType.Out(lastIndex)
|
||||
|
||||
if lastType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
|
||||
if fnType.NumOut() > 1 {
|
||||
returnSignature = goTypeToTSType(fnType.Out(0))
|
||||
} else {
|
||||
returnSignature = "void"
|
||||
}
|
||||
} else {
|
||||
returnSignature = goTypeToTSType(lastType)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("declare function %s(%s): %s;", name, strings.Join(params, ", "), returnSignature)
|
||||
}
|
||||
|
||||
func extractStructParamName(structType reflect.Type) (string, bool) {
|
||||
if structType.Name() != "" {
|
||||
return strings.ToLower(structType.Name()), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func goTypeToTSType(t reflect.Type) string {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return "string"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return "number"
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return "number"
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return "number"
|
||||
case reflect.Bool:
|
||||
return "boolean"
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
return "any"
|
||||
case reflect.Slice:
|
||||
return fmt.Sprintf("%s[]", goTypeToTSType(t.Elem()))
|
||||
case reflect.Map:
|
||||
if t.Key().Kind() == reflect.String && t.Elem().Kind() == reflect.Interface {
|
||||
return "Record<string, any>"
|
||||
}
|
||||
return "Record<string, any>"
|
||||
case reflect.Struct:
|
||||
return "any"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
}
|
||||
|
||||
func GetBuiltinsDeclarations() string {
|
||||
registryMutex.RLock()
|
||||
defer registryMutex.RUnlock()
|
||||
|
||||
var decls []string
|
||||
for _, builtin := range builtinRegistry {
|
||||
decls = append(decls, builtin.Definition)
|
||||
}
|
||||
return strings.Join(decls, "\n")
|
||||
}
|
||||
|
||||
func RegisterBuiltins(vm *goja.Runtime) {
|
||||
registryMutex.RLock()
|
||||
defer registryMutex.RUnlock()
|
||||
|
||||
for name, builtin := range builtinRegistry {
|
||||
if wrapperFactory, ok := builtin.Function.(func(*goja.Runtime) any); ok {
|
||||
_ = vm.Set(name, wrapperFactory(vm))
|
||||
} else {
|
||||
_ = vm.Set(name, builtin.Function)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user