WIP: Implements class in napi-go #10

Draft
Sirherobrine23 wants to merge 3 commits from class into main
7 changed files with 239 additions and 29 deletions

125
class.go Normal file
View File

@ -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
}

View File

@ -6,19 +6,44 @@ import (
"sirherobrine23.com.br/Sirherobrine23/napi-go/internal/napi" "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 // 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] // 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) { func CreateFunction(env EnvType, name string, callback Callback) (*Function, error) {
return CreateFunctionNapi(env, name, func(napiEnv napi.Env, info napi.CallbackInfo) napi.Value { return CreateFunctionNapi(env, name, func(napiEnv napi.Env, info napi.CallbackInfo) napi.Value {
env := N_APIEnv(napiEnv) env := N_APIEnv(napiEnv)
cbInfo, err := napi.GetCbInfo(napiEnv, info) cbInfo, status := napi.GetCbInfo(napiEnv, info)
if err := err.ToError(); err != nil { if err := status.ToError(); err != nil {
ThrowError(env, "", err.Error()) ThrowError(env, "", err.Error())
return nil return nil
} }
@ -40,8 +65,7 @@ func CreateFunction(env EnvType, name string, callback Callback) (*Function, err
} }
}() }()
{ res, err := callback(&CallbackInfo{env, this, args, info})
res, err := callback(env, this, args)
switch { switch {
case err != nil: case err != nil:
ThrowError(env, "", err.Error()) ThrowError(env, "", err.Error())
@ -57,7 +81,6 @@ func CreateFunction(env EnvType, name string, callback Callback) (*Function, err
} }
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 { if err := err.ToError(); err != nil {
return nil, err 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) { func (fn *Function) internalCall(this napi.Value, argc int, argv []napi.Value) (ValueType, error) {

View File

@ -14,7 +14,7 @@ var waitTime = time.Second * 3
//go:linkname Register sirherobrine23.com.br/Sirherobrine23/napi-go/module.Register //go:linkname Register sirherobrine23.com.br/Sirherobrine23/napi-go/module.Register
func Register(env napi.EnvType, export *napi.Object) { 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 var Test *napi.String
return napi.CreateAsyncWorker(env, return napi.CreateAsyncWorker(env,
func(env napi.EnvType) { func(env napi.EnvType) {

View File

@ -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
}

View File

@ -12,12 +12,12 @@ import (
//go:linkname Register sirherobrine23.com.br/Sirherobrine23/napi-go/module.Register //go:linkname Register sirherobrine23.com.br/Sirherobrine23/napi-go/module.Register
func Register(env napi.EnvType, export *napi.Object) { 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 var waitTime = time.Second * 3
if len(args) == 1 { if len(ci.Args) == 1 {
if typof, _ := args[0].Type(); typof == napi.TypeNumber { if typof, _ := ci.Args[0].Type(); typof == napi.TypeNumber {
wait := napi.As[*napi.Number](args[0]) wait := napi.As[*napi.Number](ci.Args[0])
_waitTime, _ := wait.Int() _waitTime, _ := wait.Int()
waitTime = time.Duration(_waitTime) waitTime = time.Duration(_waitTime)
} }

27
internal/napi/class.go Normal file
View File

@ -0,0 +1,27 @@
package napi
// #include <node/node_api.h>
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
}

View File

@ -37,7 +37,7 @@ func funcOf(env EnvType, ptr reflect.Value) (ValueType, error) {
case internalNapi.Callback: // return internal/napi function value case internalNapi.Callback: // return internal/napi function value
return CreateFunctionNapi(env, funcName, v) return CreateFunctionNapi(env, funcName, v)
default: // Convert go function to javascript function 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 var goFnReturn []reflect.Value
in, out, veridict := ptr.Type().NumIn(), ptr.Type().NumOut(), ptr.Type().IsVariadic() 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 case in == 0 && out == 0: // only call
goFnReturn = ptr.Call([]reflect.Value{}) goFnReturn = ptr.Call([]reflect.Value{})
case !veridict: // call same args 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 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 // Check for last element is error