Skip to content

Commit 9c762d7

Browse files
committed
Ease Ruby-style array and math ports
1 parent 6e68d7d commit 9c762d7

4 files changed

Lines changed: 139 additions & 20 deletions

File tree

vibes/execution_members_array.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import "fmt"
44

55
func arrayMember(array Value, property string) (Value, error) {
66
switch property {
7-
case "size", "each", "map", "select", "find", "find_index", "reduce", "include?", "index", "rindex", "count", "any?", "all?", "none?":
7+
case "size", "length", "empty?", "each", "map", "select", "find", "find_index", "reduce", "include?", "index", "rindex", "fetch", "count", "any?", "all?", "none?":
88
return arrayMemberQuery(property)
99
case "push", "pop", "uniq", "first", "last", "sum", "compact", "flatten", "chunk", "window", "join", "reverse":
1010
return arrayMemberTransforms(property)

vibes/execution_members_array_query.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ import "fmt"
44

55
func arrayMemberQuery(property string) (Value, error) {
66
switch property {
7-
case "size":
8-
return NewAutoBuiltin("array.size", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
7+
case "size", "length":
8+
name := property
9+
return NewAutoBuiltin("array."+name, func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
910
if len(args) > 0 {
10-
return NewNil(), fmt.Errorf("array.size does not take arguments")
11+
return NewNil(), fmt.Errorf("array.%s does not take arguments", name)
1112
}
1213
return NewInt(int64(len(receiver.Array()))), nil
1314
}), nil
15+
case "empty?":
16+
return NewAutoBuiltin("array.empty?", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
17+
if len(args) > 0 {
18+
return NewNil(), fmt.Errorf("array.empty? does not take arguments")
19+
}
20+
return NewBool(len(receiver.Array()) == 0), nil
21+
}), nil
1422
case "each":
1523
return NewAutoBuiltin("array.each", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
1624
if err := ensureBlock(block, "array.each"); err != nil {
@@ -203,6 +211,24 @@ func arrayMemberQuery(property string) (Value, error) {
203211
}
204212
return NewNil(), nil
205213
}), nil
214+
case "fetch":
215+
return NewAutoBuiltin("array.fetch", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
216+
if len(args) < 1 || len(args) > 2 {
217+
return NewNil(), fmt.Errorf("array.fetch expects index and optional default")
218+
}
219+
index, err := valueToInt(args[0])
220+
if err != nil {
221+
return NewNil(), fmt.Errorf("array.fetch index must be integer")
222+
}
223+
arr := receiver.Array()
224+
if index >= 0 && index < len(arr) {
225+
return arr[index], nil
226+
}
227+
if len(args) == 2 {
228+
return args[1], nil
229+
}
230+
return NewNil(), nil
231+
}), nil
206232
case "count":
207233
return NewAutoBuiltin("array.count", func(exec *Execution, receiver Value, args []Value, kwargs map[string]Value, block Value) (Value, error) {
208234
if len(args) > 1 {

vibes/execution_values_arithmetic.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ func multiplyValues(left, right Value) (Value, error) {
121121

122122
func divideValues(left, right Value) (Value, error) {
123123
switch {
124+
case left.Kind() == KindInt && right.Kind() == KindInt:
125+
if right.Int() == 0 {
126+
return NewNil(), errors.New("division by zero")
127+
}
128+
return NewInt(left.Int() / right.Int()), nil
124129
case (left.Kind() == KindInt || left.Kind() == KindFloat) && (right.Kind() == KindInt || right.Kind() == KindFloat):
125130
if right.Float() == 0 {
126131
return NewNil(), errors.New("division by zero")

vibes/runtime_test.go

Lines changed: 104 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,10 @@ func TestArrayPhaseTwoHelpers(t *testing.T) {
404404
end
405405
406406
{
407+
size: values.size,
408+
length: values.length,
409+
empty_false: values.empty?,
410+
empty_true: [].empty?,
407411
include_hit: values.include?(2),
408412
include_miss: values.include?(9),
409413
index_hit: values.index(1),
@@ -412,6 +416,9 @@ func TestArrayPhaseTwoHelpers(t *testing.T) {
412416
rindex_hit: values.rindex(1),
413417
rindex_offset_hit: values.rindex(1, 2),
414418
rindex_miss: values.rindex(9),
419+
fetch_hit: values.fetch(2),
420+
fetch_default: values.fetch(9, 42),
421+
fetch_miss: values.fetch(9),
415422
find_hit: find_hit,
416423
find_miss: find_miss,
417424
find_index_hit: find_index_hit,
@@ -446,6 +453,12 @@ func TestArrayPhaseTwoHelpers(t *testing.T) {
446453
t.Fatalf("expected hash, got %v", result.Kind())
447454
}
448455
got := result.Hash()
456+
if !got["size"].Equal(NewInt(4)) || !got["length"].Equal(NewInt(4)) {
457+
t.Fatalf("size/length mismatch: size=%v length=%v", got["size"], got["length"])
458+
}
459+
if got["empty_false"].Bool() || !got["empty_true"].Bool() {
460+
t.Fatalf("empty? mismatch: false=%v true=%v", got["empty_false"], got["empty_true"])
461+
}
449462
if !got["include_hit"].Bool() || got["include_miss"].Bool() {
450463
t.Fatalf("include? mismatch: %#v", got)
451464
}
@@ -461,6 +474,12 @@ func TestArrayPhaseTwoHelpers(t *testing.T) {
461474
if got["rindex_miss"].Kind() != KindNil {
462475
t.Fatalf("rindex_miss expected nil, got %v", got["rindex_miss"])
463476
}
477+
if !got["fetch_hit"].Equal(NewInt(2)) || !got["fetch_default"].Equal(NewInt(42)) {
478+
t.Fatalf("fetch mismatch: hit=%v default=%v", got["fetch_hit"], got["fetch_default"])
479+
}
480+
if got["fetch_miss"].Kind() != KindNil {
481+
t.Fatalf("fetch_miss expected nil, got %v", got["fetch_miss"])
482+
}
464483
if !got["find_hit"].Equal(NewInt(3)) || got["find_miss"].Kind() != KindNil {
465484
t.Fatalf("find mismatch: hit=%v miss=%v", got["find_hit"], got["find_miss"])
466485
}
@@ -642,20 +661,20 @@ func TestArrayConcatAndSubtract(t *testing.T) {
642661

643662
func TestLogicalOperatorsShortCircuit(t *testing.T) {
644663
script := compileScript(t, `
645-
def bad_index()
664+
def bad_index
646665
[1][4]
647666
end
648667
649-
def explode()
668+
def explode
650669
raise "boom"
651670
end
652671
653-
def false_and_bad_index()
654-
false && bad_index()
672+
def false_and_bad_index
673+
false && bad_index
655674
end
656675
657-
def true_or_explode()
658-
true || explode()
676+
def true_or_explode
677+
true || explode
659678
end
660679
661680
def adjacent_run(values, index)
@@ -677,6 +696,65 @@ func TestLogicalOperatorsShortCircuit(t *testing.T) {
677696
}
678697
}
679698

699+
func TestIntegerDivisionAndModulo(t *testing.T) {
700+
script := compileScript(t, `
701+
def gcd(a, b)
702+
while b != 0
703+
next_value = a % b
704+
a = b
705+
b = next_value
706+
end
707+
a
708+
end
709+
710+
def hailstone(n)
711+
out = [n]
712+
while n != 1
713+
if n % 2 == 0
714+
n = n / 2
715+
else
716+
n = n * 3 + 1
717+
end
718+
out = out + [n]
719+
end
720+
out
721+
end
722+
723+
def arithmetic
724+
{
725+
int_div: 7 / 2,
726+
float_div: 7.0 / 2,
727+
mod_chain: 10 / 2 % 3,
728+
gcd: gcd(54, 24),
729+
hailstone: hailstone(7)
730+
}
731+
end
732+
`)
733+
734+
result := callFunc(t, script, "arithmetic", nil)
735+
if result.Kind() != KindHash {
736+
t.Fatalf("expected hash, got %v", result.Kind())
737+
}
738+
got := result.Hash()
739+
if !got["int_div"].Equal(NewInt(3)) {
740+
t.Fatalf("int_div mismatch: %v", got["int_div"])
741+
}
742+
if got["float_div"].Kind() != KindFloat || got["float_div"].Float() != 3.5 {
743+
t.Fatalf("float_div mismatch: %v", got["float_div"])
744+
}
745+
if !got["mod_chain"].Equal(NewInt(2)) {
746+
t.Fatalf("mod_chain mismatch: %v", got["mod_chain"])
747+
}
748+
if !got["gcd"].Equal(NewInt(6)) {
749+
t.Fatalf("gcd mismatch: %v", got["gcd"])
750+
}
751+
compareArrays(t, got["hailstone"], []Value{
752+
NewInt(7), NewInt(22), NewInt(11), NewInt(34), NewInt(17), NewInt(52),
753+
NewInt(26), NewInt(13), NewInt(40), NewInt(20), NewInt(10), NewInt(5),
754+
NewInt(16), NewInt(8), NewInt(4), NewInt(2), NewInt(1),
755+
})
756+
}
757+
680758
func TestHashLiteralSyntaxRestriction(t *testing.T) {
681759
_ = compileScriptErrorDefault(t, `
682760
def broken()
@@ -1032,31 +1110,31 @@ func TestUntilLoops(t *testing.T) {
10321110

10331111
func TestLineTerminatedHeadersAndStatements(t *testing.T) {
10341112
script := compileScript(t, `
1035-
def if_empty_array()
1113+
def if_empty_array
10361114
if true
10371115
[]
10381116
else
10391117
[2]
10401118
end
10411119
end
10421120
1043-
def if_array()
1121+
def if_array
10441122
if true
10451123
[1]
10461124
else
10471125
[2]
10481126
end
10491127
end
10501128
1051-
def if_hash()
1129+
def if_hash
10521130
if false
10531131
[1]
10541132
else
10551133
{ a: 1 }
10561134
end
10571135
end
10581136
1059-
def while_body()
1137+
def while_body
10601138
seen = []
10611139
i = 0
10621140
while i < 1
@@ -1067,7 +1145,7 @@ func TestLineTerminatedHeadersAndStatements(t *testing.T) {
10671145
seen
10681146
end
10691147
1070-
def until_body()
1148+
def until_body
10711149
seen = []
10721150
i = 0
10731151
until i == 1
@@ -1078,7 +1156,7 @@ func TestLineTerminatedHeadersAndStatements(t *testing.T) {
10781156
seen
10791157
end
10801158
1081-
def for_body()
1159+
def for_body
10821160
seen = []
10831161
for item in [1, 2]
10841162
[item]
@@ -1087,17 +1165,17 @@ func TestLineTerminatedHeadersAndStatements(t *testing.T) {
10871165
seen
10881166
end
10891167
1090-
def return_value()
1168+
def return_value
10911169
return true
10921170
[1]
10931171
end
10941172
1095-
def bare_return()
1173+
def bare_return
10961174
return
10971175
[1]
10981176
end
10991177
1100-
def raise_message()
1178+
def raise_message
11011179
raise "boom"
11021180
[1]
11031181
end
@@ -1252,7 +1330,7 @@ func TestBeginRescueEnsure(t *testing.T) {
12521330
end
12531331
`)
12541332

1255-
if got := callFunc(t, script, "safe_div", []Value{NewInt(10), NewInt(2)}); !got.Equal(NewFloat(5)) {
1333+
if got := callFunc(t, script, "safe_div", []Value{NewInt(10), NewInt(2)}); !got.Equal(NewInt(5)) {
12561334
t.Fatalf("safe_div success mismatch: %v", got)
12571335
}
12581336
if got := callFunc(t, script, "safe_div", []Value{NewInt(10), NewInt(0)}); !got.Equal(NewString("fallback")) {
@@ -3571,6 +3649,16 @@ func TestMethodErrorHandling(t *testing.T) {
35713649
script: `def run() [].rindex(1, -1) end`,
35723650
errMsg: "offset must be non-negative integer",
35733651
},
3652+
{
3653+
name: "array.fetch with missing index",
3654+
script: `def run() [1, 2, 3].fetch end`,
3655+
errMsg: "expects index and optional default",
3656+
},
3657+
{
3658+
name: "array.fetch with non-integer index",
3659+
script: `def run() [1, 2, 3].fetch("1") end`,
3660+
errMsg: "index must be integer",
3661+
},
35743662
{
35753663
name: "array.count with argument and block",
35763664
script: `def run()

0 commit comments

Comments
 (0)