forked from dckc/go-duktape
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathduktape.go
232 lines (197 loc) · 6.56 KB
/
duktape.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
package duktape
/*
#cgo linux LDFLAGS: -lm
# include "duktape.h"
extern duk_ret_t goFinalize(duk_context *ctx);
extern duk_ret_t goCall(duk_context *ctx);
*/
import "C"
import "sync"
import "errors"
import "unsafe"
const goFuncProp = "goFuncData"
const goObjProp = "goObjData"
const (
DUK_ENUM_INCLUDE_NONENUMERABLE = C.DUK_ENUM_INCLUDE_NONENUMERABLE
DUK_ENUM_INCLUDE_INTERNAL = C.DUK_ENUM_INCLUDE_INTERNAL
DUK_ENUM_OWN_PROPERTIES_ONLY = C.DUK_ENUM_OWN_PROPERTIES_ONLY
DUK_ENUM_ARRAY_INDICES_ONLY = C.DUK_ENUM_ARRAY_INDICES_ONLY
DUK_ENUM_SORT_ARRAY_INDICES = C.DUK_ENUM_SORT_ARRAY_INDICES
DUK_ENUM_NO_PROXY_BEHAVIOR = C.DUK_ENUM_NO_PROXY_BEHAVIOR
)
const (
DUK_RET_UNIMPLEMENTED_ERROR = C.DUK_RET_UNIMPLEMENTED_ERROR
DUK_RET_UNSUPPORTED_ERROR = C.DUK_RET_UNSUPPORTED_ERROR
DUK_RET_INTERNAL_ERROR = C.DUK_RET_INTERNAL_ERROR
DUK_RET_ALLOC_ERROR = C.DUK_RET_ALLOC_ERROR
DUK_RET_ASSERTION_ERROR = C.DUK_RET_ASSERTION_ERROR
DUK_RET_API_ERROR = C.DUK_RET_API_ERROR
DUK_RET_UNCAUGHT_ERROR = C.DUK_RET_UNCAUGHT_ERROR
DUK_RET_ERROR = C.DUK_RET_ERROR
DUK_RET_EVAL_ERROR = C.DUK_RET_EVAL_ERROR
DUK_RET_RANGE_ERROR = C.DUK_RET_RANGE_ERROR
DUK_RET_REFERENCE_ERROR = C.DUK_RET_REFERENCE_ERROR
DUK_RET_SYNTAX_ERROR = C.DUK_RET_SYNTAX_ERROR
DUK_RET_TYPE_ERROR = C.DUK_RET_TYPE_ERROR
DUK_RET_URI_ERROR = C.DUK_RET_URI_ERROR
)
const (
DUK_TYPE_NONE Type = iota
DUK_TYPE_UNDEFINED
DUK_TYPE_NULL
DUK_TYPE_BOOLEAN
DUK_TYPE_NUMBER
DUK_TYPE_STRING
DUK_TYPE_OBJECT
DUK_TYPE_BUFFER
DUK_TYPE_POINTER
)
const (
DUK_COMPILE_EVAL = C.DUK_COMPILE_EVAL
DUK_COMPILE_FUNCTION = C.DUK_COMPILE_FUNCTION
DUK_COMPILE_STRICT = C.DUK_COMPILE_STRICT
)
const (
DUK_ERR_UNIMPLEMENTED_ERROR = C.DUK_ERR_UNIMPLEMENTED_ERROR
DUK_ERR_UNSUPPORTED_ERROR = C.DUK_ERR_UNSUPPORTED_ERROR
DUK_ERR_INTERNAL_ERROR = C.DUK_ERR_INTERNAL_ERROR
DUK_ERR_ALLOC_ERROR = C.DUK_ERR_ALLOC_ERROR
DUK_ERR_ASSERTION_ERROR = C.DUK_ERR_ASSERTION_ERROR
DUK_ERR_API_ERROR = C.DUK_ERR_API_ERROR
DUK_ERR_UNCAUGHT_ERROR = C.DUK_ERR_UNCAUGHT_ERROR
DUK_ERR_ERROR = C.DUK_ERR_ERROR
DUK_ERR_EVAL_ERROR = C.DUK_ERR_EVAL_ERROR
DUK_ERR_RANGE_ERROR = C.DUK_ERR_RANGE_ERROR
DUK_ERR_REFERENCE_ERROR = C.DUK_ERR_REFERENCE_ERROR
DUK_ERR_SYNTAX_ERROR = C.DUK_ERR_SYNTAX_ERROR
DUK_ERR_TYPE_ERROR = C.DUK_ERR_TYPE_ERROR
DUK_ERR_URI_ERROR = C.DUK_ERR_URI_ERROR
)
type Type int
func (t Type) IsNone() bool { return t == DUK_TYPE_NONE }
func (t Type) IsUndefined() bool { return t == DUK_TYPE_UNDEFINED }
func (t Type) IsNull() bool { return t == DUK_TYPE_NULL }
func (t Type) IsBool() bool { return t == DUK_TYPE_BOOLEAN }
func (t Type) IsNumber() bool { return t == DUK_TYPE_NUMBER }
func (t Type) IsString() bool { return t == DUK_TYPE_STRING }
func (t Type) IsObject() bool { return t == DUK_TYPE_OBJECT }
func (t Type) IsBuffer() bool { return t == DUK_TYPE_BUFFER }
func (t Type) IsPointer() bool { return t == DUK_TYPE_POINTER }
var objectMutex sync.Mutex
var objectMap map[unsafe.Pointer]interface{} = make(map[unsafe.Pointer]interface{})
type Context struct {
duk_context unsafe.Pointer
}
// Returns initialized duktape context object
func NewContext() *Context {
ctx := &Context{
// TODO: "A caller SHOULD implement a fatal error handler in most applications."
duk_context: C.duk_create_heap(nil, nil, nil, nil, nil),
}
return ctx
}
func (d *Context) PutInternalPropString(objIndex int, key string) bool {
cKey := C.CString("\xff" + key) // \xff as the first char designates an internal property
defer C.free(unsafe.Pointer(cKey))
return int(C.duk_put_prop_string(d.duk_context, C.duk_idx_t(objIndex), cKey)) == 1
}
func (d *Context) GetInternalPropString(objIndex int, key string) bool {
cKey := C.CString("\xff" + key) // \xff as the first char designates an internal property
defer C.free(unsafe.Pointer(cKey))
return int(C.duk_get_prop_string(d.duk_context, C.duk_idx_t(objIndex), cKey)) == 1
}
//export goFinalize
func goFinalize(ctx unsafe.Pointer) C.duk_ret_t {
d := &Context{ctx}
d.PushCurrentFunction()
d.GetInternalPropString(-1, goFuncProp)
if !Type(d.GetType(-1)).IsPointer() {
d.Pop2()
return C.duk_ret_t(C.DUK_RET_TYPE_ERROR)
}
key := d.GetPointer(-1)
d.Pop2()
objectMutex.Lock()
delete(objectMap, key)
objectMutex.Unlock()
C.free(key)
return C.duk_ret_t(0)
}
func (d *Context) putGoObjectRef(prop string, o interface{}) {
key := C.malloc(1) // guaranteed to be unique until freed
objectMutex.Lock()
objectMap[key] = o
objectMutex.Unlock()
d.PushCFunction((*[0]byte)(C.goFinalize), 1)
d.PushPointer(key)
d.PutInternalPropString(-2, goFuncProp)
d.SetFinalizer(-2)
d.PushPointer(key)
d.PutInternalPropString(-2, prop)
}
func (d *Context) PushGoObject(o interface{}) {
d.PushObject()
d.putGoObjectRef(goObjProp, o)
}
func (d *Context) getGoObjectRef(objIndex int, prop string) interface{} {
d.GetInternalPropString(objIndex, prop)
if !Type(d.GetType(-1)).IsPointer() {
d.Pop()
return nil
}
key := d.GetPointer(-1)
d.Pop()
objectMutex.Lock()
defer objectMutex.Unlock()
return objectMap[key]
}
func (d *Context) GetGoObject(objIndex int) interface{} {
return d.getGoObjectRef(objIndex, goObjProp)
}
//export goCall
func goCall(ctx unsafe.Pointer) C.duk_ret_t {
d := &Context{ctx}
/*
d.PushContextDump()
log.Printf("goCall context: %s", d.GetString(-1))
d.Pop()
*/
d.PushCurrentFunction()
if fd, _ := d.getGoObjectRef(-1, goFuncProp).(*GoFuncData); fd == nil {
d.Pop()
return C.duk_ret_t(C.DUK_RET_TYPE_ERROR)
} else {
d.Pop()
return C.duk_ret_t(fd.f(d))
}
}
type GoFunc func(d *Context) int
type GoFuncData struct {
f GoFunc
}
// Push goCall with its "goFuncData" property set to fd
func (d *Context) PushGoFunc(f GoFunc) {
fd := &GoFuncData{f}
d.PushCFunction((*[0]byte)(C.goCall), C.DUK_VARARGS)
d.putGoObjectRef(goFuncProp, fd)
}
type MethodSuite map[string]GoFunc
func (d *Context) EvalWith(source string, suite MethodSuite) error {
if err := d.PevalString(source); err != 0 {
return errors.New(d.SafeToString(-1))
}
d.PushObject()
for prop, f := range suite {
d.PushGoFunc(f)
d.PutPropString(-2, prop)
}
if err := d.Pcall(1); err != 0 {
return errors.New(d.SafeToString(-1))
}
return nil
}
// TBD: panic handling.
// When a goroutine panics, mark the context as panicking and throw a special JS
// exception, saving the value passed to panic() to the context.
// When the exception is caught, retrieve the value that was passed to panic(),
// and call panic() again.