diff --git a/class.go b/class.go new file mode 100644 index 0000000..b614116 --- /dev/null +++ b/class.go @@ -0,0 +1,125 @@ +package napi + +import ( + "fmt" + "reflect" + + "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" +) + +type Class[T ClassType] struct { + value + Class T +} + +type PropertyAttributes int + +type PropertyDescriptor struct { + Name string + Method Callback + Getter Callback + Setter Callback + Value ValueType + Attributes PropertyAttributes +} + +// Base to class declaration +type ClassType interface { + Contructor(*CallbackInfo) (*Object, error) +} + +var ClassFuncMathod = reflect.TypeFor[Callback]() + +const ( + PropertyAttributesWritable = PropertyAttributes(napi.Writable) + PropertyAttributesEnumerable = PropertyAttributes(napi.Enumerable) + PropertyAttributesConfigurable = PropertyAttributes(napi.Configurable) + PropertyAttributesStatic = PropertyAttributes(napi.Static) + PropertyAttributesDefault = PropertyAttributes(napi.Default) + PropertyAttributesDefaultMethod = PropertyAttributes(napi.DefaultMethod) + PropertyAttributesDefaultJSProperty = PropertyAttributes(napi.DefaultJSProperty) +) + +func CreateClass[T ClassType](env EnvType) (*Class[T], error) { + ptrType := reflect.TypeFor[T]() + switch ptrType.Kind() { + default: + return nil, fmt.Errorf("type %s is not a struct", ptrType.Name()) + case reflect.Pointer: + elem := ptrType.Elem() + if elem.Kind() == reflect.Struct { + return classMount[T](env, elem) + } + fallthrough + case reflect.Struct: + return classMount[T](env, ptrType) + } +} + +func classMount[T ClassType](env EnvType, ptr reflect.Type) (*Class[T], error) { + valueOf := reflect.New(ptr) + + var propertys []*PropertyDescriptor + for methodIndex := range ptr.NumMethod() { + method := ptr.Method(methodIndex) + if method.Type.Kind() != reflect.Func || !method.IsExported() { + continue + } else if !method.Type.Implements(ClassFuncMathod) { + continue + } else if method.Name == "Contructor" { + continue + } + + propertys = append(propertys, &PropertyDescriptor{ + Name: method.Name, + Attributes: PropertyAttributes(PropertyAttributesStatic), + Method: valueOf.Method(methodIndex).Interface().(Callback), + Getter: nil, + Setter: nil, + Value: nil, + }) + } + + var napiAtr []napi.PropertyDescriptor + for _, value := range propertys { + name, err := CreateString(env, value.Name) + if err != nil { + return nil, err + } + + method, err := CreateFunction(env, value.Name, value.Method) + if err != nil { + return nil, err + } + + napiAtr = append(napiAtr, napi.PropertyDescriptor{ + Utf8name: value.Name, + Name: name.NapiValue(), + Method: method.NapiCallback(), + Attributes: napi.PropertyAttributes(value.Attributes), + }) + } + + jsConstruct, err := CreateFunction(env, "constructor", func(ci *CallbackInfo) (ValueType, error) { + res := valueOf.MethodByName("Contructor").Call([]reflect.Value{reflect.ValueOf(ci)}) + obj, err := res[0].Interface().(*Object), res[1].Interface().(error) + return N_APIValue(obj.Env(), obj.NapiValue()), err + }) + if err != nil { + return nil, err + } + + jsValue, status := napi.DefineClass(env.NapiValue(), ptr.Name(), + jsConstruct.fn, + napiAtr, + ) + + if err := status.ToError(); err != nil { + return nil, err + } + + return &Class[T]{ + value: N_APIValue(env, jsValue), + Class: valueOf.Interface().(T), + }, nil +} diff --git a/function.go b/function.go index 84a0ac8..ff279fd 100644 --- a/function.go +++ b/function.go @@ -6,19 +6,44 @@ import ( "sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" ) -type Function struct{ value } +type Function struct { + value + fn napi.Callback +} // Function to call on Javascript caller -type Callback func(env EnvType, this ValueType, args []ValueType) (ValueType, error) +type Callback func(*CallbackInfo) (ValueType, error) + +// Values from [napi.CallbackInfo] +type CallbackInfo struct { + Env EnvType + This ValueType + Args []ValueType + + info napi.CallbackInfo +} + +func (call *CallbackInfo) NewTarget() (ValueType, error) { + v, status := napi.GetNewTarget(call.Env.NapiValue(), call.info) + if err := status.ToError(); err != nil { + return nil, err + } + return N_APIValue(call.Env, v), nil +} // Convert [ValueType] to [*Function] -func ToFunction(o ValueType) *Function { return &Function{o} } +func ToFunction(o ValueType) *Function { return &Function{o, nil} } +// CreateFunction creates a new JavaScript function in the given N-API environment with the specified name and callback. +// The callback is invoked when the JavaScript function is called, receiving a CallbackInfo containing the environment, +// 'this' value, arguments, and callback info. Any errors returned by the callback or panics are converted to JavaScript +// exceptions. If the callback returns nil, the JavaScript 'undefined' value is returned. If the callback returns a value +// of TypeError, it is thrown as a JavaScript exception. func CreateFunction(env EnvType, name string, callback Callback) (*Function, error) { return CreateFunctionNapi(env, name, func(napiEnv napi.Env, info napi.CallbackInfo) napi.Value { env := N_APIEnv(napiEnv) - cbInfo, err := napi.GetCbInfo(napiEnv, info) - if err := err.ToError(); err != nil { + cbInfo, status := napi.GetCbInfo(napiEnv, info) + if err := status.ToError(); err != nil { ThrowError(env, "", err.Error()) return nil } @@ -40,23 +65,21 @@ func CreateFunction(env EnvType, name string, callback Callback) (*Function, err } }() - { - res, err := callback(env, this, args) - switch { - case err != nil: - ThrowError(env, "", err.Error()) + res, err := callback(&CallbackInfo{env, this, args, info}) + switch { + case err != nil: + ThrowError(env, "", err.Error()) + return nil + case res == nil: + und, _ := env.Undefined() + return und.NapiValue() + default: + typeOf, _ := res.Type() + if typeOf == TypeError { + ToError(res).ThrowAsJavaScriptException() return nil - case res == nil: - und, _ := env.Undefined() - return und.NapiValue() - default: - typeOf, _ := res.Type() - if typeOf == TypeError { - ToError(res).ThrowAsJavaScriptException() - return nil - } - return res.NapiValue() } + return res.NapiValue() } }) } @@ -67,7 +90,13 @@ func CreateFunctionNapi(env EnvType, name string, callback napi.Callback) (*Func if err := err.ToError(); err != nil { return nil, err } - return ToFunction(N_APIValue(env, fnCall)), nil + fn := ToFunction(N_APIValue(env, fnCall)) + fn.fn = callback + return fn, nil +} + +func (fn *Function) NapiCallback() napi.Callback { + return fn.fn } func (fn *Function) internalCall(this napi.Value, argc int, argv []napi.Value) (ValueType, error) { diff --git a/internal/examples/asyncWorker/main.go b/internal/examples/asyncWorker/main.go index b3319df..714992c 100644 --- a/internal/examples/asyncWorker/main.go +++ b/internal/examples/asyncWorker/main.go @@ -14,7 +14,7 @@ var waitTime = time.Second * 3 //go:linkname Register sirherobrine23.com.br/Sirherobrine23/napi-go/module.Register func Register(env napi.EnvType, export *napi.Object) { - fn, _ := napi.CreateFunction(env, "", func(env napi.EnvType, _ napi.ValueType, args []napi.ValueType) (napi.ValueType, error) { + fn, _ := napi.CreateFunction(env, "", func(ci *napi.CallbackInfo) (napi.ValueType, error) { var Test *napi.String return napi.CreateAsyncWorker(env, func(env napi.EnvType) { diff --git a/internal/examples/class/main.go b/internal/examples/class/main.go new file mode 100644 index 0000000..bf284ab --- /dev/null +++ b/internal/examples/class/main.go @@ -0,0 +1,29 @@ +package main + +import ( + _ "unsafe" + + _ "sirherobrine23.com.br/Sirherobrine23/napi-go/module" + + "sirherobrine23.com.br/Sirherobrine23/napi-go" +) + +func main() {} + +//go:linkname Register sirherobrine23.com.br/Sirherobrine23/napi-go/module.Register +func Register(env napi.EnvType, export *napi.Object) { + class, err := napi.CreateClass[*ClassTest](env) + if err != nil { + panic(err) + } + + export.Set("class", class) +} + +type ClassTest struct{} + +func (class *ClassTest) Contructor(ci *napi.CallbackInfo) (*napi.Object, error) { + obj, _ := napi.CreateObject(ci.Env) + + return obj, nil +} diff --git a/internal/examples/threadsafe/main.go b/internal/examples/threadsafe/main.go index 03fcbcf..d7246a0 100644 --- a/internal/examples/threadsafe/main.go +++ b/internal/examples/threadsafe/main.go @@ -12,12 +12,12 @@ import ( //go:linkname Register sirherobrine23.com.br/Sirherobrine23/napi-go/module.Register func Register(env napi.EnvType, export *napi.Object) { - jsFunc := napi.Callback(func(env napi.EnvType, this napi.ValueType, args []napi.ValueType) (napi.ValueType, error) { + jsFunc := napi.Callback(func(ci *napi.CallbackInfo) (napi.ValueType, error) { var waitTime = time.Second * 3 - if len(args) == 1 { - if typof, _ := args[0].Type(); typof == napi.TypeNumber { - wait := napi.As[*napi.Number](args[0]) + if len(ci.Args) == 1 { + if typof, _ := ci.Args[0].Type(); typof == napi.TypeNumber { + wait := napi.As[*napi.Number](ci.Args[0]) _waitTime, _ := wait.Int() waitTime = time.Duration(_waitTime) } diff --git a/internal/napi/class.go b/internal/napi/class.go new file mode 100644 index 0000000..4879edd --- /dev/null +++ b/internal/napi/class.go @@ -0,0 +1,27 @@ +package napi + +// #include +import "C" + +import "unsafe" + +func DefineClass(env Env, name string, constructor Callback, Property []PropertyDescriptor) (Value, Status) { + var pro *C.napi_property_descriptor + if len(Property) > 0 { + pro = (*C.napi_property_descriptor)(unsafe.Pointer(&Property[0])) + } + + var result Value + call := &constructor + status := Status(C.napi_define_class( + C.napi_env(env), + C.CString(name), + C.size_t(len(name)), + C.napi_callback(unsafe.Pointer(call)), + nil, + C.size_t(len(Property)), + pro, + (*C.napi_value)(unsafe.Pointer(&result)), + )) + return result, status +} diff --git a/js_func.go b/js_func.go index e7f6c7c..c11abc6 100644 --- a/js_func.go +++ b/js_func.go @@ -37,7 +37,7 @@ func funcOf(env EnvType, ptr reflect.Value) (ValueType, error) { case internalNapi.Callback: // return internal/napi function value return CreateFunctionNapi(env, funcName, v) default: // Convert go function to javascript function - return CreateFunction(env, funcName, func(env EnvType, this ValueType, args []ValueType) (ValueType, error) { + return CreateFunction(env, funcName, func(ci *CallbackInfo) (ValueType, error) { var goFnReturn []reflect.Value in, out, veridict := ptr.Type().NumIn(), ptr.Type().NumOut(), ptr.Type().IsVariadic() @@ -46,9 +46,9 @@ func funcOf(env EnvType, ptr reflect.Value) (ValueType, error) { case in == 0 && out == 0: // only call goFnReturn = ptr.Call([]reflect.Value{}) case !veridict: // call same args - goFnReturn = ptr.Call(goValuesInFunc(ptr, args, false)) + goFnReturn = ptr.Call(goValuesInFunc(ptr, ci.Args, false)) default: // call with slice on end - goFnReturn = ptr.CallSlice(goValuesInFunc(ptr, args, true)) + goFnReturn = ptr.CallSlice(goValuesInFunc(ptr, ci.Args, true)) } // Check for last element is error