From e972be8dabda61f001faffde5f80cb5b500d8b63 Mon Sep 17 00:00:00 2001 From: visualfc Date: Mon, 30 Jun 2025 20:55:32 +0800 Subject: [PATCH] runtime/js: js for emscripten --- runtime/js/embind/_wrap/emval.cpp | 213 +++++++++ runtime/js/embind/embind.go | 6 + runtime/js/emval.go | 92 ++++ runtime/js/func.go | 95 ++++ runtime/js/js.go | 707 ++++++++++++++++++++++++++++++ 5 files changed, 1113 insertions(+) create mode 100644 runtime/js/embind/_wrap/emval.cpp create mode 100644 runtime/js/embind/embind.go create mode 100644 runtime/js/emval.go create mode 100644 runtime/js/func.go create mode 100644 runtime/js/js.go diff --git a/runtime/js/embind/_wrap/emval.cpp b/runtime/js/embind/_wrap/emval.cpp new file mode 100644 index 00000000..48da31e5 --- /dev/null +++ b/runtime/js/embind/_wrap/emval.cpp @@ -0,0 +1,213 @@ +#include +#include +#include +#include + +using namespace emscripten; +using namespace emscripten::internal; + +template +TYPEID take_typeid() { + typename WithPolicies<>::template ArgTypeList targetType; + return targetType.getTypes()[0]; +} + +template +EM_VAL take_value(T&& value, Policies...) { + typename WithPolicies::template ArgTypeList valueType; + WireTypePack argv(std::forward(value)); + return _emval_take_value(valueType.getTypes()[0], argv); +} + +template +T as_value(EM_VAL val, Policies...) { + typedef BindingType BT; + typename WithPolicies::template ArgTypeList targetType; + + EM_DESTRUCTORS destructors = nullptr; + EM_GENERIC_WIRE_TYPE result = _emval_as( + val, + targetType.getTypes()[0], + &destructors); + DestructorsRunner dr(destructors); + return fromGenericWireType(result); +} + +struct GoString { + char *data; + int len; +}; + +static TYPEID typeid_val = take_typeid(); + +extern "C" { + +// export from llgo +extern GoString llgo_export_string_from(const char *data, int n); +extern bool llgo_export_invoke(EM_VAL args); + +EM_VAL llgo_emval_get_global(const char *name) { + return _emval_get_global(name); +} + +EM_VAL llgo_emval_new_double(double v) { + return take_value(v); +} + +EM_VAL llgo_emval_new_string(const char *str) { + return _emval_new_u8string(str); +} + +EM_VAL llgo_emval_new_object() { + return _emval_new_object(); +} + +EM_VAL llgo_emval_new_array() { + return _emval_new_array(); +} + +void llgo_emval_incref(EM_VAL value) { + _emval_incref(value); +} + +void llgo_emval_decref(EM_VAL value) { + _emval_decref(value); +} + +void llgo_emval_set_property(EM_VAL object, EM_VAL key, EM_VAL value) { + _emval_set_property(object, key, value); +} + +EM_VAL llgo_emval_get_property(EM_VAL object, EM_VAL key) { + return _emval_get_property(object, key); +} + +bool llgo_emval_is_number(EM_VAL object) { + return _emval_is_number(object); +} + +bool llgo_emval_is_string(EM_VAL object) { + return _emval_is_string(object); +} + +bool llgo_emval_in(EM_VAL item, EM_VAL object) { + return _emval_in(item, object); +} + +bool llgo_emval_delete(EM_VAL object, EM_VAL property) { + return _emval_delete(object, property); +} + +EM_VAL llgo_emval_typeof(EM_VAL value) { + return _emval_typeof(value); +} + +bool llgo_emval_instanceof(EM_VAL object, EM_VAL constructor) { + return _emval_instanceof(object, constructor); +} + +double llgo_emval_as_double(EM_VAL v) { + return as_value(v); +} + +GoString llgo_emval_as_string(EM_VAL v) { + std::string value = as_value(v); + return llgo_export_string_from(value.c_str(), int(value.size())); +} + +bool llgo_emval_equals(EM_VAL first, EM_VAL second) { + return _emval_equals(first, second); +} + +EM_VAL llgo_emval_method_call(EM_VAL object, const char* name, EM_VAL args[], int nargs, int *error) { + std::vector arr; + arr.resize(nargs+1); + std::vector _args; + _args.resize(nargs); + std::vector elements; + elements.resize(nargs); + GenericWireType *cursor = elements.data(); + arr[0] = typeid_val; + for (int i = 0; i < nargs; i++) { + arr[i+1] = typeid_val; + val v = val::take_ownership(args[i]); + _args[i] = v; + writeGenericWireTypes(cursor, v); + } + EM_METHOD_CALLER caller = _emval_get_method_caller(nargs+1,&arr[0],EM_METHOD_CALLER_KIND::FUNCTION); + EM_GENERIC_WIRE_TYPE ret; + try { + EM_DESTRUCTORS destructors = nullptr; + ret = _emval_call_method(caller, object, name, &destructors, elements.data()); + } catch(const emscripten::val& jsErr) { + printf("error\n"); + *error = 1; + return EM_VAL(internal::_EMVAL_UNDEFINED); + } + return fromGenericWireType(ret).release_ownership(); +} + +/* +kind: +FUNCTION = 0, +CONSTRUCTOR = 1, +*/ +EM_VAL llgo_emval_call(EM_VAL fn, EM_VAL args[], int nargs, int kind, int *error) { + std::vector arr; + arr.resize(nargs+1); + std::vector _args; + _args.resize(nargs); + std::vector elements; + elements.resize(nargs); + GenericWireType *cursor = elements.data(); + arr[0] = typeid_val; + for (int i = 0; i < nargs; i++) { + arr[i+1] = typeid_val; + val v = val::take_ownership(args[i]); + _args[i] = v; + writeGenericWireTypes(cursor, v); + } + EM_METHOD_CALLER caller = _emval_get_method_caller(nargs+1,&arr[0],EM_METHOD_CALLER_KIND(kind)); + EM_GENERIC_WIRE_TYPE ret; + try { + EM_DESTRUCTORS destructors = nullptr; + ret = _emval_call(caller, fn, &destructors, elements.data()); + } catch(const emscripten::val& jsErr) { + *error = 1; + return EM_VAL(internal::_EMVAL_UNDEFINED); + } + return fromGenericWireType(ret).release_ownership(); +} + +/* +TYPEID llgo_emval_typeid_void() { + return take_typeid(); +} + +TYPEID llgo_emval_typeid_double() { + return take_typeid(); +} + +TYPEID llgo_emval_typeid_string() { + return take_typeid(); +} + +TYPEID llgo_emval_typeid_val() { + return take_typeid(); +} +*/ + +void llgo_emval_dump(EM_VAL v) { + val console = val::global("console"); + console.call("log", val::take_ownership(v)); +} + +} + +bool invoke(val args) { + return llgo_export_invoke(args.as_handle()); +} + +EMSCRIPTEN_BINDINGS(my_module) { + function("_llgo_invoke", &invoke); +} \ No newline at end of file diff --git a/runtime/js/embind/embind.go b/runtime/js/embind/embind.go new file mode 100644 index 00000000..165b07ea --- /dev/null +++ b/runtime/js/embind/embind.go @@ -0,0 +1,6 @@ +package embind + +const ( + LLGoFiles = "_wrap/emval.cpp" + LLGoPackage = "link: -lembind" +) diff --git a/runtime/js/emval.go b/runtime/js/emval.go new file mode 100644 index 00000000..d8034d50 --- /dev/null +++ b/runtime/js/emval.go @@ -0,0 +1,92 @@ +//go:build js && wasm +// +build js,wasm + +package js + +import ( + _ "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" + _ "github.com/goplus/llgo/runtime/js/embind" +) + +var ( + valueGlobal = emval_get_global(nil) + objectConstructor = emval_get_global(c.Str("Object")) + stringConstructor = emval_get_global(c.Str("String")) + arrayConstructor = emval_get_global(c.Str("Array")) + functionConstructor = emval_get_global(c.Str("Function")) +) + +var ( + valueUndefined = Value{2} + valueNull = Value{4} + valueTrue = Value{6} + valueFalse = Value{8} + valueNaN = emval_get_global(c.Str("NaN")) + valueZero = emval_new_double(0) +) + +//go:linkname emval_get_global C.llgo_emval_get_global +func emval_get_global(name *c.Char) Value + +//go:linkname emval_new_double C.llgo_emval_new_double +func emval_new_double(v float64) Value + +//go:linkname emval_new_string C.llgo_emval_new_string +func emval_new_string(str *c.Char) Value + +//go:linkname emval_new_object C.llgo_emval_new_object +func emval_new_object() Value + +//go:linkname emval_new_array C.llgo_emval_new_array +func emval_new_array() Value + +//go:linkname emval_set_property C.llgo_emval_set_property +func emval_set_property(object Value, key Value, value Value) + +//go:linkname emval_get_property C.llgo_emval_get_property +func emval_get_property(object Value, key Value) Value + +//go:linkname emval_delete C.llgo_emval_delete +func emval_delete(object Value, property Value) bool + +//go:linkname emval_is_number C.llgo_emval_is_number +func emval_is_number(object Value) bool + +//go:linkname emval_is_string C.llgo_emval_is_string +func emval_is_string(object Value) bool + +//go:linkname emval_in C.llgo_emval_in +func emval_in(item Value, object Value) bool + +//go:linkname emval_typeof C.llgo_emval_typeof +func emval_typeof(value Value) Value + +//go:linkname emval_instanceof C.llgo_emval_instanceof +func emval_instanceof(object Value, constructor Value) bool + +//go:linkname emval_as_double C.llgo_emval_as_double +func emval_as_double(v Value) float64 + +//go:linkname emval_as_string C.llgo_emval_as_string +func emval_as_string(v Value) string + +//go:linkname emval_equals C.llgo_emval_equals +func emval_equals(first Value, second Value) bool + +//go:linkname emval_method_call C.llgo_emval_method_call +func emval_method_call(object Value, name *c.Char, args *Value, nargs c.Int, err *c.Int) Value + +// emval_call kind: FUNCTION = 0, CONSTRUCTOR = 1 +// +//go:linkname emval_call C.llgo_emval_call +func emval_call(fn Value, args *Value, nargs c.Int, kind c.Int, err *c.Int) Value + +//go:linkname emval_dump C.llgo_emval_dump +func emval_dump(v Value) + +//export llgo_export_string_from +func llgo_export_string_from(data *c.Char, size c.Int) string { + return c.GoString(data, size) +} diff --git a/runtime/js/func.go b/runtime/js/func.go new file mode 100644 index 00000000..c6f8c2e2 --- /dev/null +++ b/runtime/js/func.go @@ -0,0 +1,95 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build js && wasm +// +build js,wasm + +package js + +import ( + "strconv" + "sync" +) + +var ( + funcsMu sync.Mutex + funcs = make(map[uint32]func(Value, []Value) any) + nextFuncID uint32 = 1 +) + +// Func is a wrapped Go function to be called by JavaScript. +type Func struct { + Value // the JavaScript function that invokes the Go function + id uint32 +} + +// FuncOf returns a function to be used by JavaScript. +// +// The Go function fn is called with the value of JavaScript's "this" keyword and the +// arguments of the invocation. The return value of the invocation is +// the result of the Go function mapped back to JavaScript according to ValueOf. +// +// Invoking the wrapped Go function from JavaScript will +// pause the event loop and spawn a new goroutine. +// Other wrapped functions which are triggered during a call from Go to JavaScript +// get executed on the same goroutine. +// +// As a consequence, if one wrapped function blocks, JavaScript's event loop +// is blocked until that function returns. Hence, calling any async JavaScript +// API, which requires the event loop, like fetch (http.Client), will cause an +// immediate deadlock. Therefore a blocking function should explicitly start a +// new goroutine. +// +// Func.Release must be called to free up resources when the function will not be invoked any more. +func FuncOf(fn func(this Value, args []Value) any) Func { + funcsMu.Lock() + id := nextFuncID + nextFuncID++ + funcs[id] = fn + funcsMu.Unlock() + sid := strconv.Itoa(int(id)) + wrap := functionConstructor.New(ValueOf(` + const event = { id:` + sid + `, this: this, args: arguments }; + Module._llgo_invoke(event); + return event.result; + `)) + return Func{ + id: id, + Value: wrap, + } +} + +// Release frees up resources allocated for the function. +// The function must not be invoked after calling Release. +// It is allowed to call Release while the function is still running. +func (c Func) Release() { + funcsMu.Lock() + delete(funcs, c.id) + funcsMu.Unlock() +} + +//export llgo_export_invoke +func llgo_export_invoke(cb Value) bool { + id := uint32(cb.Get("id").Int()) + funcsMu.Lock() + f, ok := funcs[id] + funcsMu.Unlock() + if !ok { + Global().Get("console").Call("error", "call to released function") + return true + } + + // Call the js.Func with arguments + this := cb.Get("this") + argsObj := cb.Get("args") + args := make([]Value, argsObj.Length()) + for i := range args { + args[i] = argsObj.Index(i) + } + result := f(this, args) + + // Return the result to js + cb.Set("result", result) + return true +} diff --git a/runtime/js/js.go b/runtime/js/js.go new file mode 100644 index 00000000..7f4d1bad --- /dev/null +++ b/runtime/js/js.go @@ -0,0 +1,707 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build js && wasm +// +build js,wasm + +// Package js gives access to the WebAssembly host environment when using the js/wasm architecture. +// Its API is based on JavaScript semantics. +// +// This package is EXPERIMENTAL. Its current scope is only to allow tests to run, but not yet to provide a +// comprehensive API for users. It is exempt from the Go compatibility promise. +package js + +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" +) + +// Value represents a JavaScript value. The zero value is the JavaScript value "undefined". +// Values can be checked for equality with the Equal method. +type Value struct { + ref uintptr +} + +func floatValue(f float64) Value { + if f == 0 { + return valueZero + } + if f != f { + return valueNaN + } + return emval_new_double(f) +} + +func intValue(i int) Value { + if i == 0 { + return valueZero + } + return emval_new_double(float64(i)) +} + +// Error wraps a JavaScript error. +type Error struct { + // Value is the underlying JavaScript error value. + Value +} + +// Error implements the error interface. +func (e Error) Error() string { + return "JavaScript error: " + e.Get("message").String() +} + +/* +var ( + valueUndefined = Value{ref: 0} + valueNaN = predefValue(0, typeFlagNone) + valueZero = predefValue(1, typeFlagNone) + valueNull = predefValue(2, typeFlagNone) + valueTrue = predefValue(3, typeFlagNone) + valueFalse = predefValue(4, typeFlagNone) + valueGlobal = predefValue(5, typeFlagObject) + jsGo = predefValue(6, typeFlagObject) // instance of the Go class in JavaScript + + objectConstructor = valueGlobal.Get("Object") + arrayConstructor = valueGlobal.Get("Array") +) +*/ + +// Equal reports whether v and w are equal according to JavaScript's === operator. +func (v Value) Equal(w Value) bool { + return emval_equals(v, w) && v.ref != valueNaN.ref +} + +// Undefined returns the JavaScript value "undefined". +func Undefined() Value { + return valueUndefined +} + +// IsUndefined reports whether v is the JavaScript value "undefined". +func (v Value) IsUndefined() bool { + return v.ref == valueUndefined.ref +} + +// Null returns the JavaScript value "null". +func Null() Value { + return valueNull +} + +// IsNull reports whether v is the JavaScript value "null". +func (v Value) IsNull() bool { + return v.ref == valueNull.ref +} + +// IsNaN reports whether v is the JavaScript value "NaN". +func (v Value) IsNaN() bool { + return v.ref == valueNaN.ref +} + +// Global returns the JavaScript global object, usually "window" or "global". +func Global() Value { + return valueGlobal +} + +// ValueOf returns x as a JavaScript value: +// +// | Go | JavaScript | +// | ---------------------- | ---------------------- | +// | js.Value | [its value] | +// | js.Func | function | +// | nil | null | +// | bool | boolean | +// | integers and floats | number | +// | string | string | +// | []interface{} | new array | +// | map[string]interface{} | new object | +// +// Panics if x is not one of the expected types. +func ValueOf(x any) Value { + switch x := x.(type) { + case Value: + return x + case Func: + return x.Value + case nil: + return valueNull + case bool: + if x { + return valueTrue + } else { + return valueFalse + } + case int: + return floatValue(float64(x)) + case int8: + return floatValue(float64(x)) + case int16: + return floatValue(float64(x)) + case int32: + return floatValue(float64(x)) + case int64: + return floatValue(float64(x)) + case uint: + return floatValue(float64(x)) + case uint8: + return floatValue(float64(x)) + case uint16: + return floatValue(float64(x)) + case uint32: + return floatValue(float64(x)) + case uint64: + return floatValue(float64(x)) + case uintptr: + return floatValue(float64(x)) + case unsafe.Pointer: + return floatValue(float64(uintptr(x))) + case float32: + return floatValue(float64(x)) + case float64: + return floatValue(x) + case string: + return stringVal(x) + case []any: + a := arrayConstructor.New(len(x)) + for i, s := range x { + a.SetIndex(i, s) + } + return a + case map[string]any: + o := objectConstructor.New() + for k, v := range x { + o.Set(k, v) + } + return o + default: + panic("ValueOf: invalid value") + } +} + +func stringVal(x string) Value { + return emval_new_string(c.AllocaCStr(x)) +} + +// Type represents the JavaScript type of a Value. +type Type int + +const ( + TypeUndefined Type = iota + TypeNull + TypeBoolean + TypeNumber + TypeString + TypeSymbol + TypeObject + TypeFunction +) + +func (t Type) String() string { + switch t { + case TypeUndefined: + return "undefined" + case TypeNull: + return "null" + case TypeBoolean: + return "boolean" + case TypeNumber: + return "number" + case TypeString: + return "string" + case TypeSymbol: + return "symbol" + case TypeObject: + return "object" + case TypeFunction: + return "function" + default: + panic("bad type") + } +} + +func (t Type) isObject() bool { + return t == TypeObject || t == TypeFunction +} + +// Type returns the JavaScript type of the value v. It is similar to JavaScript's typeof operator, +// except that it returns TypeNull instead of TypeObject for null. +func (v Value) Type() Type { + switch v.ref { + case valueUndefined.ref: + return TypeUndefined + case valueNull.ref: + return TypeNull + case valueTrue.ref, valueFalse.ref: + return TypeBoolean + } + if emval_is_number(v) { + return TypeNumber + } else if emval_is_string(v) { + return TypeString + } + switch emval_as_string(emval_typeof(v)) { + case "object": + return TypeObject + case "symbol": + return TypeSymbol + case "function": + return TypeFunction + default: + panic("bad type") + } +} + +// Get returns the JavaScript property p of value v. +// It panics if v is not a JavaScript object. +func (v Value) Get(p string) Value { + if vType := v.Type(); !vType.isObject() { + panic(&ValueError{"Value.Get", vType}) + } + return emval_get_property(v, stringVal(p)) + // r := makeValue(valueGet(v.ref, p)) + // runtime.KeepAlive(v) + // return r +} + +// Set sets the JavaScript property p of value v to ValueOf(x). +// It panics if v is not a JavaScript object. +func (v Value) Set(p string, x any) { + if vType := v.Type(); !vType.isObject() { + panic(&ValueError{"Value.Set", vType}) + } + // xv := ValueOf(x) + emval_set_property(v, stringVal(p), ValueOf(x)) + // valueSet(v.ref, p, xv.ref) + // runtime.KeepAlive(v) + // runtime.KeepAlive(xv) +} + +// // valueSet sets property p of ref v to ref x. +// // +// // Using go:noescape is safe because no references are maintained to the +// // Go string p after the syscall returns. +// // +// //go:wasmimport gojs syscall/js.valueSet +// //go:noescape +// func valueSet(v ref, p string, x ref) + +// Delete deletes the JavaScript property p of value v. +// It panics if v is not a JavaScript object. +func (v Value) Delete(p string) { + if vType := v.Type(); !vType.isObject() { + panic(&ValueError{"Value.Delete", vType}) + } + emval_delete(v, ValueOf(p)) + //valueDelete(v.ref, p) + //runtime.KeepAlive(v) +} + +// // valueDelete deletes the JavaScript property p of ref v. +// // +// // Using go:noescape is safe because no references are maintained to the +// // Go string p after the syscall returns. +// // +// //go:wasmimport gojs syscall/js.valueDelete +// //go:noescape +// func valueDelete(v ref, p string) + +// Index returns JavaScript index i of value v. +// It panics if v is not a JavaScript object. +func (v Value) Index(i int) Value { + if vType := v.Type(); !vType.isObject() { + panic(&ValueError{"Value.Index", vType}) + } + return emval_get_property(v, intValue(i)) + // r := makeValue(valueIndex(v.ref, i)) + // runtime.KeepAlive(v) + // return r +} + +// //go:wasmimport gojs syscall/js.valueIndex +// func valueIndex(v ref, i int) ref + +// SetIndex sets the JavaScript index i of value v to ValueOf(x). +// It panics if v is not a JavaScript object. +func (v Value) SetIndex(i int, x any) { + if vType := v.Type(); !vType.isObject() { + panic(&ValueError{"Value.SetIndex", vType}) + } + emval_set_property(v, intValue(i), ValueOf(x)) + // xv := ValueOf(x) + // valueSetIndex(v.ref, i, xv.ref) + // runtime.KeepAlive(v) + // runtime.KeepAlive(xv) +} + +// //go:wasmimport gojs syscall/js.valueSetIndex +// func valueSetIndex(v ref, i int, x ref) + +// makeArgSlices makes two slices to hold JavaScript arg data. +// It can be paired with storeArgs to make-and-store JavaScript arg slices. +// However, the two functions are separated to ensure makeArgSlices is inlined +// which will prevent the slices from being heap allocated for small (<=16) +// numbers of args. +// func makeArgSlices(size int) (argVals []Value, argRefs []ref) { +// // value chosen for being power of two, and enough to handle all web APIs +// // in particular, note that WebGL2's texImage2D takes up to 10 arguments +// const maxStackArgs = 16 +// if size <= maxStackArgs { +// // as long as makeArgs is inlined, these will be stack-allocated +// argVals = make([]Value, size, maxStackArgs) +// argRefs = make([]ref, size, maxStackArgs) +// } else { +// // allocates on the heap, but exceeding maxStackArgs should be rare +// argVals = make([]Value, size) +// argRefs = make([]ref, size) +// } +// return +// } + +// storeArgs maps input args onto respective Value and ref slices. +// It can be paired with makeArgSlices to make-and-store JavaScript arg slices. +// func storeArgs(args []any, argValsDst []Value, argRefsDst []ref) { +// // would go in makeArgs if the combined func was simple enough to inline +// for i, arg := range args { +// v := ValueOf(arg) +// argValsDst[i] = v +// argRefsDst[i] = v.ref +// } +// } + +// Length returns the JavaScript property "length" of v. +// It panics if v is not a JavaScript object. +func (v Value) Length() int { + if vType := v.Type(); !vType.isObject() { + panic(&ValueError{"Value.SetIndex", vType}) + } + return emval_get_property(v, emval_new_string(c.Str("length"))).Int() + //r := valueLength(v.ref) + //runtime.KeepAlive(v) + //return r +} + +// //go:wasmimport gojs syscall/js.valueLength +// func valueLength(v ref) int + +// Call does a JavaScript call to the method m of value v with the given arguments. +// It panics if v has no method m. +// The arguments get mapped to JavaScript values according to the ValueOf function. +func (v Value) Call(m string, args ...any) (res Value) { + var err c.Int + if len(args) == 0 { + res = emval_method_call(v, c.AllocaCStr(m), nil, 0, &err) + } else { + vargs := make([]Value, len(args)) + for i := 0; i < len(args); i++ { + vargs[i] = ValueOf(args[i]) + } + res = emval_method_call(v, c.AllocaCStr(m), *(**Value)(unsafe.Pointer(&vargs)), c.Int(len(args)), &err) + } + if err != 0 { + if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case + panic(&ValueError{"Value.Call", vType}) + } + if propType := v.Get(m).Type(); propType != TypeFunction { + panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String()) + } + panic(Error{res}) + } + return res + // argVals, argRefs := makeArgSlices(len(args)) + // storeArgs(args, argVals, argRefs) + // res, ok := valueCall(v.ref, m, argRefs) + // runtime.KeepAlive(v) + // runtime.KeepAlive(argVals) + // if !ok { + // if vType := v.Type(); !vType.isObject() { // check here to avoid overhead in success case + // panic(&ValueError{"Value.Call", vType}) + // } + // if propType := v.Get(m).Type(); propType != TypeFunction { + // panic("syscall/js: Value.Call: property " + m + " is not a function, got " + propType.String()) + // } + // panic(Error{makeValue(res)}) + // } + // return makeValue(res) +} + +// // valueCall does a JavaScript call to the method name m of ref v with the given arguments. +// // +// // Using go:noescape is safe because no references are maintained to the +// // Go string m after the syscall returns. Additionally, the args slice +// // is only used temporarily to collect the JavaScript objects for +// // the JavaScript method invocation. +// // +// //go:wasmimport gojs syscall/js.valueCall +// //go:nosplit +// //go:noescape +// func valueCall(v ref, m string, args []ref) (ref, bool) + +// Invoke does a JavaScript call of the value v with the given arguments. +// It panics if v is not a JavaScript function. +// The arguments get mapped to JavaScript values according to the ValueOf function. +func (v Value) Invoke(args ...any) (res Value) { + var err c.Int + if len(args) == 0 { + res = emval_call(v, nil, 0, 0, &err) + } else { + vargs := make([]Value, len(args)) + for i := 0; i < len(args); i++ { + vargs[i] = ValueOf(args[i]) + } + res = emval_call(v, *(**Value)(unsafe.Pointer(&vargs)), c.Int(len(args)), 0, &err) + } + if err != 0 { + if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case + panic(&ValueError{"Value.Invoke", vType}) + } + panic(Error{res}) + } + return + + // argVals, argRefs := makeArgSlices(len(args)) + // storeArgs(args, argVals, argRefs) + // res, ok := valueInvoke(v.ref, argRefs) + // runtime.KeepAlive(v) + // runtime.KeepAlive(argVals) + // if !ok { + // if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case + // panic(&ValueError{"Value.Invoke", vType}) + // } + // panic(Error{makeValue(res)}) + // } + // return makeValue(res) +} + +// valueInvoke does a JavaScript call to value v with the given arguments. +// +// Using go:noescape is safe because the args slice is only used temporarily +// to collect the JavaScript objects for the JavaScript method +// invocation. +// +// //go:wasmimport gojs syscall/js.valueInvoke +// //go:noescape +// func valueInvoke(v ref, args []ref) (ref, bool) + +// New uses JavaScript's "new" operator with value v as constructor and the given arguments. +// It panics if v is not a JavaScript function. +// The arguments get mapped to JavaScript values according to the ValueOf function. +func (v Value) New(args ...any) (res Value) { + var err c.Int + if len(args) == 0 { + res = emval_call(v, nil, 0, 1, &err) + } else { + vargs := make([]Value, len(args)) + for i := 0; i < len(args); i++ { + vargs[i] = ValueOf(args[i]) + } + res = emval_call(v, *(**Value)(unsafe.Pointer(&vargs)), c.Int(len(args)), 1, &err) + } + if err != 0 { + if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case + panic(&ValueError{"Value.Invoke", vType}) + } + panic(Error{res}) + } + return + + // argVals, argRefs := makeArgSlices(len(args)) + // storeArgs(args, argVals, argRefs) + // res, ok := valueNew(v.ref, argRefs) + // runtime.KeepAlive(v) + // runtime.KeepAlive(argVals) + // if !ok { + // if vType := v.Type(); vType != TypeFunction { // check here to avoid overhead in success case + // panic(&ValueError{"Value.Invoke", vType}) + // } + // panic(Error{makeValue(res)}) + // } + // return makeValue(res) +} + +// valueNew uses JavaScript's "new" operator with value v as a constructor and the given arguments. +// +// Using go:noescape is safe because the args slice is only used temporarily +// to collect the JavaScript objects for the constructor execution. +// +// //go:wasmimport gojs syscall/js.valueNew +// //go:noescape +// func valueNew(v ref, args []ref) (ref, bool) + +func (v Value) isNumber() bool { + return v.ref == valueZero.ref || + v.ref == valueNaN.ref || + (v.ref != valueUndefined.ref && emval_is_number(v)) +} + +func (v Value) float(method string) float64 { + if !v.isNumber() { + panic(&ValueError{method, v.Type()}) + } + if v.ref == valueZero.ref { + return 0 + } + return emval_as_double(v) + //return *(*float64)(unsafe.Pointer(&v.ref)) +} + +// Float returns the value v as a float64. +// It panics if v is not a JavaScript number. +func (v Value) Float() float64 { + return v.float("Value.Float") +} + +// Int returns the value v truncated to an int. +// It panics if v is not a JavaScript number. +func (v Value) Int() int { + return int(v.float("Value.Int")) +} + +// Bool returns the value v as a bool. +// It panics if v is not a JavaScript boolean. +func (v Value) Bool() bool { + switch v.ref { + case valueTrue.ref: + return true + case valueFalse.ref: + return false + default: + panic(&ValueError{"Value.Bool", v.Type()}) + } +} + +// Truthy returns the JavaScript "truthiness" of the value v. In JavaScript, +// false, 0, "", null, undefined, and NaN are "falsy", and everything else is +// "truthy". See https://developer.mozilla.org/en-US/docs/Glossary/Truthy. +func (v Value) Truthy() bool { + switch v.Type() { + case TypeUndefined, TypeNull: + return false + case TypeBoolean: + return v.Bool() + case TypeNumber: + return v.ref != valueNaN.ref && v.ref != valueZero.ref + case TypeString: + return v.String() != "" + case TypeSymbol, TypeFunction, TypeObject: + return true + default: + panic("bad type") + } +} + +// String returns the value v as a string. +// String is a special case because of Go's String method convention. Unlike the other getters, +// it does not panic if v's Type is not TypeString. Instead, it returns a string of the form "" +// or "" where T is v's type and V is a string representation of v's value. +func (v Value) String() string { + switch v.Type() { + case TypeString: + return jsString(v) + case TypeUndefined: + return "" + case TypeNull: + return "" + case TypeBoolean: + return "" + case TypeNumber: + return "" + case TypeSymbol: + return "" + case TypeObject: + return "" + case TypeFunction: + return "" + default: + panic("bad type") + } +} + +func jsString(v Value) string { + return emval_as_string(v) + // str, length := valuePrepareString(v.ref) + // runtime.KeepAlive(v) + // b := make([]byte, length) + // valueLoadString(str, b) + // finalizeRef(str) + // return string(b) +} + +// //go:wasmimport gojs syscall/js.valuePrepareString +// func valuePrepareString(v ref) (ref, int) + +// valueLoadString loads string data located at ref v into byte slice b. +// +// Using go:noescape is safe because the byte slice is only used as a destination +// for storing the string data and references to it are not maintained. +// +// //go:wasmimport gojs syscall/js.valueLoadString +// //go:noescape +// func valueLoadString(v ref, b []byte) + +// InstanceOf reports whether v is an instance of type t according to JavaScript's instanceof operator. +func (v Value) InstanceOf(t Value) bool { + return emval_instanceof(v, t) + // r := valueInstanceOf(v.ref, t.ref) + // runtime.KeepAlive(v) + // runtime.KeepAlive(t) + // return r +} + +// //go:wasmimport gojs syscall/js.valueInstanceOf +// func valueInstanceOf(v ref, t ref) bool + +// A ValueError occurs when a Value method is invoked on +// a Value that does not support it. Such cases are documented +// in the description of each method. +type ValueError struct { + Method string + Type Type +} + +func (e *ValueError) Error() string { + return "syscall/js: call of " + e.Method + " on " + e.Type.String() +} + +// CopyBytesToGo copies bytes from src to dst. +// It panics if src is not a Uint8Array or Uint8ClampedArray. +// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. +func CopyBytesToGo(dst []byte, src Value) int { + return 0 + // n, ok := copyBytesToGo(dst, src.ref) + // runtime.KeepAlive(src) + // if !ok { + // panic("syscall/js: CopyBytesToGo: expected src to be a Uint8Array or Uint8ClampedArray") + // } + // return n +} + +// copyBytesToGo copies bytes from src to dst. +// +// Using go:noescape is safe because the dst byte slice is only used as a dst +// copy buffer and no references to it are maintained. +// +// //go:wasmimport gojs syscall/js.copyBytesToGo +// //go:noescape +// func copyBytesToGo(dst []byte, src ref) (int, bool) + +// CopyBytesToJS copies bytes from src to dst. +// It panics if dst is not a Uint8Array or Uint8ClampedArray. +// It returns the number of bytes copied, which will be the minimum of the lengths of src and dst. +func CopyBytesToJS(dst Value, src []byte) int { + return 0 + // n, ok := copyBytesToJS(dst.ref, src) + // runtime.KeepAlive(dst) + // if !ok { + // panic("syscall/js: CopyBytesToJS: expected dst to be a Uint8Array or Uint8ClampedArray") + // } + // return n +} + +// copyBytesToJS copies bytes from src to dst. +// +// Using go:noescape is safe because the src byte slice is only used as a src +// copy buffer and no references to it are maintained. +// +// //go:wasmimport gojs syscall/js.copyBytesToJS +// //go:noescape +// func copyBytesToJS(dst ref, src []byte) (int, bool)