-
Notifications
You must be signed in to change notification settings - Fork 0
/
v8_create_darwin.go
286 lines (265 loc) · 8.6 KB
/
v8_create_darwin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
package js8
import (
"fmt"
"path"
"reflect"
"runtime"
"sort"
"strings"
"time"
"unicode"
"unsafe"
)
// #include <stdlib.h>
// #include <string.h>
// #include "v8_c_bridge.h"
// #cgo CXXFLAGS: -I${SRCDIR}/libv8-darwin -I${SRCDIR}/libv8-darwin/include -fno-rtti -fpic -std=c++11 -DV8_COMPRESS_POINTERS
// #cgo LDFLAGS: -pthread -L${SRCDIR}/libv8-darwin/libv8 -lv8
import "C"
var float64Type = reflect.TypeOf(float64(0))
var callbackType = reflect.TypeOf(Callback(nil))
var stringType = reflect.TypeOf(string(""))
var valuePtrType = reflect.TypeOf((*Value)(nil))
var timeType = reflect.TypeOf(time.Time{})
// Create maps Go values into corresponding JavaScript values. This value is
// created but NOT visible in the Context until it is explicitly passed to the
// Context (either via a .Set() call or as a callback return value).
//
// Create can automatically map the following types of values:
// * bool
// * all integers and floats are mapped to JS numbers (float64)
// * strings
// * maps (keys must be strings, values must be convertible)
// * time.Time values (converted to js Date object)
// * structs (exported field values must be convertible)
// * slices of convertible types
// * pointers to any convertible field
// * v8.Callback function (automatically bind'd)
// * *v8.Value (returned as-is)
//
// Any nil pointers are converted to undefined in JS.
//
// Values for elements in maps, structs, and slices may be any of the above
// types.
//
// When structs are being converted, any fields with json struct tags will
// respect the json naming entry. For example:
// var x = struct {
// Ignored string `json:"-"`
// Renamed string `json:"foo"`
// DefaultName string `json:",omitempty"`
// Bar string
// }{"a", "b", "c", "d"}
// will be converted as:
// {
// foo: "a",
// DefaultName: "b",
// Bar: "c",
// }
// Also, embedded structs (or pointers-to-structs) will get inlined.
//
// Byte slices tagged as 'v8:"arraybuffer"' will be converted into a javascript
// ArrayBuffer object for more efficient conversion. For example:
// var y = struct {
// Buf []byte `v8:"arraybuffer"`
// }{[]byte{1,2,3}}
// will be converted as
// {
// Buf: new Uint8Array([1,2,3]).buffer
// }
func (ctx *Context) Create(val interface{}) (*Value, error) {
v, _, err := ctx.create(reflect.ValueOf(val))
return v, err
}
func (ctx *Context) createVal(v C.ImmediateValue, kinds kindMask) *Value {
return ctx.newValue(C.v8_Context_Create(ctx.ptr, v), C.KindMask(kinds))
}
func getJsName(fieldName, jsonTag string) string {
jsonName := strings.TrimSpace(strings.Split(jsonTag, ",")[0])
if jsonName == "-" {
return "" // skip this field
}
if jsonName == "" {
return fieldName // use the default name
}
return jsonName // explict name specified
}
func (ctx *Context) create(val reflect.Value) (v *Value, allocated bool, err error) {
return ctx.createWithTags(val, []string{})
}
func (ctx *Context) createWithTags(val reflect.Value, tags []string) (v *Value, allocated bool, err error) {
if !val.IsValid() {
return ctx.createVal(C.ImmediateValue{Type: C.tUNDEFINED}, mask(KindUndefined)), true, nil
}
if val.Type() == valuePtrType {
// This is the only time that we return an already-allocated Value, so
// allocated is false.
return val.Interface().(*Value), false, nil
} else if val.Type() == timeType {
msec := C.double(val.Interface().(time.Time).UnixNano()) / 1e6
return ctx.createVal(C.ImmediateValue{Type: C.tDATE, Float64: msec}, unionKindDate), true, nil
}
switch val.Kind() {
case reflect.Bool:
bval := C.int(0)
if val.Bool() {
bval = 1
}
return ctx.createVal(C.ImmediateValue{Type: C.tBOOL, Bool: bval}, mask(KindBoolean)), true, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
num := C.double(val.Convert(float64Type).Float())
return ctx.createVal(C.ImmediateValue{Type: C.tFLOAT64, Float64: num}, mask(KindNumber)), true, nil
case reflect.String:
gostr := val.String()
str := C.ByteArray{ptr: C.CString(gostr), len: C.int(len(gostr))}
defer C.free(unsafe.Pointer(str.ptr))
return ctx.createVal(C.ImmediateValue{Type: C.tSTRING, Mem: str}, unionKindString), true, nil
case reflect.UnsafePointer, reflect.Uintptr:
return nil, false, fmt.Errorf("Uintptr not supported: %#v", val.Interface())
case reflect.Complex64, reflect.Complex128:
return nil, false, fmt.Errorf("Complex not supported: %#v", val.Interface())
case reflect.Chan:
return nil, false, fmt.Errorf("Chan not supported: %#v", val.Interface())
case reflect.Func:
if val.Type().ConvertibleTo(callbackType) {
name := path.Base(runtime.FuncForPC(val.Pointer()).Name())
return ctx.Bind(name, val.Convert(callbackType).Interface().(Callback)), true, nil
}
return nil, false, fmt.Errorf("Func not supported: %#v", val.Interface())
case reflect.Interface, reflect.Ptr:
return ctx.create(val.Elem())
case reflect.Map:
if val.Type().Key() != stringType {
return nil, false, fmt.Errorf("Map keys must be strings, %s not allowed", val.Type().Key())
}
ob := ctx.createVal(C.ImmediateValue{Type: C.tOBJECT}, mask(KindObject))
keys := val.MapKeys()
sort.Sort(stringKeys(keys))
for _, key := range keys {
v, wasAllocated, err := ctx.create(val.MapIndex(key))
if err != nil {
return nil, false, fmt.Errorf("map key %q: %v", key.String(), err)
}
if err := ob.Set(key.String(), v); err != nil {
return nil, false, err
}
if wasAllocated {
v.release()
}
}
return ob, true, nil
case reflect.Struct:
ob := ctx.createVal(C.ImmediateValue{Type: C.tOBJECT}, mask(KindObject))
return ob, true, ctx.writeStructFields(ob, val)
case reflect.Array, reflect.Slice:
arrayBuffer := false
for _, tag := range tags {
if strings.TrimSpace(tag) == "arraybuffer" {
arrayBuffer = true
}
}
if arrayBuffer && val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8 {
// Special case for byte array -> arraybuffer
bytes := val.Bytes()
var ptr *C.char
if bytes != nil && len(bytes) > 0 {
ptr = (*C.char)(unsafe.Pointer(&val.Bytes()[0]))
}
ob := ctx.createVal(
C.ImmediateValue{
Type: C.tARRAYBUFFER,
Mem: C.ByteArray{ptr: ptr, len: C.int(val.Len())},
},
unionKindArrayBuffer,
)
return ob, true, nil
} else {
ob := ctx.createVal(
C.ImmediateValue{
Type: C.tARRAY,
Mem: C.ByteArray{ptr: nil, len: C.int(val.Len())},
},
unionKindArray,
)
for i := 0; i < val.Len(); i++ {
v, wasAllocated, err := ctx.create(val.Index(i))
if err != nil {
return nil, false, fmt.Errorf("index %d: %v", i, err)
}
if err := ob.SetIndex(i, v); err != nil {
return nil, false, err
}
if wasAllocated {
v.release()
}
}
return ob, true, nil
}
}
panic("Unknown kind!")
}
func (ctx *Context) writeStructFields(ob *Value, val reflect.Value) error {
t := val.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
name := getJsName(f.Name, f.Tag.Get("json"))
if name == "" {
continue // skip field with tag `json:"-"`
}
// Inline embedded fields.
if f.Anonymous {
sub := val.Field(i)
for sub.Kind() == reflect.Ptr && !sub.IsNil() {
sub = sub.Elem()
}
if sub.Kind() == reflect.Struct {
err := ctx.writeStructFields(ob, sub)
if err != nil {
return fmt.Errorf("Writing embedded field %q: %v", f.Name, err)
}
continue
}
}
if !unicode.IsUpper(rune(f.Name[0])) {
continue // skip unexported fields
}
v8Tags := strings.Split(f.Tag.Get("v8"), ",")
v, wasAllocated, err := ctx.createWithTags(val.Field(i), v8Tags)
if err != nil {
return fmt.Errorf("field %q: %v", f.Name, err)
}
if err := ob.Set(name, v); err != nil {
return err
}
if wasAllocated {
v.release()
}
}
// Also export any methods of the struct that match the callback type.
for i := 0; i < t.NumMethod(); i++ {
name := t.Method(i).Name
if !unicode.IsUpper(rune(name[0])) {
continue // skip unexported values
}
m := val.Method(i)
if m.Type().ConvertibleTo(callbackType) {
v, wasAllocated, err := ctx.create(m)
if err != nil {
return fmt.Errorf("method %q: %v", name, err)
}
if err := ob.Set(name, v); err != nil {
return err
}
if wasAllocated {
v.release()
}
}
}
return nil
}
type stringKeys []reflect.Value
func (s stringKeys) Len() int { return len(s) }
func (s stringKeys) Swap(a, b int) { s[a], s[b] = s[b], s[a] }
func (s stringKeys) Less(a, b int) bool { return s[a].String() < s[b].String() }