This repository has been archived by the owner on Jul 22, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 35
/
mruby.go
400 lines (324 loc) · 11 KB
/
mruby.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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
package mruby
import "unsafe"
// #cgo CFLAGS: -Imruby-build/mruby/include
// #cgo LDFLAGS: ${SRCDIR}/libmruby.a -lm
// #include <stdlib.h>
// #include "gomruby.h"
import "C"
// Mrb represents a single instance of mruby.
type Mrb struct {
state *C.mrb_state
}
// GetGlobalVariable returns the value of the global variable by the given name.
func (m *Mrb) GetGlobalVariable(name string) *MrbValue {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
return newValue(m.state, C._go_mrb_gv_get(m.state, C.mrb_intern_cstr(m.state, cs)))
}
// SetGlobalVariable sets the value of the global variable by the given name.
func (m *Mrb) SetGlobalVariable(name string, value Value) {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
v := value.MrbValue(m)
C._go_mrb_gv_set(m.state, C.mrb_intern_cstr(m.state, cs), v.value)
}
// ArenaIndex represents the index into the arena portion of the GC.
//
// See ArenaSave for more information.
type ArenaIndex int
// NewMrb creates a new instance of Mrb, representing the state of a single
// Ruby VM.
//
// When you're finished with the VM, clean up all resources it is using
// by calling the Close method.
func NewMrb() *Mrb {
state := C.mrb_open()
return &Mrb{
state: state,
}
}
// ArenaRestore restores the arena index so the objects between the save and this point
// can be garbage collected in the future.
//
// See ArenaSave for more documentation.
func (m *Mrb) ArenaRestore(idx ArenaIndex) {
C.mrb_gc_arena_restore(m.state, C.int(idx))
}
// ArenaSave saves the index into the arena.
//
// Restore the arena index later by calling ArenaRestore.
//
// The arena is where objects returned by functions such as LoadString
// are stored. By saving the index and then later restoring it with
// ArenaRestore, these objects can be garbage collected. Otherwise, the
// objects will never be garbage collected.
//
// The recommended usage pattern for memory management is to save
// the arena index prior to any Ruby execution, to turn the resulting
// Ruby value into Go values as you see fit, then to restore the arena
// index so that GC can collect any values.
//
// Of course, when Close() is called, all objects in the arena are
// garbage collected anyways, so if you're only calling mruby for a short
// period of time, you might not have to worry about saving/restoring the
// arena.
func (m *Mrb) ArenaSave() ArenaIndex {
return ArenaIndex(C.mrb_gc_arena_save(m.state))
}
// EnableGC enables the garbage collector for this mruby instance. It returns
// true if garbage collection was previously disabled.
func (m *Mrb) EnableGC() {
C._go_enable_gc(m.state)
}
// DisableGC disables the garbage collector for this mruby instance. It returns
// true if it was previously disabled.
func (m *Mrb) DisableGC() {
C._go_disable_gc(m.state)
}
// LiveObjectCount returns the number of objects that have not been collected (aka, alive).
func (m *Mrb) LiveObjectCount() int {
return int(C._go_gc_live(m.state))
}
// Class returns the class with the kgiven name and superclass. Note that
// if you call this with a class that doesn't exist, mruby will abort the
// application (like a panic, but not a Go panic).
//
// super can be nil, in which case the Object class will be used.
func (m *Mrb) Class(name string, super *Class) *Class {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
var class *C.struct_RClass
if super == nil {
class = C.mrb_class_get(m.state, cs)
} else {
class = C.mrb_class_get_under(m.state, super.class, cs)
}
return newClass(m, class)
}
// Module returns the named module as a *Class. If the module is invalid,
// NameError is triggered within your program and SIGABRT is sent to the
// application.
func (m *Mrb) Module(name string) *Class {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
class := C.mrb_module_get(m.state, cs)
return newClass(m, class)
}
// Close a Mrb, this must be called to properly free resources, and
// should only be called once.
func (m *Mrb) Close() {
// Delete all the methods from the state
stateMethodTable.Mutex.Lock()
delete(stateMethodTable.Map, m.state)
stateMethodTable.Mutex.Unlock()
// Close the state
C.mrb_close(m.state)
}
// ConstDefined checks if the given constant is defined in the scope.
//
// This should be used, for example, before a call to Class, because a
// failure in Class will crash your program (by design). You can retrieve
// the Value of a Class by calling Value().
func (m *Mrb) ConstDefined(name string, scope Value) bool {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
scopeV := scope.MrbValue(m).value
b := C.mrb_const_defined(
m.state, scopeV, C.mrb_intern_cstr(m.state, cs))
return C.ushort(b) != 0
}
// FullGC executes a complete GC cycle on the VM.
func (m *Mrb) FullGC() {
C.mrb_full_gc(m.state)
}
// GetArgs returns all the arguments that were given to the currnetly
// called function (currently on the stack).
func (m *Mrb) GetArgs() []*MrbValue {
getArgLock.Lock()
defer getArgLock.Unlock()
// Clear reset the accumulator to zero length
getArgAccumulator = make([]C.mrb_value, 0, C._go_get_max_funcall_args())
// Get all the arguments and put it into our accumulator
count := C._go_mrb_get_args_all(m.state)
// Convert those all to values
values := make([]*MrbValue, count)
for i := 0; i < int(count); i++ {
values[i] = newValue(m.state, getArgAccumulator[i])
}
return values
}
// IncrementalGC runs an incremental GC step. It is much less expensive
// than a FullGC, but must be called multiple times for GC to actually
// happen.
//
// This function is best called periodically when executing Ruby in
// the VM many times (thousands of times).
func (m *Mrb) IncrementalGC() {
C.mrb_incremental_gc(m.state)
}
// LoadString loads the given code, executes it, and returns its final
// value that it might return.
func (m *Mrb) LoadString(code string) (*MrbValue, error) {
cs := C.CString(code)
defer C.free(unsafe.Pointer(cs))
value := C._go_mrb_load_string(m.state, cs)
if exc := checkException(m.state); exc != nil {
return nil, exc
}
return newValue(m.state, value), nil
}
// Run executes the given value, which should be a proc type.
//
// If you're looking to execute code directly a string, look at LoadString.
//
// If self is nil, it is set to the top-level self.
func (m *Mrb) Run(v Value, self Value) (*MrbValue, error) {
if self == nil {
self = m.TopSelf()
}
mrbV := v.MrbValue(m)
mrbSelf := self.MrbValue(m)
proc := C._go_mrb_proc_ptr(mrbV.value)
value := C.mrb_run(m.state, proc, mrbSelf.value)
if exc := checkException(m.state); exc != nil {
return nil, exc
}
return newValue(m.state, value), nil
}
// RunWithContext is a context-aware parser (aka, it does not discard state
// between runs). It returns a magic integer that describes the stack in place,
// so that it can be re-used on the next call. This is how local variables can
// traverse ruby parse invocations.
//
// Otherwise, it is very similar in function to Run()
func (m *Mrb) RunWithContext(v Value, self Value, stackKeep int) (int, *MrbValue, error) {
if self == nil {
self = m.TopSelf()
}
mrbV := v.MrbValue(m)
mrbSelf := self.MrbValue(m)
proc := C._go_mrb_proc_ptr(mrbV.value)
i := C.int(stackKeep)
value := C._go_mrb_context_run(m.state, proc, mrbSelf.value, &i)
if exc := checkException(m.state); exc != nil {
return stackKeep, nil, exc
}
return int(i), newValue(m.state, value), nil
}
// Yield yields to a block with the given arguments.
//
// This should be called within the context of a Func.
func (m *Mrb) Yield(block Value, args ...Value) (*MrbValue, error) {
mrbBlock := block.MrbValue(m)
var argv []C.mrb_value
var argvPtr *C.mrb_value
if len(args) > 0 {
// Make the raw byte slice to hold our arguments we'll pass to C
argv = make([]C.mrb_value, len(args))
for i, arg := range args {
argv[i] = arg.MrbValue(m).value
}
argvPtr = &argv[0]
}
result := C._go_mrb_yield_argv(
m.state,
mrbBlock.value,
C.mrb_int(len(argv)),
argvPtr)
if exc := checkException(m.state); exc != nil {
return nil, exc
}
return newValue(m.state, result), nil
}
//-------------------------------------------------------------------
// Functions handling defining new classes/modules in the VM
//-------------------------------------------------------------------
// DefineClass defines a new top-level class.
//
// If super is nil, the class will be defined under Object.
func (m *Mrb) DefineClass(name string, super *Class) *Class {
if super == nil {
super = m.ObjectClass()
}
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
return newClass(
m, C.mrb_define_class(m.state, cs, super.class))
}
// DefineClassUnder defines a new class under another class.
//
// This is, for example, how you would define the World class in
// `Hello::World` where Hello is the "outer" class.
func (m *Mrb) DefineClassUnder(name string, super *Class, outer *Class) *Class {
if super == nil {
super = m.ObjectClass()
}
if outer == nil {
outer = m.ObjectClass()
}
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
return newClass(m, C.mrb_define_class_under(
m.state, outer.class, cs, super.class))
}
// DefineModule defines a top-level module.
func (m *Mrb) DefineModule(name string) *Class {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
return newClass(m, C.mrb_define_module(m.state, cs))
}
// DefineModuleUnder defines a module under another class/module.
func (m *Mrb) DefineModuleUnder(name string, outer *Class) *Class {
if outer == nil {
outer = m.ObjectClass()
}
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
return newClass(m,
C.mrb_define_module_under(m.state, outer.class, cs))
}
//-------------------------------------------------------------------
// Functions below return Values or constant Classes
//-------------------------------------------------------------------
// ObjectClass returns the Object top-level class.
func (m *Mrb) ObjectClass() *Class {
return newClass(m, m.state.object_class)
}
// KernelModule returns the Kernel top-level module.
func (m *Mrb) KernelModule() *Class {
return newClass(m, m.state.kernel_module)
}
// TopSelf returns the top-level `self` value.
func (m *Mrb) TopSelf() *MrbValue {
return newValue(m.state, C.mrb_obj_value(unsafe.Pointer(m.state.top_self)))
}
// FalseValue returns a Value for "false"
func (m *Mrb) FalseValue() *MrbValue {
return newValue(m.state, C.mrb_false_value())
}
// NilValue returns "nil"
func (m *Mrb) NilValue() *MrbValue {
return newValue(m.state, C.mrb_nil_value())
}
// TrueValue returns a Value for "true"
func (m *Mrb) TrueValue() *MrbValue {
return newValue(m.state, C.mrb_true_value())
}
// FixnumValue returns a Value for a fixed number.
func (m *Mrb) FixnumValue(v int) *MrbValue {
return newValue(m.state, C.mrb_fixnum_value(C.mrb_int(v)))
}
// StringValue returns a Value for a string.
func (m *Mrb) StringValue(s string) *MrbValue {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
return newValue(m.state, C.mrb_str_new_cstr(m.state, cs))
}
func checkException(state *C.mrb_state) error {
if state.exc == nil {
return nil
}
err := newExceptionValue(state)
state.exc = nil
return err
}