-
Notifications
You must be signed in to change notification settings - Fork 171
/
block.go
168 lines (152 loc) · 4.73 KB
/
block.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
package vm
import (
"fmt"
"github.com/goby-lang/goby/vm/classes"
"github.com/goby-lang/goby/vm/errors"
)
// BlockObject represents an instance of `Block` class.
// In Goby, block literals can be used to define an "anonymous function" by using the `Block` class.
//
// A block literal consists of `do`-`end` and code snippets between them,
// containing optional "block parameters" surrounded by `| |`
// that can be referred to within the block as "block variables".
//
// `Block.new` can take a block literal, returning a "block" object.
//
// You can call `#call` method on the block object to execute the block whenever and wherever you want.
// You can even pass around the block objects across your codebase.
//
// ```ruby
// bl = Block.new do |array|
// array.reduce do |sum, i|
// sum + i
// end
// end
// #=> <Block: REPL>
// bl.call([1, 2, 3, 4]) #=> 10
// ```
//
// You can even form a `closure` (note that you can do that without using `Block.new`):
//
// ```ruby
// n = 1
// bl = Block.new do
// n = n + 1
// end
// #=> <Block: REPL>
// bl.call
// #=> 2
// bl.call
// #=> 3
// bl.call
// #=> 4
// ```
//
type BlockObject struct {
*baseObj
instructionSet *instructionSet
ep *normalCallFrame
self Object
}
// Class methods --------------------------------------------------------
func builtinBlockClassMethods() []*BuiltinMethodObject {
return []*BuiltinMethodObject{
{
// @param block literal
// @return [Block]
Name: "new",
Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
if blockFrame == nil {
return t.vm.InitErrorObject(errors.ArgumentError, sourceLine, "Can't initialize block object without block argument")
}
return t.vm.initBlockObject(blockFrame.instructionSet, blockFrame.ep, blockFrame.self)
},
},
}
}
// Instance methods -----------------------------------------------------
func builtinBlockInstanceMethods() []*BuiltinMethodObject {
return []*BuiltinMethodObject{
{
// Executes the block and returns the result.
// It can take arbitrary number of arguments and passes them to the block arguments of the block object,
// keeping the order of the arguments.
//
// ```ruby
// bl = Block.new do |array|
// array.reduce do |sum, i|
// sum + i
// end
// end
// #=> <Block: REPL>
// bl.call([1, 2, 3, 4]) #=> 10
// ```
//
// TODO: should check if the following behavior is OK or not
// Note that the method does NOT check the number of the arguments and the number of block parameters.
// * if the number of the arguments exceed, the rest will just be truncated:
//
// ```ruby
// p = Block.new do |i, j, k|
// [i, j, k]
// end
// p.call(1, 2, 3, 4, 5) #=> [1, 2, 3]
// ```
//
// * if the number of the block parameters exceeds, the rest will just be filled with `nil`:
//
// ```ruby
// p = Block.new do |i, j, k|
// [i, j, k]
// end
// p.call #=> [nil, nil, nil]
// ```
//
// @param object [Object]...
// @return [Object]
Name: "call",
Fn: func(receiver Object, sourceLine int, t *Thread, args []Object, blockFrame *normalCallFrame) Object {
block := receiver.(*BlockObject)
c := newNormalCallFrame(block.instructionSet, block.instructionSet.filename, sourceLine)
c.ep = block.ep
c.self = block.self
c.isBlock = true
return t.builtinMethodYield(c, args...).Target
},
},
}
}
// Internal functions ===================================================
// Functions for initialization -----------------------------------------
func (vm *VM) initBlockClass() *RClass {
class := vm.initializeClass(classes.BlockClass)
class.setBuiltinMethods(builtinBlockClassMethods(), true)
class.setBuiltinMethods(builtinBlockInstanceMethods(), false)
return class
}
func (vm *VM) initBlockObject(is *instructionSet, ep *normalCallFrame, self Object) *BlockObject {
return &BlockObject{
baseObj: &baseObj{class: vm.topLevelClass(classes.BlockClass)},
instructionSet: is,
ep: ep,
self: self,
}
}
// Polymorphic helper functions -----------------------------------------
// Value returns the object
func (bo *BlockObject) Value() interface{} {
return bo.instructionSet
}
// toString returns the object's name as the string format
func (bo *BlockObject) toString() string {
return fmt.Sprintf("<Block: %s>", bo.instructionSet.filename)
}
// toJSON just delegates to toString
func (bo *BlockObject) toJSON(t *Thread) string {
return bo.toString()
}
// copy returns the duplicate of the Array object
func (bo *BlockObject) copy() Object {
newC := &BlockObject{baseObj: &baseObj{class: bo.class}, instructionSet: bo.instructionSet}
return newC
}