package quickjs /* #include "bridge.h" */ import "C" import ( "errors" "math/big" "unsafe" ) type Error struct { Cause string Stack string } func (err Error) Error() string { return err.Cause } // Object property names and some strings are stored as Atoms (unique strings) to save memory and allow fast comparison. Atoms are represented as a 32 bit integer. Half of the atom range is reserved for immediate integer literals from 0 to 2^{31}-1. type Atom struct { ctx *Context ref C.JSAtom } // Free the value. func (a Atom) Free() { C.JS_FreeAtom(a.ctx.ref, a.ref) } // String returns the string representation of the value. func (a Atom) String() string { ptr := C.JS_AtomToCString(a.ctx.ref, a.ref) defer C.JS_FreeCString(a.ctx.ref, ptr) return C.GoString(ptr) } // Value returns the value of the Atom object. func (a Atom) Value() Value { return Value{ctx: a.ctx, ref: C.JS_AtomToValue(a.ctx.ref, a.ref)} } // propertyEnum is a wrapper around JSAtom. type propertyEnum struct { IsEnumerable bool atom Atom } // String returns the atom string representation of the value. func (p propertyEnum) String() string { return p.atom.String() } // JSValue represents a Javascript value which can be a primitive type or an object. Reference counting is used, so it is important to explicitly duplicate (JS_DupValue(), increment the reference count) or free (JS_FreeValue(), decrement the reference count) JSValues. type Value struct { ctx *Context ref C.JSValue } // Free the value. func (v Value) Free() { C.JS_FreeValue(v.ctx.ref, v.ref) } // Context represents a Javascript context. func (v Value) Context() *Context { return v.ctx } // Bool returns the boolean value of the value. func (v Value) Bool() bool { return C.JS_ToBool(v.ctx.ref, v.ref) == 1 } // String returns the string representation of the value. func (v Value) String() string { ptr := C.JS_ToCString(v.ctx.ref, v.ref) defer C.JS_FreeCString(v.ctx.ref, ptr) return C.GoString(ptr) } // JSONString returns the JSON string representation of the value. func (v Value) JSONStringify() string { ref := C.JS_JSONStringify(v.ctx.ref, v.ref, C.JS_NewNull(), C.JS_NewNull()) ptr := C.JS_ToCString(v.ctx.ref, ref) defer C.JS_FreeCString(v.ctx.ref, ptr) return C.GoString(ptr) } func (v Value) ToByteArray(size uint) ([]byte, error) { if v.ByteLen() < int64(size) { return nil, errors.New("exceeds the maximum length of the current binary array") } cSize := C.size_t(size) outBuf := C.JS_GetArrayBuffer(v.ctx.ref, &cSize, v.ref) return C.GoBytes(unsafe.Pointer(outBuf), C.int(size)), nil } // IsByteArray return true if the value is array buffer func (v Value) IsByteArray() bool { return v.IsObject() && v.globalInstanceof("ArrayBuffer") || v.String() == "[object ArrayBuffer]" } // Int64 returns the int64 value of the value. func (v Value) Int64() int64 { val := C.int64_t(0) C.JS_ToInt64(v.ctx.ref, &val, v.ref) return int64(val) } // Int32 returns the int32 value of the value. func (v Value) Int32() int32 { val := C.int32_t(0) C.JS_ToInt32(v.ctx.ref, &val, v.ref) return int32(val) } // Uint32 returns the uint32 value of the value. func (v Value) Uint32() uint32 { val := C.uint32_t(0) C.JS_ToUint32(v.ctx.ref, &val, v.ref) return uint32(val) } // Float64 returns the float64 value of the value. func (v Value) Float64() float64 { val := C.double(0) C.JS_ToFloat64(v.ctx.ref, &val, v.ref) return float64(val) } // BigInt returns the big.Int value of the value. func (v Value) BigInt() *big.Int { if !v.IsBigInt() { return nil } val, ok := new(big.Int).SetString(v.String(), 10) if !ok { return nil } return val } // BigFloat returns the big.Float value of the value. func (v Value) BigFloat() *big.Float { if !v.IsBigDecimal() && !v.IsBigFloat() { return nil } val, ok := new(big.Float).SetString(v.String()) if !ok { return nil } return val } // ToArray // // @Description: return array object // @receiver v : // @return *Array func (v Value) ToArray() *Array { if !v.IsArray() { return nil } return NewQjsArray(v, v.ctx) } // ToMap // // @Description: return map object // @receiver v : // @return *Map func (v Value) ToMap() *Map { if !v.IsMap() { return nil } return NewQjsMap(v, v.ctx) } // ToSet // // @Description: return set object // @receiver v : // @return *Set func (v Value) ToSet() *Set { if v.IsSet() { return nil } return NewQjsSet(v, v.ctx) } // IsMap return true if the value is a map func (v Value) IsMap() bool { return v.IsObject() && v.globalInstanceof("Map") || v.String() == "[object Map]" } // IsSet return true if the value is a set func (v Value) IsSet() bool { return v.IsObject() && v.globalInstanceof("Set") || v.String() == "[object Set]" } // Len returns the length of the array. func (v Value) Len() int64 { return v.Get("length").Int64() } // ByteLen returns the length of the ArrayBuffer. func (v Value) ByteLen() int64 { return v.Get("byteLength").Int64() } // Set sets the value of the property with the given name. func (v Value) Set(name string, val Value) { namePtr := C.CString(name) defer C.free(unsafe.Pointer(namePtr)) C.JS_SetPropertyStr(v.ctx.ref, v.ref, namePtr, val.ref) } // SetIdx sets the value of the property with the given index. func (v Value) SetIdx(idx int64, val Value) { C.JS_SetPropertyUint32(v.ctx.ref, v.ref, C.uint32_t(idx), val.ref) } // Get returns the value of the property with the given name. func (v Value) Get(name string) Value { namePtr := C.CString(name) defer C.free(unsafe.Pointer(namePtr)) return Value{ctx: v.ctx, ref: C.JS_GetPropertyStr(v.ctx.ref, v.ref, namePtr)} } // GetIdx returns the value of the property with the given index. func (v Value) GetIdx(idx int64) Value { return Value{ctx: v.ctx, ref: C.JS_GetPropertyUint32(v.ctx.ref, v.ref, C.uint32_t(idx))} } // Call calls the function with the given arguments. func (v Value) Call(fname string, args ...Value) Value { if !v.IsObject() { return v.ctx.Error(errors.New("Object not a object")) } fn := v.Get(fname) // get the function by name defer fn.Free() if !fn.IsFunction() { return v.ctx.Error(errors.New("Object not a function")) } cargs := []C.JSValue{} for _, x := range args { cargs = append(cargs, x.ref) } if len(cargs) == 0 { return Value{ctx: v.ctx, ref: C.JS_Call(v.ctx.ref, fn.ref, v.ref, C.int(0), nil)} } return Value{ctx: v.ctx, ref: C.JS_Call(v.ctx.ref, fn.ref, v.ref, C.int(len(cargs)), &cargs[0])} } // Error returns the error value of the value. func (v Value) Error() error { if !v.IsError() { return nil } cause := v.String() stack := v.Get("stack") defer stack.Free() if stack.IsUndefined() { return &Error{Cause: cause} } return &Error{Cause: cause, Stack: stack.String()} } // propertyEnum is a wrapper around JSValue. func (v Value) propertyEnum() ([]propertyEnum, error) { var ptr *C.JSPropertyEnum var size C.uint32_t result := int(C.JS_GetOwnPropertyNames(v.ctx.ref, &ptr, &size, v.ref, C.int(1<<0|1<<1|1<<2))) if result < 0 { return nil, errors.New("value does not contain properties") } defer C.js_free(v.ctx.ref, unsafe.Pointer(ptr)) entries := unsafe.Slice(ptr, size) // Go 1.17 and later names := make([]propertyEnum, len(entries)) for i := 0; i < len(names); i++ { names[i].IsEnumerable = entries[i].is_enumerable == 1 names[i].atom = Atom{ctx: v.ctx, ref: entries[i].atom} names[i].atom.Free() } return names, nil } // PropertyNames returns the names of the properties of the value. func (v Value) PropertyNames() ([]string, error) { pList, err := v.propertyEnum() if err != nil { return nil, err } names := make([]string, len(pList)) for i := 0; i < len(names); i++ { names[i] = pList[i].String() } return names, nil } // Has returns true if the value has the property with the given name. func (v Value) Has(name string) bool { prop := v.ctx.Atom(name) defer prop.Free() return C.JS_HasProperty(v.ctx.ref, v.ref, prop.ref) == 1 } // HasIdx returns true if the value has the property with the given index. func (v Value) HasIdx(idx int64) bool { prop := v.ctx.AtomIdx(idx) defer prop.Free() return C.JS_HasProperty(v.ctx.ref, v.ref, prop.ref) == 1 } // Delete deletes the property with the given name. func (v Value) Delete(name string) bool { prop := v.ctx.Atom(name) defer prop.Free() return C.JS_DeleteProperty(v.ctx.ref, v.ref, prop.ref, C.int(1)) == 1 } // DeleteIdx deletes the property with the given index. func (v Value) DeleteIdx(idx int64) bool { return C.JS_DeletePropertyInt64(v.ctx.ref, v.ref, C.int64_t(idx), C.int(1)) == 1 } // globalInstanceof checks if the value is an instance of the given global constructor func (v Value) globalInstanceof(name string) bool { ctor := v.ctx.Globals().Get(name) defer ctor.Free() if ctor.IsUndefined() { return false } return C.JS_IsInstanceOf(v.ctx.ref, v.ref, ctor.ref) == 1 } func (v Value) IsNumber() bool { return C.JS_IsNumber(v.ref) == 1 } func (v Value) IsBigInt() bool { return C.JS_IsBigInt(v.ctx.ref, v.ref) == 1 } func (v Value) IsBigFloat() bool { return C.JS_IsBigFloat(v.ref) == 1 } func (v Value) IsBigDecimal() bool { return C.JS_IsBigDecimal(v.ref) == 1 } func (v Value) IsBool() bool { return C.JS_IsBool(v.ref) == 1 } func (v Value) IsNull() bool { return C.JS_IsNull(v.ref) == 1 } func (v Value) IsUndefined() bool { return C.JS_IsUndefined(v.ref) == 1 } func (v Value) IsException() bool { return C.JS_IsException(v.ref) == 1 } func (v Value) IsUninitialized() bool { return C.JS_IsUninitialized(v.ref) == 1 } func (v Value) IsString() bool { return C.JS_IsString(v.ref) == 1 } func (v Value) IsSymbol() bool { return C.JS_IsSymbol(v.ref) == 1 } func (v Value) IsObject() bool { return C.JS_IsObject(v.ref) == 1 } func (v Value) IsArray() bool { return C.JS_IsArray(v.ctx.ref, v.ref) == 1 } func (v Value) IsError() bool { return C.JS_IsError(v.ctx.ref, v.ref) == 1 } func (v Value) IsFunction() bool { return C.JS_IsFunction(v.ctx.ref, v.ref) == 1 } // func (v Value) IsConstructor() bool { return C.JS_IsConstructor(v.ctx.ref, v.ref) == 1 }