package goja
import (
"fmt"
"go/ast"
"reflect"
"strings"
"github.com/dop251/goja/parser"
"github.com/dop251/goja/unistring"
)
type JsonEncodable interface {
JsonEncodable () interface {}
}
type FieldNameMapper interface {
FieldName (t reflect .Type , f reflect .StructField ) string
MethodName (t reflect .Type , m reflect .Method ) string
}
type tagFieldNameMapper struct {
tagName string
uncapMethods bool
}
func (tfm tagFieldNameMapper ) FieldName (_ reflect .Type , f reflect .StructField ) string {
tag := f .Tag .Get (tfm .tagName )
if idx := strings .IndexByte (tag , ',' ); idx != -1 {
tag = tag [:idx ]
}
if parser .IsIdentifier (tag ) {
return tag
}
return ""
}
func uncapitalize(s string ) string {
return strings .ToLower (s [0 :1 ]) + s [1 :]
}
func (tfm tagFieldNameMapper ) MethodName (_ reflect .Type , m reflect .Method ) string {
if tfm .uncapMethods {
return uncapitalize (m .Name )
}
return m .Name
}
type uncapFieldNameMapper struct {
}
func (u uncapFieldNameMapper ) FieldName (_ reflect .Type , f reflect .StructField ) string {
return uncapitalize (f .Name )
}
func (u uncapFieldNameMapper ) MethodName (_ reflect .Type , m reflect .Method ) string {
return uncapitalize (m .Name )
}
type reflectFieldInfo struct {
Index []int
Anonymous bool
}
type reflectFieldsInfo struct {
Fields map [string ]reflectFieldInfo
Names []string
}
type reflectMethodsInfo struct {
Methods map [string ]int
Names []string
}
type reflectValueWrapper interface {
esValue() Value
reflectValue() reflect .Value
setReflectValue(reflect .Value )
}
func isContainer(k reflect .Kind ) bool {
switch k {
case reflect .Struct , reflect .Slice , reflect .Array :
return true
}
return false
}
func copyReflectValueWrapper(w reflectValueWrapper ) {
v := w .reflectValue ()
c := reflect .New (v .Type ()).Elem ()
c .Set (v )
w .setReflectValue (c )
}
type objectGoReflect struct {
baseObject
origValue, fieldsValue reflect .Value
fieldsInfo *reflectFieldsInfo
methodsInfo *reflectMethodsInfo
methodsValue reflect .Value
valueCache map [string ]reflectValueWrapper
toString, valueOf func () Value
toJson func () interface {}
}
func (o *objectGoReflect ) init () {
o .baseObject .init ()
switch o .fieldsValue .Kind () {
case reflect .Bool :
o .class = classBoolean
o .prototype = o .val .runtime .getBooleanPrototype ()
o .toString = o ._toStringBool
o .valueOf = o ._valueOfBool
case reflect .String :
o .class = classString
o .prototype = o .val .runtime .getStringPrototype ()
o .toString = o ._toStringString
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
o .class = classNumber
o .prototype = o .val .runtime .getNumberPrototype ()
o .valueOf = o ._valueOfInt
case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 :
o .class = classNumber
o .prototype = o .val .runtime .getNumberPrototype ()
o .valueOf = o ._valueOfUint
case reflect .Float32 , reflect .Float64 :
o .class = classNumber
o .prototype = o .val .runtime .getNumberPrototype ()
o .valueOf = o ._valueOfFloat
default :
o .class = classObject
o .prototype = o .val .runtime .global .ObjectPrototype
}
if o .fieldsValue .Kind () == reflect .Struct {
o .fieldsInfo = o .val .runtime .fieldsInfo (o .fieldsValue .Type ())
}
var methodsType reflect .Type
if o .fieldsValue .Kind () != reflect .Interface {
methodsType = reflect .PtrTo (o .fieldsValue .Type ())
} else {
methodsType = o .fieldsValue .Type ()
}
o .methodsInfo = o .val .runtime .methodsInfo (methodsType )
if !o .origValue .CanAddr () && (isContainer (o .origValue .Kind ()) || len (o .methodsInfo .Names ) > 0 ) {
value := reflect .New (o .origValue .Type ()).Elem ()
value .Set (o .origValue )
o .origValue = value
if value .Kind () != reflect .Ptr {
o .fieldsValue = value
}
}
o .extensible = true
switch o .origValue .Interface ().(type ) {
case fmt .Stringer :
o .toString = o ._toStringStringer
case error :
o .toString = o ._toStringError
}
if len (o .methodsInfo .Names ) > 0 && o .fieldsValue .Kind () != reflect .Interface {
o .methodsValue = o .fieldsValue .Addr ()
} else {
o .methodsValue = o .fieldsValue
}
if j , ok := o .origValue .Interface ().(JsonEncodable ); ok {
o .toJson = j .JsonEncodable
}
}
func (o *objectGoReflect ) getStr (name unistring .String , receiver Value ) Value {
if v := o ._get (name .String ()); v != nil {
return v
}
return o .baseObject .getStr (name , receiver )
}
func (o *objectGoReflect ) _getField (jsName string ) reflect .Value {
if o .fieldsInfo != nil {
if info , exists := o .fieldsInfo .Fields [jsName ]; exists {
return o .fieldsValue .FieldByIndex (info .Index )
}
}
return reflect .Value {}
}
func (o *objectGoReflect ) _getMethod (jsName string ) reflect .Value {
if o .methodsInfo != nil {
if idx , exists := o .methodsInfo .Methods [jsName ]; exists {
return o .methodsValue .Method (idx )
}
}
return reflect .Value {}
}
func (o *objectGoReflect ) elemToValue (ev reflect .Value ) (Value , reflectValueWrapper ) {
if isContainer (ev .Kind ()) {
ret := o .val .runtime .toValue (ev .Interface (), ev )
if obj , ok := ret .(*Object ); ok {
if w , ok := obj .self .(reflectValueWrapper ); ok {
return ret , w
}
}
return ret , nil
}
if ev .Kind () == reflect .Interface {
ev = ev .Elem ()
}
if ev .Kind () == reflect .Invalid {
return _null , nil
}
return o .val .runtime .toValue (ev .Interface (), ev ), nil
}
func (o *objectGoReflect ) _getFieldValue (name string ) Value {
if v := o .valueCache [name ]; v != nil {
return v .esValue ()
}
if v := o ._getField (name ); v .IsValid () {
res , w := o .elemToValue (v )
if w != nil {
if o .valueCache == nil {
o .valueCache = make (map [string ]reflectValueWrapper )
}
o .valueCache [name ] = w
}
return res
}
return nil
}
func (o *objectGoReflect ) _get (name string ) Value {
if o .fieldsValue .Kind () == reflect .Struct {
if ret := o ._getFieldValue (name ); ret != nil {
return ret
}
}
if v := o ._getMethod (name ); v .IsValid () {
return o .val .runtime .toValue (v .Interface (), v )
}
return nil
}
func (o *objectGoReflect ) getOwnPropStr (name unistring .String ) Value {
n := name .String ()
if o .fieldsValue .Kind () == reflect .Struct {
if v := o ._getFieldValue (n ); v != nil {
return &valueProperty {
value : v ,
writable : true ,
enumerable : true ,
}
}
}
if v := o ._getMethod (n ); v .IsValid () {
return &valueProperty {
value : o .val .runtime .toValue (v .Interface (), v ),
enumerable : true ,
}
}
return o .baseObject .getOwnPropStr (name )
}
func (o *objectGoReflect ) setOwnStr (name unistring .String , val Value , throw bool ) bool {
has , ok := o ._put (name .String (), val , throw )
if !has {
if res , ok := o ._setForeignStr (name , nil , val , o .val , throw ); !ok {
o .val .runtime .typeErrorResult (throw , "Cannot assign to property %s of a host object" , name )
return false
} else {
return res
}
}
return ok
}
func (o *objectGoReflect ) setForeignStr (name unistring .String , val , receiver Value , throw bool ) (bool , bool ) {
return o ._setForeignStr (name , trueValIfPresent (o ._has (name .String ())), val , receiver , throw )
}
func (o *objectGoReflect ) setForeignIdx (idx valueInt , val , receiver Value , throw bool ) (bool , bool ) {
return o ._setForeignIdx (idx , nil , val , receiver , throw )
}
func (o *objectGoReflect ) _put (name string , val Value , throw bool ) (has , ok bool ) {
if o .fieldsValue .Kind () == reflect .Struct {
if v := o ._getField (name ); v .IsValid () {
cached := o .valueCache [name ]
if cached != nil {
copyReflectValueWrapper (cached )
}
err := o .val .runtime .toReflectValue (val , v , &objectExportCtx {})
if err != nil {
if cached != nil {
cached .setReflectValue (v )
}
o .val .runtime .typeErrorResult (throw , "Go struct conversion error: %v" , err )
return true , false
}
if cached != nil {
delete (o .valueCache , name )
}
return true , true
}
}
return false , false
}
func (o *objectGoReflect ) _putProp (name unistring .String , value Value , writable , enumerable , configurable bool ) Value {
if _ , ok := o ._put (name .String (), value , false ); ok {
return value
}
return o .baseObject ._putProp (name , value , writable , enumerable , configurable )
}
func (r *Runtime ) checkHostObjectPropertyDescr (name unistring .String , descr PropertyDescriptor , throw bool ) bool {
if descr .Getter != nil || descr .Setter != nil {
r .typeErrorResult (throw , "Host objects do not support accessor properties" )
return false
}
if descr .Writable == FLAG_FALSE {
r .typeErrorResult (throw , "Host object field %s cannot be made read-only" , name )
return false
}
if descr .Configurable == FLAG_TRUE {
r .typeErrorResult (throw , "Host object field %s cannot be made configurable" , name )
return false
}
return true
}
func (o *objectGoReflect ) defineOwnPropertyStr (name unistring .String , descr PropertyDescriptor , throw bool ) bool {
if o .val .runtime .checkHostObjectPropertyDescr (name , descr , throw ) {
n := name .String ()
if has , ok := o ._put (n , descr .Value , throw ); !has {
o .val .runtime .typeErrorResult (throw , "Cannot define property '%s' on a host object" , n )
return false
} else {
return ok
}
}
return false
}
func (o *objectGoReflect ) _has (name string ) bool {
if o .fieldsValue .Kind () == reflect .Struct {
if v := o ._getField (name ); v .IsValid () {
return true
}
}
if v := o ._getMethod (name ); v .IsValid () {
return true
}
return false
}
func (o *objectGoReflect ) hasOwnPropertyStr (name unistring .String ) bool {
return o ._has (name .String ()) || o .baseObject .hasOwnPropertyStr (name )
}
func (o *objectGoReflect ) _valueOfInt () Value {
return intToValue (o .fieldsValue .Int ())
}
func (o *objectGoReflect ) _valueOfUint () Value {
return intToValue (int64 (o .fieldsValue .Uint ()))
}
func (o *objectGoReflect ) _valueOfBool () Value {
if o .fieldsValue .Bool () {
return valueTrue
} else {
return valueFalse
}
}
func (o *objectGoReflect ) _valueOfFloat () Value {
return floatToValue (o .fieldsValue .Float ())
}
func (o *objectGoReflect ) _toStringStringer () Value {
return newStringValue (o .origValue .Interface ().(fmt .Stringer ).String ())
}
func (o *objectGoReflect ) _toStringString () Value {
return newStringValue (o .fieldsValue .String ())
}
func (o *objectGoReflect ) _toStringBool () Value {
if o .fieldsValue .Bool () {
return stringTrue
} else {
return stringFalse
}
}
func (o *objectGoReflect ) _toStringError () Value {
return newStringValue (o .origValue .Interface ().(error ).Error())
}
func (o *objectGoReflect ) deleteStr (name unistring .String , throw bool ) bool {
n := name .String ()
if o ._has (n ) {
o .val .runtime .typeErrorResult (throw , "Cannot delete property %s from a Go type" , n )
return false
}
return o .baseObject .deleteStr (name , throw )
}
type goreflectPropIter struct {
o *objectGoReflect
idx int
}
func (i *goreflectPropIter ) nextField () (propIterItem , iterNextFunc ) {
names := i .o .fieldsInfo .Names
if i .idx < len (names ) {
name := names [i .idx ]
i .idx ++
return propIterItem {name : newStringValue (name ), enumerable : _ENUM_TRUE }, i .nextField
}
i .idx = 0
return i .nextMethod ()
}
func (i *goreflectPropIter ) nextMethod () (propIterItem , iterNextFunc ) {
names := i .o .methodsInfo .Names
if i .idx < len (names ) {
name := names [i .idx ]
i .idx ++
return propIterItem {name : newStringValue (name ), enumerable : _ENUM_TRUE }, i .nextMethod
}
return propIterItem {}, nil
}
func (o *objectGoReflect ) iterateStringKeys () iterNextFunc {
r := &goreflectPropIter {
o : o ,
}
if o .fieldsInfo != nil {
return r .nextField
}
return r .nextMethod
}
func (o *objectGoReflect ) stringKeys (_ bool , accum []Value ) []Value {
if o .fieldsInfo != nil {
for _ , name := range o .fieldsInfo .Names {
accum = append (accum , newStringValue (name ))
}
}
for _ , name := range o .methodsInfo .Names {
accum = append (accum , newStringValue (name ))
}
return accum
}
func (o *objectGoReflect ) export (*objectExportCtx ) interface {} {
return o .origValue .Interface ()
}
func (o *objectGoReflect ) exportType () reflect .Type {
return o .origValue .Type ()
}
func (o *objectGoReflect ) equal (other objectImpl ) bool {
if other , ok := other .(*objectGoReflect ); ok {
k1 , k2 := o .fieldsValue .Kind (), other .fieldsValue .Kind ()
if k1 == k2 {
if isContainer (k1 ) {
return o .fieldsValue == other .fieldsValue
}
return o .fieldsValue .Interface () == other .fieldsValue .Interface ()
}
}
return false
}
func (o *objectGoReflect ) reflectValue () reflect .Value {
return o .fieldsValue
}
func (o *objectGoReflect ) setReflectValue (v reflect .Value ) {
o .fieldsValue = v
o .origValue = v
o .methodsValue = v .Addr ()
}
func (o *objectGoReflect ) esValue () Value {
return o .val
}
func (r *Runtime ) buildFieldInfo (t reflect .Type , index []int , info *reflectFieldsInfo ) {
n := t .NumField ()
for i := 0 ; i < n ; i ++ {
field := t .Field (i )
name := field .Name
isExported := ast .IsExported (name )
if !isExported && !field .Anonymous {
continue
}
if r .fieldNameMapper != nil {
name = r .fieldNameMapper .FieldName (t , field )
}
if name != "" && isExported {
if inf , exists := info .Fields [name ]; !exists {
info .Names = append (info .Names , name )
} else {
if len (inf .Index ) <= len (index ) {
continue
}
}
}
if name != "" || field .Anonymous {
idx := make ([]int , len (index )+1 )
copy (idx , index )
idx [len (idx )-1 ] = i
if name != "" && isExported {
info .Fields [name ] = reflectFieldInfo {
Index : idx ,
Anonymous : field .Anonymous ,
}
}
if field .Anonymous {
typ := field .Type
for typ .Kind () == reflect .Ptr {
typ = typ .Elem ()
}
if typ .Kind () == reflect .Struct {
r .buildFieldInfo (typ , idx , info )
}
}
}
}
}
var emptyMethodsInfo = reflectMethodsInfo {}
func (r *Runtime ) buildMethodsInfo (t reflect .Type ) (info *reflectMethodsInfo ) {
n := t .NumMethod ()
if n == 0 {
return &emptyMethodsInfo
}
info = new (reflectMethodsInfo )
info .Methods = make (map [string ]int , n )
info .Names = make ([]string , 0 , n )
for i := 0 ; i < n ; i ++ {
method := t .Method (i )
name := method .Name
if !ast .IsExported (name ) {
continue
}
if r .fieldNameMapper != nil {
name = r .fieldNameMapper .MethodName (t , method )
if name == "" {
continue
}
}
if _ , exists := info .Methods [name ]; !exists {
info .Names = append (info .Names , name )
}
info .Methods [name ] = i
}
return
}
func (r *Runtime ) buildFieldsInfo (t reflect .Type ) (info *reflectFieldsInfo ) {
info = new (reflectFieldsInfo )
n := t .NumField ()
info .Fields = make (map [string ]reflectFieldInfo , n )
info .Names = make ([]string , 0 , n )
r .buildFieldInfo (t , nil , info )
return
}
func (r *Runtime ) fieldsInfo (t reflect .Type ) (info *reflectFieldsInfo ) {
var exists bool
if info , exists = r .fieldsInfoCache [t ]; !exists {
info = r .buildFieldsInfo (t )
if r .fieldsInfoCache == nil {
r .fieldsInfoCache = make (map [reflect .Type ]*reflectFieldsInfo )
}
r .fieldsInfoCache [t ] = info
}
return
}
func (r *Runtime ) methodsInfo (t reflect .Type ) (info *reflectMethodsInfo ) {
var exists bool
if info , exists = r .methodsInfoCache [t ]; !exists {
info = r .buildMethodsInfo (t )
if r .methodsInfoCache == nil {
r .methodsInfoCache = make (map [reflect .Type ]*reflectMethodsInfo )
}
r .methodsInfoCache [t ] = info
}
return
}
func (r *Runtime ) SetFieldNameMapper (mapper FieldNameMapper ) {
r .fieldNameMapper = mapper
r .fieldsInfoCache = nil
r .methodsInfoCache = nil
}
func TagFieldNameMapper (tagName string , uncapMethods bool ) FieldNameMapper {
return tagFieldNameMapper {
tagName : tagName ,
uncapMethods : uncapMethods ,
}
}
func UncapFieldNameMapper () FieldNameMapper {
return uncapFieldNameMapper {}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .