361 lines
9.9 KiB
Go
361 lines
9.9 KiB
Go
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 }
|