Skip to content

Commit a7cfa40

Browse files
authored
Add support for block arguments in methods (#21)
* Add support for block arguments in methods * Fix regression in block methods requiring parens even without args
1 parent 948b484 commit a7cfa40

File tree

11 files changed

+323
-24
lines changed

11 files changed

+323
-24
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# vibe: 0.2
2+
3+
def times(n)
4+
i = 0
5+
while i < n
6+
yield(i)
7+
i = i + 1
8+
end
9+
end
10+
11+
def with_retry(max_attempts, delay_seconds)
12+
attempt = 0
13+
while attempt < max_attempts
14+
result = yield(attempt)
15+
if result
16+
return result
17+
end
18+
attempt = attempt + 1
19+
end
20+
nil
21+
end
22+
23+
def benchmark
24+
start = Time.now
25+
result = yield
26+
elapsed = Time.now - start
27+
{ result: result, elapsed: elapsed }
28+
end
29+
30+
def tap(value)
31+
yield(value)
32+
value
33+
end
34+
35+
def transform_keys(hash)
36+
result = {}
37+
hash.keys.each do |key|
38+
new_key = yield(key)
39+
result[new_key] = hash[key]
40+
end
41+
result
42+
end

tests/blocks/error_cases.vibe

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
def each_without_block
2+
[1, 2, 3].each
3+
end
4+
5+
def map_without_block
6+
[1, 2, 3].map
7+
end
8+
9+
def reduce_empty_without_init
10+
[].reduce do |acc, x|
11+
acc + x
12+
end
13+
end
14+
15+
def reduce_empty_with_init
16+
[].reduce(10) do |acc, x|
17+
acc + x
18+
end
19+
end

tests/complex/advanced_blocks.vibe

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
def call_with_three
2+
yield(1, 2, 3)
3+
end
4+
5+
def nested_sum(arr)
6+
total = 0
7+
arr.each do |x|
8+
arr.each do |y|
9+
total = total + yield(x, y)
10+
end
11+
end
12+
total
13+
end
14+
15+
def yield_one
16+
yield(7)
17+
end
18+
19+
def run
20+
captured = []
21+
call_with_three do |a, b|
22+
captured = [a, b]
23+
a + b
24+
end
25+
26+
nested = nested_sum([1, 2, 3]) do |x, y|
27+
x * y
28+
end
29+
30+
defaulted = yield_one do |a, b|
31+
if b == nil
32+
a
33+
else
34+
a + b
35+
end
36+
end
37+
38+
{ captured: captured, nested: nested, defaulted: defaulted }
39+
end

tests/complex/with_blocks.vibe

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
class Collection
2+
def initialize(items)
3+
@items = items
4+
end
5+
6+
def each
7+
@items.each do |item|
8+
yield(item)
9+
end
10+
end
11+
12+
def map_items
13+
result = []
14+
@items.each do |item|
15+
result = result.push(yield(item))
16+
end
17+
result
18+
end
19+
20+
def find
21+
result = nil
22+
done = false
23+
@items.each do |item|
24+
if done == false
25+
if yield(item)
26+
result = item
27+
done = true
28+
end
29+
end
30+
end
31+
result
32+
end
33+
end
34+
35+
def run
36+
coll = Collection.new([1, 2, 3, 4, 5])
37+
38+
sum = 0
39+
coll.each do |x|
40+
sum = sum + x
41+
end
42+
43+
doubled = coll.map_items do |x|
44+
x * 2
45+
end
46+
47+
first_even = coll.find do |x|
48+
x % 2 == 0
49+
end
50+
51+
{ sum: sum, doubled: doubled, first_even: first_even }
52+
end

tests/complex/yield_basics.vibe

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
def twice
2+
yield
3+
yield
4+
end
5+
6+
def with_value(val)
7+
yield(val)
8+
end
9+
10+
def transform(a, b)
11+
yield(a) + yield(b)
12+
end
13+
14+
def run
15+
count = 0
16+
twice do
17+
count = count + 1
18+
end
19+
20+
doubled = with_value(21) do |x|
21+
x * 2
22+
end
23+
24+
sum = transform(3, 4) do |n|
25+
n * 10
26+
end
27+
28+
{ count: count, doubled: doubled, sum: sum }
29+
end

vibes/ast.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,14 @@ type BlockLiteral struct {
259259
func (b *BlockLiteral) exprNode() {}
260260
func (b *BlockLiteral) Pos() Position { return b.position }
261261

262+
type YieldExpr struct {
263+
Args []Expression
264+
position Position
265+
}
266+
267+
func (y *YieldExpr) exprNode() {}
268+
func (y *YieldExpr) Pos() Position { return y.position }
269+
262270
type PropertyDecl struct {
263271
Names []string
264272
Kind string // property/getter/setter

vibes/execution.go

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ func (exec *Execution) assign(target Expression, value Value, env *Env) error {
263263
if fn.Private && !exec.isCurrentReceiver(obj) {
264264
return exec.errorAt(t.Pos(), "private method %s", setterName)
265265
}
266-
_, err := exec.callFunction(fn, obj, []Value{value}, nil, t.Pos())
266+
_, err := exec.callFunction(fn, obj, []Value{value}, nil, NewNil(), t.Pos())
267267
return err
268268
}
269269
if _, hasGetter := obj.Instance().Class.Methods[t.Property]; hasGetter {
@@ -278,7 +278,7 @@ func (exec *Execution) assign(target Expression, value Value, env *Env) error {
278278
if fn.Private && !exec.isCurrentReceiver(obj) {
279279
return exec.errorAt(t.Pos(), "private method %s", setterName)
280280
}
281-
_, err := exec.callFunction(fn, obj, []Value{value}, nil, t.Pos())
281+
_, err := exec.callFunction(fn, obj, []Value{value}, nil, NewNil(), t.Pos())
282282
return err
283283
}
284284
if _, hasGetter := cl.ClassMethods[t.Property]; hasGetter {
@@ -531,6 +531,8 @@ func (exec *Execution) evalExpressionWithAuto(expr Expression, env *Env, autoCal
531531
return exec.evalCallExpr(e, env)
532532
case *BlockLiteral:
533533
return exec.evalBlockLiteral(e, env)
534+
case *YieldExpr:
535+
return exec.evalYield(e, env)
534536
default:
535537
return NewNil(), exec.errorAt(expr.Pos(), "unsupported expression")
536538
}
@@ -556,10 +558,7 @@ func (exec *Execution) invokeCallable(callee Value, receiver Value, args []Value
556558
switch callee.Kind() {
557559
case KindFunction:
558560
fn := callee.Function()
559-
if block.Kind() != KindNil {
560-
return NewNil(), exec.errorAt(pos, "script functions do not accept blocks")
561-
}
562-
return exec.callFunction(fn, receiver, args, kwargs, pos)
561+
return exec.callFunction(fn, receiver, args, kwargs, block, pos)
563562
case KindBuiltin:
564563
result, err := callee.Builtin().Fn(exec, receiver, args, kwargs, block)
565564
if err != nil {
@@ -571,11 +570,12 @@ func (exec *Execution) invokeCallable(callee Value, receiver Value, args []Value
571570
}
572571
}
573572

574-
func (exec *Execution) callFunction(fn *ScriptFunction, receiver Value, args []Value, kwargs map[string]Value, pos Position) (Value, error) {
573+
func (exec *Execution) callFunction(fn *ScriptFunction, receiver Value, args []Value, kwargs map[string]Value, block Value, pos Position) (Value, error) {
575574
callEnv := newEnv(fn.Env)
576575
if receiver.Kind() != KindNil {
577576
callEnv.Define("self", receiver)
578577
}
578+
callEnv.Define("__block__", block)
579579
if err := exec.bindFunctionArgs(fn, callEnv, args, kwargs, pos); err != nil {
580580
return NewNil(), err
581581
}
@@ -729,6 +729,22 @@ func (exec *Execution) callBlock(block Value, args []Value) (Value, error) {
729729
return val, nil
730730
}
731731

732+
func (exec *Execution) evalYield(expr *YieldExpr, env *Env) (Value, error) {
733+
block, ok := env.Get("__block__")
734+
if !ok || block.Kind() == KindNil {
735+
return NewNil(), exec.errorAt(expr.Pos(), "no block given")
736+
}
737+
var args []Value
738+
for _, arg := range expr.Args {
739+
val, err := exec.evalExpression(arg, env)
740+
if err != nil {
741+
return NewNil(), err
742+
}
743+
args = append(args, val)
744+
}
745+
return exec.callBlock(block, args)
746+
}
747+
732748
func (exec *Execution) evalRangeExpr(expr *RangeExpr, env *Env) (Value, error) {
733749
startVal, err := exec.evalExpression(expr.Start, env)
734750
if err != nil {
@@ -829,13 +845,10 @@ func (exec *Execution) getMember(obj Value, property string, pos Position) (Valu
829845
cl := obj.Class()
830846
if property == "new" {
831847
return NewAutoBuiltin(cl.Name+".new", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
832-
if block.Kind() != KindNil {
833-
return NewNil(), fmt.Errorf("script functions do not accept blocks")
834-
}
835848
inst := &Instance{Class: cl, Ivars: make(map[string]Value)}
836849
instVal := NewInstance(inst)
837850
if initFn, ok := cl.Methods["initialize"]; ok {
838-
if _, err := exec.callFunction(initFn, instVal, args, kwargs, pos); err != nil {
851+
if _, err := exec.callFunction(initFn, instVal, args, kwargs, block, pos); err != nil {
839852
return NewNil(), err
840853
}
841854
}
@@ -847,10 +860,7 @@ func (exec *Execution) getMember(obj Value, property string, pos Position) (Valu
847860
return NewNil(), exec.errorAt(pos, "private method %s", property)
848861
}
849862
return NewAutoBuiltin(cl.Name+"."+property, func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
850-
if block.Kind() != KindNil {
851-
return NewNil(), fmt.Errorf("script functions do not accept blocks")
852-
}
853-
return exec.callFunction(fn, obj, args, kwargs, pos)
863+
return exec.callFunction(fn, obj, args, kwargs, block, pos)
854864
}), nil
855865
}
856866
if val, ok := cl.ClassVars[property]; ok {
@@ -867,10 +877,7 @@ func (exec *Execution) getMember(obj Value, property string, pos Position) (Valu
867877
return NewNil(), exec.errorAt(pos, "private method %s", property)
868878
}
869879
return NewAutoBuiltin(inst.Class.Name+"#"+property, func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
870-
if block.Kind() != KindNil {
871-
return NewNil(), fmt.Errorf("script functions do not accept blocks")
872-
}
873-
return exec.callFunction(fn, obj, args, kwargs, pos)
880+
return exec.callFunction(fn, obj, args, kwargs, block, pos)
874881
}), nil
875882
}
876883
if val, ok := inst.Ivars[property]; ok {
@@ -1149,7 +1156,7 @@ func arrayMember(array Value, property string) (Value, error) {
11491156
return NewInt(int64(len(receiver.Array()))), nil
11501157
}), nil
11511158
case "each":
1152-
return NewBuiltin("array.each", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
1159+
return NewAutoBuiltin("array.each", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
11531160
if block.Block() == nil {
11541161
return NewNil(), fmt.Errorf("array.each requires a block")
11551162
}
@@ -1161,7 +1168,7 @@ func arrayMember(array Value, property string) (Value, error) {
11611168
return receiver, nil
11621169
}), nil
11631170
case "map":
1164-
return NewBuiltin("array.map", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
1171+
return NewAutoBuiltin("array.map", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
11651172
if block.Block() == nil {
11661173
return NewNil(), fmt.Errorf("array.map requires a block")
11671174
}
@@ -1177,7 +1184,7 @@ func arrayMember(array Value, property string) (Value, error) {
11771184
return NewArray(result), nil
11781185
}), nil
11791186
case "select":
1180-
return NewBuiltin("array.select", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
1187+
return NewAutoBuiltin("array.select", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
11811188
if block.Block() == nil {
11821189
return NewNil(), fmt.Errorf("array.select requires a block")
11831190
}
@@ -1195,7 +1202,7 @@ func arrayMember(array Value, property string) (Value, error) {
11951202
return NewArray(out), nil
11961203
}), nil
11971204
case "reduce":
1198-
return NewBuiltin("array.reduce", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
1205+
return NewAutoBuiltin("array.reduce", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
11991206
if block.Block() == nil {
12001207
return NewNil(), fmt.Errorf("array.reduce requires a block")
12011208
}

0 commit comments

Comments
 (0)