Merge pull request #1180 from visualfc/emcc_js

[WIP] runtime/js: js for emscripten
This commit is contained in:
xushiwei
2025-07-01 13:24:04 +08:00
committed by GitHub
5 changed files with 1113 additions and 0 deletions

View File

@@ -0,0 +1,213 @@
#include <string>
#include <emscripten.h>
#include <emscripten/val.h>
#include <emscripten/bind.h>
using namespace emscripten;
using namespace emscripten::internal;
template<typename T>
TYPEID take_typeid() {
typename WithPolicies<>::template ArgTypeList<T> targetType;
return targetType.getTypes()[0];
}
template<typename T, typename... Policies>
EM_VAL take_value(T&& value, Policies...) {
typename WithPolicies<Policies...>::template ArgTypeList<T> valueType;
WireTypePack<T> argv(std::forward<T>(value));
return _emval_take_value(valueType.getTypes()[0], argv);
}
template<typename T, typename ...Policies>
T as_value(EM_VAL val, Policies...) {
typedef BindingType<T> BT;
typename WithPolicies<Policies...>::template ArgTypeList<T> targetType;
EM_DESTRUCTORS destructors = nullptr;
EM_GENERIC_WIRE_TYPE result = _emval_as(
val,
targetType.getTypes()[0],
&destructors);
DestructorsRunner dr(destructors);
return fromGenericWireType<T>(result);
}
struct GoString {
char *data;
int len;
};
static TYPEID typeid_val = take_typeid<val>();
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<double>(v);
}
GoString llgo_emval_as_string(EM_VAL v) {
std::string value = as_value<std::string>(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<TYPEID> arr;
arr.resize(nargs+1);
std::vector<val> _args;
_args.resize(nargs);
std::vector<GenericWireType> 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<val>(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<TYPEID> arr;
arr.resize(nargs+1);
std::vector<val> _args;
_args.resize(nargs);
std::vector<GenericWireType> 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<val>(ret).release_ownership();
}
/*
TYPEID llgo_emval_typeid_void() {
return take_typeid<void>();
}
TYPEID llgo_emval_typeid_double() {
return take_typeid<double>();
}
TYPEID llgo_emval_typeid_string() {
return take_typeid<std::string>();
}
TYPEID llgo_emval_typeid_val() {
return take_typeid<val>();
}
*/
void llgo_emval_dump(EM_VAL v) {
val console = val::global("console");
console.call<void>("log", val::take_ownership(v));
}
}
bool invoke(val args) {
return llgo_export_invoke(args.as_handle());
}
EMSCRIPTEN_BINDINGS(my_module) {
function("_llgo_invoke", &invoke);
}

View File

@@ -0,0 +1,6 @@
package embind
const (
LLGoFiles = "_wrap/emval.cpp"
LLGoPackage = "link: -lembind"
)

92
runtime/js/emval.go Normal file
View File

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

95
runtime/js/func.go Normal file
View File

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

707
runtime/js/js.go Normal file
View File

@@ -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 "<T>"
// or "<T: V>" 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 "<undefined>"
case TypeNull:
return "<null>"
case TypeBoolean:
return "<boolean: " + jsString(v) + ">"
case TypeNumber:
return "<number: " + jsString(v) + ">"
case TypeSymbol:
return "<symbol>"
case TypeObject:
return "<object>"
case TypeFunction:
return "<function>"
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)