-
Notifications
You must be signed in to change notification settings - Fork 176
Contibuting a Method
In Goby we have different classes and methods. If you are interested in adding one, here's what to do:
All built-in classes are all in /vm directory, each file represents a class. Its instance methods are stored in an array variable, with its name prefixed with "builtin" and suffixed with "Methods". The following table offers several naming examples:
| class | file | methods stored in variable |
|---|---|---|
| Integer | integer.go | builtinIntegerMethods |
| String | string.go | builtinStringMethods |
| Array | array.go | builtinArrayMethods |
In vm/ dir, the functions in each builtin (native) class file are organized as follows:
(except vm-related files such as vm.go, instruction.go or thread.go)
- type declaration -- Add comments to describe the class for API doc
- variable/constant/interface declaration (optional)
- Goby class methods -- Add comments to each method for API doc
- Goby instance methods -- Add comments to each method for API doc
keep the instance methods in alphabetical order
- internal functions
- a: initialization functions
- b: polymorphic helper functions
- c: other (non-polymorphic) helper functions
- addition (optional)
- declarations (such as type)
- helper functions
For adding comments as API doc, see Documenting-Goby-Code.

Adding a new method is adding an element in the array. The element includes two parts, Name and Fn. A Name is what the method is called, and Fn usually looks like:
func(receiver Object) builtinMethodBody {
return func(vm *VM, args []Object, blockFrame *callFrame) Object {
// ...
}
}Usually you don't have to modify this structure, neither variable names, but you do need to know several things:
-
receiveris the object that performs this action. For instance, if a code runs like"10".to_i, the"10"is the receiver. -
argis the arguments of this method. For example,10 + 20takes20as its argument. -
blockFrameis a block brought into this method. -
Objectis always the returned value because in Goby everything is an object!
Let's try adding a index method to the Array class that allows us to find a String or Integer in an array!
Supposedly we're building a method that allows us to do this:
a = ["a", "b", "c", "d", 2]
a.index("a") #=> 0
c = a.index do |x|
x == "c"
end
c #=> 2Find the builtinArrayMethods variable in vm/array.go. Follow the comments and see how we add it:
{
// First we give it a name.
Name: "index",
// Remember, receiver is the Array instance.
Fn: func(receiver Object) builtinMethodBody {
// `vm` is a pointer to VM.
// `args` is an array of arguments.
//
// Let's say our method will run like `array.index("c")` and return "c", so the returned value is:
//
// []Object{
// 0: StringObject{
// Class: *RString
// Value: "c"
// }
// }
//
// `blockFrame` is our block argument, this method returns `nil` if there's no block.
return func(vm *VM, args []Object, blockFrame *callFrame) Object {
arr := receiver.(*ArrayObject) // Retrieve array ["a", "b", "c", "d", 2]
arg = args[0] // Get the value we are looking for in the array
// Check the type of object
elInt, isInt := arg.(*IntegerObject)
elStr, isStr := arg.(*StringObject)
// `index` searches given value in the array and returns its index if found
// i: index of element
// o: value to compare with arg[0]
for i, o := range arr.Elements {
switch o.(type) {
case *IntegerObject:
el := o.(*IntegerObject)
// If both objects are integers, returns an IntegerObject i
if isInt && el.equal(elInt) {
return initilaizeInteger(i)
}
case *StringObject:
el := o.(*StringObject)
if isStr && el.equal(elStr) {
return initilaizeInteger(i)
}
}
}
return initilaizeInteger(nil)
}
},
}We can't have a method without a test case. Let's add a test case.
The best way to test it is to write Goby code directly and see if evaluating the code returns the desired result. We're using the built-in testing package in Go, so please follow Go's convention in writing tests, especially keeping the failing message understandable.
- Find the test file for your class. If you added a method in
integer.gothen add a test ininteger_test.go. - Create a function name starting with "Test", so Go will run this function in testing.
- In this function, use
testEval()function to evaluate a piece of Goby code. - Validate if the returned object is as you expect. If not, write a failing message. The message should include what we expect, and what is the actual output.
Let's add a test case for the #index method we just added. In array_test.go, we add the following code block.
func TestIndexMethod(t *testing.T) {
tests := []struct {
input string
expected *IntegerObject
}{
{
// The following is a piece of Goby code
`
a = [1, 2, "a", 3, "5", "r"]
a.index("r")
`,
// We expect it to generate an Integer object
initilaizeInteger(5)},
}
for _, tt := range tests {
// Evaluate the code
evaluated := testEval(t, tt.input)
// Use our function to evaluate if it is as expected
testIntegerObject(t, evaluated, tt.expected.Value)
}
}Run make test in project root directory to see if all tests passed. If so, congratulations! Now push and create a pull request!