Skip to content

Commit

Permalink
Merge pull request #672 from goby-lang/Support-method-missing
Browse files Browse the repository at this point in the history
Support method missing
  • Loading branch information
eliothedeman authored May 22, 2018
2 parents 54255dd + bfd36be commit 53a083b
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 23 deletions.
145 changes: 145 additions & 0 deletions vm/evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1966,6 +1966,151 @@ func TestMethodCallWithoutParens(t *testing.T) {
}
}

func TestMethodMissing(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{`
class Foo
def method_missing(name)
10
end
end
Foo.new.bar
`, 10},
{`
class Foo
def method_missing
10
end
end
f = Foo.new
def f.method_missing(name)
20
end
f.bar
`, 20},
{`
class Foo
def method_missing(name)
name
end
end
Foo.new.bar
`, "bar"},
{`
class Foo
def method_missing(name, *args)
args[0]
end
end
Foo.new.bar(1)
`, 1},
{`
class Foo
def method_missing(name, *args)
name + args[0]
end
end
Foo.new.foo("bar")
`, "foobar"},
{`
class Foo
def method_missing(name)
yield
end
end
Foo.new.bar do
10
end
`, 10},
{`
class Foo
def method_missing(name)
get_block.call
end
end
Foo.new.bar do
10
end
`, 10},
}

for i, tt := range tests {
v := initTestVM()
evaluated := v.testEval(t, tt.input, getFilename())

if isError(evaluated) {
t.Fatalf("got Error: %s", evaluated.(*Error).message)
}

VerifyExpected(t, i, evaluated, tt.expected)
v.checkCFP(t, i, 0)
v.checkSP(t, i, 1)
}
}

func TestMethodMissingFail(t *testing.T) {
tests := []struct {
input string
expected string
}{
{`
class Foo
def method_missing
10
end
end
Foo.new.bar
`, "ArgumentError: Expect at most 0 args for method 'method_missing'. got: 1"},
{`
class Bar
def method_missing
10
end
end
class Foo < Bar
end
Foo.new.bar
`, "UndefinedMethodError: Undefined Method 'bar' for <Instance of: Foo>"},
{`
module Bar
def method_missing
10
end
end
class Foo
include Bar
end
Foo.new.bar
`, "UndefinedMethodError: Undefined Method 'bar' for <Instance of: Foo>"},
}

for i, tt := range tests {
v := initTestVM()
evaluated := v.testEval(t, tt.input, getFilename())

checkErrorMsg(t, i, evaluated, tt.expected)
v.checkCFP(t, i, 1)
v.checkSP(t, i, 1)
}
}

func TestMinusPrefixMethodCall(t *testing.T) {
tests := []struct {
input string
Expand Down
67 changes: 44 additions & 23 deletions vm/instruction.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ var builtinActions = map[operationType]*action{
name: bytecode.NewArray,
operation: func(t *Thread, sourceLine int, cf *normalCallFrame, args ...interface{}) {
argCount := args[0].(int)
elems := []Object{}
var elems []Object

for i := 0; i < argCount; i++ {
v := t.Stack.Pop()
Expand All @@ -228,7 +228,7 @@ var builtinActions = map[operationType]*action{
t.pushErrorObject(errors.TypeError, sourceLine, "Expect stack top's value to be an Array when executing 'expandarray' instruction.")
}

elems := []Object{}
var elems []Object

for i := 0; i < arrLength; i++ {
var elem Object
Expand Down Expand Up @@ -279,10 +279,10 @@ var builtinActions = map[operationType]*action{
name: bytecode.BranchUnless,
operation: func(t *Thread, sourceLine int, cf *normalCallFrame, args ...interface{}) {
v := t.Stack.Pop()
bool, isBool := v.Target.(*BooleanObject)
bo, isBool := v.Target.(*BooleanObject)

if isBool {
if bool.value {
if bo.value {
return
}

Expand All @@ -304,9 +304,9 @@ var builtinActions = map[operationType]*action{
name: bytecode.BranchIf,
operation: func(t *Thread, sourceLine int, cf *normalCallFrame, args ...interface{}) {
v := t.Stack.Pop()
bool, isBool := v.Target.(*BooleanObject)
bo, isBool := v.Target.(*BooleanObject)

if isBool && !bool.value {
if isBool && !bo.value {
return
}

Expand Down Expand Up @@ -496,7 +496,28 @@ var builtinActions = map[operationType]*action{
method = receiver.findMethod(methodName)

if method == nil {
t.setErrorObject(receiverPr, argPr, errors.UndefinedMethodError, sourceLine, "Undefined Method '%+v' for %+v", methodName, receiver.toString())
mm := receiver.findMethodMissing()

if mm == nil {
t.setErrorObject(receiverPr, argPr, errors.UndefinedMethodError, sourceLine, "Undefined Method '%+v' for %+v", methodName, receiver.toString())
} else {
// Move up args for missed method's name
// before: | arg 1 | arg 2 |
// after: | method name | arg 1 | arg 2 |
// TODO: Improve this
t.Stack.Push(nil)

for i := argCount - 1; i >= 0; i-- {
position := argPr + i
arg := t.Stack.data[argPr+i]
t.Stack.Set(position+1, arg)
}

t.Stack.Set(argPr, &Pointer{Target: t.vm.initStringObject(methodName)})
argCount++

method = mm
}
}

// Find Block
Expand Down Expand Up @@ -604,39 +625,39 @@ var builtinActions = map[operationType]*action{
},
}

func (vm *VM) InitObjectFromGoType(value interface{}) Object {
switch v := value.(type) {
func (v *VM) InitObjectFromGoType(value interface{}) Object {
switch val := value.(type) {
case nil:
return NULL
case int:
return vm.InitIntegerObject(v)
return v.InitIntegerObject(val)
case int64:
return vm.InitIntegerObject(int(v))
return v.InitIntegerObject(int(val))
case int32:
return vm.InitIntegerObject(int(v))
return v.InitIntegerObject(int(val))
case float64:
return vm.initFloatObject(v)
return v.initFloatObject(val)
case []uint8:
bytes := []byte{}
var bytes []byte

for _, i := range v {
for _, i := range val {
bytes = append(bytes, byte(i))
}

return vm.initStringObject(string(bytes))
return v.initStringObject(string(bytes))
case string:
return vm.initStringObject(v)
return v.initStringObject(val)
case bool:
return toBooleanObject(v)
return toBooleanObject(val)
case []interface{}:
var objs []Object
var objects []Object

for _, elem := range v {
objs = append(objs, vm.InitObjectFromGoType(elem))
for _, elem := range val {
objects = append(objects, v.InitObjectFromGoType(elem))
}

return vm.InitArrayObject(objs)
return v.InitArrayObject(objects)
default:
return vm.initGoObject(value)
return v.initGoObject(value)
}
}
13 changes: 13 additions & 0 deletions vm/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Object interface {
SingletonClass() *RClass
SetSingletonClass(*RClass)
findMethod(string) Object
findMethodMissing() Object
toString() string
toJSON(t *Thread) string
id() int
Expand Down Expand Up @@ -78,6 +79,18 @@ func (b *baseObj) findMethod(methodName string) (method Object) {
return
}

func (b *baseObj) findMethodMissing() (method Object) {
if b.SingletonClass() != nil {
method, _ = b.SingletonClass().Methods.get("method_missing")
}

if method == nil {
method, _ = b.Class().Methods.get("method_missing")
}

return
}

func (b *baseObj) id() int {
r, e := strconv.ParseInt(fmt.Sprintf("%p", b), 0, 64)
if e != nil {
Expand Down

0 comments on commit 53a083b

Please sign in to comment.